自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

手寫一個 Ts-Node 來深入理解它的原理

開發(fā) 前端
當我們用 Typesript 來寫 Node.js 的代碼,寫完代碼之后要用 tsc 作編譯,之后再用 Node.js 來跑,這樣比較麻煩,所以我們會用 ts-node 來直接跑 ts 代碼,省去了編譯階段。

[[437960]]

本文轉(zhuǎn)載自微信公眾號「神光的編程秘籍」,作者神說要有光zxg。轉(zhuǎn)載本文請聯(lián)系神光的編程秘籍公眾號。

當我們用 Typesript 來寫 Node.js 的代碼,寫完代碼之后要用 tsc 作編譯,之后再用 Node.js 來跑,這樣比較麻煩,所以我們會用 ts-node 來直接跑 ts 代碼,省去了編譯階段。

有沒有覺得很神奇,ts-node 怎么做到的直接跑 ts 代碼的?

其實原理并不難,今天我們來實現(xiàn)一個 ts-node 吧。

相關(guān)基礎

實現(xiàn) ts-node 需要 3 方面的基礎知識:

  • require hook
  • repl 模塊、vm 模塊
  • ts compiler api

我們先學下這些基礎

require hook

Node.js 當 require 一個 js 模塊的時候,內(nèi)部會分別調(diào)用 Module.load、 Module._extensions['.js'],Module._compile 這三個方法,然后才是執(zhí)行。

