欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

如何使node也支持從url加載一個module詳解

 更新時間:2018年06月05日 11:22:08   作者:zhangzhao  
這篇文章主要給大家介紹了關(guān)于如何使node也支持從url加載一個module的相關(guān)資料,文中通過示例代碼將實現(xiàn)的方法介紹非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧

前言

最近兩天 ry 大神的 deno 火了一把。作為 node 項目的發(fā)起人,現(xiàn)在又基于 go 重新寫了一個類似 node 的項目命名為 deno,引發(fā)了大家的強烈關(guān)注。

在 deno 項目 readme 的開始就列舉出了這個項目的優(yōu)勢和需要解決的問題,里面最讓我矚目的就是模塊原生支持 ts ,同時也能也必須從 url 加載模塊,這也是與現(xiàn)有的 CommonJS 最大的不同。

仔細(xì)思考一下,deno 的模塊化與 CommonJS 相比,更多的是一些 runtime 的能力?,F(xiàn)有的 CommonJS 底層實現(xiàn)過程并不是靜態(tài)化,考慮了很多的動態(tài)配置,所以基于現(xiàn)有到 CommonJS 改造起來還是比較容易的,支持 url 加載或者 ts 模塊也并不復(fù)雜,主要難點在于與系統(tǒng)調(diào)用的耦合度上。所以周六在家準(zhǔn)備擼個小項目,從上層入手,算是仿照 deno 的這幾個特性使得一個仿原生 node 的 CommonJS 模塊語法也能支持這些特性。

CommonJS 的執(zhí)行過程

想要讓 CommonJS 支持 url 訪問或者原生加載 ts 模塊,必須從 CommonJS 的執(zhí)行過程中入手,在中間階段將模塊注入進去。而 CommonJS 的執(zhí)行過程其實總結(jié)起來很簡單,大概分為以下幾點:

  • 處理路徑依賴

處理路徑依賴應(yīng)該也是所有模塊化加載規(guī)范的第一步,換言之就是根據(jù)路徑找到文件的位置。無論是 CommonJS 的 require 還是 ESModule 的 import,無論是相對路徑還是絕對路徑,都必須首先在內(nèi)部對這個路徑進行處理,找到合適的文件地址。

模塊路徑有可能是絕對路徑,有可能是相對路徑,有可能省略了后綴(js、node、json),有可能省略了文件名(index),甚至是動態(tài)路徑(運行時基于變量的動態(tài)拼接)等等。

首先就是遵守約定,同時按照一定的策略找到這個文件的真實位置,中間的過程就是補齊上面模塊化省略的東西。一般都是根據(jù) CommonJS 的這張流程圖

  • 加載文件

確認(rèn)了路徑并且確保了文件存在之后,加載文件這一步就簡單粗暴的多。最簡單的方式就是直接讀取硬盤上的文件,將純文本的模塊源代碼讀取至內(nèi)存。

  • 拼接函數(shù)

在上一步中獲取到的只是代碼的文本形式源文件,并不具有執(zhí)行能力。在接下來的步驟中需要將它變?yōu)橐粋€可執(zhí)行的代碼段。

如果有同學(xué)看過 webpack 打包出來的結(jié)果,可以發(fā)現(xiàn)有這么一個現(xiàn)象,所有模塊化的內(nèi)容都處在一個函數(shù)的閉包中,內(nèi)部所有的模塊加載函數(shù)都替換成了 __webpack_require__ 這類的 webpack 內(nèi)部變量。

還有一個問題,在 CommonJS 模塊化規(guī)范中我們或多或少在每個文件中會寫 module, module.exports require 等等這樣的「字眼」,因為這里的 module 和 require 講道理并不能稱為關(guān)鍵字,JS 中關(guān)于模塊加載方面的關(guān)鍵字只有 ESModule 中 import 和 export 等等相關(guān)的內(nèi)容,他們是真真正正的關(guān)鍵字。而這里 CommonJS 里面帶來的 module 和 require 則完全算是自己實現(xiàn)的一種 hack,在日常的 CommonJS 模塊書寫過程中,module 對象和 require 函數(shù)完全是 node 在包解析時注入進去的(類似上面的 __webpack_require__)

這也就給了我們極大的想象空間,我們也完全可以將上面拿到的 module 進行包裹然后注入我們傳遞的每一個變量。簡單的例子:

// 純文本代碼 無法執(zhí)行
var str = 1;
console.log(str);

將函數(shù)進行拼接,結(jié)果依舊是一個純文本代碼。但是已經(jīng)可以給這個文件內(nèi)部注入 require module 等變量,只需后續(xù)將它變?yōu)榭蓤?zhí)行文件并執(zhí)行,就能把模塊取出來。

function(require, module, exports, __dirname, __filename) {
 // 純文本代碼
 var str = 1;
 console.log(str);
}
  • 轉(zhuǎn)化為可執(zhí)行代碼

