<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【webpack】做了一夜動(dòng)畫,讓大家十分鐘搞懂Webpack

          共 7906字,需瀏覽 16分鐘

           ·

          2021-05-22 23:04

          一、什么是webpack

          webpack是一個(gè)打包工具,他的宗旨是一切靜態(tài)資源皆可打包。


          二、原理分析

          首先我們通過一個(gè)制作一個(gè)打包文件的原型。

          假設(shè)有兩個(gè)js模塊,這里我們先假設(shè)這兩個(gè)模塊是復(fù)合commomjs標(biāo)準(zhǔn)的es5模塊。

          語法和模塊化規(guī)范轉(zhuǎn)換的事我們先放一放,后面說。

          我們的目的是將這兩個(gè)模塊打包為一個(gè)能在瀏覽器端運(yùn)行的文件,這個(gè)文件其實(shí)叫bundle.js。

          比如

          // index.js
          var add = require('add.js').default
          console.log(add(1 , 2))

          // add.js
          exports.default = function(a,b{return a + b}

          假設(shè)在瀏覽器中直接執(zhí)行這個(gè)程序肯定會(huì)有問題 最主要的問題是瀏覽器中沒有exports對(duì)象與require方法所以一定會(huì)報(bào)錯(cuò)。

          我們需要通過模擬exports對(duì)象和require方法

          1. 模擬exports對(duì)象

          首先我們知道如果在nodejs打包的時(shí)候我們會(huì)使用fs.readfileSync()來讀取js文件。這樣的話js文件會(huì)是一個(gè)字符串。而如果需要將字符串中的代碼運(yùn)行會(huì)有兩個(gè)方法分別是new Function與Eval。

          在這里面我們選用執(zhí)行效率較高的eval。

          exports = {}
          eval('exports.default = function(a,b) {return a + b}'// node文件讀取后的代碼字符串
          exports.default(1,3)
          image-20210507102528679

          上面這段代碼的運(yùn)行結(jié)果可以將模塊中的方法綁定在exports對(duì)象中。由于子模塊中會(huì)聲明變量,為了不污染全局我們使用一個(gè)自運(yùn)行函數(shù)來封裝一下。

          var exports = {}
          (function (exports, code{
           eval(code)
          })(exports, 'exports.default = function(a,b){return a + b}')

          2. 模擬require函數(shù)

          require函數(shù)的功能比較簡(jiǎn)單,就是根據(jù)提供的file名稱加載對(duì)應(yīng)的模塊。

          首先我們先看看如果只有一個(gè)固定模塊應(yīng)該怎么寫。

          Kapture 2021-05-09 at 11.20.31.gif
          function require(file{
           var exports = {};
           (function (exports, code{
            eval(code)
           })(exports, 'exports.default = function(a,b){return a + b}')
            return exports
          }
          var add = require('add.js').default
          console.log(add(1 , 2))

          完成了固定模塊,我們下面只需要稍加改動(dòng),將所有模塊的文件名和代碼字符串整理為一張key-value表就可以根據(jù)傳入的文件名加載不同的模塊了。

          (function (list{
            function require(file{
              var exports = {};
              (function (exports, code{
                eval(code);
              })(exports, list[file]);
              return exports;
            }
            require("index.js");
          })({
            "index.js"`
              var add = require('add.js').default
              console.log(add(1 , 2))
                  `
          ,
            "add.js"`exports.default = function(a,b){return a + b}`,
          });

          當(dāng)然要說明的一點(diǎn)是真正webpack生成的bundle.js文件中還需要增加模塊間的依賴關(guān)系。

          叫做依賴圖(Dependency Graph)

          類似下面的情況。

          {
            "./src/index.js": {
              "deps": { "./add.js""./src/add.js" },
              "code""....."
            },
            "./src/add.js": {
              "deps": {},
              "code""......"
            }
          }

          另外,由于大多數(shù)前端程序都習(xí)慣使用es6語法所以還需要預(yù)先將es6語法轉(zhuǎn)換為es5語法。

          總結(jié)一下思路,webpack打包可以分為以下三個(gè)步驟:

          1. 分析依賴

          2. ES6轉(zhuǎn)ES5

          3. 替換exports和require

          下面進(jìn)入功能實(shí)現(xiàn)階段。

          三、功能實(shí)現(xiàn)

          我們的目標(biāo)是將以下兩個(gè)個(gè)互相依賴的ES6Module打包為一個(gè)可以在瀏覽器中運(yùn)行的一個(gè)JS文件(bundle.js)

          • 處理模塊化
          • 多模塊合并打包 - 優(yōu)化網(wǎng)絡(luò)請(qǐng)求

          /src/add.js

          export default (a, b) => a + b 

          /src/index.js

          import add from "./add.js";
          console.log(add(1 , 2))

          1. 分析模塊

          分析模塊分為以下三個(gè)步驟:

          模塊的分析相當(dāng)于對(duì)讀取的文件代碼字符串進(jìn)行解析。這一步其實(shí)和高級(jí)語言的編譯過程一致。需要將模塊解析為抽象語法樹AST。我們借助babel/parser來完成。

          AST (Abstract Syntax Tree)抽象語法樹 在計(jì)算機(jī)科學(xué)中,或簡(jiǎn)稱語法樹(Syntax tree),是源代碼語法結(jié)構(gòu)的一種抽象表示。它以樹狀的形式表現(xiàn)編程語言的語法結(jié)構(gòu),樹上的每個(gè)節(jié)點(diǎn)都表示源代碼中的一種結(jié)構(gòu)。( https://astexplorer.net/)

          yarn add @babel/parser
          yarn add @babel/traverse
          yarn add @babel/core
          yarn add @babel/preset-env
          • 讀取文件

          • 收集依賴

          • 編譯與AST解析

          const fs = require("fs");
          const path = require("path");
          const parser = require("@babel/parser");
          const traverse = require("@babel/traverse").default;
          const babel = require("@babel/core");

          function getModuleInfo(file{
            // 讀取文件
            const body = fs.readFileSync(file, "utf-8");

            // 轉(zhuǎn)化AST語法樹
            const ast = parser.parse(body, {
              sourceType"module"//表示我們要解析的是ES模塊
            });

            // 依賴收集
            const deps = {};
            traverse(ast, {
              ImportDeclaration({ node }) {
                const dirname = path.dirname(file);
                const abspath = "./" + path.join(dirname, node.source.value);
                deps[node.source.value] = abspath;
              },
            });

            // ES6轉(zhuǎn)成ES5
            const { code } = babel.transformFromAst(ast, null, {
              presets: ["@babel/preset-env"],
            });
            const moduleInfo = { file, deps, code };
            return moduleInfo;
          }
          const info = getModuleInfo("./src/index.js");
          console.log("info:", info);

          運(yùn)行結(jié)果如下:

          ![image-20210507152817221](/Users/xiaran/Library/Application Support/typora-user-images/image-20210507152817221.png)

          2. 收集依賴

          上一步開發(fā)的函數(shù)可以單獨(dú)解析某一個(gè)模塊,這一步我們需要開發(fā)一個(gè)函數(shù)從入口模塊開始根據(jù)依賴關(guān)系進(jìn)行遞歸解析。最后將依賴關(guān)系構(gòu)成為依賴圖(Dependency Graph)

          /**
           * 模塊解析
           * @param {*} file 
           * @returns 
           */

          function parseModules(file{
            const entry = getModuleInfo(file);
            const temp = [entry];
            const depsGraph = {};

            getDeps(temp, entry);

            temp.forEach((moduleInfo) => {
              depsGraph[moduleInfo.file] = {
                deps: moduleInfo.deps,
                code: moduleInfo.code,
              };
            });
            return depsGraph;
          }

          /**
           * 獲取依賴
           * @param {*} temp 
           * @param {*} param1 
           */

          function getDeps(temp, { deps }{
            Object.keys(deps).forEach((key) => {
              const child = getModuleInfo(deps[key]);
              temp.push(child);
              getDeps(temp, child);
            });
          }

          3. 生成bundle文件

          這一步我們需要將剛才編寫的執(zhí)行函數(shù)和依賴圖合成起來輸出最后的打包文件。

          function bundle(file{
            const depsGraph = JSON.stringify(parseModules(file));
            return `(function (graph) {
                  function require(file) {
                      function absRequire(relPath) {
                          return require(graph[file].deps[relPath])
                      }
                      var exports = {};
                      (function (require,exports,code) {
                          eval(code)
                      })(absRequire,exports,graph[file].code)
                      return exports
                  }
                  require('${file}')
              })(${depsGraph})`
          ;
          }


          !fs.existsSync("./dist") && fs.mkdirSync("./dist");
          fs.writeFileSync("./dist/bundle.js", content);

          最后可以編寫一個(gè)簡(jiǎn)單的測(cè)試程序測(cè)試一下結(jié)果。

          <script src="./dist/bundle.js"></script>
          image-20210507154015030

          ok 學(xué)費(fèi)了。

          后面有興趣的話大家可以在考慮一下如何加載css文件或者圖片base64 Vue SFC .vue。



          瀏覽 94
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  男男精品一区二区三区 | 日本在线视频播放69 | 亚洲第一香蕉视频在线观看 | 国产精品色婷婷99久久精品 | 国产福利网 |