久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

手把手教你寫一個迷你 Webpack

 xvdo 2021-11-12

一、前言

最近正好在學習 Webpack,,覺得 Webpack 這種通過構(gòu)建模塊依賴圖來打包項目文件的思想很有意思,,于是參考了網(wǎng)上的一些文章實現(xiàn)了一個簡陋版本的 mini-webpack,通過入口文件將依賴的模塊打包在一起,,生成一份最終運行的代碼,。想了解 Webpack 的構(gòu)建原理還需要補充一些相關(guān)的背景知識,下面一起來看看,。

二,、背景知識

1. 抽象語法樹(AST)

什么是抽象語法樹?

平時我們編寫程序的時候,,會經(jīng)常在代碼中根據(jù)需要 import 一些模塊,,那 Webpack 在構(gòu)建項目、分析依賴的時候是如何得知我們代碼中是否有 import 文件,,import 的是什么文件的呢,?Webpack 并不是人,,無法像我們一樣一看到代碼語句就明白其含義,所以我們需要將編寫的代碼轉(zhuǎn)換成 Webpack 認識的格式讓他它進行處理,,這份轉(zhuǎn)換后生成的東西就是抽象語法樹,。下面這張圖能很好地說明什么是抽象語法樹:

可以看到,抽象語法樹是源代碼的抽象語法結(jié)構(gòu)樹狀表現(xiàn)形式,,我們每條編寫的代碼語句都可以被解析成一個個的節(jié)點,,將一整個代碼文件解析后就會生成一顆節(jié)點樹,作為程序代碼的抽象表示,。通過抽象語法樹,,我們可以做以下事情:

  • IDE 的錯誤提示、代碼格式化,、代碼高亮,、代碼自動補全等

  • JSLint、JSHint,、ESLint 對代碼錯誤或風格的檢查等

  • Webpack,、rollup 進行代碼打包等

  • Babel 轉(zhuǎn)換 ES6 到 ES5 語法

  • 注入代碼統(tǒng)計單元測試覆蓋率

想看看你的代碼會生成怎樣的抽象語法樹嗎?這里有一個工具能夠在線預覽你的代碼生成的抽象語法樹,,感興趣的不妨上去試一試,。

2. Babel

Babel 是一個工具鏈,主要用于將采用 ECMAScript 2015+ 語法編寫的代碼轉(zhuǎn)換為向后兼容的 JavaScript 語法,,以便能夠運行在當前和舊版本的瀏覽器或其他環(huán)境中,。通過 Babel 我們可以做以下事情:

  • 語法轉(zhuǎn)換

  • 通過 Polyfill 方式在目標環(huán)境中添加缺失的特性(通過第三方 Polyfill 模塊,例如 core-js ,,實現(xiàn))

  • 源碼轉(zhuǎn)換 (codemods)

一般來說項目使用 Webpack 來打包文件都會配置 babel-loader 將 ES6 的代碼轉(zhuǎn)換成 ES5 的格式以兼容瀏覽器,,這個過程就需要將我們的代碼轉(zhuǎn)換成抽象語法樹后再進行轉(zhuǎn)換處理,轉(zhuǎn)換完成后再將抽象語法樹還原成代碼,。

// Babel 輸入:ES2015 箭頭函數(shù) [1, 2, 3].map((n) => n + 1); // Babel 輸出:ES5 語法實現(xiàn)的同等功能 [1, 2, 3].map(function(n) { return n + 1;});

3. Webpack 打包原理

Webpack 的構(gòu)建過程一般會分為以下幾步:

  • 讀取 Webpack 基礎配置

    // 讀取 webpack.config.js 配置文件: const path = require('path') module.exports = { entry:'./src/index.js' mode:'development' output:{ path:path.resolve(__dirname,'./dist'), filename:'bundle.js' }}
  • 入口文件分析

    • 分析依賴模塊

    • 分析內(nèi)容

    • 編譯內(nèi)容

  • 依賴模塊分析

    • 分析依賴模塊是否有其他模塊

    • 分析內(nèi)容

    • 編譯內(nèi)容

  • 生成打包文件