拼接完成之后我們拿到的是還是純字符串的代碼,接下來就需要將這個字符串變成真正的代碼,也就是將字符串變?yōu)榭蓤?zhí)行代碼片段,這種操作在 JS 的歷史上一直是危險的代名詞…一直以來也有多種方法可以使用,eval、new Function(str) 等等。而在 node 環(huán)境中可以直接使用原生提供的 vm 模塊,內(nèi)部的沙盒環(huán)境支持我們手動注入一些變量,相對來說安全性還有所保證。

var txt = "function(require, module, exports, __dirname, __filename) {
 module.exports = 1;
}"

var vm = require('vm');
var script = new vm.Script(txt);
var func = script.runInThisContext();

上面這個示例中,func 就已經(jīng)是經(jīng)過 vm 從字符串變?yōu)榭蓤?zhí)行代碼段的結(jié)果,我們的 txt 給定的是一個函數(shù),所以此時我們需要調(diào)用這個函數(shù)來最后完成模塊的導(dǎo)出。

var m = {
 exports: {}
};
func(null, m, m.exports);

這樣的話,內(nèi)部導(dǎo)出的內(nèi)容就會被外面全局對象 m 所截獲,將每一個模塊導(dǎo)出的結(jié)果緩存到全局的 m 對象上面來。

而對于 require 函數(shù)來講,注入時我們需要考慮的就是走完上面的幾個步驟,require 接受一個字符串變量路徑,然后依次通過路徑找到文件,獲取文件,拼接函數(shù),變?yōu)榭蓤?zhí)行代碼段并執(zhí)行,之后仍給全局的緩存對象,這就是 「require」需要做的內(nèi)容。

過程中的切面

  • 最終形態(tài)是什么

對于最終的形態(tài),本質(zhì)上我們是要提供一個 require 函數(shù),它的目標(biāo)就是在 runtime 能夠從遠(yuǎn)端 url 加載 js 模塊,能夠加載 ts 模塊甚至類似 babel 提供 preset 加載各種各樣的模塊。

但是我們的 require 無法注入到 node bootstrap 階段,所以最終結(jié)果一定得是 bootsrap 文件使用 CommonJS 模塊加載,通過我們自定義的 require 加載的所有文件都能實現(xiàn)功能。

  • 生命周期的設(shè)計

就如上面的第二部分介紹的那樣,對于 require 函數(shù)我們要依次做這些事情,完全可以把每個階段看做一個切面,任何一個階段只關(guān)注輸入和輸出而不關(guān)注上個階段是如何產(chǎn)出的。

經(jīng)過仔細(xì)的思考,最終設(shè)置了兩個核心的過程,包裹模塊內(nèi)容 和 編譯文件結(jié)果。

包裹模塊內(nèi)容就是將字符串的文件結(jié)果包裹一下函數(shù),專注于處理字符串結(jié)果,將普通文件的文本進行包裹。

編譯文件結(jié)果這一步就是將代碼結(jié)果編譯成 node 能夠直接識別的 js 而使得下一步沙盒環(huán)境進行執(zhí)行,每次通過文件結(jié)果動態(tài)在內(nèi)存進行編譯,從而使得下一步 js 的執(zhí)行。

  • 同步還是異步?

這個問題其實困擾了很久。最大的問題就是里面涉及了部分異步加載的問題,按照傳統(tǒng)前端的做法,這里一般都是使用 callback 或者 promise(async/await) 的方式,但這樣就會帶來一個很大的問題。

如果是 callback 的方式,那么意味著最終我的 require 可能得這樣調(diào)用:

var r = require("nedo");
var moduleA = r("./moduleA");
var moduleB = r("./moduleB");

function log(module) {
 // 所有執(zhí)行過程作為 callback
 // 這里拿到 module 的結(jié)果
 console.log(module);
}

moduleA(log); // 傳入 callback,moduleA 加載結(jié)束執(zhí)行回調(diào)
moduleB(log); // 傳入 callback,moduleB 加載結(jié)束執(zhí)行回調(diào)

這樣就顯得很愚蠢,即使改成 AMD 那樣的 callback 調(diào)用也感覺是在開歷史的倒車。

如果是 promise(async/await) 這樣的異步方式,那么意味著最終我的 require 可能得這樣調(diào)用:

var r = require("nedo");
var moduleA = r("./moduleA");

moduleA.then(module => {
 // 這里拿到 module 結(jié)果
});

(async function() {
 var moduleB = await r("./moduleB");
 // 這里拿到 module 的結(jié)果
})();

說實話這種方式也顯得很愚蠢。不過中間我想了個方法,包裹函數(shù)時多包一層,包一個 IIFE 然后自執(zhí)行一個 async 的 wrapper,不過這樣的話 bootstrap 文件就必須還得手動包裹在 async 的函數(shù)中,子函數(shù)的問題解決了但是上層沒有解決,不夠完美。

其實后來仔細(xì)的思考了一下,造成這樣的問題的原因究其根本是因為 request 是 async 的,這就導(dǎo)致了后續(xù)的代碼必須以 async 的方式出現(xiàn)。如果我們想要從硬盤讀取一個文件,那么我們可以使用 promise 包裹的 fs.readFile,當(dāng)然我們也可以使用 fs.readFileSync 。前者的方法會讓后續(xù)的所有調(diào)用都變成異步,而后者的代碼還是同步,雖然性能很差但是完全符合直覺。

