日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Rollup-構(gòu)建原理及簡易實(shí)現(xiàn)

一、Rollup 概述

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

Rollup 是什么

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

對比 Webpack

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

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

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

簡單總結(jié)

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

二、Rollup 前置知識

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

magic-string

magic-string 是 Rollup 作者寫的一個(gè)關(guān)于字符串操作的庫,這個(gè)庫主要是對字符串一些常用方法進(jìn)行了封裝

 
 
 
 
  1. var MagicString = require('magic-string')
  2. var magicString = new MagicString('export var name = "zhangsan"')
  3. // 以下所有操作都是基于原生字符串
  4. // 類似于截取字符串
  5. console.log(magicString.snip(0, 6).toString()) // export
  6. // 從開始到結(jié)束刪除
  7. console.log(magicString.remove(0, 7).toString()) //  var name = "zhangsan"
  8. // 多個(gè)模塊,把他們打包在一個(gè)文件里,需要把很多文件的源代碼合并在一起
  9. let bundleString = new MagicString.Bundle();
  10. bundleString.addSource({
  11.     content: 'console.log(hello)',
  12.     separator: '\n'
  13. })
  14. bundleString.addSource({
  15.     content: 'console.log(world)',
  16.     separator: '\n'
  17. })
  18. // // 原理類似
  19. // let str = ''
  20. // str += 'console.log(hello);\n'
  21. // str += 'console.log(world);\n'
  22. console.log(bundleString.toString()) 
  23. // hello
  24. // world

AST

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

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

轉(zhuǎn)化為 AST 是長這樣子的,如下:

 
 
 
 
  1. {
  2.   "type": "Program", // 這個(gè) AST 類型為 Program,表明是一個(gè)程序
  3.   "start": 0,
  4.   "end": 40,
  5.   "body": [ // body 是一個(gè)數(shù)組,每一條語句都對應(yīng) body 下的一個(gè)語句
  6.     {
  7.       "type": "ImportDeclaration", // 導(dǎo)入聲明類型
  8.       "start": 0,
  9.       "end": 23,
  10.       "specifiers": [
  11.         {
  12.           "type": "ImportSpecifier",
  13.           "start": 9,
  14.           "end": 10,
  15.           "imported": {
  16.             "type": "Identifier",
  17.             "start": 9,
  18.             "end": 10,
  19.             "name": "a" // 導(dǎo)入模塊命名 name 'a'
  20.           },
  21.           "local": {
  22.             "type": "Identifier",
  23.             "start": 9,
  24.             "end": 10,
  25.             "name": "a" // 本地模塊命名,同 imported.name
  26.           }
  27.         }
  28.       ],
  29.       "source": {
  30.         "type": "Literal",
  31.         "start": 18,
  32.         "end": 23,
  33.         "value": "./a", // 導(dǎo)入路徑 './a'
  34.         "raw": "'./a'"
  35.       }
  36.     },
  37.     {
  38.       "type": "ExpressionStatement", // 表達(dá)式類型
  39.       "start": 24,
  40.       "end": 38,
  41.       "expression": {
  42.         "type": "CallExpression", // 調(diào)用表達(dá)式類型
  43.         "start": 24,
  44.         "end": 38,
  45.         "callee": {
  46.           "type": "MemberExpression",
  47.           "start": 24,
  48.           "end": 35,
  49.           "object": {
  50.             "type": "Identifier",
  51.             "start": 24,
  52.             "end": 31,
  53.             "name": "console"
  54.           },
  55.           "property": {
  56.             "type": "Identifier",
  57.             "start": 32,
  58.             "end": 35,
  59.             "name": "log"
  60.           },
  61.           "computed": false,
  62.           "optional": false
  63.         },
  64.         "arguments": [
  65.           {
  66.             "type": "Identifier",
  67.             "start": 36,
  68.             "end": 37,
  69.             "name": "a"
  70.           }
  71.         ],
  72.         "optional": false
  73.       }
  74.     }
  75.   ],
  76.   "sourceType": "module"
  77. }

AST 工作流

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

acorn

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

作用域/作用域鏈

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

三、Rollup

Rollup 是怎樣工作的呢?

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

假設(shè) index.js 文件頭部有這樣一行:

 
 
 
 
  1. import foo from './foo.js';

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

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

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

