<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>

          rollup - 構建原理及簡易實現(xiàn)

          共 44119字,需瀏覽 89分鐘

           ·

          2021-06-24 13:53


          黃丹丹,微醫(yī)前端技術部醫(yī)療支撐組。一個資深貓奴,愛靜亦愛動無痕切換的精分程序媛。

          一、Rollup 概述

          官網(wǎng)地址:https://rollupjs.org/guide/en/

          Rollup 是什么

          我們先看看 Rollup 的作者 Rich Harris 是怎么講的?Rollup 是一個模塊化的打包工具。本質(zhì)上,它會合并 JavaScript 文件。而且你不需要去手動指定它們的順序,或者去擔心文件之間的變量名沖突。它的內(nèi)部實現(xiàn)會比說的復雜一點,但是它就是這么做的 —— 合并

          對比 Webpack

          webpack 對前端來說是再熟悉不過的工具了,它提供了強大的功能來構建前端的資源,包括 html/js/ts/css/less/scss ... 等語言腳本,也包括 images/fonts ... 等二進制文件。正是因為 webpack 擁有如此強大的功能,所以 webpack 在進行資源打包的時候,就會產(chǎn)生很多冗余的代碼(如果你有查看過 webpack 的 bundle 文件,便會發(fā)現(xiàn))。

          而對于一些項目(特別是類庫)只有 js,而沒有其他的靜態(tài)資源文件,使用 webpack 就有點大才小用了,因為 webpack bundle 文件的體積略大,運行略慢,可讀性略低。這個時候就可以選擇 Rollup

          Rollup是一個模塊打包器,支持 ES6 模塊,支持 Tree-shaking,但不支持 webpack 的 code-splitting、模塊熱更新等,這意味著它更適合用來做類庫項目的打包器而不是應用程序項目的打包器。

          簡單總結

          對于應用適用于 webpack,對于類庫更適用于 Rollup,react/vue/anngular 都在用Rollup作為打包工具

          二、Rollup 前置知識

          在闡述 Rollup 的構建原理之前,我們需要了解一些前置知識

          magic-string

          magic-string 是 Rollup 作者寫的一個關于字符串操作的庫,這個庫主要是對字符串一些常用方法進行了封裝

          var MagicString = require('magic-string')
          var magicString = new MagicString('export var name = "zhangsan"')
          // 以下所有操作都是基于原生字符串
          // 類似于截取字符串
          console.log(magicString.snip(0, 6).toString()) // export
          // 從開始到結束刪除
          console.log(magicString.remove(0, 7).toString()) //  var name = "zhangsan"

          // 多個模塊,把他們打包在一個文件里,需要把很多文件的源代碼合并在一起
          let bundleString = new MagicString.Bundle();
          bundleString.addSource({
              content: 'console.log(hello)',
              separator: '\n'
          })
          bundleString.addSource({
              content: 'console.log(world)',
              separator: '\n'
          })
          // // 原理類似
          // let str = ''
          // str += 'console.log(hello);\n'
          // str += 'console.log(world);\n'
          console.log(bundleString.toString()) 
          // hello
          // world

          AST

          通過 javascript parse 可以把代碼轉化為一顆抽象語法樹 AST,這顆樹定義了代碼的結構,通過操縱這個樹,我們可以精確的定位到聲明語句、賦值語句、運算符語句等等,實現(xiàn)對代碼的分析、優(yōu)化、變更等操作 源代碼:main.js

          // main.js
          import { a } from './a'
          console.log(a)

          轉化為 AST 是長這樣子的,如下:

          {
            "type""Program", // 這個 AST 類型為 Program,表明是一個程序
            "start": 0,
            "end": 40,
            "body": [ // body 是一個數(shù)組,每一條語句都對應 body 下的一個語句
              {
                "type""ImportDeclaration", // 導入聲明類型
                "start": 0,
                "end": 23,
                "specifiers": [
                  {
                    "type""ImportSpecifier",
                    "start": 9,
                    "end": 10,
                    "imported": {
                      "type""Identifier",
                      "start": 9,
                      "end": 10,
                      "name""a" // 導入模塊命名 name 'a'
                    },
                    "local": {
                      "type""Identifier",
                      "start": 9,
                      "end": 10,
                      "name""a" // 本地模塊命名,同 imported.name
                    }
                  }
                ],
                "source": {
                  "type""Literal",
                  "start": 18,
                  "end": 23,
                  "value""./a", // 導入路徑 './a'
                  "raw""'./a'"
                }
              },
              {
                "type""ExpressionStatement", // 表達式類型
                "start": 24,
                "end": 38,
                "expression": {
                  "type""CallExpression", // 調(diào)用表達式類型
                  "start": 24,
                  "end": 38,
                  "callee": {
                    "type""MemberExpression",
                    "start": 24,
                    "end": 35,
                    "object": {
                      "type""Identifier",
                      "start": 24,
                      "end": 31,
                      "name""console"
                    },
                    "property": {
                      "type""Identifier",
                      "start": 32,
                      "end": 35,
                      "name""log"
                    },
                    "computed"false,
                    "optional"false
                  },
                  "arguments": [
                    {
                      "type""Identifier",
                      "start": 36,
                      "end": 37,
                      "name""a"
                    }
                  ],
                  "optional"false
                }
              }
            ],
            "sourceType""module"
          }


          AST 工作流

          Parse(解析)將代碼轉化成抽象語法樹,樹上有很多的 estree 節(jié)點 Transform(轉換) 對抽象語法樹進行轉換 Generate(代碼生成) 將上一步經(jīng)過轉換過的抽象語法樹生成新的代碼

          acorn

          acorn 是一個 JavaScript 語法解析器,它將 JavaScript 字符串解析成語法抽象樹 AST 如果想了解 AST 語法樹可以點下這個網(wǎng)址https://astexplorer.net/

          作用域/作用域鏈

          在 js 中,作用域是用來規(guī)定變量訪問范圍的規(guī)則, 作用域鏈是由當前執(zhí)行環(huán)境和上層執(zhí)行環(huán)境的一系列變量對象組成的,它保證了當前執(zhí)行環(huán)境對符合訪問權限的變量和函數(shù)的有序訪問

          三、Rollup

          Rollup 是怎樣工作的呢?

          你給它一個入口文件 —— 通常是 index.js。Rollup 將使用 Acorn 讀取解析文件 —— 將返回給我們一種叫抽象語法樹(AST)的東西。一旦有了 AST ,你就可以發(fā)現(xiàn)許多關于代碼的東西,比如它包含哪些 import 聲明。

          假設 index.js 文件頭部有這樣一行:

          import foo from './foo.js';

          這就意味著 Rollup 需要去加載,解析,分析在 index.js 中引入的 ./foo.js。重復解析直到?jīng)]有更多的模塊被加載進來。更重要的是,所有的這些操作都是可插拔的,所以您可以從 node_modules 中導入或者使用 sourcemap-aware 的方式將 ES2015 編譯成 ES5 代碼。

          在 Rollup 中,一個文件就是一個模塊,每個模塊都會根據(jù)文件的代碼生成一個 AST 抽象語法樹。

          分析 AST 節(jié)點,就是看這個節(jié)點有沒有調(diào)用函數(shù)方法,有沒有讀到變量,有,就查看是否在當前作用域,如果不在就往上找,直到找到模塊頂級作用域為止。如果本模塊都沒找到,說明這個函數(shù)、方法依賴于其他模塊,需要從其他模塊引入。如果發(fā)現(xiàn)其他模塊中有方法依賴其他模塊,就會遞歸讀取其他模塊,如此循環(huán)直到?jīng)]有依賴的模塊為止 找到這些變量或著方法是在哪里定義的,把定義語句包含進來即可 其他無關代碼一律不要

          看如下代碼,我們先實際操作一下:

          // index.js
          import { foo } from "./foo";
          foo()
          var city = 'hangzhou'

          function test() {
              console.log('test')
          }

          console.log(test())
          // foo.js
          import { bar } from "./bar";
          export function foo() {
              console.log('foo')
          }
          // bar.js
          export function bar() {
              console.log('bar')
          }
          // rollup.config.js
          export default {
              input: './src/index.js',
              output: {
                  file: './dist/bundle.js', // 打包后的存放文件
                  format: 'cjs', //輸出格式 amd es6 life umd cjs
                  name: 'bundleName', //如果輸出格式 life,umd 需要指定一個全局變量
              }
          };

          執(zhí)行 npm run build,會得到如下結果:

          'use strict';

          function foo() {
              console.log('foo');
          }

          foo();

          function test() {
              console.log('test');
          }

          console.log(test());

          以上,我們可以看到Rollup 只是會合并你的代碼 —— 沒有任何浪費。所產(chǎn)生的包也可以更好的縮小。有人稱之為 “作用域提升(scope hoisting)”。其次,它把你導入的模塊中的未使用代碼移除。這被稱為“(搖樹優(yōu)化)treeshaking”??傊?,Rollup 就是一個模塊化的打包工具。

          接下來我們進入源碼,具體分析下 Rollup 的構建流程

          Rollup 構建流程分析

          Rollup 源碼結構


          │  bundle.js // Bundle 打包器,在打包過程中會生成一個 bundle 實例,用于收集其他模塊的代碼,最后再將收集的代碼打包到一起。
          │  external-module.js // ExternalModule 外部模塊,例如引入了 'path' 模塊,就會生成一個 ExternalModule 實例。
          │  module.js // Module 模塊,module 實例。
          │  rollup.js // rollup 函數(shù),一切的開始,調(diào)用它進行打包。

          ├─ast // ast 目錄,包含了和 AST 相關的類和函數(shù)
          │      analyse.js // 主要用于分析 AST 節(jié)點的作用域和依賴項。
          │      Scope.js // 在分析 AST 節(jié)點時為每一個節(jié)點生成對應的 Scope 實例,主要是記錄每個 AST 節(jié)點對應的作用域。
          │      walk.js // walk 就是遞歸調(diào)用 AST 節(jié)點進行分析。

          ├─finalisers
          │      cjs.js
          │      index.js

          └─utils // 一些幫助函數(shù)
                  map-helpers.js
                  object.js
                  promise.js
                  replaceIdentifiers.js

          Rollup 構建流程

          我們以 index.js 入口文件,index 依賴了 foo.js,foo 依賴了 bar.js

          // index.js
          import { foo } from "./foo";
          foo()
          var city = 'hangzhou'

          function test() {
              console.log('test')
          }

          console.log(test())
          // foo.js
          import { bar } from "./bar";
          export function foo() {
              console.log('foo')
          }
          // bar.js
          export function bar() {
              console.log('bar')
          }

          debug 起來!!!

          // debug.js
          const path = require('path')
          const rollup = require('./lib/rollup')
          // 入口文件的絕對路徑
          let entry = path.resolve(__dirname, 'src/main.js')
          // 和源碼有所不同,這里使用的是同步,增加可讀性
          rollup(entry, 'bundle.js')

          1.new Bundle(), build()

          首先生成一個 Bundle 實例,也就是打包器。然后執(zhí)行 build 打包編譯

          // rollup.js
          let Bundle = require('./bundle')
          function rollup(entry, outputFileName) {
              // Bundle 代表打包對象,里面包含所有的模塊信息
              const bundle = new Bundle({ entry })
              // 調(diào)用 build 方法開始進行編譯
              bundle.build(outputFileName)
          }
          module.exports = rollup

          lib/bundle.js根據(jù)入口路徑出發(fā)(在 bundle 中,我們會首先統(tǒng)一處理下入口文件的后綴),去找到他的模塊定義,在 fetchModule 中,會生成一個 module 實例我們關注紅框中的代碼,會發(fā)現(xiàn)返回了一個 module

          2.new Module()

          每個文件都是一個模塊,每個模塊都會有一個 Module 實例。在 Module 實例中,會調(diào)用 acorn 庫的 parse() 方法將代碼解析成 AST。對生成的 AST 進行分析analyse我們先看一下入口文件 index.js 生成的 AST可以看到 ast.body 是一個數(shù)組,分別對應 index.js 的五條語句 展開這個 ast 樹如下:

          {
            "type""Program",
            "start": 0,
            "end": 128,
            "body": [
              {
                "type""ImportDeclaration", // 導入聲明
                "start": 0,
                "end": 31,
                "specifiers": [
                  {
                    "type""ImportSpecifier",
                    "start": 9,
                    "end": 12,
                    "imported": {
                      "type""Identifier",
                      "start": 9,
                      "end": 12,
                      "name""foo"
                    },
                    "local": {
                      "type""Identifier",
                      "start": 9,
                      "end": 12,
                      "name""foo"
                    }
                  }
                ],
                "source": {
                  "type""Literal",
                  "start": 20,
                  "end": 30,
                  "value""./foo.js",
                  "raw""\"./foo.js\""
                }
              },
              {
                "type""ExpressionStatement",
                "start": 32,
                "end": 37,
                "expression": {
                  "type""CallExpression",
                  "start": 32,
                  "end": 37,
                  "callee": {
                    "type""Identifier",
                    "start": 32,
                    "end": 35,
                    "name""foo"
                  },
                  "arguments": [],
                  "optional"false
                }
              },
              {
                "type""VariableDeclaration",
                "start": 38,
                "end": 59,
                "declarations": [
                  {
                    "type""VariableDeclarator",
                    "start": 42,
                    "end": 59,
                    "id": {
                      "type""Identifier",
                      "start": 42,
                      "end": 46,
                      "name""city"
                    },
                    "init": {
                      "type""Literal",
                      "start": 49,
                      "end": 59,
                      "value""hangzhou",
                      "raw""'hangzhou'"
                    }
                  }
                ],
                "kind""var"
              },
              {
                "type""FunctionDeclaration",
                "start": 61,
                "end": 104,
                "id": {
                  "type""Identifier",
                  "start": 70,
                  "end": 74,
                  "name""test"
                },
                "expression"false,
                "generator"false,
                "async"false,
                "params": [],
                "body": {
                  "type""BlockStatement",
                  "start": 77,
                  "end": 104,
                  "body": [
                    {
                      "type""ExpressionStatement",
                      "start": 83,
                      "end": 102,
                      "expression": {
                        "type""CallExpression",
                        "start": 83,
                        "end": 102,
                        "callee": {
                          "type""MemberExpression",
                          "start": 83,
                          "end": 94,
                          "object": {
                            "type""Identifier",
                            "start": 83,
                            "end": 90,
                            "name""console"
                          },
                          "property": {
                            "type""Identifier",
                            "start": 91,
                            "end": 94,
                            "name""log"
                          },
                          "computed"false,
                          "optional"false
                        },
                        "arguments": [
                          {
                            "type""Literal",
                            "start": 95,
                            "end": 101,
                            "value""test",
                            "raw""'test'"
                          }
                        ],
                        "optional"false
                      }
                    }
                  ]
                }
              },
              {
                "type""ExpressionStatement",
                "start": 106,
                "end": 125,
                "expression": {
                  "type""CallExpression",
                  "start": 106,
                  "end": 125,
                  "callee": {
                    "type""MemberExpression",
                    "start": 106,
                    "end": 117,
                    "object": {
                      "type""Identifier",
                      "start": 106,
                      "end": 113,
                      "name""console"
                    },
                    "property": {
                      "type""Identifier",
                      "start": 114,
                      "end": 117,
                      "name""log"
                    },
                    "computed"false,
                    "optional"false
                  },
                  "arguments": [
                    {
                      "type""CallExpression",
                      "start": 118,
                      "end": 124,
                      "callee": {
                        "type""Identifier",
                        "start": 118,
                        "end": 122,
                        "name""test"
                      },
                      "arguments": [],
                      "optional"false
                    }
                  ],
                  "optional"false
                }
              }
            ],
            "sourceType""module"
          }

          我們通過這個 AST 樹,分析 **analyse **具體做了什么???

          第一步:分析當前模塊導入【import】和導出【exports】模塊,將引入的模塊和導出的模塊存儲起來this.imports = {};//存放著當前模塊所有的導入
          this.exports = {};//存放著當前模塊所有的導出

              this.imports = {};//存放著當前模塊所有的導入
              this.exports = {};//存放著當前模塊所有的導出
              this.ast.body.forEach(node => {
                if (node.type === 'ImportDeclaration') {// 說明這是一個 import 語句
                  let source = node.source.value; // 從哪個模塊導入的
                  let specifiers = node.specifiers; // 導入標識符
                  specifiers.forEach(specifier => {
                    const name = specifier.imported.name; //name
                    const localName = specifier.local.name; //name
                    //本地的哪個變量,是從哪個模塊的的哪個變量導出的
                    this.imports[localName] = { name, localName, source }
                  });
                  //}else if(/^Export/.test(node.type)){ // 導出方法有很多
                } else if (node.type === 'ExportNamedDeclaration') { // 說明這是一個 exports 語句
                  let declaration = node.declaration;//VariableDeclaration
                  if (declaration.type === 'VariableDeclaration') {
                    let name = declaration.declarations[0].id.name;
                    this.exports[name] = {
                      node, localName: name, expression: declaration
                    }
                  }
                }
              });
              analyse(this.ast, this.code, this);//找到了_defines 和 _dependsOn

          打斷點可以看到,foo 已經(jīng)被存入 imports  =》** import { foo } from "./foo";  ** exports:{} 表示沒有導出語句

          第二步:analyse(this.ast, this.code, this); //找到_defines 和 _dependsOn

          找出當前模塊使用到了哪些變量 標記哪些變量時當前模塊聲明的,哪些變量是導入別的模塊的變量 我們定義以下字段用來存放:_defines: { value: {} },//存放當前模塊定義的所有的全局變量
          _dependsOn: { value: {} },//當前模塊沒有定義但是使用到的變量,也就是依賴的外部變量_included: { value: false, writable: true },//此語句是否已經(jīng)被包含到打包結果中,防止重復打包_source: { value: magicString.snip(statement.start, statement.end) } //magicString.snip 返回的還是 magicString 實例 clone

          分析每個 AST 節(jié)點之間的作用域,構建 scope tree,

          function analyse(ast, magicString, module) {
              let scope = new Scope();//先創(chuàng)建一個模塊內(nèi)的全局作用域
              //遍歷當前的所有的語法樹的所有的頂級節(jié)點
              ast.body.forEach(statement => {
              
                  //給作用域添加變量 var function const let 變量聲明
                  function addToScope(declaration) {
                      var name = declaration.id.name;//獲得這個聲明的變量
                      scope.add(name);
                      if (!scope.parent) {//如果當前是全局作用域的話
                          statement._defines[name] = true;
                      }
                  }

                  Object.defineProperties(statement, {
                      _defines: { value: {} },//存放當前模塊定義的所有的全局變量
                      _dependsOn: { value: {} },//當前模塊沒有定義但是使用到的變量,也就是依賴的外部變量
                      _included: { value: false, writable: true },//此語句是否已經(jīng) 被包含到打包結果中了
                      //start 指的是此節(jié)點在源代碼中的起始索引,end 就是結束索引
                      //magicString.snip 返回的還是 magicString 實例 clone
                      _source: { value: magicString.snip(statement.start, statement.end) }
                  });
                  
                  //這一步在構建我們的作用域鏈
                  walk(statement, {
                      enter(node) {
                          let newScope;
                          if (!node) return
                          switch (node.type) {
                              case 'FunctionDeclaration':
                                  const params = node.params.map(x => x.name);
                                  if (node.type === 'FunctionDeclaration') {
                                      addToScope(node);
                                  }
                                  //如果遍歷到的是一個函數(shù)聲明,我會創(chuàng)建一個新的作用域?qū)ο?br>                        newScope = new Scope({
                                      parent: scope,//父作用域就是當前的作用域
                                      params
                                  });
                                  break;
                              case 'VariableDeclaration': //并不會生成一個新的作用域
                                  node.declarations.forEach(addToScope);
                                  break;
                          }
                          if (newScope) {//當前節(jié)點聲明一個新的作用域
                              //如果此節(jié)點生成一個新的作用域,那么會在這個節(jié)點放一個_scope,指向新的作用域
                              Object.defineProperty(node, '_scope', { value: newScope });
                              scope = newScope;
                          }
                      },
                      leave(node) {
                          if (node._scope) {//如果此節(jié)點產(chǎn)出了一個新的作用域,那等離開這個節(jié)點,scope 回到父作用法域
                              scope = scope.parent;
                          }
                      }
                  });
              });
              ast._scope = scope;
              //找出外部依賴_dependsOn
              ast.body.forEach(statement => {
                  walk(statement, {
                      enter(node) {
                          if (node._scope) {
                              scope = node._scope;
                          } //如果這個節(jié)點放有一個 scope 屬性,說明這個節(jié)點產(chǎn)生了一個新的作用域
                          if (node.type === 'Identifier') {
                              //從當前的作用域向上遞歸,找這個變量在哪個作用域中定義
                              const definingScope = scope.findDefiningScope(node.name);
                              if (!definingScope) {
                                  statement._dependsOn[node.name] = true;//表示這是一個外部依賴的變量
                              }
                          }

                      },
                      leave(node) {
                          if (node._scope) {
                              scope = scope.parent;
                          }

                      }
                  });
              });
          }

          斷點可以看到**_defines 和 _dependsOn **分別存入了當前變量和引入的變量第三步:this.definitions = {};把全局變量的定義語句存放到 definitions 里

          // module.js
          this.definitions = {};//存放著所有的全局變量的定義語句
              this.ast.body.forEach(statement => {
                Object.keys(statement._defines).forEach(name => {
                  //key 是全局變量名,值是定義這個全局變量的語句
                  this.definitions[name] = statement;
                });
              });

          第四步:展開語句,展開當前模塊的所有語句,把這些語句中定義的變量的語句都放到結果里

          if (statement.type === 'ImportDeclaration') {return} 如果是導入聲明語句,既 import { foo } from "./foo";這條語句我們是不需要的,return 掉

          //展開這個模塊里的語句,把些語句中定義的變量的語句都放到結果里
            expandAllStatements() {
              let allStatements = [];
              this.ast.body.forEach(statement => {
                if (statement.type === 'ImportDeclaration') {return}
                let statements = this.expandStatement(statement);
                allStatements.push(...statements);
              });
              return allStatements;
            }

          **expandStatement:**找到當前節(jié)點依賴的變量,找到這些變量的聲明語句。這些語句可能是在當前模塊聲明的,也也可能是在導入的模塊的聲明的 然后放入結果里


            expandStatement(statement) {
              let result = [];
              const dependencies = Object.keys(statement._dependsOn);//外部依賴 [name]
              dependencies.forEach(name => {
                //找到定義這個變量的聲明節(jié)點,這個節(jié)點可以有在當前模塊內(nèi),也可能在依賴的模塊里
                let definition = this.define(name);
                result.push(...definition);
              });
              if (!statement._included) {
                statement._included = true;//表示這個節(jié)點已經(jīng)確定被納入結果里了,以后就不需要重復添加了
                result.push(statement);
              }
              return result;
            }
            

          define: 找到定義這個變量的聲明節(jié)點,這個節(jié)點可以有在當前模塊內(nèi),也可能在依賴的模塊里const module = this.bundle.fetchModule(importData.source, this.path);獲取導入變量的模塊

             define(name) {
              //查找一下導入變量里有沒有 name
              if (hasOwnProperty(this.imports, name)) {
                const importData = this.imports[name];
                // 獲取導入變量的模塊
                const module = this.bundle.fetchModule(importData.source, this.path);
                // 這個 module 模塊也有導入導出
                const exportData = module.exports[importData.name];
                // 返回這個導入模塊變量的聲明語句
                return module.define(exportData.localName);
              } else {
                //definitions 是對象,key 當前模塊的變量名,值是定義這個變量的語句
                let statement = this.definitions[name];
                if (statement && !statement._included) {
                  return this.expandStatement(statement);
                } else {
                  return [];
                }
              }
            }

          this.statements 里就是所有我們分析標記之后返回的數(shù)組

          以上分析了很多,但總結來就是做了以下事情:

          • 收集導入和導出變量
          • 建立映射關系,方便后續(xù)使用
          • 收集所有語句定義的變量
          • 建立變量和聲明語句之間的對應關系,方便后續(xù)使用
          • 過濾 import 語句
          • 刪除關鍵詞
          • 輸出語句時,判斷變量是否為 import
          • 如是需要遞歸再次收集依賴文件的變量
          • 否則直接輸出
          • 構建依賴關系,創(chuàng)建作用域鏈,交由./src/ast/analyse.js 文件處理
          • 在抽象語法樹的每一條語句上掛載_source(源代碼)、_defines(當前模塊定義的變量)、_dependsOn(外部依賴的變量)、_included(是否已經(jīng)包含在輸出語句中)
          • 收集每個語句上定義的變量,創(chuàng)建作用域鏈

          3.generate()

          第一步:移除額外代碼 例如從 foo.js 中引入的 foo() 函數(shù)代碼是這樣的:export function foo() {}。rollup 會移除掉 export,變    成 function foo() {}。因為它們就要打包在一起了,所以就不需要 export 了。

          第二步:把 AST 節(jié)點的源碼 addSource 到 magicString,這個操作本質(zhì)上相當于拼字符串,第三步:**return magicString.toString() **。返回合并后源代碼

          generate() {
              let magicString = new MagicString.Bundle();
              this.statements.forEach(statement => {
                const source = statement._source;
                if (statement.type === 'ExportNamedDeclaration') {
                  source.remove(statement.start, statement.declaration.start);
                } 
                magicString.addSource({
                  content: source,
                  separator: '\n'
                });
              });
              return { code: magicString.toString() };
            }

          最后輸出到'dist/bundle.js'中

          小結

          簡單來說,Rollup 構建其實就是做了以下幾件事:

          • 獲取入口文件的內(nèi)容,包裝成 module,生成抽象語法樹
          • 對入口文件抽象語法樹進行依賴解析
          • 生成最終代碼
          • 寫入目標文件

          四、總結

          以上代碼實現(xiàn)過程可以幫你簡單的實現(xiàn)一個 rollup 打包流程,但也僅僅是對 rollup 源碼進行了抽象,方便大家理解 rollup 打包的原理,其中很多細節(jié)沒有寫出來,如果感興趣的話,可以深入閱讀一下源碼。

          五、參考資料

          https://zhuanlan.zhihu.com/p/372808332

          https://github.com/xitu/gold-miner/blob/master/TODO/rollup-interview.md

          https://blog.csdn.net/q411020382/article/details/108302906

          瀏覽 76
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  天天色色| 天天色官网 | 日韩一级视频在线观看 | 日韩丝袜足交 | 日逼网站国产 |