-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
528 lines (346 loc) · 228 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>费默 - 分享</title>
<subtitle>Just run.</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="https://lilong7676.github.io/"/>
<updated>2024-01-12T09:22:26.372Z</updated>
<id>https://lilong7676.github.io/</id>
<author>
<name>lilong7676</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>使用TabbyML/tabby在本地部署一个类github copilot</title>
<link href="https://lilong7676.github.io/2024/01/11/GPT/%E4%BD%BF%E7%94%A8TabbyML-tabby%E5%9C%A8%E6%9C%AC%E5%9C%B0%E9%83%A8%E7%BD%B2%E4%B8%80%E4%B8%AA%E7%B1%BBgithub-copilot/"/>
<id>https://lilong7676.github.io/2024/01/11/GPT/使用TabbyML-tabby在本地部署一个类github-copilot/</id>
<published>2024-01-11T05:54:03.000Z</published>
<updated>2024-01-12T09:22:26.372Z</updated>
<content type="html"><![CDATA[<p>刚好自己的 github copilot 过期了,就逛了下 github,发现了一个有趣的仓库 <a href="https://github.com/TabbyML/tabby" target="_blank" rel="noopener">tabby</a>,它的描述瞬间吸引了我哈哈:</p><blockquote><p>Tabby is a self-hosted AI coding assistant, offering an open-source and on-premises alternative to GitHub Copilot. It boasts several key features:</p></blockquote><blockquote><p>Self-contained, with no need for a DBMS or cloud service.<br>OpenAPI interface, easy to integrate with existing infrastructure (e.g Cloud IDE).<br>Supports consumer-grade GPUs. </p></blockquote><p>逛了下官网,发现可以在 Apple M1/M2 芯片的电脑上本地安装和使用,所以试了下,这里记录下。</p><a id="more"></a><p>安装的命令很简单 </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">brew install tabbyml/tabby/tabby</span><br><span class="line"></span><br><span class="line"><span class="comment"># Start server with StarCoder-1B</span></span><br><span class="line">tabby serve --device metal --model TabbyML/StarCoder-1B</span><br></pre></td></tr></table></figure><p>第二步如果启动的时候报类似于这个错误,则考虑科学上网下:<br><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/202401111358602.png" alt></p><p>下面是启动成功的截图:<br><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/202401111359077.png" alt></p><p>下一步就是安装对应的 vscode 插件,参考<a href="https://tabby.tabbyml.com/docs/extensions/installation/vscode" target="_blank" rel="noopener">官网链接</a></p><p>使用效果 -><br><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/202401111505414.png" alt></p><p><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/202401111508794.png" alt></p><p>我目前的电脑配置 macbook air m1 丐中丐,总的来说,可以用,有的提示耗时过长了,有的直接没有提示。效果还需要后面用多了才能评价,但是总比没有的好。<br>还是很期待类似的技术能够硬件配置小型化,让个人电脑都能够部署。</p>]]></content>
<summary type="html">
<p>刚好自己的 github copilot 过期了,就逛了下 github,发现了一个有趣的仓库 <a href="https://github.com/TabbyML/tabby" target="_blank" rel="noopener">tabby</a>,它的描述瞬间吸引了我哈哈:</p>
<blockquote>
<p>Tabby is a self-hosted AI coding assistant, offering an open-source and on-premises alternative to GitHub Copilot. It boasts several key features:</p>
</blockquote>
<blockquote>
<p>Self-contained, with no need for a DBMS or cloud service.<br>OpenAPI interface, easy to integrate with existing infrastructure (e.g Cloud IDE).<br>Supports consumer-grade GPUs. </p>
</blockquote>
<p>逛了下官网,发现可以在 Apple M1/M2 芯片的电脑上本地安装和使用,所以试了下,这里记录下。</p>
</summary>
<category term="GPT" scheme="https://lilong7676.github.io/categories/GPT/"/>
</entry>
<entry>
<title>rust学习有感-2</title>
<link href="https://lilong7676.github.io/2023/12/28/rust/rust%E5%AD%A6%E4%B9%A0%E6%9C%89%E6%84%9F-2/"/>
<id>https://lilong7676.github.io/2023/12/28/rust/rust学习有感-2/</id>
<published>2023-12-28T09:09:06.000Z</published>
<updated>2024-01-11T07:25:11.056Z</updated>
<content type="html"><![CDATA[<p>上一篇已经学习到了第五章 rust 结构体,看了下教程共有 20 章,继续学吧。。</p><h3 id="枚举和模式匹配"><a href="#枚举和模式匹配" class="headerlink" title="枚举和模式匹配"></a>枚举和模式匹配</h3><ul><li><p>枚举定义</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">enum</span> <span class="title">ipAddrKind</span></span> {</span><br><span class="line"> v4,</span><br><span class="line"> v6,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">#[derive(Debug)]</span></span><br><span class="line"><span class="class"><span class="keyword">enum</span> <span class="title">Message</span></span> {</span><br><span class="line"> Quit,</span><br><span class="line"> Move { x: <span class="built_in">i32</span>, y: <span class="built_in">i32</span> },</span><br><span class="line"> Write(<span class="built_in">String</span>),</span><br><span class="line"> ChangeColor(<span class="built_in">i32</span>, <span class="built_in">i32</span>, <span class="built_in">i32</span>),</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">impl</span> Message {</span><br><span class="line"> <span class="function"><span class="keyword">fn</span> <span class="title">call</span></span>(&<span class="keyword">self</span>) {</span><br><span class="line"> dbg!(<span class="string">"call {}"</span>, <span class="keyword">self</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fn</span> <span class="title">main</span></span>() {</span><br><span class="line"> <span class="keyword">let</span> m = Message::Write(<span class="built_in">String</span>::from(<span class="string">"hello"</span>));</span><br><span class="line"> m.call();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> change_color = Message::ChangeColor(<span class="number">12</span>, <span class="number">255</span>, <span class="number">255</span>);</span><br><span class="line"> change_color.call();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> move_msg = Message::Move { x: <span class="number">18</span>, y: <span class="number">18</span> };</span><br><span class="line"> <span class="comment">// 怎么取出枚举中的值?</span></span><br><span class="line"> <span class="comment">// println!("move.x {} move.y {}", move_msg.x ???);</span></span><br><span class="line"> move_msg.call();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> quit = Message::Quit;</span><br><span class="line"> quit.call();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p><code>match</code> 控制流</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">enum</span> <span class="title">Coin</span></span> {</span><br><span class="line"> Penny,</span><br><span class="line"> Nickel,</span><br><span class="line"> Dime,</span><br><span class="line"> Quarter,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fn</span> <span class="title">value_in_cents</span></span>(coin: Coin) -> <span class="built_in">u8</span> {</span><br><span class="line"> <span class="keyword">match</span> coin {</span><br><span class="line"> Coin::Penny => {</span><br><span class="line"> <span class="comment">// 也可以写上大括号,表达式末尾是返回值</span></span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"i am penny"</span>);</span><br><span class="line"> <span class="number">1</span></span><br><span class="line"> } <span class="comment">// 如果有大括号,则表达式末尾的逗号可以去掉</span></span><br><span class="line"> Coin::Nickel => <span class="number">5</span>,</span><br><span class="line"> Coin::Dime => <span class="number">10</span>,</span><br><span class="line"> Coin::Quarter => <span class="number">25</span>,</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>首先,<code>match</code> 是一个表达式,所以会返回某一个值</li><li><code>match</code> 每一个分支相关联的代码也是一个表达式,而表达式的结果将作为整个 <code>match</code> 表达式的返回值</li><li>穷举性、通配符 <code>_</code></li><li><code>if let</code> 简洁控制流<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// if let 的使用</span></span><br><span class="line"><span class="keyword">let</span> config_max = <span class="literal">Some</span>(<span class="number">20</span>);</span><br><span class="line"><span class="keyword">if</span> <span class="keyword">let</span> <span class="literal">Some</span>(max) = config_max {</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"config_max is {}"</span>, max);</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"not config_max"</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><p>总的来说,rust 的模式匹配是为了减少代码出现“漏分支”的情况,也提高了一些编码效率。而 rust 的枚举就变得不像其他语言那么纯粹了。感觉是得适应一段时间。</p><h3 id="包、crate、模块管理"><a href="#包、crate、模块管理" class="headerlink" title="包、crate、模块管理"></a>包、crate、模块管理</h3></li></ul>]]></content>
<summary type="html">
<p>上一篇已经学习到了第五章 rust 结构体,看了下教程共有 20 章,继续学吧。。</p>
<h3 id="枚举和模式匹配"><a href="#枚举和模式匹配" class="headerlink" title="枚举和模式匹配"></a>枚举和模式匹配</h3><ul>
</summary>
<category term="rust" scheme="https://lilong7676.github.io/categories/rust/"/>
</entry>
<entry>
<title>rust学习有感-1</title>
<link href="https://lilong7676.github.io/2023/12/20/rust/rust%E5%AD%A6%E4%B9%A0%E6%9C%89%E6%84%9F-1/"/>
<id>https://lilong7676.github.io/2023/12/20/rust/rust学习有感-1/</id>
<published>2023-12-20T03:17:06.000Z</published>
<updated>2023-12-28T09:07:28.120Z</updated>
<content type="html"><![CDATA[<p>前端都已经这么卷了,现在要往“锈化“卷了。当然这是玩笑话,毕竟目前锈化的只有一些前端基础工具而已。<br>但是结合工作实际,学习Rust也是有必要的,比如现在正在完善的云函数运行时服务,采用 nodejs + isolated-vm 的实现形式,<br>受限于 node 本身的性能限制,还有 isolated-vm 与底层 v8 通信的性能损失等。<br>一些开源库已经迁移到基于 rust 封装的v8了,所以顺便学习下 rust。</p><p>现在除了学校里学过的静态编译型语言(C\C++\Java),基于这些知识,学习 rust 应该很简单吧。(哈哈,等着后面打脸)。</p><p>主要记录一些学习过程中记忆深刻的点。</p><p>PS: </p><ul><li>学习网站<a href="https://kaisery.github.io/trpl-zh-cn" target="_blank" rel="noopener">Rust 程序设计语言 简体中文版</a></li><li>很有意思的rust学习网站<a href="https://github.com/rust-lang/rustlings/tree/rustlings-1?tab=readme-ov-file" target="_blank" rel="noopener">rustlings</a></li></ul><a id="more"></a><h3 id="猜数字游戏"><a href="#猜数字游戏" class="headerlink" title="猜数字游戏"></a>猜数字游戏</h3><p><a href="https://kaisery.github.io/trpl-zh-cn/ch02-00-guessing-game-tutorial.html" target="_blank" rel="noopener">猜数字游戏</a>,这一章写得很不错,一个小示例,能够窥视 rust 的很多东西了。</p><ul><li>依赖管理工具 cargo 的简单使用</li><li>怎么引入依赖</li><li>怎么定义变量,变量的可变性</li><li>怎么处理Result类型的错误</li><li>新的 match 模式匹配语法(和Flutter的模式匹配好像哈哈)</li><li>怎么使用 loop 循环</li><li>怎么做类型转换</li></ul><h3 id="常见编程概念"><a href="#常见编程概念" class="headerlink" title="常见编程概念"></a>常见编程概念</h3><ul><li>rust中,if 是一个表达式,而不是语句。所以<ul><li>if 表达式的返回值可以赋值给一个变量</li></ul></li><li>loop 循环中,break 语句可以后接一个表达式,该表达式的值会返回给loop循环的执行结果中</li><li>循环标签(loop label),可以使用 break 语句指定要退出的循环体</li></ul><h3 id="所有权"><a href="#所有权" class="headerlink" title="所有权"></a>所有权</h3><ul><li><p>关键词:移动、浅拷贝、深拷贝</p></li><li><p>没有默认实现 Copy 方法的类型的值,在被赋值给一个变量时(直接赋值、作为函数参数赋值),其所有权会移动(转移),转移后,原有值的变量不可使用</p><pre><code class="rust"><span class="comment">// 这段代码会报错</span><span class="keyword">let</span> s1 = <span class="built_in">String</span>::from(<span class="string">"hello"</span>);<span class="keyword">let</span> s2 = s1;<span class="built_in">println!</span>(<span class="string">"{}, world!"</span>, s1);</code></pre></li><li><p>如果不想变量转移所有权,可以通过值的引用传递(跟C中的指针一样)</p></li><li><p>值的引用(&val1)只是一个指向值的指针,并不拥有这个值,所以不会发生所有权移动。当不再使用值的饮用时,并不影响其所指向的值</p><pre><code class="rust"><span class="function"><span class="keyword">fn</span> <span class="title">calculate_length</span></span>(s: &<span class="built_in">String</span>) -> <span class="built_in">usize</span> { <span class="comment">// s 是 String 的引用</span> s.len()} <span class="comment">// 这里,s 离开了作用域。但因为它并不拥有引用值的所有权,</span> <span class="comment">// 所以什么也不会发生</span></code></pre></li><li><p>引用 与 可变引用</p><pre><code class="rust"><span class="function"><span class="keyword">fn</span> <span class="title">main</span></span>() { <span class="keyword">let</span> <span class="keyword">mut</span> s = <span class="built_in">String</span>::from(<span class="string">"hello"</span>); change(&<span class="keyword">mut</span> s);}<span class="function"><span class="keyword">fn</span> <span class="title">change</span></span>(some_string: &<span class="keyword">mut</span> <span class="built_in">String</span>) { some_string.push_str(<span class="string">", world"</span>);}</code></pre><ul><li>数据竞争(data race),不能在同一时间创建多个可变引用</li><li>slice<pre><code class="rust"><span class="function"><span class="keyword">fn</span> <span class="title">main</span></span>() {<span class="keyword">let</span> <span class="keyword">mut</span> <span class="built_in">str</span> = <span class="built_in">String</span>::from(<span class="string">"hello world"</span>);<span class="keyword">let</span> index = first_word(&<span class="built_in">str</span>);<span class="built_in">println!</span>(<span class="string">"{}"</span>, index);<span class="built_in">str</span>.clear();</code></pre></li></ul><p>}</p><p>fn first_word(s: &str) -> &str {</p><pre><code>let bytes = s.as_bytes();for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; }}return &s[..];</code></pre><p>}</p><pre><code></code></pre></li></ul><h3 id="结构体"><a href="#结构体" class="headerlink" title="结构体"></a>结构体</h3><ul><li><p>结构体定义</p><pre><code class="rust"><span class="class"><span class="keyword">struct</span> <span class="title">User</span></span> {active: <span class="built_in">bool</span>,username: <span class="built_in">String</span>,email: <span class="built_in">String</span>,sign_in_count: <span class="built_in">u64</span>,}</code></pre></li><li><p>结构体初始化</p><pre><code class="rust"><span class="keyword">let</span> user1 = User { active: <span class="literal">true</span>, username: <span class="built_in">String</span>::from(<span class="string">"someusername123"</span>), email: <span class="built_in">String</span>::from(<span class="string">"[email protected]"</span>), sign_in_count: <span class="number">1</span>,};</code></pre></li><li><p>结构体更新语法(跟 es6 对象解构赋值差不多,但是是两个点 <code>..</code>)</p><pre><code class="rust"><span class="keyword">let</span> user2 = User { email: <span class="built_in">String</span>::from(<span class="string">"[email protected]"</span>), ..user1};</code></pre></li><li><p>元组结构体(tuple structs)</p><pre><code class="rust"><span class="class"><span class="keyword">struct</span> <span class="title">Color</span></span>(<span class="built_in">i32</span>, <span class="built_in">i32</span>, <span class="built_in">i32</span>);<span class="class"><span class="keyword">struct</span> <span class="title">Color2</span></span>(<span class="built_in">i32</span>, <span class="built_in">i32</span>, <span class="built_in">i32</span>);<span class="keyword">let</span> c1 = Color(<span class="number">11</span>, <span class="number">12</span>, <span class="number">13</span>);<span class="keyword">let</span> c2 = Color2(<span class="number">11</span>, <span class="number">12</span>, <span class="number">13</span>);</code></pre></li><li><p>没有任何字段的类单元结构体(unit-like structs)</p></li><li><p>如何使用 <code>println!</code> 打印一个结构体?</p></li><li><p>初步接触到 “外部属性 #[derive(Debug)]”</p><pre><code class="rust"><span class="meta">#[derive(Debug)]</span><span class="class"><span class="keyword">struct</span> <span class="title">Rectangle</span></span> { width: <span class="built_in">i32</span>, height: <span class="built_in">i32</span>,}<span class="function"><span class="keyword">fn</span> <span class="title">main</span></span>() { <span class="keyword">let</span> rect = Rectangle { width: <span class="number">10</span>, height: <span class="number">20</span>, }; dbg!(&rect); <span class="built_in">println!</span>(<span class="string">"area is {}"</span>, area(&rect));}<span class="function"><span class="keyword">fn</span> <span class="title">area</span></span>(rect: &Rectangle) -> <span class="built_in">i32</span> { <span class="keyword">return</span> rect.height * rect.width;}</code></pre></li><li><p><code>dbg!</code> 宏的用法</p><ul><li><code>dbg!</code> 宏接收一个表达式的所有权(<code>println!</code> 接收的是值的引用),打印出代码中调用 dbg! 宏时所在的文件和行号,以及该表达式的结果值,并返回该值的所有权。</li></ul></li><li><p>结构体中的方法(不就是类中的方法么)</p><pre><code class="rust"><span class="meta">#[derive(Debug)]</span><span class="class"><span class="keyword">struct</span> <span class="title">Rectangle</span></span> { width: <span class="built_in">i32</span>, height: <span class="built_in">i32</span>,}<span class="keyword">impl</span> Rectangle { <span class="function"><span class="keyword">fn</span> <span class="title">area</span></span>(&<span class="keyword">self</span>) -> <span class="built_in">i32</span> { <span class="keyword">return</span> <span class="keyword">self</span>.height * <span class="keyword">self</span>.width; }}<span class="function"><span class="keyword">fn</span> <span class="title">main</span></span>() { <span class="keyword">let</span> rect = Rectangle { width: <span class="number">10</span>, height: <span class="number">20</span>, }; dbg!(&rect); <span class="built_in">println!</span>(<span class="string">"area is {}"</span>, rect.area());}</code></pre></li><li><p>关联函数与命名空间 (不就是类的静态方法么?)</p><pre><code class="rust"><span class="keyword">impl</span> Rectangle {<span class="comment">// 这是一个关联函数,可以不以 self 作为第一参数</span><span class="comment">// 调用方式: Rectangle::square(3)</span><span class="function"><span class="keyword">fn</span> <span class="title">square</span></span>(size: <span class="built_in">u32</span>) -> <span class="keyword">Self</span> { <span class="keyword">Self</span> { width: size, height: size, }}}</code></pre></li><li><p>rust 支持自动引用和解引用(automatic reference and dereference)</p><ul><li>不像c++ 那样还要显式的标记</li></ul></li></ul><h3 id="目前为止的学习感想"><a href="#目前为止的学习感想" class="headerlink" title="目前为止的学习感想"></a>目前为止的学习感想</h3><p>到目前,初步了解了 rust 中所有权的概念和基本使用,也了解结构体的使用。<br>总的来说,语法上的确吸收了很多语言的有点,比如自动引用与解引用、解构赋值、元组等。<br>也从编译阶段消除了一些开发者可能会难以注意到的陷阱,比如所有权的转移、变量的生命周期等,这是目前最需要适应的地方,但也是我看到的 rust 目前最大的亮点吧。</p>]]></content>
<summary type="html">
<p>前端都已经这么卷了,现在要往“锈化“卷了。当然这是玩笑话,毕竟目前锈化的只有一些前端基础工具而已。<br>但是结合工作实际,学习Rust也是有必要的,比如现在正在完善的云函数运行时服务,采用 nodejs + isolated-vm 的实现形式,<br>受限于 node 本身的性能限制,还有 isolated-vm 与底层 v8 通信的性能损失等。<br>一些开源库已经迁移到基于 rust 封装的v8了,所以顺便学习下 rust。</p>
<p>现在除了学校里学过的静态编译型语言(C\C++\Java),基于这些知识,学习 rust 应该很简单吧。(哈哈,等着后面打脸)。</p>
<p>主要记录一些学习过程中记忆深刻的点。</p>
<p>PS: </p>
<ul>
<li>学习网站<a href="https://kaisery.github.io/trpl-zh-cn" target="_blank" rel="noopener">Rust 程序设计语言 简体中文版</a></li>
<li>很有意思的rust学习网站<a href="https://github.com/rust-lang/rustlings/tree/rustlings-1?tab=readme-ov-file" target="_blank" rel="noopener">rustlings</a></li>
</ul>
</summary>
<category term="rust" scheme="https://lilong7676.github.io/categories/rust/"/>
</entry>
<entry>
<title>mytoolbox - 有趣的想法之实现一个粘贴板历史管理工具</title>
<link href="https://lilong7676.github.io/2023/11/21/flutter/mytoolbox-%E6%9C%89%E8%B6%A3%E7%9A%84%E6%83%B3%E6%B3%95%E4%B9%8B%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E7%B2%98%E8%B4%B4%E6%9D%BF%E5%8E%86%E5%8F%B2%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7/"/>
<id>https://lilong7676.github.io/2023/11/21/flutter/mytoolbox-有趣的想法之实现一个粘贴板历史管理工具/</id>
<published>2023-11-21T10:09:07.000Z</published>
<updated>2023-12-20T02:49:41.970Z</updated>
<content type="html"><![CDATA[<p>windows 上好像已经有了自带的粘贴板历史工具,但是 mac 上没有自带的。之前一直在使用一个叫 pastebot 的工具,后面不支持 mac 新系统了(我的是 13.2.1),然后又找到一个叫 Paste 的工具,但是是收费的。<br>我觉得这种软件实现起来应该很简单吧,所以不如动手自己做一个。</p><h2 id="技术选型"><a href="#技术选型" class="headerlink" title="技术选型"></a>技术选型</h2><p>考虑到主要是桌面端使用,首先想到的就是 Electron 了,之前也做过相关开发,对 web 前端开发者算是很友好了。但是总觉得 Electron 实现的效果不够优雅(总觉得太像网页了哈哈)。<br>想了一下,最终选择了 Flutter,可以同时支持桌面端和移动端(因为我后面还想做一些小工具,比如私人GPT小助手,想支持跨端)。<br>哈哈,我的想法可太多了。</p><a id="more"></a><h2 id="实现效果"><a href="#实现效果" class="headerlink" title="实现效果"></a>实现效果</h2><p>先来看看最终实现的效果吧<br><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/202312201038188.png?x-oss-process=image/resize,w_800" alt="粘贴板历史管理器实现效果"></p><p><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/202312201039776.png?x-oss-process=image/resize,w_800" alt="点击历史项就可以复制到粘贴板"></p><h2 id="实现思路"><a href="#实现思路" class="headerlink" title="实现思路"></a>实现思路</h2><p>其实本质上就是通过监听系统粘贴板事件,然后取出里面的内容,通过 Flutter Listview 展示出来。做一些简单的交互即可。<br>这其中用到了一些库,主要有:</p><ul><li><a href="https://pub.dev/packages/clipboard_watcher" target="_blank" rel="noopener">clipboard_watcher</a>,用来监听系统粘贴板事件</li><li><a href="https://pub.dev/packages/super_clipboard" target="_blank" rel="noopener">super_clipboard</a>,用来读取和写入到粘贴板</li><li><a href="https://pub.dev/packages/super_hot_key" target="_blank" rel="noopener">super_hot_key</a>,用来定义系统级全局快捷键</li></ul><h2 id="关键代码"><a href="#关键代码" class="headerlink" title="关键代码"></a>关键代码</h2><p><a href="https://github.com/lilong7676/mytoolbox" target="_blank" rel="noopener">mytoolbox仓库地址</a> </p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">'dart:async'</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">'package:flutter/services.dart'</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">'package:flutter/material.dart'</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">'package:clipboard_watcher/clipboard_watcher.dart'</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">'package:super_clipboard/super_clipboard.dart'</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Paster</span> <span class="keyword">extends</span> <span class="title">StatefulWidget</span> </span>{</span><br><span class="line"> <span class="keyword">const</span> Paster({Key? key}) : <span class="keyword">super</span>(key: key);</span><br><span class="line"></span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> PasterState createState() => PasterState();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">PasterState</span> <span class="keyword">extends</span> <span class="title">State</span><<span class="title">Paster</span>> <span class="title">with</span> <span class="title">ClipboardListener</span> </span>{</span><br><span class="line"> <span class="built_in">List</span><ClipboardHistoryItem> historyList = [];</span><br><span class="line"></span><br><span class="line"> <span class="built_in">bool</span> tempDisableListener = <span class="keyword">false</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> <span class="keyword">void</span> initState() {</span><br><span class="line"> <span class="comment">// 监听粘贴板</span></span><br><span class="line"> clipboardWatcher.addListener(<span class="keyword">this</span>);</span><br><span class="line"> <span class="comment">// start watch</span></span><br><span class="line"> clipboardWatcher.start();</span><br><span class="line"> <span class="keyword">super</span>.initState();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> <span class="keyword">void</span> dispose() {</span><br><span class="line"> clipboardWatcher.removeListener(<span class="keyword">this</span>);</span><br><span class="line"> <span class="comment">// stop watch</span></span><br><span class="line"> clipboardWatcher.stop();</span><br><span class="line"> <span class="keyword">super</span>.dispose();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> <span class="keyword">void</span> onClipboardChanged() <span class="keyword">async</span> {</span><br><span class="line"> <span class="keyword">if</span> (tempDisableListener) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">final</span> item = <span class="keyword">await</span> ClipboardHistoryItem.readFromClipboard();</span><br><span class="line"> <span class="keyword">if</span> (item.contentType == ClipboardContentType.empty) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> setState(() {</span><br><span class="line"> historyList.insert(<span class="number">0</span>, item);</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> Widget build(BuildContext context) {</span><br><span class="line"> <span class="keyword">return</span> Column(</span><br><span class="line"> children: [</span><br><span class="line"> <span class="keyword">const</span> Padding(padding: EdgeInsets.all(<span class="number">8</span>), child: Text(<span class="string">'粘贴板历史'</span>)),</span><br><span class="line"> <span class="keyword">const</span> Divider(),</span><br><span class="line"> Flexible(</span><br><span class="line"> child: Align(</span><br><span class="line"> alignment: Alignment.topLeft,</span><br><span class="line"> child: _buildClipboardHistoryList(context))),</span><br><span class="line"> ],</span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> Widget _buildClipboardHistoryList(BuildContext context) {</span><br><span class="line"> <span class="comment">// 横向滚动</span></span><br><span class="line"> <span class="keyword">return</span> ConstrainedBox(</span><br><span class="line"> constraints: <span class="keyword">const</span> BoxConstraints(maxHeight: <span class="number">300</span>),</span><br><span class="line"> child: ListView.separated(</span><br><span class="line"> padding: <span class="keyword">const</span> EdgeInsets.all(<span class="number">8</span>),</span><br><span class="line"> scrollDirection: Axis.horizontal,</span><br><span class="line"> itemCount: historyList.length,</span><br><span class="line"> itemBuilder: (BuildContext context, <span class="built_in">int</span> index) {</span><br><span class="line"> <span class="keyword">final</span> historyItem = historyList[index];</span><br><span class="line"></span><br><span class="line"> late Widget title;</span><br><span class="line"> <span class="keyword">switch</span> (historyItem.contentType) {</span><br><span class="line"> <span class="keyword">case</span> ClipboardContentType.text:</span><br><span class="line"> title = Text(historyItem.text!);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> ClipboardContentType.image:</span><br><span class="line"> title = Image.memory(historyItem.imageBytes!);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> title = <span class="keyword">const</span> Text(<span class="string">'未知类型'</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> Container(</span><br><span class="line"> width: <span class="number">280</span>,</span><br><span class="line"> padding: <span class="keyword">const</span> EdgeInsets.all(<span class="number">8</span>),</span><br><span class="line"> decoration: BoxDecoration(</span><br><span class="line"> border: Border.all(color: Colors.grey),</span><br><span class="line"> borderRadius: BorderRadius.circular(<span class="number">8</span>)),</span><br><span class="line"> child: Column(</span><br><span class="line"> children: [</span><br><span class="line"> Row(</span><br><span class="line"> children: [</span><br><span class="line"> Expanded(</span><br><span class="line"> child: Text(</span><br><span class="line"> <span class="string">'<span class="subst">${historyItem.timestamp!.hour}</span>:<span class="subst">${historyItem.timestamp!.minute}</span>:<span class="subst">${historyItem.timestamp!.second}</span>'</span>)),</span><br><span class="line"> IconButton(</span><br><span class="line"> onPressed: () {</span><br><span class="line"> setState(() {</span><br><span class="line"> historyList.removeAt(index);</span><br><span class="line"> });</span><br><span class="line"> },</span><br><span class="line"> icon: <span class="keyword">const</span> Icon(Icons.delete))</span><br><span class="line"> ],</span><br><span class="line"> ),</span><br><span class="line"> <span class="keyword">const</span> Divider(),</span><br><span class="line"> Expanded(</span><br><span class="line"> child: ListTile(</span><br><span class="line"> title: title,</span><br><span class="line"> onTap: () <span class="keyword">async</span> {</span><br><span class="line"> <span class="comment">// 临时禁用监听</span></span><br><span class="line"> tempDisableListener = <span class="keyword">true</span>;</span><br><span class="line"> <span class="comment">// 写入剪贴板</span></span><br><span class="line"> <span class="keyword">await</span> historyItem.writeToClipboard();</span><br><span class="line"> <span class="comment">// TODO 待解决:理想情况下,首先需要使应用进入后台,然后激活之前的应用(有输入框的应用)</span></span><br><span class="line"> <span class="comment">// 然后进行粘贴操作</span></span><br><span class="line"> <span class="comment">// await FlutterClipboard.paste();</span></span><br><span class="line"> <span class="comment">// 延迟后再次启用监听</span></span><br><span class="line"> Future.delayed(<span class="keyword">const</span> <span class="built_in">Duration</span>(milliseconds: <span class="number">100</span>),</span><br><span class="line"> () {</span><br><span class="line"> tempDisableListener = <span class="keyword">false</span>;</span><br><span class="line"> <span class="comment">// 并将当前的历史记录移动到第一位</span></span><br><span class="line"> setState(() {</span><br><span class="line"> historyList.removeAt(index);</span><br><span class="line"> historyList.insert(<span class="number">0</span>, historyItem);</span><br><span class="line"> });</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 展示提示信息</span></span><br><span class="line"> <span class="keyword">const</span> snackBar = SnackBar(</span><br><span class="line"> content: Text(<span class="string">'已复制到粘贴板~'</span>),</span><br><span class="line"> duration: <span class="built_in">Duration</span>(seconds: <span class="number">2</span>),</span><br><span class="line"> showCloseIcon: <span class="keyword">true</span>,</span><br><span class="line"> );</span><br><span class="line"> <span class="keyword">if</span> (!context.mounted) <span class="keyword">return</span>;</span><br><span class="line"> ScaffoldMessenger.of(context)</span><br><span class="line"> .showSnackBar(snackBar);</span><br><span class="line"> }))</span><br><span class="line"> ],</span><br><span class="line"> ));</span><br><span class="line"> },</span><br><span class="line"> separatorBuilder: (BuildContext context, <span class="built_in">int</span> index) =></span><br><span class="line"> <span class="keyword">const</span> VerticalDivider(width: <span class="number">10</span>, color: Colors.transparent),</span><br><span class="line"> ));</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">enum</span> ClipboardContentType { empty, text, image }</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ClipboardHistoryItem</span> </span>{</span><br><span class="line"> <span class="built_in">String</span>? text;</span><br><span class="line"> Uint8List? imageBytes;</span><br><span class="line"> <span class="built_in">DateTime</span>? timestamp;</span><br><span class="line"></span><br><span class="line"> late ClipboardContentType contentType;</span><br><span class="line"></span><br><span class="line"> ClipboardHistoryItem({<span class="keyword">this</span>.text, <span class="keyword">this</span>.imageBytes}) {</span><br><span class="line"> <span class="keyword">if</span> (text != <span class="keyword">null</span>) {</span><br><span class="line"> contentType = ClipboardContentType.text;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (imageBytes != <span class="keyword">null</span>) {</span><br><span class="line"> contentType = ClipboardContentType.image;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> contentType = ClipboardContentType.empty;</span><br><span class="line"> }</span><br><span class="line"> timestamp = <span class="built_in">DateTime</span>.now();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> Future<<span class="keyword">void</span>> writeToClipboard() <span class="keyword">async</span> {</span><br><span class="line"> <span class="keyword">final</span> item = DataWriterItem();</span><br><span class="line"> <span class="keyword">if</span> (imageBytes != <span class="keyword">null</span>) {</span><br><span class="line"> item.add(Formats.png(imageBytes!));</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (text != <span class="keyword">null</span>) {</span><br><span class="line"> item.add(Formats.plainText(text!));</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">await</span> ClipboardWriter.instance.write([item]);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">static</span> Future<ClipboardHistoryItem> readFromClipboard() <span class="keyword">async</span> {</span><br><span class="line"> <span class="keyword">final</span> reader = <span class="keyword">await</span> ClipboardReader.readClipboard();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (reader.canProvide(Formats.plainText)) {</span><br><span class="line"> <span class="keyword">final</span> text = <span class="keyword">await</span> reader.readValue(Formats.plainText);</span><br><span class="line"> <span class="keyword">return</span> ClipboardHistoryItem(text: text);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (reader.canProvide(Formats.png)) {</span><br><span class="line"> <span class="comment">// 暂时只支持 png file</span></span><br><span class="line"> <span class="keyword">final</span> imageBytes =</span><br><span class="line"> <span class="keyword">await</span> ClipboardHistoryItem.readFile(reader, Formats.png);</span><br><span class="line"> <span class="keyword">return</span> ClipboardHistoryItem(imageBytes: imageBytes);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> ClipboardHistoryItem();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">static</span> Future<Uint8List?>? readFile(</span><br><span class="line"> ClipboardReader reader, FileFormat format) {</span><br><span class="line"> <span class="keyword">final</span> c = Completer<Uint8List?>();</span><br><span class="line"> <span class="keyword">final</span> progress = reader.getFile(format, (file) <span class="keyword">async</span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">final</span> all = <span class="keyword">await</span> file.readAll();</span><br><span class="line"> c.complete(all);</span><br><span class="line"> } <span class="keyword">catch</span> (e) {</span><br><span class="line"> c.completeError(e);</span><br><span class="line"> }</span><br><span class="line"> }, onError: (e) {</span><br><span class="line"> c.completeError(e);</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">if</span> (progress == <span class="keyword">null</span>) {</span><br><span class="line"> c.complete(<span class="keyword">null</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> c.future;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p>windows 上好像已经有了自带的粘贴板历史工具,但是 mac 上没有自带的。之前一直在使用一个叫 pastebot 的工具,后面不支持 mac 新系统了(我的是 13.2.1),然后又找到一个叫 Paste 的工具,但是是收费的。<br>我觉得这种软件实现起来应该很简单吧,所以不如动手自己做一个。</p>
<h2 id="技术选型"><a href="#技术选型" class="headerlink" title="技术选型"></a>技术选型</h2><p>考虑到主要是桌面端使用,首先想到的就是 Electron 了,之前也做过相关开发,对 web 前端开发者算是很友好了。但是总觉得 Electron 实现的效果不够优雅(总觉得太像网页了哈哈)。<br>想了一下,最终选择了 Flutter,可以同时支持桌面端和移动端(因为我后面还想做一些小工具,比如私人GPT小助手,想支持跨端)。<br>哈哈,我的想法可太多了。</p>
</summary>
<category term="flutter" scheme="https://lilong7676.github.io/categories/flutter/"/>
</entry>
<entry>
<title>v8中isolate和context的关系</title>
<link href="https://lilong7676.github.io/2023/10/27/uncategorized/v8%E4%B8%ADisolate%E5%92%8Ccontext%E7%9A%84%E5%85%B3%E7%B3%BB/"/>
<id>https://lilong7676.github.io/2023/10/27/uncategorized/v8中isolate和context的关系/</id>
<published>2023-10-27T02:23:56.000Z</published>
<updated>2023-11-14T05:56:10.757Z</updated>
<content type="html"><![CDATA[<h3 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h3><p>一个 isolate 是否可以创建多个 context ?<br>如果可以,那这多个context是否会互相影响?是否会存在全局变量污染? </p><h3 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h3><p>其实问题的真正背景是在做云函数运行时的性能优化时提出来的。所以主要是 nodejs 环境,使用的库是 (isolated-vm)[<a href="https://github.com/laverdet/isolated-vm],底层使用的就是" target="_blank" rel="noopener">https://github.com/laverdet/isolated-vm],底层使用的就是</a> v8 运行时,所以初步调研了一下。</p><h4 id="isolate"><a href="#isolate" class="headerlink" title="isolate"></a>isolate</h4><p>isolate 是 v8 实例的概念。一般情况下,isolated 和 线程(threads)是 1:1 的关系,一个 isolate 与主线程(main thread)相关联,一个 isolate 也可以与工作线程相关联(worker thread)。</p><h4 id="context"><a href="#context" class="headerlink" title="context"></a>context</h4><p>context(上下文)是 V8 中全局变量作用域的概念。粗略地说,一个 window 对象对应一个 context。例如,iframe 具有与其 parent 的 window 不同的 window 对象。因此 iframe 的 context 与 parent iframe 的 context 不同。由于这些 context 创建自己的全局变量 scope ,因此 iframe 的全局变量和原型链与 parent iframe 的全局变量和原型链是隔离的。</p><a id="more"></a><p>举个例子: </p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// main.html</span></span><br><span class="line"><html><body></span><br><span class="line"><iframe src="iframe.html"></iframe></span><br><span class="line"><script></span><br><span class="line">var foo = 1234;</span><br><span class="line">String.prototype.substr =</span><br><span class="line"> function (position, length) { // Hijacks String.prototype.substr</span><br><span class="line"> console.log(length);</span><br><span class="line"> return "hijacked";</span><br><span class="line"> };</span><br><span class="line"></script></span><br><span class="line"></body></html></span><br><span class="line"></span><br><span class="line"><span class="comment">// iframe.html</span></span><br><span class="line"><script></span><br><span class="line"><span class="built_in">console</span>.log(foo); <span class="comment">// undefined</span></span><br><span class="line"><span class="keyword">var</span> bar = <span class="string">"aaaa"</span>.substr(<span class="number">0</span>, <span class="number">2</span>); <span class="comment">// Nothing is logged.</span></span><br><span class="line"><span class="built_in">console</span>.log(bar); <span class="comment">// "aa"</span></span><br><span class="line"><<span class="regexp">/script></span></span><br></pre></td></tr></table></figure><p>总的来说,每一个 frame 有一个 window 对象,每一个 window 对象有一个 context,每一个 context 有它自己的全局变量作用范围和原型链。</p><h3 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h3><p>结论很明显了,一个 isolate 可以创建多个 context,而且 context 之间不会存在全局变量污染的情况。</p><h3 id="reference"><a href="#reference" class="headerlink" title="reference"></a>reference</h3><p>(What exactly is the difference between v8::Isolate and v8::Context?)[<a href="https://stackoverflow.com/questions/19383724/what-exactly-is-the-difference-between-v8isolate-and-v8context]" target="_blank" rel="noopener">https://stackoverflow.com/questions/19383724/what-exactly-is-the-difference-between-v8isolate-and-v8context]</a></p><p>(Design of V8 bindings)[<a href="https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/bindings/core/v8/V8BindingDesign.md]" target="_blank" rel="noopener">https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/bindings/core/v8/V8BindingDesign.md]</a></p>]]></content>
<summary type="html">
<h3 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h3><p>一个 isolate 是否可以创建多个 context ?<br>如果可以,那这多个context是否会互相影响?是否会存在全局变量污染? </p>
<h3 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h3><p>其实问题的真正背景是在做云函数运行时的性能优化时提出来的。所以主要是 nodejs 环境,使用的库是 (isolated-vm)[<a href="https://github.com/laverdet/isolated-vm],底层使用的就是" target="_blank" rel="noopener">https://github.com/laverdet/isolated-vm],底层使用的就是</a> v8 运行时,所以初步调研了一下。</p>
<h4 id="isolate"><a href="#isolate" class="headerlink" title="isolate"></a>isolate</h4><p>isolate 是 v8 实例的概念。一般情况下,isolated 和 线程(threads)是 1:1 的关系,一个 isolate 与主线程(main thread)相关联,一个 isolate 也可以与工作线程相关联(worker thread)。</p>
<h4 id="context"><a href="#context" class="headerlink" title="context"></a>context</h4><p>context(上下文)是 V8 中全局变量作用域的概念。粗略地说,一个 window 对象对应一个 context。例如,iframe 具有与其 parent 的 window 不同的 window 对象。因此 iframe 的 context 与 parent iframe 的 context 不同。由于这些 context 创建自己的全局变量 scope ,因此 iframe 的全局变量和原型链与 parent iframe 的全局变量和原型链是隔离的。</p>
</summary>
</entry>
<entry>
<title>jest中使用msw(Mock Service Worker)</title>
<link href="https://lilong7676.github.io/2023/10/26/uncategorized/jest%E4%B8%AD%E4%BD%BF%E7%94%A8msw%EF%BC%88Mock-Service-Worker%EF%BC%89/"/>
<id>https://lilong7676.github.io/2023/10/26/uncategorized/jest中使用msw(Mock-Service-Worker)/</id>
<published>2023-10-26T09:04:26.000Z</published>
<updated>2023-11-14T00:59:23.147Z</updated>
<content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>在写单元测试的时候,经常会遇到需要mock请求响应内容,如果用传统的mock方式,比如mock fetch等,会比较繁琐。有没有类似于可以提供一个后端server的能力,使得请求时可以无感知而且也不需要手动patch一些依赖等。</p><p>msw(Mock Service Worker)就提供了这个能力,<a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers" target="_blank" rel="noopener">Service Worker</a>扮演的角色其实就很类似于一个Backend Server。所以,在浏览器端,可以很自然的利用浏览器标准的Service Worker API来拦截网络请求,有现成的为啥不用呢?</p><p>即使在nodejs端,虽然没有Service Worker的概念,但是msw也是基本抹平了使用上的差异,刚好手头有个node项目需要在单测中mock网络请求,下面简单记录下msw的使用方式。</p><a id="more"></a><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><p>下面的代码基于 <code>[email protected]</code> 版本 </p><p>首先我们需要在单元测试启动的时候让 msw 的拦截能力生效,所以在 <code>jest.config.js</code> 中需要配置 <code>setupFiles 或者 setupFilesAfterEnv</code>,这个根据实际情况配置即可。我配置的值为<code>['<rootDir>/test/jest.setup.ts']</code>,接着写入 msw 初始化内容:</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// jest.setup.ts</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> server <span class="keyword">from</span> <span class="string">'./server/setup-server'</span>;</span><br><span class="line"></span><br><span class="line">beforeAll(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="comment">// 开始监听拦截请求</span></span><br><span class="line"> server.listen();</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">afterEach(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="comment">// 为了防止在运行时添加了额外的 handlers 影响到其他的单测,</span></span><br><span class="line"> <span class="comment">// 这里需要重置 handlers 为初始值</span></span><br><span class="line"> server.resetHandlers();</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">afterAll(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="comment">// 关闭拦截 & 清理工作</span></span><br><span class="line"> server.close();</span><br><span class="line">});</span><br></pre></td></tr></table></figure><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// server/setup-server.ts</span></span><br><span class="line"><span class="keyword">import</span> { setupServer } <span class="keyword">from</span> <span class="string">'msw/node'</span>;</span><br><span class="line"><span class="keyword">import</span> { handlers } <span class="keyword">from</span> <span class="string">'./handler'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 为 request handlers 提供服务端 api </span></span><br><span class="line"><span class="keyword">const</span> server = setupServer(...handlers);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> server;</span><br></pre></td></tr></table></figure><p>上面基本就是一些 msw 初始化的模板代码,开发者关心的真正核心逻辑其实就是 handlers 的编写了: </p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// server/handlers.ts</span></span><br><span class="line"><span class="keyword">import</span> { rest } <span class="keyword">from</span> <span class="string">'msw'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> handlers = [</span><br><span class="line"> rest.get(</span><br><span class="line"> <span class="string">'http://www.xxx.com/'</span>,</span><br><span class="line"> (req, res, ctx) => {</span><br><span class="line"> <span class="keyword">return</span> res(</span><br><span class="line"> ctx.json({</span><br><span class="line"> hello: <span class="string">'msw'</span></span><br><span class="line"> })</span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line"> )</span><br><span class="line">];</span><br></pre></td></tr></table></figure><p>使用方式还是很简单的,而且不局限在单元测试上,还可以在接口的 mock 上使用,使用体验还是比较友好的。</p>]]></content>
<summary type="html">
<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>在写单元测试的时候,经常会遇到需要mock请求响应内容,如果用传统的mock方式,比如mock fetch等,会比较繁琐。有没有类似于可以提供一个后端server的能力,使得请求时可以无感知而且也不需要手动patch一些依赖等。</p>
<p>msw(Mock Service Worker)就提供了这个能力,<a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers" target="_blank" rel="noopener">Service Worker</a>扮演的角色其实就很类似于一个Backend Server。所以,在浏览器端,可以很自然的利用浏览器标准的Service Worker API来拦截网络请求,有现成的为啥不用呢?</p>
<p>即使在nodejs端,虽然没有Service Worker的概念,但是msw也是基本抹平了使用上的差异,刚好手头有个node项目需要在单测中mock网络请求,下面简单记录下msw的使用方式。</p>
</summary>
</entry>
<entry>
<title>jest单元测试中如何mock模块</title>
<link href="https://lilong7676.github.io/2023/10/23/javascript/jest%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E4%B8%AD%E5%A6%82%E4%BD%95mock%E6%A8%A1%E5%9D%97/"/>
<id>https://lilong7676.github.io/2023/10/23/javascript/jest单元测试中如何mock模块/</id>
<published>2023-10-23T06:02:52.000Z</published>
<updated>2023-10-24T01:40:43.497Z</updated>
<content type="html"><![CDATA[<h3 id="问题背景"><a href="#问题背景" class="headerlink" title="问题背景"></a>问题背景</h3><blockquote><p>在给一个nodejs express项目写jest单元测试的过程中,发现有些依赖在单元测试代码中会报错,原因是某些配置没有加载,当然在正常的运行中肯定是ok的,只有在单元测试中会出现。然而这些配置的加载逻辑是在所依赖的框架内部写的,而且有些配置是线上获取的配置,只有在内网环境才能获取到。所以问题就来了,怎么在jest中mock这个依赖(的配置加载逻辑)?</p></blockquote><a id="more"></a><h3 id="解决方式"><a href="#解决方式" class="headerlink" title="解决方式"></a>解决方式</h3><p>经过查阅<a href="https://jestjs.io/docs/manual-mocks#mocking-node-modules" target="_blank" rel="noopener">jest文档</a>,下面简单记录下核心点。<br>比如我想mock一个依赖名为 <code>@foo/bar</code>,那么可以在<code>node_modules</code>同级的地方新建一个文件夹 <code>__mocks__</code>,并且在其下建立<code>@foo/bar.js</code>。 </p><p>此时jest在执行单元测试的过程中,如果遇到如<code>import bar from '@foo/bar'</code>时,就会从 <code>__mocks__</code>中的对应目录中加载该依赖。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── __mocks__/</span><br><span class="line">│ └── @foo/</span><br><span class="line">│ └── bar.js</span><br><span class="line">├── node_modules</span><br><span class="line">├── src</span><br><span class="line">└── ...</span><br></pre></td></tr></table></figure><p>此时,就可以针对依赖的逻辑进行随心所欲的修改(mock)了。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// __mocks__/@foo/bar.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = {</span><br><span class="line"> <span class="comment">// 逻辑重写</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>那么此时问题又来了,一个模块会导出很多方法,只想覆盖某一个导出的方法逻辑,其他保持不变,如何实现?</p><p>也很简单,使用<code>jest.requireActual(moduleName)</code>即可:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// __mocks__/@foo/bar.js</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 拿到真实的模块导出</span></span><br><span class="line"><span class="keyword">const</span> bar = jest.requireActual(<span class="string">'@foo/bar'</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 重新导致 mock 后的模块</span></span><br><span class="line"><span class="built_in">module</span>.exports = {</span><br><span class="line"> <span class="comment">// 其他导出保持不变</span></span><br><span class="line"> ...bar,</span><br><span class="line"> <span class="comment">// 只重写部分导出的逻辑</span></span><br><span class="line"> getConfig() {</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="自定义mock模块的逻辑"><a href="#自定义mock模块的逻辑" class="headerlink" title="自定义mock模块的逻辑"></a>自定义mock模块的逻辑</h3><p>上面我们mock了<code>@foo/bar</code>模块的逻辑,注意到这个mock会针对所有的测试文件生效,无需显式的调用<code>jest.mock('@foo/bar')</code>。 那么如果我想要某一个测试文件单独使用不同逻辑的<code>@foo/bar</code>,怎么实现?<br>很简单,jest同样提供了简单的 api,看下面这个例子就知道了:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// my.test.ts</span></span><br><span class="line"><span class="comment">// 使用 jest.mock('moduleName', implFactory) api 来单独mock模块逻辑</span></span><br><span class="line">jest.mock(<span class="string">'@foo.bar'</span>, () => {</span><br><span class="line"> getConfig() {</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><a href="https://jestjs.io/docs/manual-mocks" target="_blank" rel="noopener">jest manual-mocks</a></li></ul>]]></content>
<summary type="html">
<h3 id="问题背景"><a href="#问题背景" class="headerlink" title="问题背景"></a>问题背景</h3><blockquote>
<p>在给一个nodejs express项目写jest单元测试的过程中,发现有些依赖在单元测试代码中会报错,原因是某些配置没有加载,当然在正常的运行中肯定是ok的,只有在单元测试中会出现。然而这些配置的加载逻辑是在所依赖的框架内部写的,而且有些配置是线上获取的配置,只有在内网环境才能获取到。所以问题就来了,怎么在jest中mock这个依赖(的配置加载逻辑)?</p>
</blockquote>
</summary>
<category term="javascript" scheme="https://lilong7676.github.io/categories/javascript/"/>
<category term="unit test" scheme="https://lilong7676.github.io/tags/unit-test/"/>
</entry>
<entry>
<title>nodejs中 的 AsyncLocalStorage</title>
<link href="https://lilong7676.github.io/2023/07/21/javascript/nodejs%E4%B8%AD%E7%9A%84-AsyncLocalStorage/"/>
<id>https://lilong7676.github.io/2023/07/21/javascript/nodejs中的-AsyncLocalStorage/</id>
<published>2023-07-21T06:47:16.000Z</published>
<updated>2023-10-20T09:47:16.979Z</updated>
<content type="html"><![CDATA[<h2 id="开门见山:什么是-AsyncLocalStorage"><a href="#开门见山:什么是-AsyncLocalStorage" class="headerlink" title="开门见山:什么是 AsyncLocalStorage"></a>开门见山:什么是 AsyncLocalStorage</h2><p>根据 Node.js <a href="https://nodejs.org/docs/latest-v14.x/api/async_hooks.html#async_hooks_class_asynclocalstorage" target="_blank" rel="noopener">官方文档</a>:”This class is used to create asynchronous state within callbacks and promise chains. It allows storing data throughout the lifetime of a web request or any other asynchronous duration. It is similar to thread-local storage in other languages.”,</p><p>为了进一步简化解释,AsyncLocalStorage 允许你在执行异步函数时存储状态,然后使其可用于该函数中的所有代码路径。</p><a id="more"></a><h3 id="场景:一个案例引入"><a href="#场景:一个案例引入" class="headerlink" title="场景:一个案例引入"></a>场景:一个案例引入</h3><p>如何实现请求的链路追踪?就是说如何确定一个Request从发起到返回处理结束过程中的所有调用路径?一般是通过发起方携带一个唯一请求标识,可以叫traceId,以此来实现链路追踪,实现日志溯源等。</p><h3 id="如何实现请求的链路追踪机制?"><a href="#如何实现请求的链路追踪机制?" class="headerlink" title="如何实现请求的链路追踪机制?"></a>如何实现请求的链路追踪机制?</h3><h4 id="方案一:全局变量-globalTraceId-?"><a href="#方案一:全局变量-globalTraceId-?" class="headerlink" title="方案一:全局变量 globalTraceId ?"></a>方案一:全局变量 globalTraceId ?</h4><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// Raw Node.js HTTP server</span></span><br><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">'http'</span>);</span><br><span class="line"><span class="keyword">let</span> globalTraceId <span class="comment">// 全局变量</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 0. 处理请求的方法</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">handleRequest</span>(<span class="params">req, res</span>) </span>{</span><br><span class="line"> <span class="comment">// 生成唯一 traceId,每次请求进入,复写 globalTraceId 的值</span></span><br><span class="line"> globalTraceId = generateTraceId()</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 检查用户cookie是否有效</span></span><br><span class="line"> cookieValidator().then(<span class="function">(<span class="params">res</span>) =></span> {</span><br><span class="line"> <span class="comment">// 校验成功,返回给用户他需要的内容</span></span><br><span class="line"> res.writeHead(<span class="number">200</span>, { <span class="string">'Content-Type'</span>: <span class="string">'text/plain'</span> });</span><br><span class="line"> res.write(<span class="string">'Congrats! Your damn cookie is the best one!'</span>);</span><br><span class="line"> res.end();</span><br><span class="line"> }).catch(<span class="function">(<span class="params">err</span>) =></span> {</span><br><span class="line"> <span class="comment">// 把 traceId 连同 error 上报给异常监控系统</span></span><br><span class="line"> reportError(err, globalTraceId)</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 写状态码500和错误信息等</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> });</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 1. 创建 server </span></span><br><span class="line"><span class="keyword">const</span> server = http.createServer(<span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> handleRequest(req, res)</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. 让 server 和 port:3000 建立 socket 链接,持续接收端口信息</span></span><br><span class="line">server.listen(<span class="number">3000</span>, () => {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Server listening on port 3000'</span>);</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>这里的会出现的问题是由于nodejs单线程执行,第一个请求进来的时候,globalTraceId被赋值,然后进入到异步检查用户cookie的逻辑中,此时node的main stack是空的,事件循环会执行处理下一个请求。此时就会导致globalTraceId被覆写,导致第一个请求的异步检查中错误上报的globalTraceId是错误的。</p><h4 id="方案二:直接透传参数"><a href="#方案二:直接透传参数" class="headerlink" title="方案二:直接透传参数"></a>方案二:直接透传参数</h4><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">'http'</span>);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">handleRequest</span>(<span class="params">req, res</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> traceId = req.headers[<span class="string">'x-trace-id'</span>] || generateTraceId();</span><br><span class="line"> <span class="comment">// 把 traceId 写入 req 这个 object,将参数一路带下去</span></span><br><span class="line"> req.traceId = traceId;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 同上</span></span><br><span class="line"> cookieValidator().then(<span class="function">(<span class="params">result</span>) =></span> {</span><br><span class="line"> <span class="comment">// 校验成功,返回给用户他需要的内容</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }).catch(<span class="function">(<span class="params">err</span>) =></span> {</span><br><span class="line"> <span class="comment">// 上报 traceId</span></span><br><span class="line"> reportError(err, req.traceId)</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 写状态码500和错误信息等</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> });</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">cookieValidator</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="comment">// do someting</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }, <span class="number">1000</span>);</span><br><span class="line"> });</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 此后省略监听等操作</span></span><br><span class="line"><span class="comment">// ...</span></span><br></pre></td></tr></table></figure><p>这种方式简单粗暴,的确可行。问题就是每次都得透传参数,写起来有点麻烦。</p><h3 id="方案三:使用-AsyncLocalStorage-实现"><a href="#方案三:使用-AsyncLocalStorage-实现" class="headerlink" title="方案三:使用 AsyncLocalStorage 实现"></a>方案三:使用 AsyncLocalStorage 实现</h3><p>下面这个例子,实现了一个日志追踪的最简逻辑。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> http <span class="keyword">from</span> <span class="string">"http"</span>;</span><br><span class="line"><span class="keyword">import</span> { AsyncLocalStorage } <span class="keyword">from</span> <span class="string">"async_hooks"</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 初始化一个 AsyncLocalStorage 实例,可以初始化多个实例,每一个实例都是互相独立的</span></span><br><span class="line"><span class="keyword">const</span> myAls = <span class="keyword">new</span> AsyncLocalStorage();</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">logWithId</span>(<span class="params">msg</span>) </span>{</span><br><span class="line"> <span class="comment">// 取出当前的als上下文</span></span><br><span class="line"> <span class="keyword">const</span> context = myAls.getStore();</span><br><span class="line"> <span class="keyword">const</span> id = context.reqId;</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">`<span class="subst">${id !== <span class="literal">undefined</span> ? id : <span class="string">"-"</span>}</span>:`</span>, msg);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> reqId = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">http</span><br><span class="line"> .createServer(<span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> <span class="comment">// 我们创建一个模拟的上下文,这个上下文会被传递给 als 回调函数</span></span><br><span class="line"> <span class="keyword">const</span> myContext = {</span><br><span class="line"> reqId: reqId++,</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> 在提供的上下文环境中同步执行一个函数并返回其返回值。这里的 store 在回调函数外部是不可访问的,</span></span><br><span class="line"><span class="comment"> 但是在这个回调函数内部创建的任何异步操作都可以访问 store。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"> myAls.run(myContext, () => {</span><br><span class="line"> logWithId(<span class="string">"start"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 模拟一个异步操作</span></span><br><span class="line"> setImmediate(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> logWithId(<span class="string">"end"</span>);</span><br><span class="line"> res.end();</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> });</span><br><span class="line"> })</span><br><span class="line"> .listen(<span class="number">8080</span>);</span><br><span class="line"></span><br><span class="line">http.get(<span class="string">"http://localhost:8080"</span>);</span><br><span class="line">http.get(<span class="string">"http://localhost:8080"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 打印</span></span><br><span class="line"><span class="comment">// 0: start</span></span><br><span class="line"><span class="comment">// 1: start</span></span><br><span class="line"><span class="comment">// 0: end</span></span><br><span class="line"><span class="comment">// 1: end</span></span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>主要是简单了解了下 nodejs 中 AsyncLocalStorage 的初步使用,当然更深层次的理解还需要具体的业务场景来体验。</p>]]></content>
<summary type="html">
<h2 id="开门见山:什么是-AsyncLocalStorage"><a href="#开门见山:什么是-AsyncLocalStorage" class="headerlink" title="开门见山:什么是 AsyncLocalStorage"></a>开门见山:什么是 AsyncLocalStorage</h2><p>根据 Node.js <a href="https://nodejs.org/docs/latest-v14.x/api/async_hooks.html#async_hooks_class_asynclocalstorage" target="_blank" rel="noopener">官方文档</a>:”This class is used to create asynchronous state within callbacks and promise chains. It allows storing data throughout the lifetime of a web request or any other asynchronous duration. It is similar to thread-local storage in other languages.”,</p>
<p>为了进一步简化解释,AsyncLocalStorage 允许你在执行异步函数时存储状态,然后使其可用于该函数中的所有代码路径。</p>
</summary>
<category term="javascript" scheme="https://lilong7676.github.io/categories/javascript/"/>
<category term="AsyncLocalStorage" scheme="https://lilong7676.github.io/tags/AsyncLocalStorage/"/>
</entry>
<entry>
<title>如何通过 SSH 在服务器上远程执行命令</title>
<link href="https://lilong7676.github.io/2023/03/14/Shell%E8%84%9A%E6%9C%AC/%E5%A6%82%E4%BD%95%E9%80%9A%E8%BF%87-SSH-%E5%9C%A8%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%B8%8A%E8%BF%9C%E7%A8%8B%E6%89%A7%E8%A1%8C%E5%91%BD%E4%BB%A4/"/>
<id>https://lilong7676.github.io/2023/03/14/Shell脚本/如何通过-SSH-在服务器上远程执行命令/</id>
<published>2023-03-14T02:38:04.000Z</published>
<updated>2023-06-08T06:26:43.567Z</updated>
<content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>最近买了一个腾讯云服务器,主要用来托管个人项目,经常执行的操作是ssh登陆到服务器,然后执行某些命令。这里有两个步骤: </p><ol><li>打开ssh登陆软件,然后登陆到服务器</li><li>执行相关命令</li><li>关闭连接</li></ol><p>那有没有办法一气呵成呢?一个命令完成这两个操作,查了下资料,当然可以:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh -t username@host <span class="string">'commands'</span></span><br></pre></td></tr></table></figure><p>比如我想看下 root 目录有哪些文件:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh -i ~/.ssh/tencent_ecs [email protected] <span class="string">"ls -a; echo \"done\""</span></span><br></pre></td></tr></table></figure><p><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20230314105027.png?x-oss-process=image/resize,w_800" alt></p><a id="more"></a><h2 id="reference"><a href="#reference" class="headerlink" title="reference"></a>reference</h2><p><a href="https://www.cnet.com/tech/computing/ssh-tip-send-commands-remotely/" target="_blank" rel="noopener">SSH tip: Send commands remotely</a></p>]]></content>
<summary type="html">
<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>最近买了一个腾讯云服务器,主要用来托管个人项目,经常执行的操作是ssh登陆到服务器,然后执行某些命令。这里有两个步骤: </p>
<ol>
<li>打开ssh登陆软件,然后登陆到服务器</li>
<li>执行相关命令</li>
<li>关闭连接</li>
</ol>
<p>那有没有办法一气呵成呢?一个命令完成这两个操作,查了下资料,当然可以:</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh -t username@host <span class="string">'commands'</span></span><br></pre></td></tr></table></figure>
<p>比如我想看下 root 目录有哪些文件:</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh -i ~/.ssh/tencent_ecs [email protected] <span class="string">"ls -a; echo \"done\""</span></span><br></pre></td></tr></table></figure>
<p><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20230314105027.png?x-oss-process=image/resize,w_800" alt></p>
</summary>
<category term="Shell脚本" scheme="https://lilong7676.github.io/categories/Shell%E8%84%9A%E6%9C%AC/"/>
<category term="shell" scheme="https://lilong7676.github.io/tags/shell/"/>
</entry>
<entry>
<title>使用peerjs实现一个基于webrtc的简单远程网络摄像头</title>
<link href="https://lilong7676.github.io/2023/03/03/uncategorized/%E4%BD%BF%E7%94%A8peerjs%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E5%9F%BA%E4%BA%8Ewebrtc%E7%9A%84%E7%AE%80%E5%8D%95%E8%BF%9C%E7%A8%8B%E7%BD%91%E7%BB%9C%E6%91%84%E5%83%8F%E5%A4%B4/"/>
<id>https://lilong7676.github.io/2023/03/03/uncategorized/使用peerjs实现一个基于webrtc的简单远程网络摄像头/</id>
<published>2023-03-03T01:38:18.000Z</published>
<updated>2023-06-08T06:26:43.566Z</updated>
<content type="html"><![CDATA[<blockquote><p>仓库地址:<a href="https://github.com/lilong7676/cue-live" target="_blank" rel="noopener">https://github.com/lilong7676/cue-live</a><br>技术栈:nextjs + webrtc </p></blockquote>]]></content>
<summary type="html">
<blockquote>
<p>仓库地址:<a href="https://github.com/lilong7676/cue-live" target="_blank" rel="noopener">https://github.com/lilong7676/cue-live<
</summary>
</entry>
<entry>
<title>如何构建并发布自己的docker镜像 </title>
<link href="https://lilong7676.github.io/2023/02/28/uncategorized/%E5%A6%82%E4%BD%95%E6%9E%84%E5%BB%BA%E5%B9%B6%E5%8F%91%E5%B8%83%E8%87%AA%E5%B7%B1%E7%9A%84docker%E9%95%9C%E5%83%8F/"/>
<id>https://lilong7676.github.io/2023/02/28/uncategorized/如何构建并发布自己的docker镜像/</id>
<published>2023-02-28T06:28:21.000Z</published>
<updated>2023-06-08T06:26:43.567Z</updated>
<content type="html"><![CDATA[<p>最近想在云服务器上部署一个自己的 nginx 服务,通过docker 部署最方便。之前也发布过自己的docker镜像,但是时间长了老是忘记具体的命令是啥,这里做一个笔记记录下。 </p><h2 id="构建镜像"><a href="#构建镜像" class="headerlink" title="构建镜像"></a>构建镜像</h2><p><code>docker cli</code> 提供了两个命令来完成构建\发布:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker build <span class="comment"># 构建镜像</span></span><br><span class="line">docker pull/push <span class="comment"># 拉取/推送镜像</span></span><br></pre></td></tr></table></figure><p>整体流程参考下图: </p><p><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20230228143743.png?x-oss-process=image/resize,w_800" alt="docker构建发布流程"></p><h3 id="docker-build-构建镜像"><a href="#docker-build-构建镜像" class="headerlink" title="docker build 构建镜像"></a>docker build 构建镜像</h3><p>首先命令格式<a href="https://docs.docker.com/engine/reference/commandline/build/" target="_blank" rel="noopener">来自文档</a>:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker build [OPTIONS] PATH | URL | -</span><br></pre></td></tr></table></figure><p><code>docker build</code>是利用 Dockerfile 内的指令来构建镜像。通常我们会把 Dockerfile 也加入版本管理,所以对镜像的修改操作也会有记录,这相比 <code>docker commit</code>的黑箱操作好很多。Dockerfile 就不细讲了。</p><a id="more"></a><p>下面就以发布一个自定义配置的 nginx 镜像为例子,记录一下操作步骤:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">mkdir nginx && <span class="built_in">cd</span> nginx</span><br><span class="line">vi Dockerfile</span><br><span class="line">``` </span><br><span class="line">输入如下内容:</span><br><span class="line">![](https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20230228145132.png?x-oss-process=image/resize,w_800) </span><br><span class="line"></span><br><span class="line">构建镜像:</span><br><span class="line">```bash</span><br><span class="line">docker build -t lilong7676/nginx:v1 .</span><br></pre></td></tr></table></figure><p>也可以通过以下命令打标签:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">docker tag <span class="built_in">local</span>-image:tagname new-repo:tagname</span><br><span class="line"><span class="comment"># 即</span></span><br><span class="line">docker tag nginx:v1 lilong7676/nginx:v1</span><br></pre></td></tr></table></figure><p>构建完成后,查看本地镜像列表,可以看到已经打上了标签v1:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker image list</span><br></pre></td></tr></table></figure><p><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20230228145327.png?x-oss-process=image/resize,w_800" alt></p><h2 id="发布镜像"><a href="#发布镜像" class="headerlink" title="发布镜像"></a>发布镜像</h2><p>登陆 <a href="https://hub.docker.com/" target="_blank" rel="noopener">docker hub</a>,创建仓库<br><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20230228150507.png?x-oss-process=image/resize,w_800" alt></p><p>发布镜像:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker push lilong7676/nginx:v1</span><br></pre></td></tr></table></figure><p><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20230228151436.png?x-oss-process=image/resize,w_800" alt> </p><p>docker hub查看:</p><p><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20230228151455.png?x-oss-process=image/resize,w_800" alt></p>]]></content>
<summary type="html">
<p>最近想在云服务器上部署一个自己的 nginx 服务,通过docker 部署最方便。之前也发布过自己的docker镜像,但是时间长了老是忘记具体的命令是啥,这里做一个笔记记录下。 </p>
<h2 id="构建镜像"><a href="#构建镜像" class="headerlink" title="构建镜像"></a>构建镜像</h2><p><code>docker cli</code> 提供了两个命令来完成构建\发布:</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker build <span class="comment"># 构建镜像</span></span><br><span class="line">docker pull/push <span class="comment"># 拉取/推送镜像</span></span><br></pre></td></tr></table></figure>
<p>整体流程参考下图: </p>
<p><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20230228143743.png?x-oss-process=image/resize,w_800" alt="docker构建发布流程"></p>
<h3 id="docker-build-构建镜像"><a href="#docker-build-构建镜像" class="headerlink" title="docker build 构建镜像"></a>docker build 构建镜像</h3><p>首先命令格式<a href="https://docs.docker.com/engine/reference/commandline/build/" target="_blank" rel="noopener">来自文档</a>:</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker build [OPTIONS] PATH | URL | -</span><br></pre></td></tr></table></figure>
<p><code>docker build</code>是利用 Dockerfile 内的指令来构建镜像。通常我们会把 Dockerfile 也加入版本管理,所以对镜像的修改操作也会有记录,这相比 <code>docker commit</code>的黑箱操作好很多。Dockerfile 就不细讲了。</p>
</summary>
</entry>
<entry>
<title>编译并运行 Chrome Devtools Frontend</title>
<link href="https://lilong7676.github.io/2023/01/12/javascript/%E7%BC%96%E8%AF%91%E5%B9%B6%E8%BF%90%E8%A1%8C-Chrome-Devtools-Frontend/"/>
<id>https://lilong7676.github.io/2023/01/12/javascript/编译并运行-Chrome-Devtools-Frontend/</id>
<published>2023-01-12T01:59:51.000Z</published>
<updated>2023-06-08T06:26:43.567Z</updated>
<content type="html"><![CDATA[<blockquote><h4 id="Chrome-DevTools-工作原理简单介绍"><a href="#Chrome-DevTools-工作原理简单介绍" class="headerlink" title="Chrome DevTools 工作原理简单介绍"></a>Chrome DevTools 工作原理简单介绍</h4></blockquote><p>Devtools 不只是我们在浏览器中按F12所打开的调试界面,它是 Chromium 的一部分,在 Electron 中也有集成。 </p><p>Devtools 主要由四部分组成: </p><ul><li><a href="https://github.com/ChromeDevTools/devtools-frontend" target="_blank" rel="noopener">Frontend</a>: 调试器前端,默认由 Chromium 内核层集成,DevTools Frontend 是一个 Web 应用程序,就是 JS + CSS + HTML 组成的;</li><li><a href="https://github.com/ChromeDevTools/devtools-frontend/tree/main/v8" target="_blank" rel="noopener">Backend</a>: 调试器后端,广义上主要由 Chromium、V8 或 Node.js(v8)提供,用来与 Frontend 连接与通信;</li><li><a href="https://chromedevtools.github.io/devtools-protocol/" target="_blank" rel="noopener">Protocol</a>: 通信协议,调试器前端和后端通过此协议通信,协议遵循 <a href="https://www.jianshu.com/p/49cd5c9a1664" target="_blank" rel="noopener">JSON RPC2.0</a> 规范;</li><li><a href="https://github.com/ChromeDevTools/devtools-frontend/blob/b29da32422767c87749ca8132c2c0c8f378da6d7/front_end/core/sdk/Connections.ts#L13" target="_blank" rel="noopener">Connection</a>: 连接层,目前有 <code>WebSocketConnection</code>、<code>StubConnection</code>、<code>MainConnection</code> 三种连接方式,查看其他<a href="https://zhaomenghuan.js.org/blog/chrome-devtools-frontend-analysis-of-principle.html#devtools-%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86" target="_blank" rel="noopener">资料</a>,也有说是消息通道,即 Embedder Channel、WebSocket Channel、Chrome Extensions Channel、USB/ADB Channel,但是我在当前的版本没有找到这部分代码; </li></ul><p><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20230112104133.png?x-oss-process=image/resize,w_800" alt="devtools 图示"></p><a id="more"></a><blockquote><h4 id="Devtools-Frontend-编译"><a href="#Devtools-Frontend-编译" class="headerlink" title="Devtools Frontend 编译"></a>Devtools Frontend 编译</h4></blockquote><ol><li><p>clone devtools-frontend 源码</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git <span class="built_in">clone</span> [email protected]:ChromeDevTools/devtools-frontend.git</span><br></pre></td></tr></table></figure></li><li><p>安装 depot_tools</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 必须安装此工具才能编译 devtools-frontend</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 下载 depot_tools 源码</span></span><br><span class="line">$ git <span class="built_in">clone</span> https://chromium.googlesource.com/chromium/tools/depot_tools.git</span><br><span class="line"></span><br><span class="line"><span class="comment"># 将其添加到本机环境变量中</span></span><br><span class="line">$ <span class="built_in">export</span> PATH=<span class="variable">$PATH</span>:/path/to/depot_tools</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使环境变量修改立即生效</span></span><br><span class="line">$ <span class="built_in">source</span> ~/.bash_profile</span><br></pre></td></tr></table></figure></li><li><p>只检出 DevTools frontend 的代码,因为 DevTools Frontend 可独立于 Chromium 作为独立项目构建,并且如果不执行这一步,编译会报错:<code>gn.py: Could not find checkout in any parent of the current path.This must be run inside a checkout.</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ mkdir devtools</span><br><span class="line">$ <span class="built_in">cd</span> devtools</span><br><span class="line">$ fetch devtools-frontend</span><br><span class="line"><span class="comment"># 注意,上一步可能会由于网络问题导致 git 相关操作失败,可通过配置 git 和终端代理解决</span></span><br></pre></td></tr></table></figure></li><li><p>构建</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">cd</span> devtools-frontend</span><br><span class="line">$ gn gen out/Default</span><br><span class="line">$ autoninja -C out/Default</span><br><span class="line"><span class="comment"># 构建产物在 devtools-frontend/devtools/devtools-frontend/out/Default 文件夹中</span></span><br></pre></td></tr></table></figure></li></ol><blockquote><h4 id="运行-devtools-frontend-产物"><a href="#运行-devtools-frontend-产物" class="headerlink" title="运行 devtools frontend 产物"></a>运行 devtools frontend 产物</h4></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 进入到构建产物目录</span></span><br><span class="line">$ <span class="built_in">cd</span> out/Default/gen/front_end</span><br><span class="line"></span><br><span class="line"><span class="comment"># serve 构建产物</span></span><br><span class="line">$ npx http-server</span><br></pre></td></tr></table></figure><p>查看构建产物目录,这几个 html 其实就是调试前端面板:<br><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20230112111618.png?x-oss-process=image/resize,w_800,h_600" alt="构建产物目录"></p><p>比如打开 <code>http://127.0.0.1:8080/devtools_app.html</code>,就是我们熟悉的 web 页面调试界面:<br><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20230112111832.png?x-oss-process=image/resize,w_800" alt> </p><p>也可以自己尝试打开其他html,如 <code>http://127.0.0.1:8080/node_app.html</code>,<code>http://127.0.0.1:8080/inspector.html</code>等 </p><blockquote><h4 id="结合-node-inspect-来调试-nodejs-代码"><a href="#结合-node-inspect-来调试-nodejs-代码" class="headerlink" title="结合 node inspect 来调试 nodejs 代码"></a>结合 node inspect 来调试 nodejs 代码</h4></blockquote><p>如<a href="https://lilong7676.github.io/2023/01/10/uncategorized/%E4%BD%BF%E7%94%A8-ChromeRemoteInterface-%E8%B0%83%E8%AF%95-nodejs-%E4%BB%A3%E7%A0%81/#%E5%87%86%E5%A4%87%E5%BE%85%E8%B0%83%E8%AF%95%E7%9A%84-nodejs-%E5%AE%9E%E4%BE%8B">上一篇</a>所讲,先准备带调试的 nodejs 实例:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ node --inspect=9222 <span class="keyword">for</span>-inspect.js</span><br></pre></td></tr></table></figure><p>然后访问 <code>http://localhost:9222/json</code> 获取到 <code>devtoolsFrontendUrl</code>,类似于 <code>devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=localhost:9222/8c4626a1-2903-4da8-9759-5de3c74c3a35</code> </p><p>只需要将其 <code>query</code> 参数拼接到 <code>http://127.0.0.1:8080/***.html</code>后面即可,拼接后类似于 <code>http://127.0.0.1:8080/inspector.html?experiments=true&v8only=true&ws=localhost:9222/8c4626a1-2903-4da8-9759-5de3c74c3a35</code>,在浏览器中打开后的效果如图:<br><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20230112135350.png?x-oss-process=image/resize,w_800" alt></p><blockquote><h4 id="从-devtools-中查看-Chrome-Devtools-Protocol-🧐"><a href="#从-devtools-中查看-Chrome-Devtools-Protocol-🧐" class="headerlink" title="从 devtools 中查看 Chrome Devtools Protocol 🧐"></a>从 devtools 中查看 Chrome Devtools Protocol 🧐</h4></blockquote><p>在 <code>Settings -> Experiments</code> 中打开 <code>Protocol Monitor</code> 选项:<br><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20230112140245.png?x-oss-process=image/resize,w_800" alt="打开 Protocol Monitor 选项"></p><p>显示 <code>Protocol Monitor</code>:<img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20230112140441.png?x-oss-process=image/resize,w_800" alt></p><p><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20230112141045.png?x-oss-process=image/resize,w_800" alt="CDP"></p><blockquote><h4 id="定制调试面板的显示,只需要-console、source、network-面板"><a href="#定制调试面板的显示,只需要-console、source、network-面板" class="headerlink" title="定制调试面板的显示,只需要 console、source、network 面板"></a>定制调试面板的显示,只需要 console、source、network 面板</h4></blockquote><blockquote><p>很多情况下,我们想自定义调试界面需要显示哪些面板。比如我只需要 console、source、network 这三个面板,不需要其他的,而且全部集成也不太现实,因为加载速度太慢。 </p></blockquote><p>观察其产物的js,这里以 <code>inspector.html</code> 解释:</p><p><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20230112165907.png?x-oss-process=image/resize,w_800" alt="inspector.html"></p><p>说明其入口 js 为 <code>./entrypoints/inspector/inspector.ts</code>,查看其原始代码:<br><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20230112172446.png?x-oss-process=image/resize,w_800" alt><br>这里能看出来,<code>inspector_app</code> 相比 <code>devtools_app</code> 增加了 <code>screencast</code> 面板的功能。 <img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20230112165907.png?x-oss-process%3Dimage%2Fresize%2Cw_800" alt="Alt text"><br>所以我们大致得出结论,如果想要定制调试面板的现实,只需要修改对应 <code>entrypoints/***_app</code> 下的代码即可。</p><blockquote><h4 id="改造-devtools-app"><a href="#改造-devtools-app" class="headerlink" title="改造 devtools_app"></a>改造 devtools_app</h4></blockquote><p>通过查看 devtools_app 的界面,因为我们只需要 console、source、network 三个面板,所以需要移除多余的调试面板:<br><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20230112173848.png?x-oss-process=image/resize,w_800" alt></p><h5 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h5><ul><li><a href="https://www.51cto.com/article/716801.html" target="_blank" rel="noopener">玩转 Chrome DevTools,定制自己的调试工具</a></li><li><a href="https://zhaomenghuan.js.org/blog/chrome-devtools-frontend-analysis-of-principle.html#%E6%BA%90%E7%A0%81%E4%B8%8B%E8%BD%BD%E5%8F%8A%E7%BC%96%E8%AF%91" target="_blank" rel="noopener">Chrome DevTools Frontend 运行原理浅析</a></li></ul>]]></content>
<summary type="html">
<blockquote>
<h4 id="Chrome-DevTools-工作原理简单介绍"><a href="#Chrome-DevTools-工作原理简单介绍" class="headerlink" title="Chrome DevTools 工作原理简单介绍"></a>Chrome DevTools 工作原理简单介绍</h4></blockquote>
<p>Devtools 不只是我们在浏览器中按F12所打开的调试界面,它是 Chromium 的一部分,在 Electron 中也有集成。 </p>
<p>Devtools 主要由四部分组成: </p>
<ul>
<li><a href="https://github.com/ChromeDevTools/devtools-frontend" target="_blank" rel="noopener">Frontend</a>: 调试器前端,默认由 Chromium 内核层集成,DevTools Frontend 是一个 Web 应用程序,就是 JS + CSS + HTML 组成的;</li>
<li><a href="https://github.com/ChromeDevTools/devtools-frontend/tree/main/v8" target="_blank" rel="noopener">Backend</a>: 调试器后端,广义上主要由 Chromium、V8 或 Node.js(v8)提供,用来与 Frontend 连接与通信;</li>
<li><a href="https://chromedevtools.github.io/devtools-protocol/" target="_blank" rel="noopener">Protocol</a>: 通信协议,调试器前端和后端通过此协议通信,协议遵循 <a href="https://www.jianshu.com/p/49cd5c9a1664" target="_blank" rel="noopener">JSON RPC2.0</a> 规范;</li>
<li><a href="https://github.com/ChromeDevTools/devtools-frontend/blob/b29da32422767c87749ca8132c2c0c8f378da6d7/front_end/core/sdk/Connections.ts#L13" target="_blank" rel="noopener">Connection</a>: 连接层,目前有 <code>WebSocketConnection</code>、<code>StubConnection</code>、<code>MainConnection</code> 三种连接方式,查看其他<a href="https://zhaomenghuan.js.org/blog/chrome-devtools-frontend-analysis-of-principle.html#devtools-%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86" target="_blank" rel="noopener">资料</a>,也有说是消息通道,即 Embedder Channel、WebSocket Channel、Chrome Extensions Channel、USB/ADB Channel,但是我在当前的版本没有找到这部分代码; </li>
</ul>
<p><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20230112104133.png?x-oss-process=image/resize,w_800" alt="devtools 图示"></p>
</summary>
<category term="javascript" scheme="https://lilong7676.github.io/categories/javascript/"/>
<category term="ChromeDevTools" scheme="https://lilong7676.github.io/tags/ChromeDevTools/"/>
</entry>
<entry>
<title>使用 ChromeRemoteInterface 调试 nodejs 代码</title>
<link href="https://lilong7676.github.io/2023/01/10/javascript/%E4%BD%BF%E7%94%A8-ChromeRemoteInterface-%E8%B0%83%E8%AF%95-nodejs-%E4%BB%A3%E7%A0%81/"/>
<id>https://lilong7676.github.io/2023/01/10/javascript/使用-ChromeRemoteInterface-调试-nodejs-代码/</id>
<published>2023-01-10T06:59:29.000Z</published>
<updated>2023-06-08T06:26:43.566Z</updated>
<content type="html"><![CDATA[<blockquote><p>最近在调研如何远程调试一个 nodejs 进程,大致调研路径是 Chome DevTolls -> Chrome debugging protocol -> chrome-remote-interface,本文重点介绍下 chrome-remote-interface 的简单使用。 </p></blockquote><p>根据其<a href="https://github.com/cyrus-and/chrome-remote-interface" target="_blank" rel="noopener">文档</a>,这个工具主要是提供一个 js api,在连接到对应的服务端(要调试的端)后,通过 Chrome Debugging Protocol 与之交互。 </p><p>所以其基本的使用分为以下几个步骤: </p><ul><li>启动 nodejs 实例,并打开调试模式, eg. <code>node --inspect-brk=9222 for-insepct.js</code></li><li>通过 CDP 连接到远程调试实例</li><li>通过 CDP Client 操作远程调试实例</li></ul><a id="more"></a><h4 id="准备待调试的-nodejs-实例"><a href="#准备待调试的-nodejs-实例" class="headerlink" title="准备待调试的 nodejs 实例"></a>准备待调试的 nodejs 实例</h4><p>先新建 <code>for-inspect.js</code>,可以写入任意可执行的代码: </p><p><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20230111114509.png" alt="for-inspect.js"></p><p>执行代码,附加 <code>--inspect-brk=9222</code> 调试参数:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> node --inspect-brk=9222 <span class="keyword">for</span>-insepct.js</span></span><br></pre></td></tr></table></figure><p><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20230110170510.png" alt="执行结果"><br>有关 inspect-brk 参数的作用可以参考 <a href="https://nodejs.org/en/docs/guides/debugging-getting-started/#command-line-options" target="_blank" rel="noopener">node文档</a>,简单来说就是打开调试模式,并在代码首行断点执行,默认实例调试地址为 localhost:9229<br><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20230110171113.png" alt="inspect-brk 参数"> </p><p>此时,nodejs 已经在 <code>localhost:9222</code> 上启动了调试实例,可以通过访问 <code>http://localhost:9222/json</code> 查看详细信息:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">[ {</span><br><span class="line"> <span class="attr">"description"</span>: <span class="string">"node.js instance"</span>,</span><br><span class="line"> <span class="attr">"devtoolsFrontendUrl"</span>: <span class="string">"devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=localhost:9222/4d7174b7-e4cf-4d60-84c5-80f3eed2477d"</span>,</span><br><span class="line"> <span class="attr">"devtoolsFrontendUrlCompat"</span>: <span class="string">"devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=localhost:9222/4d7174b7-e4cf-4d60-84c5-80f3eed2477d"</span>,</span><br><span class="line"> <span class="attr">"faviconUrl"</span>: <span class="string">"https://nodejs.org/static/images/favicons/favicon.ico"</span>,</span><br><span class="line"> <span class="attr">"id"</span>: <span class="string">"4d7174b7-e4cf-4d60-84c5-80f3eed2477d"</span>,</span><br><span class="line"> <span class="attr">"title"</span>: <span class="string">"for-insepct.js"</span>,</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"node"</span>,</span><br><span class="line"> <span class="attr">"url"</span>: <span class="string">"file:///Users/lilonglong/Desktop/long_git/chrome-devtools-analysis/for-insepct.js"</span>,</span><br><span class="line"> <span class="attr">"webSocketDebuggerUrl"</span>: <span class="string">"ws://localhost:9222/4d7174b7-e4cf-4d60-84c5-80f3eed2477d"</span></span><br><span class="line">} ]</span><br></pre></td></tr></table></figure><p>可通过 Chrome 直接访问 <code>devtoolsFrontendUrl</code>:<img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20230110172257.png" alt="Chrome 访问 devtoolsFrontendUrl"> </p><p>当然,可以通过上面的 Chrome 来调试 nodejs 代码,所以此方案不是重点,重点是如何通过代码来远程调试 nodejs 代码。</p><h4 id="通过-chrome-remote-interface-远程调试-nodejs"><a href="#通过-chrome-remote-interface-远程调试-nodejs" class="headerlink" title="通过 chrome-remote-interface 远程调试 nodejs"></a>通过 chrome-remote-interface 远程调试 nodejs</h4><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// cdp.js</span></span><br><span class="line"><span class="keyword">const</span> CDP = <span class="built_in">require</span>(<span class="string">"chrome-remote-interface"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通过默认的连接配置,连接到对应的 调试实例(inspector agent)</span></span><br><span class="line">CDP(<span class="keyword">async</span> (client) => {</span><br><span class="line"> <span class="comment">// 连接成功,通过 CDP client 即可操作调试实例</span></span><br><span class="line"> <span class="keyword">const</span> { Debugger, Runtime, Network } = client;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 监听 scriptParsed 事件,拿到 script source code</span></span><br><span class="line"> Debugger.on(<span class="string">"scriptParsed"</span>, <span class="keyword">async</span> (params) => {</span><br><span class="line"> <span class="keyword">const</span> { scriptId, url } = params;</span><br><span class="line"> <span class="comment">// console.log(`scriptId: ${scriptId}, url: ${url}`);</span></span><br><span class="line"> <span class="keyword">if</span> (url.startsWith(<span class="string">"file://"</span>)) {</span><br><span class="line"> <span class="keyword">const</span> source = <span class="keyword">await</span> client.Debugger.getScriptSource({ scriptId });</span><br><span class="line"> <span class="comment">// 打印获取到的 script 源码</span></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"script source"</span>, source);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 监听进入到断点事件</span></span><br><span class="line"> Debugger.on(<span class="string">"paused"</span>, (pausedEvent) => {</span><br><span class="line"> <span class="comment">// pausedEvent 里面包含当前断点的位置以及 callFrames 信息等</span></span><br><span class="line"> <span class="comment">// console.log('pausedEvent ', pausedEvent);</span></span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 调试实例如果在等待附加调试器,则先继续运行调试实例</span></span><br><span class="line"> <span class="keyword">await</span> Runtime.runIfWaitingForDebugger();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 请求允许使用 agent 的 Debugger 功能</span></span><br><span class="line"> <span class="keyword">await</span> Debugger.enable();</span><br><span class="line"> <span class="comment">// 请求允许使用 agent 的 Runtime 功能</span></span><br><span class="line"> <span class="keyword">await</span> Runtime.enable();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// try {</span></span><br><span class="line"> <span class="comment">// // 网络请求域,这里注释的原因是 nodejs v8 不支持调试 Network 域</span></span><br><span class="line"> <span class="comment">// await Network.enable({ maxPostDataSize: 65536 });</span></span><br><span class="line"> <span class="comment">// } catch (error) {</span></span><br><span class="line"> <span class="comment">// console.error('Network attach failed', error);</span></span><br><span class="line"> <span class="comment">// }</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 远程执行代码</span></span><br><span class="line"> <span class="keyword">const</span> testValue = <span class="keyword">await</span> Runtime.evaluate({</span><br><span class="line"> expression: <span class="string">"eval(1 + 1)"</span>,</span><br><span class="line"> });</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"evaluate result"</span>, testValue);</span><br><span class="line"> } <span class="keyword">catch</span> (err) {</span><br><span class="line"> <span class="built_in">console</span>.error(err);</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> client.close();</span><br><span class="line"> }</span><br><span class="line">}).on(<span class="string">"error"</span>, (err) => {</span><br><span class="line"> <span class="built_in">console</span>.error(err);</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>执行结果:<br><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20230111113918.png" alt="node cdp.js"></p><h4 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h4><ul><li><a href="https://chromedevtools.github.io/devtools-protocol/v8/" target="_blank" rel="noopener">v8 CDP 文档</a></li><li><a href="https://blog.cloudflare.com/better-workers-debugging-with-a-network-panel/" target="_blank" rel="noopener">Cloudflare Worker Debugger 设计</a></li><li><a href="https://www.jianshu.com/p/49cd5c9a1664" target="_blank" rel="noopener">JSON-RPC 是什么</a></li><li><a href="https://nodejs.org/en/docs/guides/debugging-getting-started/#command-line-options" target="_blank" rel="noopener">node debugging guide</a></li><li><a href="https://garnik.medium.com/http-inspector-a-devtools-network-tab-for-node-js-process-5a008d17bf28" target="_blank" rel="noopener">http-inspector — a DevTools Network tab for Node.js process</a></li><li><a href="https://github.com/nodejs/diagnostics/issues/75" target="_blank" rel="noopener">有关 node 不支持调试 network 的讨论</a></li></ul>]]></content>
<summary type="html">
<blockquote>
<p>最近在调研如何远程调试一个 nodejs 进程,大致调研路径是 Chome DevTolls -&gt; Chrome debugging protocol -&gt; chrome-remote-interface,本文重点介绍下 chrome-remote-interface 的简单使用。 </p>
</blockquote>
<p>根据其<a href="https://github.com/cyrus-and/chrome-remote-interface" target="_blank" rel="noopener">文档</a>,这个工具主要是提供一个 js api,在连接到对应的服务端(要调试的端)后,通过 Chrome Debugging Protocol 与之交互。 </p>
<p>所以其基本的使用分为以下几个步骤: </p>
<ul>
<li>启动 nodejs 实例,并打开调试模式, eg. <code>node --inspect-brk=9222 for-insepct.js</code></li>
<li>通过 CDP 连接到远程调试实例</li>
<li>通过 CDP Client 操作远程调试实例</li>
</ul>
</summary>
<category term="javascript" scheme="https://lilong7676.github.io/categories/javascript/"/>
<category term="ChromeDevTools" scheme="https://lilong7676.github.io/tags/ChromeDevTools/"/>
</entry>
<entry>
<title>使用 babel 实现一个自创的 js 语法</title>
<link href="https://lilong7676.github.io/2022/12/09/javascript/%E4%BD%BF%E7%94%A8-babel-%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E8%87%AA%E5%88%9B%E7%9A%84-js-%E8%AF%AD%E6%B3%95/"/>
<id>https://lilong7676.github.io/2022/12/09/javascript/使用-babel-实现一个自创的-js-语法/</id>
<published>2022-12-09T06:39:26.000Z</published>
<updated>2023-06-08T06:26:43.566Z</updated>
<content type="html"><![CDATA[<h3 id="用-babel-实现用-标记一个函数时,将为函数自动加上调用日志打印的功能"><a href="#用-babel-实现用-标记一个函数时,将为函数自动加上调用日志打印的功能" class="headerlink" title="用 babel 实现用 @!标记一个函数时,将为函数自动加上调用日志打印的功能"></a>用 babel 实现用 <code>@!</code>标记一个函数时,将为函数自动加上调用日志打印的功能</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 如</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> @! <span class="title">foo</span>(<span class="params"></span>) </span>{}</span><br><span class="line"><span class="comment">// 转化为</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'function name:'</span>, <span class="string">'foo'</span>, <span class="string">'date:'</span>, <span class="built_in">Date</span>.now())</span><br><span class="line">}</span><br></pre></td></tr></table></figure><a id="more"></a><p>根据<a href="https://babeljs.io/docs/en/babel-parser#output" target="_blank" rel="noopener">文档</a>可知要在 <code>packages/babel-parser</code> 里找逻辑</p><p><strong>注意,请先 fork babel 源码</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 在 babel 源码目录里执行</span></span><br><span class="line">$ make watch <span class="comment"># 编译并 watch</span></span><br><span class="line">$ <span class="built_in">cd</span> packages/babel-parse/<span class="built_in">test</span></span><br><span class="line"><span class="comment"># 在 babel-parse 里新建单元测试文件,用来验证逻辑</span></span><br><span class="line">$ touch <span class="built_in">test</span>-parser.js</span><br></pre></td></tr></table></figure><p>test-parser.js 内容:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// test-parser.js</span></span><br><span class="line"><span class="keyword">import</span> { parse } <span class="keyword">from</span> <span class="string">"../lib/index.js"</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getParser</span>(<span class="params">code</span>) </span>{</span><br><span class="line"> <span class="comment">// sourceType 可能的值为 "script", "module", or "unambiguous",</span></span><br><span class="line"> <span class="comment">// 如果是 unambiguous,则 babel 会尝试判断代码中是否包含 import export 语句来确定具体类型</span></span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="params">()</span> =></span> parse(code, { <span class="attr">sourceType</span>: <span class="string">"module"</span> });</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">describe(<span class="string">"测试自定义语法 @! parse -> ast"</span>, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> it(<span class="string">"should parse"</span>, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> expect(getParser(<span class="string">`function @! foo() {}`</span>)()).toMatchSnapshot();</span><br><span class="line"> });</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>可通过下面的命令执行 jest:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 通过下面的命令执行 jest</span></span><br><span class="line"> $ BABEL_ENV=<span class="built_in">test</span> node_modules/.bin/jest -u packages/babel-parser/<span class="built_in">test</span>/<span class="built_in">test</span>-parser.js</span><br></pre></td></tr></table></figure><p> 执行结果报错信息:<br> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"> 测试自定义语法 @@ parse -> ast</span><br><span class="line"> ✕ should parse</span><br><span class="line"></span><br><span class="line">测试自定义语法 @@ parse -> ast > should parse</span><br><span class="line"> SyntaxError: Unexpected token (1:9)</span><br><span class="line"> at instantiate (/babel-lib/packages/babel-parser/lib/parse-error/credentials.js:27:32)</span><br><span class="line"> at constructor (/babel-lib/packages/babel-parser/lib/parse-error.js:41:41)</span><br><span class="line"> at Parser.raise (/babel-lib/packages/babel-parser/lib/tokenizer/index.js:1062:19)</span><br><span class="line"> at Parser.unexpected (/babel-lib/packages/babel-parser/lib/tokenizer/index.js:1095:16)</span><br><span class="line"> at Parser.parseIdentifierName (/babel-lib/packages/babel-parser/lib/parser/expression.js:1641:18)</span><br><span class="line"> at Parser.parseIdentifier (/babel-lib/packages/babel-parser/lib/parser/expression.js:1624:23)</span><br><span class="line"> at Parser.parseFunctionId (/babel-lib/packages/babel-parser/lib/parser/statement.js:991:79)</span><br><span class="line"> at Parser.parseFunction (/babel-lib/packages/babel-parser/lib/parser/statement.js:961:22)</span><br><span class="line"> at Parser.parseFunctionStatement (/babel-lib/packages/babel-parser/lib/parser/statement.js:603:17)</span><br><span class="line"> at Parser.parseStatementContent (/babel-lib/packages/babel-parser/lib/parser/statement.js:272:21) {</span><br><span class="line"> code: <span class="string">'BABEL_PARSER_SYNTAX_ERROR'</span>,</span><br><span class="line"> reasonCode: <span class="string">'UnexpectedToken'</span>,</span><br><span class="line"> loc: Position { line: 1, column: 9, index: 9 },</span><br><span class="line"> pos: [Getter/Setter]</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p><p>观察错误信息,发现关键错误点在 <code>parseIdentifierName</code> 里报 <code>unexpected</code> 错误,下面调试下逻辑。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// packages/babel-parser/src/parser/expression.ts</span></span><br><span class="line"></span><br><span class="line"> parseIdentifierName(liberal?: boolean): string {</span><br><span class="line"> <span class="keyword">let</span> name: string;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> { startLoc, type } = <span class="keyword">this</span>.state;</span><br><span class="line"></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"identifier value"</span>, <span class="keyword">this</span>.state.value);</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"next identifier value"</span>, <span class="keyword">this</span>.lookahead().value);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (tokenIsKeywordOrIdentifier(type)) {</span><br><span class="line"> name = <span class="keyword">this</span>.state.value;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">this</span>.unexpected();</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>所以问题是 babel 不认识 @! 标识符,那么处理一下:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// packages/babel-parser/src/tokenizer/types.js</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> tt = {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> at: createToken(<span class="string">"@"</span>),</span><br><span class="line"> atEM: createToken(<span class="string">"@!"</span>),</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// packages/babel-parser/src/parser/statement.ts</span></span><br><span class="line"></span><br><span class="line"> parseFunction<T extends N.NormalFunction>(</span><br><span class="line"> <span class="keyword">this</span>: Parser,</span><br><span class="line"> node: Undone<T>,</span><br><span class="line"> statement: number = FUNC_NO_FLAGS,</span><br><span class="line"> isAsync: boolean = <span class="literal">false</span>,</span><br><span class="line"> ): T {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> node.generator = <span class="keyword">this</span>.eat(tt.star);</span><br><span class="line"> <span class="comment">// 实现自定义语法 @!</span></span><br><span class="line"> node.logThisFunc = <span class="keyword">this</span>.eat(tt.atEM);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>再次执行 jest 成功:<br><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20221209155741.png" alt="执行成功"></p><p>查看 ast 结果,logThisFunc 已标记:<br><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20221209155905.png" alt="查看 ast 结果"></p><p>// TODO 编写插件,实现转换逻辑</p>]]></content>
<summary type="html">
<h3 id="用-babel-实现用-标记一个函数时,将为函数自动加上调用日志打印的功能"><a href="#用-babel-实现用-标记一个函数时,将为函数自动加上调用日志打印的功能" class="headerlink" title="用 babel 实现用 @!标记一个函数时,将为函数自动加上调用日志打印的功能"></a>用 babel 实现用 <code>@!</code>标记一个函数时,将为函数自动加上调用日志打印的功能</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 如</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> @! <span class="title">foo</span>(<span class="params"></span>) </span>&#123;&#125;</span><br><span class="line"><span class="comment">// 转化为</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'function name:'</span>, <span class="string">'foo'</span>, <span class="string">'date:'</span>, <span class="built_in">Date</span>.now())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</summary>
<category term="javascript" scheme="https://lilong7676.github.io/categories/javascript/"/>
<category term="Babel" scheme="https://lilong7676.github.io/tags/Babel/"/>
</entry>
<entry>
<title>babel 的原理及插件的编写(简要)</title>
<link href="https://lilong7676.github.io/2022/11/23/javascript/babel%E7%9A%84%E5%8E%9F%E7%90%86%E5%8F%8A%E6%8F%92%E4%BB%B6%E7%9A%84%E7%BC%96%E5%86%99%EF%BC%88%E7%AE%80%E8%A6%81%EF%BC%89/"/>
<id>https://lilong7676.github.io/2022/11/23/javascript/babel的原理及插件的编写(简要)/</id>
<published>2022-11-23T02:39:06.000Z</published>
<updated>2023-06-08T06:26:43.565Z</updated>
<content type="html"><![CDATA[<!-- # 什么是 AST # 访问者模式# babel-parser# babel-traverse --><h3 id="由浅入深,先从几个简单的插件的编写开始吧"><a href="#由浅入深,先从几个简单的插件的编写开始吧" class="headerlink" title="由浅入深,先从几个简单的插件的编写开始吧"></a>由浅入深,先从几个简单的插件的编写开始吧</h3><p>(本次分析的 babel 版本为 7.20.4)</p><a id="more"></a><hr><blockquote><h4 id="实现一个自动将-console-log-信息加上所属代码的位置(行列)信息的-babel-插件"><a href="#实现一个自动将-console-log-信息加上所属代码的位置(行列)信息的-babel-插件" class="headerlink" title="实现一个自动将 console.log 信息加上所属代码的位置(行列)信息的 babel 插件"></a><em>实现一个自动将 console.log 信息加上所属代码的位置(行列)信息的 babel 插件</em></h4></blockquote><p>例子代码:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">test</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> a = <span class="number">2</span>;</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'a'</span>, a);</span><br><span class="line"> <span class="keyword">return</span> a;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">test();</span><br></pre></td></tr></table></figure><ul><li>首先查看这段代码对应的<a href="https://astexplorer.net/#/gist/9357ea04e1cbba03852c2978bf5f4ecb/75b12e8e7c4e82217e17ba7ec6f9db1d7c555aae" target="_blank" rel="noopener"> AST 结构</a></li></ul><p><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20221207111654.png" alt="AST 结构"></p><ul><li><p>在上图可以看到 <code>console.log</code> 对应的节点类型为 <code>CallExpression -> MemberExpress -> Identifier</code>,所以我们的思路是:</p><ul><li>找到类型为 CallExpression 的节点</li><li>判断其代码是否是 console.log</li><li>拿到位置信息</li><li>插入到 arguments 数组中</li></ul></li></ul><p>具体实现代码:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> babel = <span class="built_in">require</span>(<span class="string">"@babel/core"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> testCode = <span class="string">`function test() {</span></span><br><span class="line"><span class="string"> const a = 2;</span></span><br><span class="line"><span class="string"> console.log('a', a);</span></span><br><span class="line"><span class="string"> return a;</span></span><br><span class="line"><span class="string">}</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">test();</span></span><br><span class="line"><span class="string">`</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">myCustomPlugin</span>(<span class="params">{ types }</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> visitor: {</span><br><span class="line"> CallExpression(path, state) {</span><br><span class="line"> <span class="comment">// 判断 callee 是否是 console.log</span></span><br><span class="line"> <span class="keyword">const</span> callee = generate(path.node.callee).code;</span><br><span class="line"> <span class="keyword">if</span> (<span class="string">"console.log"</span> === callee) {</span><br><span class="line"> <span class="comment">// 拿到其代码位置信息</span></span><br><span class="line"> <span class="keyword">const</span> { line, column } = path.node.loc.start;</span><br><span class="line"> <span class="comment">// 将位置信息插入到 arguments 数组中</span></span><br><span class="line"> path.node.arguments.unshift(</span><br><span class="line"> types.stringLiteral(<span class="string">`loc:[<span class="subst">${line}</span>,<span class="subst">${column}</span>]`</span>)</span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> },</span><br><span class="line"> };</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> output = babel.transformSync(testCode, {</span><br><span class="line"> plugins: [myCustomPlugin],</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(output.code);</span><br></pre></td></tr></table></figure><p>执行结果:<br><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20221207114055.png" alt></p><br><hr><blockquote><h4 id="把代码里的-标识符n-转换为-标识符x"><a href="#把代码里的-标识符n-转换为-标识符x" class="headerlink" title="把代码里的 标识符n 转换为 标识符x"></a><em>把代码里的 标识符n 转换为 标识符x</em></h4></blockquote><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 下面的逻辑是把 代码里的 标识符n 转换为 标识符x</span></span><br><span class="line"><span class="keyword">const</span> { parse } = <span class="built_in">require</span>(<span class="string">'../babel-lib/packages/babel-parser'</span>);</span><br><span class="line"><span class="keyword">const</span> traverse = <span class="built_in">require</span>(<span class="string">'../babel-lib/packages/babel-traverse'</span>).default;</span><br><span class="line"><span class="keyword">const</span> generate = <span class="built_in">require</span>(<span class="string">'../babel-lib/packages/babel-generator'</span>).default;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> code = <span class="string">'const n = 1'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// parse the code -> ast</span></span><br><span class="line"><span class="keyword">const</span> ast = parse(code);</span><br><span class="line"></span><br><span class="line"><span class="comment">// transform the ast</span></span><br><span class="line">traverse(ast, {</span><br><span class="line"> enter(path) {</span><br><span class="line"> <span class="comment">// 'n' to 'x'</span></span><br><span class="line"> <span class="keyword">if</span> (path.isIdentifier({ <span class="attr">name</span>: <span class="string">'n'</span> })) {</span><br><span class="line"> path.node.name = <span class="string">'x'</span>;</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="comment">// ast to code</span></span><br><span class="line"><span class="keyword">const</span> output = generate(ast);</span><br><span class="line"><span class="built_in">console</span>.log(output.code); <span class="comment">// 'const x = 1;'</span></span><br></pre></td></tr></table></figure><p>上面实现了一些简单的 babel 转换插件,实现思路也很清晰了,整体的思路就是:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">code -> AST -> transformed AST -> transformed code</span><br></pre></td></tr></table></figure><p>下面分析下其基本原理。</p><br><h3 id="首先简单介绍下什么是-AST"><a href="#首先简单介绍下什么是-AST" class="headerlink" title="首先简单介绍下什么是 AST"></a>首先简单介绍下什么是 AST</h3><p>AST(abstract syntax tree),即抽象语法树。 </p><h4 id="关键的概念"><a href="#关键的概念" class="headerlink" title="关键的概念"></a>关键的概念</h4><ol><li>Tokenizer 分词: 将整个代码字符串分割成语法单元数组(token)</li></ol><p>JS 代码中的语法单元主要指如标识符(if/else、return、function)、运算符、括号、数字、字符串、空格等等能被解析的最小单。 <a href="https://esprima.org/demo/parse.html#" target="_blank" rel="noopener">可以看下这个在线分词工具</a>;</p><p>2.语法分析: 在分词结果的基础上分析语法单元之间的关系。</p><p>语义分析则是将得到的词汇进行一个立体的组合,确定词语之间的关系。</p><p>先理解两个重要概念,即语句和表达式。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">1</span> + <span class="number">2</span>;</span><br></pre></td></tr></table></figure><p>语句(statement),👆上面就是一条语句,一般情况下,在js里每一行就是一个语句。<br>表达式(expression),如上面的 <code>1 + 2</code>就是表达式,是指最终有返回结果的一小段代码,可以嵌入到另一个表达式,且包含在语句中。<br>简单来说语法分析是对语句和表达式识别,这是个递归过程,在解析中,babel 会在解析每个语句和表达式的过程中设置一个暂存器,用来暂存当前读取到的语法单元,如果解析失败,就会返回之前的暂存点,再按照另一种方式进行解析,如果解析成功,则将暂存点销毁,不断重复以上操作,直到最后生成对应的语法树。</p><p>先看下编译原理中,词法分析到语法分析阶段图:<br><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20221207165755.png" alt="词法分析到语法分析"></p><p>上面的产物 parse tree 即解析树,代表了源码的所有信息,很详细,也很冗余(包含分号,冒号等),而 AST,抽象了一些,即精简了 parse tree:</p><p><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20221207170423.png" alt></p><ul><li>AST不含有语法细节,比如冒号、括号、分号</li><li>AST会压缩单继承节点</li><li>操作符会变成内部节点,不再会以叶子节点出现在树的末端。</li></ul><h3 id="那-babel-怎么将代码转为-AST-的?"><a href="#那-babel-怎么将代码转为-AST-的?" class="headerlink" title="那 babel 怎么将代码转为 AST 的?"></a>那 babel 怎么将代码转为 AST 的?</h3><p>根据<a href="https://babeljs.io/docs/en/babel-parser#output" target="_blank" rel="noopener">文档</a>可知要在 <code>packages/babel-parser</code> 里找逻辑</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 在 babel 源码目录里执行</span></span><br><span class="line">$ make watch <span class="comment"># 编译并 watch</span></span><br><span class="line">$ <span class="built_in">cd</span> packages/babel-parse/<span class="built_in">test</span></span><br><span class="line"><span class="comment"># 在 babel-parse 里新建单元测试文件,用来验证逻辑</span></span><br><span class="line">$ touch <span class="built_in">test</span>-parser.js</span><br></pre></td></tr></table></figure><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// test-parser.js</span></span><br><span class="line"><span class="keyword">import</span> { parse } <span class="keyword">from</span> <span class="string">"../lib/index.js"</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getParser</span>(<span class="params">code</span>) </span>{</span><br><span class="line"> <span class="comment">// sourceType 可能的值为 "script", "module", or "unambiguous",</span></span><br><span class="line"> <span class="comment">// 如果是 unambiguous,则 babel 会尝试判断代码中是否包含 import export 语句来确定具体类型</span></span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="params">()</span> =></span> parse(code, { <span class="attr">sourceType</span>: <span class="string">"module"</span> });</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">describe(<span class="string">"测试 parse -> ast"</span>, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> it(<span class="string">"should parse"</span>, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> expect(getParser(<span class="string">`function foo() {}`</span>)()).toMatchSnapshot();</span><br><span class="line"> });</span><br><span class="line">});</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 通过下面的命令执行 jest</span></span><br><span class="line"> $ BABEL_ENV=<span class="built_in">test</span> node_modules/.bin/jest -u packages/babel-parser/<span class="built_in">test</span>/<span class="built_in">test</span>-parser.js</span><br></pre></td></tr></table></figure><p>查看 <code>parse</code> 函数源码,得到其执行路径:<br><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20221208144240.png" alt="parse()执行路径"></p><p>其中 Parser 类继承关系:<br><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20221208140115.png" alt="Parser 继承路径"></p><p>关注点转移到 <code>parser.parse()</code> 函数中,查看<a href="https://github.com/lilong7676/babel/blob/871fd5ec0c22829495d6a11b1729feb7e9e52ad7/packages/babel-parser/src/parser/index.ts#L39" target="_blank" rel="noopener">源码:</a>:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">parse(): N.File {</span><br><span class="line"> <span class="comment">// 进入到初始作用域中</span></span><br><span class="line"> <span class="keyword">this</span>.enterInitialScopes();</span><br><span class="line"> <span class="comment">// 初始化 file、program 节点</span></span><br><span class="line"> <span class="keyword">const</span> file = <span class="keyword">this</span>.startNode() <span class="keyword">as</span> N.File;</span><br><span class="line"> <span class="keyword">const</span> program = <span class="keyword">this</span>.startNode() <span class="keyword">as</span> N.Program;</span><br><span class="line"> <span class="comment">// 获取并更新下一个 token 信息</span></span><br><span class="line"> <span class="keyword">this</span>.nextToken();</span><br><span class="line"> file.errors = <span class="literal">null</span>;</span><br><span class="line"> <span class="comment">// 从头节点开始解析</span></span><br><span class="line"> <span class="keyword">this</span>.parseTopLevel(file, program);</span><br><span class="line"> file.errors = <span class="keyword">this</span>.state.errors;</span><br><span class="line"> <span class="comment">// 返回解析结果,即 ast</span></span><br><span class="line"> <span class="keyword">return</span> file;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>关注点转移到 <code>this.nextToken()</code> 方法</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取并更新下一个 token 信息</span></span><br><span class="line"> nextToken(): <span class="keyword">void</span> {</span><br><span class="line"> <span class="comment">// 忽略空白字符(空格、tab、换行等)并更新当前位置信息</span></span><br><span class="line"> <span class="keyword">this</span>.skipSpace();</span><br><span class="line"> <span class="comment">// 记录当前 token 开始字符位置</span></span><br><span class="line"> <span class="keyword">this</span>.state.start = <span class="keyword">this</span>.state.pos;</span><br><span class="line"> <span class="comment">// 如果不是 “临时向前看”,则记录 token 开始位置(行、列、index)</span></span><br><span class="line"> <span class="keyword">if</span> (!<span class="keyword">this</span>.isLookahead) <span class="keyword">this</span>.state.startLoc = <span class="keyword">this</span>.state.curPosition();</span><br><span class="line"> <span class="comment">// 如果当前 tokenizer 的位置已经到了 input 的末尾</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.state.pos >= <span class="keyword">this</span>.length) {</span><br><span class="line"> <span class="comment">// 则结束本次 token 查找,并更新当前 token 的信息(位置、token类型、token值)到 state 上</span></span><br><span class="line"> <span class="keyword">this</span>.finishToken(tt.eof);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 根据当前的 字符,读取相应的 token</span></span><br><span class="line"> <span class="keyword">this</span>.getTokenFromCode(<span class="keyword">this</span>.codePointAtPos(<span class="keyword">this</span>.state.pos));</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><blockquote><p>感兴趣的可以继续查看 <code>getTokenFromCode</code> 的实现逻辑,这里其实就是 tokenzier 的核心逻辑</p></blockquote><p>关注点转移到 <code>parseTopLevel() -> parseProgram -> parseBlockBody -> parseBlockOrModuleBlockBody -> parseStatement -> parseStatementContent</code><br>具体逻辑可以查看 <a href="https://github.com/lilong7676/babel/tree/main/packages/babel-parser/src" target="_blank" rel="noopener">github</a> 上添加的代码注释。</p><hr><h3 id="babel-是如何遍历-ast的?"><a href="#babel-是如何遍历-ast的?" class="headerlink" title="babel 是如何遍历 ast的?"></a>babel 是如何遍历 ast的?</h3><p>在 <code>packages/babel-traverse</code> 有两个关键的对象:</p><p><strong>Visitor</strong><br>对于这个遍历过程,babel 通过实例化 visitor 对象完成,其实我们生成出来的 AST 结构都拥有一个 accept 方法用来接收 visitor 访问者对象的访问,而访问者其中也定义了 visit 方法(即开发者定义的函数方法)使其能够对树状结构不同节点做出不同的处理,借此做到在对象结构的一次访问过程中,我们能够遍历整个对象结构。(访问者设计模式:提供一个作用于某对象结构中的各元素的操作表示,它使得可以在不改变各元素的类的前提下定义作用于这些元素的新操作)</p><p>遍历结点让我们可以定位并找到我们想要操作的结点,在遍历每一个节点时,存在enter和exit两个时态周期,一个是进入结点时,这个时候节点的子节点还没触达,遍历子节点完成的后,会离开该节点并触发exit方法。</p><p><strong>Path</strong><br>Visitors 在遍历到每个节点的时候,都会给我们传入 path 参数,包含了节点的信息以及节点和所在的位置,供我们对特定节点进行修改,之所以称之为 path 是其表示的是两个节点之间连接的对象,而非指当前的节点对象。path属性有几个重要的组成,主要如下:<br><img src="https://lilong7676-picture.oss-cn-hangzhou.aliyuncs.com/img/20221209142949.png" alt="Path"></p><p>关键路径:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">// babel-traverse</span><br><span class="line">traverse(options) -> traverseNode(...) -> new TraversalContext() -> context.visit(node, key)</span><br></pre></td></tr></table></figure><h4 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h4><p><a href="https://medium.com/basecs/leveling-up-ones-parsing-game-with-asts-d7a6fc2400ff" target="_blank" rel="noopener">Leveling Up One’s Parsing Game With ASTs</a></p><h4 id="彩蛋:Babel-song"><a href="#彩蛋:Babel-song" class="headerlink" title="彩蛋:Babel song"></a>彩蛋:Babel song</h4><p>Hallelujah —— <a href="https://youtu.be/40abpedBKK8" target="_blank" rel="noopener">In Praise of Babel</a></p>]]></content>
<summary type="html">
<!-- # 什么是 AST
# 访问者模式
# babel-parser
# babel-traverse -->
<h3 id="由浅入深,先从几个简单的插件的编写开始吧"><a href="#由浅入深,先从几个简单的插件的编写开始吧" class="headerlink" title="由浅入深,先从几个简单的插件的编写开始吧"></a>由浅入深,先从几个简单的插件的编写开始吧</h3><p>(本次分析的 babel 版本为 7.20.4)</p>
</summary>
<category term="javascript" scheme="https://lilong7676.github.io/categories/javascript/"/>
<category term="Babel" scheme="https://lilong7676.github.io/tags/Babel/"/>
</entry>
<entry>
<title>初步安装 knative</title>
<link href="https://lilong7676.github.io/2022/09/08/Knative/%E5%88%9D%E6%AD%A5%E5%AE%89%E8%A3%85-Knative/"/>
<id>https://lilong7676.github.io/2022/09/08/Knative/初步安装-Knative/</id>
<published>2022-09-08T02:02:11.000Z</published>
<updated>2023-06-08T06:26:43.566Z</updated>
<content type="html"><![CDATA[<p>参考资料:<br><a href="https://knative.dev/docs/getting-started/quickstart-install/#before-you-begin" target="_blank" rel="noopener">https://knative.dev/docs/getting-started/quickstart-install/#before-you-begin</a></p><p>注意,由于我在虚拟机里走了vpn代理,所以以下命令都没有使用国内源。</p><p>前提条件:</p><ol><li>安装了 minikube、kubectl,参考 <a href="https://lilong7676.github.io/2022/09/08/minikube/k8s/%E5%9C%A8-vmware-centos7-%E4%B8%AD%E6%8E%A2%E7%B4%A2-k8s-%E4%B8%8E-KNative/">在 vmware centos7 中安装 minikube</a>.</li></ol><a id="more"></a><h1 id="1-安装-Knative-CLI-(kn)-参考连接"><a href="#1-安装-Knative-CLI-(kn)-参考连接" class="headerlink" title="1. 安装 Knative CLI (kn)(参考连接)"></a>1. 安装 Knative CLI (kn)(<a href="https://knative.dev/docs/getting-started/quickstart-install/#install-the-knative-cli" target="_blank" rel="noopener">参考连接</a>)</h1><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 下载二进制包</span></span><br><span class="line">$ curl -fsSL https://github.com/knative/client/releases/download/knative-v1.7.0/kn-linux-amd64 -O</span><br><span class="line"></span><br><span class="line"><span class="comment"># 重命名二进制包名为 kn</span></span><br><span class="line">$ mv kn-linux-amd64 kn</span><br><span class="line">$ chmod +x kn</span><br><span class="line"></span><br><span class="line"><span class="comment"># 移动 kn 到 bin 目录</span></span><br><span class="line">$ sudo mv kn /usr/<span class="built_in">local</span>/bin/</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看是否安装成功</span></span><br><span class="line"></span><br><span class="line">$ kn version</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/lilong7676/Picture/master/blog/image/20220914105352.png" alt="kn version"></p><h1 id="2-安装-Knative-quickstart-插件-参考链接"><a href="#2-安装-Knative-quickstart-插件-参考链接" class="headerlink" title="2. 安装 Knative quickstart 插件 (参考链接)"></a>2. 安装 Knative quickstart 插件 (<a href="https://knative.dev/docs/getting-started/quickstart-install/#install-the-knative-quickstart-plugin" target="_blank" rel="noopener">参考链接</a>)</h1><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 下载 kn-quickstart-linux-amd64 二进制文件</span></span><br><span class="line">$ curl -fsSL https://github.com/knative-sandbox/kn-plugin-quickstart/releases/download/knative-v1.7.1/kn-quickstart-linux-amd64 -O</span><br><span class="line"></span><br><span class="line"><span class="comment"># 重命名为 kn-quickstart</span></span><br><span class="line">$ mv kn-quickstart-linux-amd64 kn-quickstart</span><br><span class="line">$ chmod +x kn-quickstart</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 移动 kn-quickstart 到 bin 目录</span></span><br><span class="line">$ sudo mv kn-quickstart /usr/<span class="built_in">local</span>/bin/</span><br><span class="line"></span><br><span class="line"><span class="comment"># 验证是否安装成功</span></span><br><span class="line">$ kn quickstart --<span class="built_in">help</span></span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/lilong7676/Picture/master/blog/image/20220914110252.png" alt="kn quickstart --help"></p><h1 id="3-运行-Knative-quickstart-插件-(参考)"><a href="#3-运行-Knative-quickstart-插件-(参考)" class="headerlink" title="3.运行 Knative quickstart 插件 (参考)"></a>3.运行 Knative quickstart 插件 (<a href="https://knative.dev/docs/getting-started/quickstart-install/#run-the-knative-quickstart-plugin" target="_blank" rel="noopener">参考</a>)</h1><p><code>quickstart</code> 插件会完成以下功能: </p><ol><li>检查你是否已经安装了所选的 k8s 实例</li><li>创建一个集群,名字为 <code>knative</code></li><li>安装 Knative Serving,使用 <code>Kourier</code> 作为默认的网络层,<code>sslip.io</code>作为 DNS</li><li>安装 Knative Eventing,并且创建一个 in-memory Broker 和 Channel 的实现</li></ol><p>为了在本地部署 Knative,需要运行 <code>quickstart</code> 插件:<br>由于本地已经安装了 <code>minikube</code>,所以这里以 <code>minikube</code> 启动插件: </p><ol><li>在 <code>minikube</code> 实例中安装 Knative 和 k8s:<blockquote><p>注意:minikube 集群将使用 6GB 内存创建。如果没有足够的内存,则可以在该命令之前运行命令 <code>minikube config set memory 3078</code>,将其更改为不低于 <code>3 GB</code> 的其他值。</p></blockquote></li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ kn quickstart minikube</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/lilong7676/Picture/master/blog/image/20220914145319.png" alt="kn quickstart minikube"></p><ol start="2"><li>根据上一步中的提示,新开一个 terminal 窗口,执行:<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ minikube tunnel --profile knative</span><br></pre></td></tr></table></figure></li></ol><p>在使用 Knative <code>quickstart</code> 环境时,终端里必须运行此 <code>tunnel</code></p><ol start="3"><li><p>回到上一个窗口,按 <code>Enter</code> 键继续安装</p></li><li><p>验证安装是否成功</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">minikube profile list</span><br></pre></td></tr></table></figure></li></ol><p><img src="https://raw.githubusercontent.com/lilong7676/Picture/master/blog/image/20220914145849.png" alt="minikube profile list"></p><h1 id="4-部署一个-Knative-Service-参考"><a href="#4-部署一个-Knative-Service-参考" class="headerlink" title="4. 部署一个 Knative Service (参考)"></a>4. 部署一个 Knative Service (<a href="https://knative.dev/docs/getting-started/first-service/#deploying-a-knative-service" target="_blank" rel="noopener">参考</a>)</h1><p>有两种方式部署,可选 <code>kn</code> 命令部署 或者编写 YAML 配置文件并使用 <code>kubectl apply</code> 部署,具体可参考官网。</p><ol><li>使用 <code>kn</code> 部署<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ kn service create hello \</span><br><span class="line">--image gcr.io/knative-samples/helloworld-go \</span><br><span class="line">--port 8080 \</span><br><span class="line">--env TARGET=World</span><br></pre></td></tr></table></figure></li></ol>]]></content>
<summary type="html">
<p>参考资料:<br><a href="https://knative.dev/docs/getting-started/quickstart-install/#before-you-begin" target="_blank" rel="noopener">https://knative.dev/docs/getting-started/quickstart-install/#before-you-begin</a></p>
<p>注意,由于我在虚拟机里走了vpn代理,所以以下命令都没有使用国内源。</p>
<p>前提条件:</p>
<ol>
<li>安装了 minikube、kubectl,参考 <a href="https://lilong7676.github.io/2022/09/08/minikube/k8s/%E5%9C%A8-vmware-centos7-%E4%B8%AD%E6%8E%A2%E7%B4%A2-k8s-%E4%B8%8E-KNative/">在 vmware centos7 中安装 minikube</a>.</li>
</ol>
</summary>
<category term="Knative" scheme="https://lilong7676.github.io/categories/Knative/"/>
</entry>
<entry>
<title>在 vmware centos7 中安装与探索 k8s</title>
<link href="https://lilong7676.github.io/2022/09/08/minikube/k8s/%E5%9C%A8-vmware-centos7-%E4%B8%AD%E5%AE%89%E8%A3%85%E4%B8%8E%E6%8E%A2%E7%B4%A2-k8s/"/>
<id>https://lilong7676.github.io/2022/09/08/minikube/k8s/在-vmware-centos7-中安装与探索-k8s/</id>
<published>2022-09-08T02:02:11.000Z</published>
<updated>2023-06-08T06:26:43.566Z</updated>
<content type="html"><![CDATA[<p>参考资料:<br><a href="https://minikube.sigs.k8s.io/docs/start/" target="_blank" rel="noopener">https://minikube.sigs.k8s.io/docs/start/</a><br><a href="https://docs.docker.com/engine/install/centos/#install-using-the-convenience-script" target="_blank" rel="noopener">https://docs.docker.com/engine/install/centos/#install-using-the-convenience-script</a><br><a href="https://www.cnblogs.com/yyee/p/15071684.html" target="_blank" rel="noopener">https://www.cnblogs.com/yyee/p/15071684.html</a></p><p>注意,由于我在虚拟机里走了vpn代理,所以以下命令都没有使用国内源。</p><a id="more"></a><h3 id="使用-SSH-连接-VMware-上的-CentOS-虚拟机(可选)"><a href="#使用-SSH-连接-VMware-上的-CentOS-虚拟机(可选)" class="headerlink" title="使用 SSH 连接 VMware 上的 CentOS 虚拟机(可选)"></a>使用 SSH 连接 VMware 上的 CentOS 虚拟机(可选)</h3><ul><li><p>安装 openssh-server</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yum install openssh-server</span><br></pre></td></tr></table></figure></li><li><p>启用22端口监听</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ vi /etc/ssh/sshd_config</span><br><span class="line"><span class="comment"># 取消下面两行的注释</span></span><br><span class="line"><span class="comment"># Port 22</span></span><br><span class="line"><span class="comment"># ListenAddress 0.0.0.0</span></span><br></pre></td></tr></table></figure></li><li><p>开启 sshd 服务</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo service sshd start</span><br></pre></td></tr></table></figure></li><li><p>检测 22 端口</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ netstat -an | grep 22</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/lilong7676/Picture/master/blog/image/20220908143016.png" alt="检测 22 端口"></p></li><li><p>主机与虚拟机进行互相ping,如果ping通了,表示主机和虚拟机在同一个子网,则直接 <code>ssh root@ip</code> 即可。</p></li><li><p>如果没有ping通,则表示主机和虚拟机没有在一个字网,需要修改网络配置,可参考<a href="https://www.jianshu.com/p/f38133c1485a" target="_blank" rel="noopener">这里</a>进行配置。</p></li></ul><h3 id="安装-docker-和-minikube"><a href="#安装-docker-和-minikube" class="headerlink" title="安装 docker 和 minikube"></a>安装 docker 和 minikube</h3><ul><li><p>安装 docker engine</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># https://docs.docker.com/engine/install/centos/#install-using-the-convenience-script</span></span><br><span class="line">$ curl -fsSL https://get.docker.com -o get-docker.sh</span><br><span class="line">$ sudo sh get-docker.sh</span><br></pre></td></tr></table></figure></li><li><p>启动 docker</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ systemctl start docker</span><br></pre></td></tr></table></figure></li><li><p>Add your user to the ‘docker’ group</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo usermod -aG docker <span class="variable">$USER</span> && newgrp docker</span><br></pre></td></tr></table></figure></li><li><p>安装 minikube</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 国内最好走代理访问,参考自 https://minikube.sigs.k8s.io/docs/handbook/vpn_and_proxy/#macos-and-linux</span></span><br><span class="line"><span class="comment"># export HTTP_PROXY="http://yourip:port" </span></span><br><span class="line"><span class="comment"># export HTTPS_PROXY="http://yourip:port"</span></span><br><span class="line"><span class="comment"># export NO_PROXY=localhost,127.0.0.1,10.96.0.0/12,192.168.59.0/24,192.168.49.0/24,192.168.39.0/24</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># https://minikube.sigs.k8s.io/docs/start/</span></span><br><span class="line">$ curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64</span><br><span class="line">$ sudo install minikube-linux-amd64 /usr/<span class="built_in">local</span>/bin/minikube</span><br></pre></td></tr></table></figure></li><li><p><a href="https://minikube.sigs.k8s.io/docs/drivers/docker/#requirements" target="_blank" rel="noopener">配置 minikube(可选)</a></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Start a cluster using the docker driver:</span></span><br><span class="line">minikube start --driver=docker</span><br><span class="line"></span><br><span class="line"><span class="comment"># To make docker the default driver:</span></span><br><span class="line">minikube config <span class="built_in">set</span> driver docker</span><br></pre></td></tr></table></figure></li></ul><h3 id="启动-minikube"><a href="#启动-minikube" class="headerlink" title="启动 minikube"></a>启动 minikube</h3> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 如果已经走代理了,则直接执行</span></span><br><span class="line">$ minikube start </span><br><span class="line"><span class="comment"># 否则国内环境最好使用镜像源</span></span><br><span class="line">$ minikube start --image-mirror-country cn \</span><br><span class="line">--image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers</span><br><span class="line"><span class="comment"># 如果出现下面提示,解决方案参考: https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user</span></span><br><span class="line"><span class="comment">#❌ Exiting due to DRV_AS_ROOT: The "docker" driver should not be used with root privileges.</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 执行 `minikube start`后,会下载安装 k8s等依赖,耐心等待</span></span><br></pre></td></tr></table></figure><p> 此时执行 <code>$ docker ps</code>会发现 minikube 其实就是docker中运行的容器。<br> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"> $ docker ps</span><br><span class="line"> CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES</span><br><span class="line">c07efbc6d848 kicbase/stable:v0.0.33 <span class="string">"/usr/local/bin/entr…"</span> 14 minutes ago Up 14 minutes 127.0.0.1:49162->22/tcp, 127.0.0.1:49161->2376/tcp, 127.0.0.1:49160->5000/tcp, 127.0.0.1:49159->8443/tcp, 127.0.0.1:49158->32443/tcp minikube</span><br></pre></td></tr></table></figure></p><h3 id="安装-kubectl"><a href="#安装-kubectl" class="headerlink" title="安装 kubectl"></a>安装 kubectl</h3><ul><li>可参考这里: <a href="https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/" target="_blank" rel="noopener">https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/</a></li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">$ curl -LO <span class="string">"https://dl.k8s.io/release/<span class="variable">$(curl -L -s https://dl.k8s.io/release/stable.txt)</span>/bin/linux/amd64/kubectl"</span></span><br><span class="line"></span><br><span class="line">$ sudo install -o root -g root -m 0755 kubectl /usr/<span class="built_in">local</span>/bin/kubectl</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看版本验证安装是否成功</span></span><br><span class="line">$ kubectl version --client</span><br></pre></td></tr></table></figure><h1 id="初步使用"><a href="#初步使用" class="headerlink" title="初步使用"></a>初步使用</h1><ul><li><p>使用 <code>kubectl</code> 查看当前集群</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ kubectl get po -A</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/lilong7676/Picture/master/blog/image/20220909113846.png" alt="使用 kubectl 查看当前集群"></p></li><li><p>minikube 集成了 k8s Dashboard,可以在浏览器中查看当前集群情况</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ minikube dashboard</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/lilong7676/Picture/master/blog/image/20220909114717.png" alt="minikube dashboard"></p><p>暴露端口给外部访问:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ kubectl proxy --address=<span class="string">'0.0.0.0'</span> --<span class="built_in">disable</span>-filter=<span class="literal">true</span></span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/lilong7676/Picture/master/blog/image/20220909120236.png" alt="暴露端口给外部访问"></p><p>发现暴露了8001端口给外部访问,但是此时在宿主机无法打开 <code>http://centos_ip:8001/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/</code> ,因为防火墙的配置不允许,所以👇下面是配置防火墙的步骤。</p></li><li><p>配置 centos 防火墙以暴露指定端口给宿主机访问(<a href="https://www.cnblogs.com/yyee/p/15071684.html" target="_blank" rel="noopener">参考自这里</a>)</p><p>因为我是本地用的虚拟机安装的 centos7,所以上面的 dashboard 页面无法直接在宿主机内打开,所以参考这里修改centos 防火墙即可</p><p>首先前提是我的虚拟机网络走的是 NAT转发模式,以下为在虚拟机内操作:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 设置防火墙允许NAT转发</span></span><br><span class="line">$ sudo firewall-cmd --zone=public --add-masquerade --permanent</span><br><span class="line"></span><br><span class="line"><span class="comment"># 通过 ifconfig 命令查看 127.0.0.1 属于哪个网卡,我这里网卡的名字是 lo</span></span><br><span class="line"><span class="comment"># 下面把 lo 网卡添加到 trusted 域</span></span><br><span class="line">$ sudo firewall-cmd --permanent --zone=trusted --change-interface=lo</span><br><span class="line"></span><br><span class="line"><span class="comment"># PS: 我这里顺便把 docker0 网卡也添加到信任域了,方便以后访问docker内的端口</span></span><br><span class="line"><span class="comment"># sudo firewall-cmd --permanent --zone=trusted --change-interface=docker0</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 开放 8001 端口(minikube dashboard proxy 默认为 8001 端口)</span></span><br><span class="line">$ sudo firewall-cmd --add-port=8001/tcp --permanent</span><br><span class="line"></span><br><span class="line"><span class="comment"># 重启防火墙即可</span></span><br><span class="line">$ firewall-cmd --reload</span><br></pre></td></tr></table></figure><p>此时在宿主机即可访问虚拟机8001端口: <code>http://vm_ip:8001/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/#/workloads?namespace=default</code></p></li></ul><h1 id="部署一个应用试试"><a href="#部署一个应用试试" class="headerlink" title="部署一个应用试试"></a>部署一个应用试试</h1><p>这里可以参考官网的教程(<a href="https://minikube.sigs.k8s.io/docs/start/),官网部署的是:`k8s.gcr.io/echoserver:1.4`,但是一直显示pull" target="_blank" rel="noopener">https://minikube.sigs.k8s.io/docs/start/),官网部署的是:`k8s.gcr.io/echoserver:1.4`,但是一直显示pull</a> image 失败,应该是镜像地址访问问题,所以这了换成了另一个demo镜像,你可以换成任何docker镜像。</p><ul><li><p>根据 demo 镜像创建一个 deployment</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ kubectl create deployment wordpress --image=wordpress</span><br></pre></td></tr></table></figure></li><li><p>暴露 NodePort 端口到 80,注意,这里的端口必须与docker镜像暴露的端口一致,否则无法访问到镜像内</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ kubectl expose deployment wordpress --<span class="built_in">type</span>=NodePort --port=80</span><br></pre></td></tr></table></figure></li><li><p>显示访问地址</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 如果不加 --url,则会自动在浏览器中打开</span></span><br><span class="line">$ minikube services wordpress --url</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/lilong7676/Picture/master/blog/image/20220909160506.png" alt="显示访问地址"></p></li><li><p>一些常用命令</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 显示 services 列表</span></span><br><span class="line">$ kubectl get services <span class="comment">#可简写为 kubectl get svc</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 显示当前 deployment 列表</span></span><br><span class="line">$ kubectl get deployment</span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除一个 deployment</span></span><br><span class="line">$ kubectl delete deployment <span class="variable">$deploymentname</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除一个 service</span></span><br><span class="line">$ kubectl delete <span class="variable">$servicename</span></span><br></pre></td></tr></table></figure></li></ul><h1 id="遇到的问题"><a href="#遇到的问题" class="headerlink" title="遇到的问题"></a>遇到的问题</h1><ol><li>minikube services wordpress 在浏览器中打不开这个地址<br>解决方式:结果发现是 terminal 使用了代理,重新打开一个 terminal 再执行命令就可以了。</li></ol>]]></content>
<summary type="html">
<p>参考资料:<br><a href="https://minikube.sigs.k8s.io/docs/start/" target="_blank" rel="noopener">https://minikube.sigs.k8s.io/docs/start/</a><br><a href="https://docs.docker.com/engine/install/centos/#install-using-the-convenience-script" target="_blank" rel="noopener">https://docs.docker.com/engine/install/centos/#install-using-the-convenience-script</a><br><a href="https://www.cnblogs.com/yyee/p/15071684.html" target="_blank" rel="noopener">https://www.cnblogs.com/yyee/p/15071684.html</a></p>
<p>注意,由于我在虚拟机里走了vpn代理,所以以下命令都没有使用国内源。</p>
</summary>
<category term="minikube" scheme="https://lilong7676.github.io/categories/minikube/"/>
<category term="k8s" scheme="https://lilong7676.github.io/categories/minikube/k8s/"/>
</entry>
<entry>
<title>flutter中通过ffi使用quickjs</title>
<link href="https://lilong7676.github.io/2022/09/01/flutter/flutter%E4%B8%AD%E9%80%9A%E8%BF%87ffi%E4%BD%BF%E7%94%A8quickjs/"/>
<id>https://lilong7676.github.io/2022/09/01/flutter/flutter中通过ffi使用quickjs/</id>
<published>2022-09-01T02:43:43.000Z</published>
<updated>2023-06-08T06:26:43.565Z</updated>
<content type="html"><![CDATA[<ul><li><p>quickjs中 判断 JSValue 的类型</p><p> 在 <code>quickjs.h</code> 中 搜索 <code>JS_Is</code>开头的方法:</p><ul><li>JS_IsBool(JSValue)</li><li>JS_IsNull</li><li>JS_IsUndefined</li><li>JS_IsNumber</li><li>JS_IsString</li><li>JS_IsObject</li><li>JS_IsException</li><li>…</li><li><a id="more"></a></li></ul></li><li><p>quickjs中的 JSValue 与 c 类型的转换</p><p> 在<code>quickjs.h</code>中搜索 <code>JS_To</code>开头的方法:</p><ul><li>JS_ToBool(JSContext, JSValue)</li><li>JS_ToInt32</li><li>JS_ToInt64</li><li>JS_ToFloat64</li><li>JS_ToCString 需注意 JS_FreeCString</li></ul></li></ul><p>为什么 <code>webf</code> 中的 <code>NativeString</code> 类型,使用 uint16_t* 表示 string?</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">NativeString</span> {</span></span><br><span class="line"> <span class="keyword">const</span> <span class="keyword">uint16_t</span>* <span class="built_in">string</span>;</span><br><span class="line"> <span class="keyword">uint32_t</span> length;</span><br><span class="line"></span><br><span class="line"> <span class="function">NativeString* <span class="title">clone</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">free</span><span class="params">()</span></span>;</span><br><span class="line">};</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> 首先, uint16_t 即为 unsigned short int 类型,值范围为 0 - 65535。</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"> **/</span></span><br></pre></td></tr></table></figure><ul><li>有关 JSString、JSAtom 的分析:<ul><li><a href="https://blog.csdn.net/jayyuz/article/details/124450556" target="_blank" rel="noopener">https://blog.csdn.net/jayyuz/article/details/124450556</a></li></ul></li></ul>]]></content>
<summary type="html">
<ul>
<li><p>quickjs中 判断 JSValue 的类型</p>
<p> 在 <code>quickjs.h</code> 中 搜索 <code>JS_Is</code>开头的方法:</p>
<ul>
<li>JS_IsBool(JSValue)</li>
<li>JS_IsNull</li>
<li>JS_IsUndefined</li>
<li>JS_IsNumber</li>
<li>JS_IsString</li>
<li>JS_IsObject</li>
<li>JS_IsException</li>
<li>…</li>
<li>
</summary>
<category term="flutter" scheme="https://lilong7676.github.io/categories/flutter/"/>
</entry>
<entry>
<title>web前端Proxy沙箱实现时需注意RegExp等原生对象问题</title>
<link href="https://lilong7676.github.io/2022/08/31/javascript/web%E5%89%8D%E7%AB%AFProxy%E6%B2%99%E7%AE%B1%E5%AE%9E%E7%8E%B0%E6%97%B6%E9%9C%80%E6%B3%A8%E6%84%8FRegExp%E7%AD%89%E5%8E%9F%E7%94%9F%E5%AF%B9%E8%B1%A1%E9%97%AE%E9%A2%98/"/>
<id>https://lilong7676.github.io/2022/08/31/javascript/web前端Proxy沙箱实现时需注意RegExp等原生对象问题/</id>
<published>2022-08-31T06:52:01.000Z</published>
<updated>2023-06-08T06:26:43.565Z</updated>
<content type="html"><![CDATA[<h2 id="问题起因"><a href="#问题起因" class="headerlink" title="问题起因"></a>问题起因</h2><p>起因是一个前端js沙箱的实现中,发现通过沙箱执行代码中访问 RegExp.$n 的值一直是空字符串,类似于:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> code = <span class="string">`</span></span><br><span class="line"><span class="string"> const r = /^(\\d{4})-(\\d{1,2})-(\\d{1,2})$/</span></span><br><span class="line"><span class="string"> r.exec('2022-08-31')</span></span><br><span class="line"><span class="string"> return RegExp.$1;`</span>;</span><br><span class="line">evalScriptInSandbox(code);</span><br></pre></td></tr></table></figure><a id="more"></a><p>正常情况下,<code>evalScriptInSandbox(code)</code> 应该返回 <code>'2022'</code>,但是执行发现只会返回空字符串,最后发现是 RegExp 的执行上下文问题。</p><p>因为沙箱的实现类似于这种:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> evalScriptSandbox = <span class="function">(<span class="params">code, fakeWindow</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> resolver = <span class="keyword">new</span> <span class="built_in">Function</span>(<span class="string">`</span></span><br><span class="line"><span class="string"> return function(window) {</span></span><br><span class="line"><span class="string"> with(window) {</span></span><br><span class="line"><span class="string"> try {</span></span><br><span class="line"><span class="string"> return (function() {</span></span><br><span class="line"><span class="string"> "use strict";</span></span><br><span class="line"><span class="string"> <span class="subst">${code}</span></span></span><br><span class="line"><span class="string"> })();</span></span><br><span class="line"><span class="string"> } catch(e) {</span></span><br><span class="line"><span class="string"> console.log(e);</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> `</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 正是这里的 call(fakeWindow)导致 RegExp 执行时的上下文是 fakeWindow </span></span><br><span class="line"> <span class="comment">// 而不是真正的宿主 window,导致 bug 产生</span></span><br><span class="line"> <span class="keyword">return</span> resolver().call(fakeWindow, fakeWindow);</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>解决方式就是访问<code>fakeWindow</code>中的 <code>RegExp</code> 对象时,需要返回宿主环境的 <code>RegExp</code>,这和浏览器的内部实现有关系,暂时没发现其他的解决方式。</p><h2 id="Reproduce-Step"><a href="#Reproduce-Step" class="headerlink" title="Reproduce Step :"></a>Reproduce Step :</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ git <span class="built_in">clone</span> https://github.com/lilong7676/js-regexp-bug.git</span><br><span class="line">$ <span class="built_in">cd</span> js-regexp-bug</span><br><span class="line">$ npm i</span><br><span class="line">$ npm run start</span><br></pre></td></tr></table></figure><h2 id="⚠️"><a href="#⚠️" class="headerlink" title="⚠️"></a>⚠️</h2><p>⚠️另外需要注意,在 eval、Function 构造函数中执行正则表达式时,需要转义反斜线,即:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">eval</span>(<span class="string">`</span></span><br><span class="line"><span class="string">const r = /^(\d{4})-(\d{1,2})-(\d{1,2})$/</span></span><br><span class="line"><span class="string">r.exec('2022-08-31'); // null</span></span><br><span class="line"><span class="string">console.log(RegExp.$1) // ''</span></span><br><span class="line"><span class="string">`</span>)</span><br></pre></td></tr></table></figure><p>在 eval 中需要写为:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">eval</span>(<span class="string">`</span></span><br><span class="line"><span class="string">const r = /^(\\d{4})-(\\d{1,2})-(\\d{1,2})$/</span></span><br><span class="line"><span class="string">r.exec('2022-08-31');</span></span><br><span class="line"><span class="string">console.log(RegExp.$1); // 2022</span></span><br><span class="line"><span class="string">`</span>)</span><br></pre></td></tr></table></figure><h3 id="类似的原生对象,如-eval-等,也会在这种沙箱的实现方式中出现作用域问题,需要注意。"><a href="#类似的原生对象,如-eval-等,也会在这种沙箱的实现方式中出现作用域问题,需要注意。" class="headerlink" title="类似的原生对象,如 eval 等,也会在这种沙箱的实现方式中出现作用域问题,需要注意。"></a>类似的原生对象,如 eval 等,也会在这种沙箱的实现方式中出现作用域问题,需要注意。</h3>]]></content>
<summary type="html">
<h2 id="问题起因"><a href="#问题起因" class="headerlink" title="问题起因"></a>问题起因</h2><p>起因是一个前端js沙箱的实现中,发现通过沙箱执行代码中访问 RegExp.$n 的值一直是空字符串,类似于:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> code = <span class="string">`</span></span><br><span class="line"><span class="string"> const r = /^(\\d&#123;4&#125;)-(\\d&#123;1,2&#125;)-(\\d&#123;1,2&#125;)$/</span></span><br><span class="line"><span class="string"> r.exec('2022-08-31')</span></span><br><span class="line"><span class="string"> return RegExp.$1;`</span>;</span><br><span class="line">evalScriptInSandbox(code);</span><br></pre></td></tr></table></figure>
</summary>
<category term="javascript" scheme="https://lilong7676.github.io/categories/javascript/"/>
</entry>
<entry>
<title>记 RegExp 字面量形式在 eval 及 new Function里的行为异常问题</title>
<link href="https://lilong7676.github.io/2022/08/29/uncategorized/%E8%AE%B0RegExp-%E5%9C%A8Eval%E5%8F%8Anew-Function%E9%87%8C%E7%9A%84%E8%A1%8C%E4%B8%BA%E5%BC%82%E5%B8%B8%E9%97%AE%E9%A2%98/"/>
<id>https://lilong7676.github.io/2022/08/29/uncategorized/记RegExp-在Eval及new-Function里的行为异常问题/</id>
<published>2022-08-29T01:31:43.000Z</published>
<updated>2023-06-08T06:26:43.567Z</updated>
<content type="html"><![CDATA[<p>首先看一下最简单的代码</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> r = <span class="regexp">/^(\d{4})-(\d{1,2})-(\d{1,2})$/</span>;</span><br><span class="line">r.exec(<span class="string">'2019-10-08'</span>);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">RegExp</span>.$<span class="number">1</span>); <span class="comment">// 2019</span></span><br></pre></td></tr></table></figure><p>上面这段代码在 Chrome console 中执行结果为 2019,一切正常。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">eval</span>(<span class="string">`</span></span><br><span class="line"><span class="string">const r = /^(\d{4})-(\d{1,2})-(\d{1,2})$/;</span></span><br><span class="line"><span class="string">r.exec('2019-10-08');</span></span><br><span class="line"><span class="string">RegExp.$1;</span></span><br><span class="line"><span class="string">`</span>); <span class="comment">// ''</span></span><br></pre></td></tr></table></figure><p><code>重新打开一个 Chrome console</code>, 然后执行上面这段代码,结果为<code>空字符串</code>,经过查阅资料,发现在 eval 中执行 regx的字面量时,需要<code>额外转义</code>反斜线,即:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">eval</span>(<span class="string">`</span></span><br><span class="line"><span class="string">const r = /^(\\d{4})-(\\d{1,2})-(\\d{1,2})$/;</span></span><br><span class="line"><span class="string">r.exec('2019-10-08');</span></span><br><span class="line"><span class="string">RegExp.$1;</span></span><br><span class="line"><span class="string">`</span>); <span class="comment">// '2019'</span></span><br></pre></td></tr></table></figure><a id="more"></a><p>参考资料:</p><ol><li><a href="https://stackoverflow.com/a/61147060/11394539" target="_blank" rel="noopener">https://stackoverflow.com/a/61147060/11394539</a></li></ol>]]></content>
<summary type="html">
<p>首先看一下最简单的代码</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> r = <span class="regexp">/^(\d&#123;4&#125;)-(\d&#123;1,2&#125;)-(\d&#123;1,2&#125;)$/</span>;</span><br><span class="line">r.exec(<span class="string">'2019-10-08'</span>);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">RegExp</span>.$<span class="number">1</span>); <span class="comment">// 2019</span></span><br></pre></td></tr></table></figure>
<p>上面这段代码在 Chrome console 中执行结果为 2019,一切正常。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">eval</span>(<span class="string">`</span></span><br><span class="line"><span class="string">const r = /^(\d&#123;4&#125;)-(\d&#123;1,2&#125;)-(\d&#123;1,2&#125;)$/;</span></span><br><span class="line"><span class="string">r.exec('2019-10-08');</span></span><br><span class="line"><span class="string">RegExp.$1;</span></span><br><span class="line"><span class="string">`</span>); <span class="comment">// ''</span></span><br></pre></td></tr></table></figure>
<p><code>重新打开一个 Chrome console</code>, 然后执行上面这段代码,结果为<code>空字符串</code>,
经过查阅资料,发现在 eval 中执行 regx的字面量时,需要<code>额外转义</code>反斜线,即:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">eval</span>(<span class="string">`</span></span><br><span class="line"><span class="string">const r = /^(\\d&#123;4&#125;)-(\\d&#123;1,2&#125;)-(\\d&#123;1,2&#125;)$/;</span></span><br><span class="line"><span class="string">r.exec('2019-10-08');</span></span><br><span class="line"><span class="string">RegExp.$1;</span></span><br><span class="line"><span class="string">`</span>); <span class="comment">// '2019'</span></span><br></pre></td></tr></table></figure>
</summary>
</entry>
</feed>