看如下代碼,我們先實(shí)際操作一下:

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

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

 
 
 
 
  1. 'use strict';
  2. function foo() {
  3.     console.log('foo');
  4. }
  5. foo();
  6. function test() {
  7.     console.log('test');
  8. }
  9. console.log(test());

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

接下來我們進(jìn)入源碼,具體分析下 Rollup 的構(gòu)建流程

Rollup 構(gòu)建流程分析

Rollup 源碼結(jié)構(gòu)

 
 
 
 
  1. │  bundle.js // Bundle 打包器,在打包過程中會生成一個(gè) bundle 實(shí)例,用于收集其他模塊的代碼,最后再將收集的代碼打包到一起。
  2. │  external-module.js // ExternalModule 外部模塊,例如引入了 'path' 模塊,就會生成一個(gè) ExternalModule 實(shí)例。
  3. │  module.js // Module 模塊,module 實(shí)例。
  4. │  rollup.js // rollup 函數(shù),一切的開始,調(diào)用它進(jìn)行打包。
  5. ├─ast // ast 目錄,包含了和 AST 相關(guān)的類和函數(shù)
  6. │      analyse.js // 主要用于分析 AST 節(jié)點(diǎn)的作用域和依賴項(xiàng)。
  7. │      Scope.js // 在分析 AST 節(jié)點(diǎn)時(shí)為每一個(gè)節(jié)點(diǎn)生成對應(yīng)的 Scope 實(shí)例,主要是記錄每個(gè) AST 節(jié)點(diǎn)對應(yīng)的作用域。
  8. │      walk.js // walk 就是遞歸調(diào)用 AST 節(jié)點(diǎn)進(jìn)行分析。
  9. ├─finalisers
  10. │      cjs.js
  11. │      index.js
  12. └─utils // 一些幫助函數(shù)
  13.         map-helpers.js
  14.         object.js
  15.         promise.js
  16.         replaceIdentifiers.js

Rollup 構(gòu)建流程

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

 
 
 
 
  1. // index.js
  2. import { foo } from "./foo";
  3. foo()
  4. var city = 'hangzhou'
  5. function test() {
  6.     console.log('test')
  7. }
  8. console.log(test())
 
 
 
 
  1. // foo.js
  2. import { bar } from "./bar";
  3. export function foo() {
  4.     console.log('foo')
  5. }
 
 
 
 
  1. // bar.js
  2. export function bar() {
  3.     console.log('bar')
  4. }

debug 起來!!!

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

1.new Bundle(), build()

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

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

lib/bundle.js根據(jù)入口路徑出發(fā)(在 bundle 中,我們會首先統(tǒng)一處理下入口文件的后綴),去找到他的模塊定義,在 fetchModule 中,會生成一個(gè) module 實(shí)例

我們關(guān)注紅框中的代碼,會發(fā)現(xiàn)返回了一個(gè) module

2.new Module()

每個(gè)文件都是一個(gè)模塊,每個(gè)模塊都會有一個(gè) Module 實(shí)例。在 Module 實(shí)例中,會調(diào)用 acorn 庫的 parse() 方法將代碼解析成 AST。

對生成的 AST 進(jìn)行分析analyse我們先看一下入口文件 index.js 生成的 AST