同理,ts 模塊、json 模塊等也是一樣的流程,那么我們只需要修改 Module._extensions[擴展名] 的方法,就能達到 hook 的目的:

  1. require.extensions['.ts'] = function(module, filename) { 
  2.  
  3. // 修改代碼 
  4.  
  5. module._compile(修改后的代碼, filename); 
  6.  

比如上面我們注冊了 ts 的處理函數(shù),這樣當處理 ts 模塊時就會調(diào)用這個方法,所以我們在這里面做編譯就可以了,這就是 ts-node 能夠直接執(zhí)行 ts 的原理。

repl 模塊

Node.js 提供了 repl 模塊可以創(chuàng)建 Read、Evaluate、Print、Loop 的命令行交互環(huán)境,就是那種一問一答的方式。ts-node 也支持 repl 的模式,可以直接寫 ts 代碼然后執(zhí)行,原理就是基于 repl 模塊做的擴展。

repl 的 api 是這樣的:通過 start 方法來創(chuàng)建一個 repl 的交互,可以指定提示符 prompt,可以自己實現(xiàn) eval 的處理邏輯:

  1. const repl = require('repl'); 
  2.  
  3. const r = repl.start({  
  4.     prompt: '- . - > ',  
  5.     eval: myEval  
  6. }); 
  7.  
  8. function myEval(cmd, context, filename, callback) { 
  9.     // 對輸入的命令做處理 
  10.     callback(null, 處理后的內(nèi)容); 

repl 的執(zhí)行時有一個上下文的,在這里就是 r.context,我們在這個上下文里執(zhí)行代碼要使用 vm 模塊:

  1. const vm = require('vm'); 
  2.  
  3. const res = vm.runInContext(要執(zhí)行的代碼, r.context); 

這兩個模塊結(jié)合,就可以實現(xiàn)一問一答的命令行交互,而且 ts 的編譯也可以放在 eval 的時候做,這樣就實現(xiàn)了直接執(zhí)行 ts 代碼。

ts compiler api

ts 的編譯我們主要是使用 tsc 的命令行工具,但其實它同樣也提供了編譯的 api,叫做 ts compiler api。我們做工具的時候就需要直接調(diào)用 compiler api 來做編譯。

轉(zhuǎn)換 ts 代碼為 js 代碼的 api 是這個:

  1. const { outputText } = ts.transpileModule(ts代碼, { 
  2.     compilerOptions: { 
  3.         strict: false
  4.         sourceMap: false
  5.         // 其他編譯選項 
  6.     } 
  7. }); 

當然,ts 也提供了類型檢查的 api,因為參數(shù)比較多,我們后面一篇文章再做展開,這里只了解 transpileModule 的 api 就夠了。

了解了 require hook、repl 和 vm、ts compiler api 這三方面的知識之后,ts-node 的實現(xiàn)原理就呼之欲出了,接下來我們就來實現(xiàn)一下。

實現(xiàn) ts-node

直接執(zhí)行的模式

我們可以使用 ts-node + 某個 ts 文件,來直接執(zhí)行這個 ts 文件,它的原理就是修改了 require hook,也就是 Module._extensions['.ts'] 來實現(xiàn)的。

在 require hook 里面做 ts 的編譯,然后后面直接執(zhí)行編譯后的 js,這樣就能達到直接執(zhí)行 ts 文件的效果。

所以我們重寫 Module._extensions['.ts'] 方法,在里面讀取文件內(nèi)容,然后調(diào)用 ts.transpileModule 來把 ts 轉(zhuǎn)成 js,之后調(diào)用 Module._compile 來處理編譯后的 js。

這樣,我們就可以直接執(zhí)行 ts 模塊了,具體的模塊路徑是通過命令行參數(shù)執(zhí)行的,可以用 process.argv 來取。

  1. const path = require('path'); 
  2. const ts = require('typescript'); 
  3. const fs = require('fs'); 
  4.  
  5. const filePath = process.argv[2]; 
  6.  
  7. require.extensions['.ts'] = function(module, filename) { 
  8.     const fileFullPath = path.resolve(__dirname, filename); 
  9.     const content = fs.readFileSync(fileFullPath, 'utf-8'); 
  10.  
  11.     const { outputText } = ts.transpileModule(content, { 
  12.         compilerOptions: require('./tsconfig.json'
  13.     }); 
  14.  
  15.     module._compile(outputText, filename); 
  16.  
  17. require(filePath); 

我們準備一個這樣的 ts 文件 test.ts:

  1. const a = 1; 
  2. const b = 2; 
  3.  
  4. function add(a: number, b: number): number { 
  5.     return a + b; 
  6.  
  7. console.log(add(a, b)); 

然后用這個工具 hook.js 來跑:

可以看到,成功的執(zhí)行了 ts,這就是 ts-node 的原理。

當然,細節(jié)的邏輯還有很多,但是最主要的原理就是 require hook + ts compiler api。

repl 模式

ts-node 支持啟動一個 repl 的環(huán)境,交互式的輸入 ts 代碼然后執(zhí)行,它的原理就是基于 Node.js 提供的 repl 模塊做的擴展,在自定義的 eval 函數(shù)里面做了 ts 的編譯,然后使用 vm.runInContext 的 api 在 repl 的上下文中執(zhí)行 js 代碼。

我們也啟動一個 repl 的環(huán)境,設置提示符和自定義的 eval 實現(xiàn)。

  1. const repl = require('repl'); 
  2.  
  3. const r = repl.start({  
  4.     prompt: '- . - > ',  
  5.     eval: myEval  
  6. }); 
  7.  
  8. function myEval(cmd, context, filename, callback) { 
  9.  

eval 的實現(xiàn)就是編譯 ts 代碼為 js,然后用 vm.runInContext 來執(zhí)行編譯后的 js 代碼,執(zhí)行的 context 指定為 repl 的 context:

  1. function myEval(cmd, context, filename, callback) { 
  2.     const { outputText } = ts.transpileModule(cmd, { 
  3.         compilerOptions: { 
  4.             strict: false
  5.             sourceMap: false 
  6.         } 
  7.     }); 
  8.     const res = vm.runInContext(outputText, r.context); 
  9.     callback(null, res); 

同時,我們還可以對 repl 的 context 做一些擴展,比如注入一個 who 的環(huán)境變量:

  1. Object.defineProperty(r.context, 'who', { 
  2.   configurable: false
  3.   enumerable: true
  4.   value: '神說要有光' 
  5. }); 

我們來測試下效果:

可以看到,執(zhí)行后啟動了一個 repl 環(huán)境,提示符修改成了 -.- >,可以直接執(zhí)行 ts 代碼,還可以訪問全局變量 who。

這就是 ts-node 的 repl 模式的大概原理:repl + vm + ts compiler api。

全部代碼如下:

  1. const repl = require('repl'); 
  2. const ts = require('typescript'); 
  3. const vm = require('vm'); 
  4.  
  5. const r = repl.start({  
  6.     prompt: '- . - > ',  
  7.     eval: myEval  
  8. }); 
  9.  
  10. Object.defineProperty(r.context, 'who', { 
  11.   configurable: false
  12.   enumerable: true
  13.   value: '神說要有光' 
  14. }); 
  15.  
  16. function myEval(cmd, context, filename, callback) { 
  17.     const { outputText } = ts.transpileModule(cmd, { 
  18.         compilerOptions: { 
  19.             strict: false
  20.             sourceMap: false 
  21.         } 
  22.     }); 
  23.     const res = vm.runInContext(outputText, r.context); 
  24.     callback(null, res); 

總結(jié)

ts-node 可以直接執(zhí)行 ts 代碼,不需要手動編譯,為了深入理解它,我們我們實現(xiàn)了一個簡易 ts-node,支持了直接執(zhí)行和 repl 模式。

直接執(zhí)行的原理是通過 require hook,也就是 Module._extensions[ext] 里通過 ts compiler api 對代碼做轉(zhuǎn)換,之后再執(zhí)行,這樣的效果就是可以直接執(zhí)行 ts 代碼。

repl 的原理是基于 Node.js 的 repl 模塊做的擴展,可以定制提示符、上下文、eval 邏輯等,我們在 eval 里用 ts compiler api 做了編譯,然后通過 vm.runInContext 在 repl 的 context 中執(zhí)行編譯后的 js。這樣的效果就是可以在 repl 里直接執(zhí)行 ts 代碼。

當然,完整的 ts-node 還有很多細節(jié),但是大概的原理我們已經(jīng)懂了,而且還學到了 require hook、repl 和 vm 模塊、 ts compiler api 等知識。

 

責任編輯:武曉燕 來源: 神光的編程秘籍
相關(guān)推薦

2022-04-26 08:32:36

CSS前端

2020-07-03 17:20:07

Redux前端代碼

2021-08-05 05:46:06

Node.jsInspector工具

2021-10-16 05:00:32

.js Buffer模塊

2022-01-14 12:28:18

架構(gòu)OpenFeign遠程

2022-11-04 09:43:05

Java線程

2024-03-12 00:00:00

Sora技術(shù)數(shù)據(jù)

2022-09-05 08:39:04

kubernetesk8s

2021-03-10 10:55:51

SpringJava代碼

2024-11-01 08:57:07

2020-08-10 18:03:54

Cache存儲器CPU

2024-04-15 00:00:00

技術(shù)Attention架構(gòu)

2021-08-12 01:00:29

NodejsAsync

2021-08-26 13:57:56

Node.jsEncodingBuffer

2023-09-19 22:47:39

Java內(nèi)存

2022-09-26 08:01:31

線程LIFO操作方式

2020-03-26 16:40:07

MySQL索引數(shù)據(jù)庫

2019-07-01 13:34:22

vue系統(tǒng)數(shù)據(jù)

2022-09-05 22:22:00

Stream操作對象

2020-03-17 08:36:22

數(shù)據(jù)庫存儲Mysql
點贊
收藏

51CTO技術(shù)棧公眾號