所以就必須找到一個 sync 的 request 的形式,才能讓最終調(diào)用變的完美,最終的想法結(jié)果應(yīng)該如下:

var r = require("nedo");
var moduleA = r("./moduleA");
// moduleA 結(jié)果

var moduleB = r("https://baidu.com");
// moduleB 結(jié)果,同步阻塞

思考了半天不知道 sync 的 request 應(yīng)該怎么寫,后來只得求助萬能的 npmjs,結(jié)果真的發(fā)現(xiàn)了一個 sync-request 的包,仔細(xì)研究了一下代碼發(fā)現(xiàn)核心是借助了 sync-rpc 這個包,雖然這個包 github 只有 5 個 star,下載量也不大。但是感覺卻是非常的厲害,能夠?qū)⑷魏萎惒降拇a轉(zhuǎn)化為同步調(diào)用的形式,戰(zhàn)略性 star,日后可能大有所為…

  • runtime 編譯

解決了 request async 的問題之后其他問題都變的非常簡單,ts 使用 babel + ts preset 在內(nèi)存中完成了編譯,如果想要增加任何文件的支持,只需要在 lib/compile 下加入對應(yīng)的文件后綴即可,在內(nèi)存中只要能夠完成編譯就能夠最終保證代碼結(jié)果。

  • top level await

在之前的過程中我們只是包了一層注入?yún)?shù)的函數(shù)進去,當(dāng)然也可以上層包裹一層 async 函數(shù),這樣就可以在使用 nedo require 的包內(nèi)部直接使用頂層 await,不需要再使用 async 進行包裹

最終結(jié)果

最后經(jīng)過幾個小時的不懈努力,最終能夠?qū)?hello world 跑起來了,代碼還處于 pre-pre-pre-prototype 的階段。倉庫地址 nedo ,希望大家多幫忙 review,提供更多建設(shè)性的意見…

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。

相關(guān)文章

  • express中創(chuàng)建 websocket 接口及問題解答

    express中創(chuàng)建 websocket 接口及問題解答

    本文主要介紹了express中創(chuàng)建 websocket 接口及問題解答,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-05-05
  • Node?文件查找優(yōu)先級及?Require?方法文件查找策略

    Node?文件查找優(yōu)先級及?Require?方法文件查找策略

    這篇文章主要介紹了Node文件查找優(yōu)先級及Require方法文件查找策略。文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-09-09
  • 簡單兩步使用node發(fā)送qq郵件的方法

    簡單兩步使用node發(fā)送qq郵件的方法

    這篇文章主要介紹了簡單兩步使用node發(fā)送qq郵件的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-03-03
  • node.js [superAgent] 請求使用示例

    node.js [superAgent] 請求使用示例

    這篇文章主要介紹了node.js [superAgent] 請求使用示例,分別給大家匯總了post請求、get請求、delete請求和put請求的示例,推薦給大家,希望大家能夠喜歡。
    2015-03-03
  • nodejs和npm版本不匹配報錯的解決方法

    nodejs和npm版本不匹配報錯的解決方法

    當(dāng)公司要求使用固定nodejs的版本時,自己不小心更新了npm,就會導(dǎo)致npm和nodejs不匹配,下面這篇文章主要給大家介紹了關(guān)于nodejs和npm版本不匹配報錯的解決方法,需要的朋友可以參考下
    2023-04-04
  • node版本過高該如何將node版本降低

    node版本過高該如何將node版本降低

    我們常使用nvm來管理node.js的版本,這樣就可以根據(jù)自己的需要來回切換node.js版本,下面這篇文章主要給大家介紹了關(guān)于node版本過高該如何將node版本降低的相關(guān)資料,需要的朋友可以參考下
    2023-01-01
  • node通過express搭建自己的服務(wù)器

    node通過express搭建自己的服務(wù)器

    本篇文章主要介紹了node通過express搭建自己的服務(wù)器 ,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-09-09
  • nodejs入門教程四:URL相關(guān)模塊用法分析

    nodejs入門教程四:URL相關(guān)模塊用法分析

    這篇文章主要介紹了nodejs入門教程四之URL相關(guān)模塊用法,較為詳細(xì)的分析了URL相關(guān)模塊功能、方法與使用技巧,需要的朋友可以參考下
    2017-04-04
  • 利用yarn代替npm管理前端項目模塊依賴的方法詳解

    利用yarn代替npm管理前端項目模塊依賴的方法詳解

    這篇文章主要給大家介紹了關(guān)于利用yarn代替npm管理前端項目模塊依賴的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-09-09
  • Node.js assert斷言原理與用法分析

    Node.js assert斷言原理與用法分析

    這篇文章主要介紹了Node.js assert斷言原理與用法,結(jié)合實例形式分析了assert模塊斷言函數(shù)與使用技巧,需要的朋友可以參考下
    2019-01-01

最新評論