可以看到 ast.body 是一個(gè)數(shù)組,分別對應(yīng) index.js 的五條語句 展開這個(gè) ast 樹如下:

 
 
 
 
  1. {
  2.   "type": "Program",
  3.   "start": 0,
  4.   "end": 128,
  5.   "body": [
  6.     {
  7.       "type": "ImportDeclaration", // 導(dǎo)入聲明
  8.       "start": 0,
  9.       "end": 31,
  10.       "specifiers": [
  11.         {
  12.           "type": "ImportSpecifier",
  13.           "start": 9,
  14.           "end": 12,
  15.           "imported": {
  16.             "type": "Identifier",
  17.             "start": 9,
  18.             "end": 12,
  19.             "name": "foo"
  20.           },
  21.           "local": {
  22.             "type": "Identifier",
  23.             "start": 9,
  24.             "end": 12,
  25.             "name": "foo"
  26.           }
  27.         }
  28.       ],
  29.       "source": {
  30.         "type": "Literal",
  31.         "start": 20,
  32.         "end": 30,
  33.         "value": "./foo.js",
  34.         "raw": "\"./foo.js\""
  35.       }
  36.     },
  37.     {
  38.       "type": "ExpressionStatement",
  39.       "start": 32,
  40.       "end": 37,
  41.       "expression": {
  42.         "type": "CallExpression",
  43.         "start": 32,
  44.         "end": 37,
  45.         "callee": {
  46.           "type": "Identifier",
  47.           "start": 32,
  48.           "end": 35,
  49.           "name": "foo"
  50.         },
  51.         "arguments": [],
  52.         "optional": false
  53.       }
  54.     },
  55.     {
  56.       "type": "VariableDeclaration",
  57.       "start": 38,
  58.       "end": 59,
  59.       "declarations": [
  60.         {
  61.           "type": "VariableDeclarator",
  62.           "start": 42,
  63.           "end": 59,
  64.           "id": {
  65.             "type": "Identifier",
  66.             "start": 42,
  67.             "end": 46,
  68.             "name": "city"
  69.           },
  70.           "init": {
  71.             "type": "Literal",
  72.             "start": 49,
  73.             "end": 59,
  74.             "value": "hangzhou",
  75.             "raw": "'hangzhou'"
  76.           }
  77.         }
  78.       ],
  79.       "kind": "var"
  80.     },
  81.     {
  82.       "type": "FunctionDeclaration",
  83.       "start": 61,
  84.       "end": 104,
  85.       "id": {
  86.         "type": "Identifier",
  87.         "start": 70,
  88.         "end": 74,
  89.         "name": "test"
  90.       },
  91.       "expression": false,
  92.       "generator": false,
  93.       "async": false,
  94.       "params": [],
  95.       "body": {
  96.         "type": "BlockStatement",
  97.         "start": 77,
  98.         "end": 104,
  99.         "body": [
  100.           {
  101.             "type": "ExpressionStatement",
  102.             "start": 83,
  103.             "end": 102,
  104.             "expression": {
  105.               "type": "CallExpression",
  106.               "start": 83,
  107.               "end": 102,
  108.               "callee": {
  109.                 "type": "MemberExpression",
  110.                 "start": 83,
  111.                 "end": 94,
  112.                 "object": {
  113.                   "type": "Identifier",
  114.                   "start": 83,
  115.                   "end": 90,
  116.                   "name": "console"
  117.                 },
  118.                 "property": {
  119.                   "type": "Identifier",
  120.                   "start": 91,
  121.                   "end": 94,
  122.                   "name": "log"
  123.                 },
  124.                 "computed": false,
  125.                 "optional": false
  126.               },
  127.               "arguments": [
  128.                 {
  129.                   "type": "Literal",
  130.                   "start": 95,
  131.                   "end": 101,
  132.                   "value": "test",
  133.                   "raw": "'test'"
  134.                 }
  135.               ],
  136.               "optional": false
  137.             }
  138.           }
  139.         ]
  140.       }
  141.     },
  142.     {
  143.       "type": "ExpressionStatement",
  144.       "start": 106,
  145.       "end": 125,
  146.       "expression": {
  147.         "type": "CallExpression",
  148.         "start": 106,
  149.         "end": 125,
  150.         "callee": {
  151.           "type": "MemberExpression",
  152.           "start": 106,
  153.           "end": 117,
  154.           "object": {
  155.             "type": "Identifier",
  156.             "start": 106,
  157.             "end": 113,
  158.             "name": "console"
  159.           },
  160.           "property": {
  161.             "type": "Identifier",
  162.             "start": 114,
  163.             "end": 117,
  164.             "name": "log"
  165.           },
  166.           "computed": false,
  167.           "optional": false
  168.         },
  169.         "arguments": [
  170.           {
  171.             "type": "CallExpression",
  172.             "start": 118,
  173.             "end": 124,
  174.             "callee": {
  175.               "type": "Identifier",
  176.               "start": 118,
  177.               "end": 122,
  178.               "name": "test"
  179.             },
  180.             "arguments": [],
  181.             "optional": false
  182.           }
  183.         ],
  184.         "optional": false
  185.       }
  186.     }
  187.   ],
  188.   "sourceType": "module"
  189. }

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

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