// 基礎結(jié)構(gòu)為一個IIFE自執(zhí)行函數(shù) // 接收一個對象參數(shù),,key 為入口文件的目錄,value為一個執(zhí)行入口文件里面代碼的函數(shù) (function (modules) { // installedModules 用來存放緩存 const installedModules = {}; // __webpack_require__用來轉(zhuǎn)化入口文件里面的代碼 function __webpack_require__(moduleIid) { ... } // IIFE將 modules 中的 key 傳遞給 __webpack_require__ 函數(shù)并返回,。 return __webpack_require__(__webpack_require__.s = './src/index.js');}({ './src/index.js': (function (module, exports) { eval('console.log('test webpack entry')'); }),}));

三,、具體實現(xiàn)

1. 安裝相關(guān)依賴

我們需要用到以下幾個包:

  • @babel/parser:用于將輸入代碼解析成抽象語法樹(AST)

  • @babel/traverse:用于對輸入的抽象語法樹(AST)進行遍歷

  • @babel/core:babel 的核心模塊,進行代碼的轉(zhuǎn)換

  • @babel/preset-env:可根據(jù)配置的目標瀏覽器或者運行環(huán)境來自動將 ES2015 + 的代碼轉(zhuǎn)換為 es5

使用 npm 命令安裝一下:

npm install @babel/parser @babel/traverse @babel/core @babel/preset-env -D

2. 讀取基本配置

要讀取 Webpack 的基本配置,,首先我們得有一個全局的配置文件:

// mini-webpack.config.js const path = require('path'); module.exports ={ entry: './src/index.js', mode: 'development', output: { path: path.resolve(__dirname,'./dist'), filename: 'bundle.js' }}

然后我們新建一個類,,用于實現(xiàn)分析編譯等函數(shù),并在構(gòu)造函數(shù)中初始化配置信息:

const options = require('./mini-webpack.config'); class MiniWebpack{ constructor(options){ this.options = options;} // ... }

3. 代碼轉(zhuǎn)換,,獲取模塊信息

我們使用 fs 讀取文件內(nèi)容,,使用  parser 將模塊代碼轉(zhuǎn)換成抽象語法樹,再使用  traverse 遍歷抽象語法樹,,針對其中的  ImportDeclaration 節(jié)點保存模塊的依賴信息,,最終使用  babel.transformFromAst 方法將抽象語法樹還原成 ES5 風格的代碼。

parse = filename => { // 讀取文件 const fileBuffer = fs.readFileSync(filename, 'utf-8'); // 轉(zhuǎn)換成抽象語法樹 const ast = parser.parse(fileBuffer, { sourceType: 'module' }); const dependencies = {}; // 遍歷抽象語法樹 traverse(ast, { // 處理ImportDeclaration節(jié)點 ImportDeclaration({node}){ const dirname = path.dirname(filename); const newDirname = './' + path.join(dirname, node.source.value).replace('\', '/');dependencies[node.source.value] = newDirname;}}) // 將抽象語法樹轉(zhuǎn)換成代碼 const { code } = babel.transformFromAst(ast, null, { presets:['@babel/preset-env']}); return {filename,dependencies,code}}

4. 分析依賴關(guān)系

從入口文件開始,,循環(huán)解析每個文件與其依賴文件的信息,,最終生成以文件名為 key ,以包含依賴關(guān)系與編譯后模塊代碼的對象為  value 的依賴圖譜對象并返回,。

analyse = entry => { // 解析入口文件 const entryModule = this.parse(entry); const graphArray = [entryModule]; // 循環(huán)解析模塊,,保存信息 for(let i=0;i<graphArray.length;++i){ const { dependencies } = graphArray[i]; Object.keys(dependencies).forEach(filename => {graphArray.push(this.parse(dependencies[filename]));})} const graph = {}; // 生成依賴圖譜對象 graphArray.forEach(({filename, dependencies, code})=>{graph[filename] = {dependencies,code};}) return graph;}

5. 生成打包代碼

生成依賴圖譜對象,作為參數(shù)傳入一個自執(zhí)行函數(shù)當中,??梢钥吹剑詧?zhí)行函數(shù)中有個 require 函數(shù),,它的作用是通過調(diào)用 eval 執(zhí)行模塊代碼來獲取模塊內(nèi)部 export 出來的值,。最終我們返回打包的代碼。

generate = (graph, entry) => { return ` (function(graph){ function require(filename){ function localRequire(relativePath){ return require(graph[filename].dependencies[relativePath]); } const exports = {}; (function(require, exports, code){ eval(code); })(localRequire, exports, graph[filename].code) return exports; } require('${entry}'); })(${graph}) ` }

6. 輸出最終文件

通過獲取 this.options 中的 output 信息,,將打包代碼輸出到對應文件中,。

fileOutput = (output, code) => { const { path: dirPath, filename } = output; const outputPath = path.join(dirPath, filename); // 如果沒有文件夾的話,生成文件夾  if(!fs.existsSync(dirPath)){  fs.mkdirSync(dirPath)  }  // 寫入文件中  fs.writeFileSync(outputPath, code, 'utf-8'); }

7. 模擬 run 函數(shù)

我們將上面的流程集成到一個 run 函數(shù)中,,通過調(diào)用該函數(shù)來將整個構(gòu)建打包流程跑通,。

run = () => { const { entry, output } = this.options; const graph = this.analyse(entry); // stringify依賴圖譜對象,防止在模板字符串中調(diào)用toString()返回[object Object] const graphStr = JSON.stringify(graph); const code = this.generate(graphStr, entry); this.fileOutput(output, code); }

8.mini-webpack 大功告成

通過上面的流程,,我們的 mini-webpack 已經(jīng)完成了,。我們將文件保存為 main.js,新建一個 MiniWebpack 對象并執(zhí)行它的 run 函數(shù):

// main.js const options = require('./mini-webpack.config'); class MiniWebpack{ constructor(options){ // ... }parse = filename => { // ... }analyse = entry => { // ... }generate = (graph, entry) => { // ... }fileOutput = (output, code) => { // ... }run = () => { // ... }} const miniWebpack = new MiniWebpack(options);miniWebpack.run();

四,、實際演示

我們來實際試驗一下,,看看這個 mini-webpack 能不能正常運行。

1. 新建測試文件

首先在根目錄下創(chuàng)建 src 文件夾,,新建  a.js ,、 b.js 、 index.js 三個文件

三個文件內(nèi)容如下:

  • a.js

export default 1;
  • b.js

export default function(){ console.log('I am b');}
  • index.js

import a from './a.js'; import b from './b.js'; console.log(a); console.log(b);

2. 填入配置文件

配置好入口文件,、輸出文件等信息:

const path = require('path'); module.exports ={ entry: './src/index.js', mode: 'development', output: { path: path.resolve(__dirname,'./dist'), filename: 'bundle.js' }}

3. 完善 package.json

我們在 package.json 的 scripts 中新增一個  build 命令,,內(nèi)容為執(zhí)行 main.js:

{ 'name': 'mini-webpack', 'version': '1.0.0', 'description': '', 'main': 'index.js', 'scripts': { 'test': 'echo 'Error: no test specified' && exit 1', 'build': 'node main.js' }, 'author': '', 'license': 'ISC', 'devDependencies': { '@babel/core': '^7.15.4', '@babel/parser': '^7.15.4', '@babel/preset-env': '^7.15.4', '@babel/traverse': '^7.15.4' }}

4. 效果演示

我們執(zhí)行 npm run build 命令,可以看到在根目錄下生成了 dist 文件夾,,里面有個 bundle.js 文件,,內(nèi)容正是我們輸出的打包代碼:

執(zhí)行下 bundle.js 文件,看看會有什么輸出:

可以看到,,bundle.js 的輸出正是 index.js 文件中兩個 console.log 輸出的值,,說明我們的代碼轉(zhuǎn)換沒有問題,到這里試驗算是成功了,。

    本站是提供個人知識管理的網(wǎng)絡存儲空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導購買等信息,,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請點擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多