this.exports = {};//存放著當(dāng)前模塊所有的導(dǎo)出

 
 
 
 
  1. this.imports = {};//存放著當(dāng)前模塊所有的導(dǎo)入
  2.   this.exports = {};//存放著當(dāng)前模塊所有的導(dǎo)出
  3.   this.ast.body.forEach(node => {
  4.     if (node.type === 'ImportDeclaration') {// 說明這是一個(gè) import 語句
  5.       let source = node.source.value; // 從哪個(gè)模塊導(dǎo)入的
  6.       let specifiers = node.specifiers; // 導(dǎo)入標(biāo)識符
  7.       specifiers.forEach(specifier => {
  8.         const name = specifier.imported.name; //name
  9.         const localName = specifier.local.name; //name
  10.         //本地的哪個(gè)變量,是從哪個(gè)模塊的的哪個(gè)變量導(dǎo)出的
  11.         this.imports[localName] = { name, localName, source }
  12.       });
  13.       //}else if(/^Export/.test(node.type)){ // 導(dǎo)出方法有很多
  14.     } else if (node.type === 'ExportNamedDeclaration') { // 說明這是一個(gè) exports 語句
  15.       let declaration = node.declaration;//VariableDeclaration
  16.       if (declaration.type === 'VariableDeclaration') {
  17.         let name = declaration.declarations[0].id.name;
  18.         this.exports[name] = {
  19.           node, localName: name, expression: declaration
  20.         }
  21.       }
  22.     }
  23.   });
  24.   analyse(this.ast, this.code, this);//找到了_defines 和 _dependsOn

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

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

找出當(dāng)前模塊使用到了哪些變量 標(biāo)記哪些變量時(shí)當(dāng)前模塊聲明的,哪些變量是導(dǎo)入別的模塊的變量 我們定義以下字段用來存放:_defines: { value: {} },//存放當(dāng)前模塊定義的所有的全局變量

_dependsOn: { value: {} },//當(dāng)前模塊沒有定義但是使用到的變量,也就是依賴的外部變量_included: { value: false, writable: true },//此語句是否已經(jīng)被包含到打包結(jié)果中,防止重復(fù)打包_source: { value: magicString.snip(statement.start, statement.end) } //magicString.snip 返回的還是 magicString 實(shí)例 clone

分析每個(gè) AST 節(jié)點(diǎn)之間的作用域,構(gòu)建 scope tree,

 
 
 
 
  1. function analyse(ast, magicString, module) {
  2.     let scope = new Scope();//先創(chuàng)建一個(gè)模塊內(nèi)的全局作用域
  3.     //遍歷當(dāng)前的所有的語法樹的所有的頂級節(jié)點(diǎn)
  4.     ast.body.forEach(statement => {
  5.     
  6.         //給作用域添加變量 var function const let 變量聲明
  7.         function addToScope(declaration) {
  8.             var name = declaration.id.name;//獲得這個(gè)聲明的變量
  9.             scope.add(name);
  10.             if (!scope.parent) {//如果當(dāng)前是全局作用域的話
  11.                 statement._defines[name] = true;
  12.             }
  13.         }
  14.         Object.defineProperties(statement, {
  15.             _defines: { value: {} },//存放當(dāng)前模塊定義的所有的全局變量
  16.             _dependsOn: { value: {} },//當(dāng)前模塊沒有定義但是使用到的變量,也就是依賴的外部變量
  17.             _included: { value: false, writable: true },//此語句是否已經(jīng) 被包含到打包結(jié)果中了
  18.             //start 指的是此節(jié)點(diǎn)在源代碼中的起始索引,end 就是結(jié)束索引
  19.             //magicString.snip 返回的還是 magicString 實(shí)例 clone
  20.             _source: { value: magicString.snip(statement.start, statement.end) }
  21.         });
  22.         
  23.         //這一步在構(gòu)建我們的作用域鏈
  24.         walk(statement, {
  25.             enter(node) {
  26.                 let newScope;
  27.   &nb
    文章標(biāo)題:Rollup-構(gòu)建原理及簡易實(shí)現(xiàn)
    當(dāng)前網(wǎng)址:http://www.dlmjj.cn/article/cccegcg.html