溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Parcel源碼的案例分析

發(fā)布時(shí)間:2020-12-02 14:07:14 來源:億速云 閱讀:210 作者:小新 欄目:web開發(fā)

小編給大家分享一下Parcel源碼的案例分析,希望大家閱讀完這篇文章后大所收獲,下面讓我們一起去探討吧!

本篇文章是對 Parce 的源碼解析,代碼基本架構(gòu)與執(zhí)行流程,在這之前你如果對 parcel 不熟悉可以先到 Parcel官網(wǎng) 了解

介紹

下面是偷懶從官網(wǎng)抄下來的介紹:

極速零配置Web應(yīng)用打包工具極速打包

Parcel 使用 worker 進(jìn)程去啟用多核編譯。同時(shí)有文件系統(tǒng)緩存,即使在重啟構(gòu)建后也能快速再編譯。

將你所有的資源打包

Parcel 具備開箱即用的對 JS, CSS, HTML, 文件 及更多的支持,而且不需要插件。

自動轉(zhuǎn)換

如若有需要,Babel, PostCSS, 和 PostHTML 甚至 node_modules 包會被用于自動轉(zhuǎn)換代碼.

零配置代碼分拆

使用動態(tài) import() 語法, Parcel 將你的輸出文件束(bundles)分拆,因此你只需要在初次加載時(shí)加載你所需要的代碼。

熱模塊替換

Parcel 無需配置,在開發(fā)環(huán)境的時(shí)候會自動在瀏覽器內(nèi)隨著你的代碼更改而去更新模塊。

友好的錯誤日志

當(dāng)遇到錯誤時(shí),Parcel 會輸出 語法高亮的代碼片段,幫助你定位問題。

打包工具時(shí)間
browserify22.98s
webpack20.71s
parcel9.98s
parcel - with cache2.64s

打包工具

我們常用的打包工具大致功能:

模塊化(代碼的拆分, 合并, Tree-Shaking 等)編譯(es6,7,8  sass typescript 等)壓縮 (js, css, html包括圖片的壓縮)HMR (熱替換)

version

parcel-bundler 版本:

"version": "1.11.0"

文件架構(gòu)

|-- assets          資源目錄 繼承自 Asset.js
|-- builtins        用于最終構(gòu)建
|-- packagers       打包
|-- scope-hoisting  作用域提升 Tree-Shake
|-- transforms      轉(zhuǎn)換代碼為 AST
|-- utils           工具
|-- visitors        遍歷 js AST樹 收集依賴等

|-- Asset.js          資源
|-- Bundle.js         用于構(gòu)建 bundle 樹
|-- Bundler.js        主目錄  
|-- FSCache.js        緩存
|-- HMRServer.js      HMR服務(wù)器提供 WebSocket
|-- Parser.js         根據(jù)文件擴(kuò)展名獲取對應(yīng) Asset
|-- Pipeline.js       多線程執(zhí)行方法
|-- Resolver.js       解析模塊路徑
|-- Server.js         靜態(tài)資源服務(wù)器
|-- SourceMap.js      SourceMap
|-- cli.js            cli入口 解析命令行參數(shù)
|-- worker.js         多線程入口

流程

說明

Parcel是面向資源的,JavaScript,CSS,HTML 這些都是資源,并不是 webpack 中 js 是一等公民,Parcel 會自動的從入口文件開始分析這些文件 和 模塊中的依賴,然后構(gòu)建一個 bundle 樹,并對其進(jìn)行打包輸出到指定目錄

一個簡單的例子

我們從一個簡單的例子開始了解 parcel 內(nèi)部源碼與流程

index.html
  |-- index.js
    |-- module1.js
    |-- module2.js

上面是我們例子的結(jié)構(gòu),入口為 index.html, 在 index.html 中我們用 script 標(biāo)簽引用了 src/index.js,在 index.js 中我們引入了2個子模塊

執(zhí)行

npx parcel index.html 或者 ./node_modules/.bin/parcel index.html,或者使用 npm script

cli

"bin": {
    "parcel": "bin/cli.js"
}

查看 parcel-bundler的 package.json 找到 bin/cli.js,在cli.js里又指向 ../src/cli

const program = require('commander');

program
  .command('serve [input...]') // watch build
  ...
  .action(bundle);

program.parse(process.argv);

async function bundle(main, command) {
  const Bundler = require('./Bundler');

  const bundler = new Bundler(main, command);

  if (command.name() === 'serve' && command.target === 'browser') {
    const server = await bundler.serve();

    if (server && command.open) {...啟動自動打開瀏覽器}
  } else {
    bundler.bundle();
  }
}

在 cli.js 中利用 commander 解析命令行并調(diào)用 bundle 方法
有 serve, watch, build 3個命令來調(diào)用 bundle 函數(shù),執(zhí)行 pracel index.html 默認(rèn)為 serve,所以調(diào)用的是 bundler.serve 方法

進(jìn)入 Bundler.js

bundler.serve

async serve(port = 1234, https = false, host) {
    this.server = await Server.serve(this, port, host, https);
    try {
      await this.bundle();
    } catch (e) {}
    return this.server;
  }

bundler.serve 方法 調(diào)用 serveStatic 起了一個靜態(tài)服務(wù)指向 最終打包的文件夾
下面就是重要的 bundle 方法

bundler.bundle

async bundle() {
    // 加載插件 設(shè)置env 啟動多線程 watcher hmr
    await this.start();

    if (isInitialBundle) {
      // 創(chuàng)建 輸出目錄
      await fs.mkdirp(this.options.outDir);

      this.entryAssets = new Set();
      for (let entry of this.entryFiles) {
          let asset = await this.resolveAsset(entry);
          this.buildQueue.add(asset);
          this.entryAssets.add(asset);
      }
    }

    // 打包隊(duì)列中的資源
    let loadedAssets = await this.buildQueue.run();

    // findOrphanAssets 獲取所有資源中獨(dú)立的沒有父Bundle的資源
    let changedAssets = [...this.findOrphanAssets(), ...loadedAssets];

    // 因?yàn)榻酉聛硪獦?gòu)建 Bundle 樹,先對上一次的 Bundle樹 進(jìn)行 clear 操作
    for (let asset of this.loadedAssets.values()) {
      asset.invalidateBundle();
    }

    // 構(gòu)建 Bundle 樹
    this.mainBundle = new Bundle();
    for (let asset of this.entryAssets) {
      this.createBundleTree(asset, this.mainBundle);
    }

    // 獲取新的最終打包文件的url
    this.bundleNameMap = this.mainBundle.getBundleNameMap(
      this.options.contentHash
    );
    // 將代碼中的舊文件url替換為新的
    for (let asset of changedAssets) {
      asset.replaceBundleNames(this.bundleNameMap);
    }

    // 將改變的資源通過websocket發(fā)送到瀏覽器
    if (this.hmr && !isInitialBundle) {
      this.hmr.emitUpdate(changedAssets);
    }

    // 對資源打包
    this.bundleHashes = await this.mainBundle.package(
      this,
      this.bundleHashes
    );

    // 將獨(dú)立的資源刪除
    this.unloadOrphanedAssets();

    return this.mainBundle;
  }

我們一步步先從 this.start 看

start

if (this.farm) {
  return;
}

await this.loadPlugins();

if (!this.options.env) {
  await loadEnv(Path.join(this.options.rootDir, 'index'));
  this.options.env = process.env;
}

if (this.options.watch) {
  this.watcher = new Watcher();
  this.watcher.on('change', this.onChange.bind(this));
}

if (this.options.hmr) {
  this.hmr = new HMRServer();
  this.options.hmrPort = await this.hmr.start(this.options);
}

this.farm = await WorkerFarm.getShared(this.options, {
  workerPath: require.resolve('./worker.js')
  });

start:

開頭的判斷 防止多次執(zhí)行,也就是說 this.start 只會執(zhí)行一次loadPlugins 加載插件,找到 package.json 文件 dependencies, devDependencies 中 parcel-plugin-開頭的插件進(jìn)行調(diào)用loadEnv 加載環(huán)境變量,利用 dotenv, dotenv-expand 包將 env.development.local, .env.development, .env.local, .env 擴(kuò)展至 process.envwatch 初始化監(jiān)聽文件并綁定 change 回調(diào)函數(shù),內(nèi)部 child_process.fork 起一個子進(jìn)程,使用 chokidar 包來監(jiān)聽文件改變hmr 起一個服務(wù),WebSocket 向?yàn)g覽器發(fā)送更改的資源farm 初始化多進(jìn)程并指定 werker 工作文件,開啟多個 child_process 去解析編譯資源

接下來回到 bundle,isInitialBundle 是一個判斷是否是第一次構(gòu)建
fs.mkdirp 創(chuàng)建輸出文件夾
遍歷入口文件,通過 resolveAsset,內(nèi)部調(diào)用 resolver 解析路徑,并 getAsset 獲取到對應(yīng)的 asset(這里我們?nèi)肟谑?index.html,根據(jù)擴(kuò)展名獲取到的是 HTMLAsset)
將 asset 添加進(jìn)隊(duì)列
然后啟動 this.buildQueue.run() 對資源從入口遞歸開始打包

PromiseQueue

這里 buildQueue 是一個 PromiseQueue 異步隊(duì)列
PromiseQueue 在初始化的時(shí)候傳入一個回調(diào)函數(shù) callback,內(nèi)部維護(hù)一個參數(shù)隊(duì)列 queue,add 往隊(duì)列里 push 一個參數(shù),run 的時(shí)候while遍歷隊(duì)列 callback(...queue.shift()),隊(duì)列全部執(zhí)行完畢 Promise 置為完成(resolved)(可以將其理解為 Promise.all)
這里定義的回調(diào)函數(shù)是 processAsset,參數(shù)就是入口文件 index.html 的 HTMLAsset

async processAsset(asset, isRebuild) {
  if (isRebuild) {
    asset.invalidate();
    if (this.cache) {
      this.cache.invalidate(asset.name);
    }
  }

  await this.loadAsset(asset);
}

processAsset 函數(shù)內(nèi)先判斷是否是 Rebuild ,是第一次構(gòu)建,還是 watch 監(jiān)聽文件改變進(jìn)行的重建,如果是重建則對資源的屬性重置,并使其緩存失效
之后調(diào)用 loadAsset 加載資源編譯資源

loadAsset

async loadAsset(asset) {
    if (asset.processed) {
      return;
    }

    // Mark the asset processed so we don't load it twice
    asset.processed = true;

    // 先嘗試讀緩存,緩存沒有在后臺加載和編譯
    asset.startTime = Date.now();
    let processed = this.cache && (await this.cache.read(asset.name));
    let cacheMiss = false;
    if (!processed || asset.shouldInvalidate(processed.cacheData)) {
      processed = await this.farm.run(asset.name);
      cacheMiss = true;
    }

    asset.endTime = Date.now();
    asset.buildTime = asset.endTime - asset.startTime;
    asset.id = processed.id;
    asset.generated = processed.generated;
    asset.hash = processed.hash;
    asset.cacheData = processed.cacheData;

    // 解析和加載當(dāng)前資源的依賴項(xiàng)
    let assetDeps = await Promise.all(
      dependencies.map(async dep => {
          dep.parent = asset.name;
          let assetDep = await this.resolveDep(asset, dep);
          if (assetDep) {
            await this.loadAsset(assetDep);
          }
          return assetDep;
      })
    );

    if (this.cache && cacheMiss) {
      this.cache.write(asset.name, processed);
    }
  }

loadAsset 在開始有個判斷防止重復(fù)編譯
之后去讀緩存,讀取失敗就調(diào)用 this.farm.run 在多進(jìn)程里編譯資源
編譯完就去加載并編譯依賴的文件
最后如果是新的資源沒有用到緩存,就重新設(shè)置一下緩存
下面說一下這里嗎涉及的兩個東西:緩存 FSCache 和 多進(jìn)程 WorkerFarm

FSCache

read 讀取緩存,并判斷最后修改時(shí)間和緩存的修改時(shí)間
write 寫入緩存

Parcel源碼的案例分析

緩存目錄為了加速讀取,避免將所有的緩存文件放在一個文件夾里,parcel 將 16進(jìn)制 兩位數(shù)的 256 種可能創(chuàng)建為文件夾,這樣存取緩存文件的時(shí)候,將目標(biāo)文件路徑 md5 加密轉(zhuǎn)換為 16進(jìn)制,然后截取前兩位是目錄,后面幾位是文件名

WorkerFarm

在上面 start 里初始化 farm 的時(shí)候,workerPath 指向了 worker.js 文件,worker.js 里有兩個函數(shù),init 和 run
WorkerFarm.getShared 初始化的時(shí)候會創(chuàng)建一個 new WorkerFarm ,調(diào)用 worker.js 的 init 方法,根據(jù) cpu 獲取最大的 Worker 數(shù),并啟動一半的子進(jìn)程
farm.run 會通知子進(jìn)程執(zhí)行 worker.js 的 run 方法,如果進(jìn)程數(shù)沒有達(dá)到最大會再次開啟一個新的子進(jìn)程,子進(jìn)程執(zhí)行完畢后將 Promise狀態(tài)更改為完成
worker.run -> pipeline.process -> pipeline.processAsset -> asset.process
Asset.process 處理資源:

async process() {
    if (!this.generated) {
      await this.loadIfNeeded();
      await this.pretransform();
      await this.getDependencies();
      await this.transform();
      this.generated = await this.generate();
    }

    return this.generated;
  }

將上面的代碼內(nèi)部擴(kuò)展一下:

async process() {
  // 已經(jīng)有就不需要編譯
  if (!this.generated) {
    // 加載代碼
    if (this.contents == null) {
      this.contents = await this.load();
    }
    // 可選。在收集依賴之前轉(zhuǎn)換。
    await this.pretransform();
    // 將代碼解析為 AST 樹
    if (!this.ast) {
      this.ast = await this.parse(this.contents);
    }
    // 收集依賴
    await this.collectDependencies();
    // 可選。在收集依賴之后轉(zhuǎn)換。
    await this.transform();
    // 生成代碼
    this.generated = await this.generate();
  }

  return this.generated;
}

// 最后處理代碼
async postProcess(generated) {
  return generated
}

processAsset 中調(diào)用 asset.process 生成 generated 這個generated 不一定是最終代碼 ,像 html里內(nèi)聯(lián)的 script ,vue 的 html, js, css,都會進(jìn)行二次或多次遞歸處理,最終調(diào)用 asset.postProcess 生成代碼

Asset

下面說幾個實(shí)現(xiàn)

HTMLAsset:

pretransform 調(diào)用 posthtml 將 html 解析為 PostHTMLTree(如果沒有設(shè)置posthtmlrc之類的不會走)

parse 調(diào)用 posthtml-parser 將 html 解析為 PostHTMLTree

collectDependencies 用 walk 遍歷 ast,找到 script, img 的 src,link 的 href 等的地址,將其加入到依賴

transform htmlnano 壓縮代碼

generate 處理內(nèi)聯(lián)的 script 和 css

postProcess posthtml-render 生成 html 代碼

JSAsset:

pretransform 調(diào)用 @babel/core 將 js 解析為 AST,處理 process.env

parse 調(diào)用 @babel/parser 將 js 解析為 AST

collectDependencies 用 babylon-walk 遍歷 ast, 如 ImportDeclaration,import xx from 'xx' 語法,CallExpression 找到 require調(diào)用,import 被標(biāo)記為 dynamic 動態(tài)導(dǎo)入,將這些模塊加入到依賴

transform 處理 readFileSync,__dirname, __filename, global等,如果沒有設(shè)置scopeHoist 并存在 es6 module 就將代碼轉(zhuǎn)換為 commonjs,terser 壓縮代碼

generate @babel/generator 獲取 js 與 sourceMap 代碼

VueAsset:

parse @vue/component-compiler-utils 與 vue-template-compiler 對 .vue 文件進(jìn)行解析

generate 對 html, js, css 處理,就像上面說到會對其分別調(diào)用 processAsset 進(jìn)行二次解析

postProcess component-compiler-utils 的 compileTemplate, compileStyle處理 html,css,vue-hot-reload-api HMR處理,壓縮代碼

回到 bundle 方法:

let loadedAssets = await this.buildQueue.run() 就是上面說到的PromiseQueue 和 WorkerFarm 結(jié)合起來:buildQueue.run —> processAsset -> loadAsset -> farm.run -> worker.run -> pipeline.process -> pipeline.processAsset -> asset.process,執(zhí)行之后所有資源編譯完畢,并返回入口資源loadedAssets就是 index.html 對應(yīng)的 HTMLAsset 資源

之后是 let changedAssets = [...this.findOrphanAssets(), ...loadedAssets] 獲取到改變的資源

findOrphanAssets 是從所有資源中查找沒有 parentBundle 的資源,也就是獨(dú)立的資源,這個 parentBundle 會在等會的構(gòu)建 Bundle 樹中被賦值,第一次構(gòu)建都沒有 parentBundle,所以這里會重復(fù)入口文件,這里的 findOrphanAssets 的作用是在第一次構(gòu)建之后,文件change的時(shí)候,在這個文件 import了新的一個文件,因?yàn)樾挛募]有被構(gòu)建過 Bundle 樹,所以沒有 parentBundle,這個新文件也被標(biāo)記物 change

invalidateBundle 因?yàn)榻酉聛硪獦?gòu)建新的樹所以調(diào)用重置所有資源上一次樹的屬性

createBundleTree 構(gòu)建 Bundle 樹:

首先一個入口資源會被創(chuàng)建成一個 bundle,然后動態(tài)的 import() 會被創(chuàng)建成子 bundle ,這引發(fā)了代碼的拆分。

當(dāng)不同類型的文件資源被引入,兄弟 bundle 就會被創(chuàng)建。例如你在 JavaScript 中引入了 CSS 文件,那它會被放置在一個與 JavaScript 文件對應(yīng)的兄弟 bundle 中。

如果資源被多于一個 bundle 引用,它會被提升到 bundle 樹中最近的公共祖先中,這樣該資源就不會被多次打包。

Bundle:

type:它包含的資源類型 (例如:js, css, map, ...)

name:bundle 的名稱 (使用 entryAsset 的 Asset.generateBundleName() 生成)

parentBundle:父 bundle ,入口 bundle 的父 bundle 是 null

entryAsset:bundle 的入口,用于生成名稱(name)和聚攏資源(assets)

assets:bundle 中所有資源的集合(Set)

childBundles:所有子 bundle 的集合(Set)

siblingBundles:所有兄弟 bundle 的集合(Set)

siblingBundlesMap:所有兄弟 bundle 的映射 Map<String(Type: js, css, map, ...), Bundle>

offsets:所有 bundle 中資源位置的映射 Map<Asset, number(line number inside the bundle)> ,用于生成準(zhǔn)確的 sourcemap 。

我們的例子會被構(gòu)建成:

html            ( index.html )
  |-- js        ( index.js, module1.js, module2.js )
    |-- map     ( index.js, module1.js, module2.js )

module1.js 和 module2.js 被提到了與 index.js 同級,map 因?yàn)轭愋筒煌环诺搅?子bundle

一個復(fù)雜點(diǎn)的樹:

// 資源樹
index.html
  |-- index.css
  |-- bg.png
  |-- index.js
    |-- module.js
// mainBundle
html            ( index.html )
  |-- js        ( index.js, module.js )
    |-- map     ( index.map, module.map )
  |-- css       ( index.css )
    |-- js      ( index.css, css-loader.js bundle-url.js )
    |-- map     ( css-loader.js, bundle-url.js )
  |-- png       ( bg.png )

因?yàn)橐獙?css 熱更新,所以新增了 css-loader.js, bundle-url.js 兩個 js

replaceBundleNames替換引用:生成樹之后將代碼中的文件引用替換為最終打包的文件名,如果是生產(chǎn)環(huán)境會替換為 contentHash 根據(jù)內(nèi)容生成 hash

hmr更新: 判斷啟用 hmr 并且不是第一次構(gòu)建的情況,調(diào)用 hmr.emitUpdate 將改變的資源發(fā)送給瀏覽器

Bundle.package 打包

unloadOrphanedAssets 將獨(dú)立的資源刪除

package

package 將generated 寫入到文件
有6種打包:
CSSPackager,HTMLPackager,SourceMapPackager,JSPackager,JSConcatPackager,RawPackager
當(dāng)開啟 scopeHoist 時(shí)用 JSConcatPackager 否則 JSPackager
圖片等資源用 RawPackager

最終我們的例子被打包成 index.html, src.[hash].js, src.[hash].map 3個文件

index.html 里的 js 路徑被替換成立最終打包的地址

我們看一下打包的 js:

parcelRequire = (function (modules, cache, entry, globalName) {
  // Save the require from previous bundle to this closure if any
  var previousRequire = typeof parcelRequire === 'function' && parcelRequire;
  var nodeRequire = typeof require === 'function' && require;

  function newRequire(name, jumped) {
    if (!cache[name]) {
      localRequire.resolve = resolve;
      localRequire.cache = {};

      var module = cache[name] = new newRequire.Module(name);

      modules[name][0].call(module.exports, localRequire, module, module.exports, this);
    }

    return cache[name].exports;

    function localRequire(x){
      return newRequire(localRequire.resolve(x));
    }

    function resolve(x){
      return modules[name][4][x] || x;
    }
  }
  for (var i = 0; i < entry.length; i++) {
    newRequire(entry[i]);
  }
  // Override the current require with this new one
  return newRequire;
})({"src/module1.js":[function(require,module,exports) {
"use strict";

},{}],"src/module2.js":[function(require,module,exports) {
"use strict";

},{}],"src/index.js":[function(require,module,exports) {
"use strict";

var _module = require("./module");

var _module2 = require("./module1");

var _module3 = require("./module2");
console.log(_module.m);
},{"./module":"src/module.js","./module1":"src/module1.js","./module2":"src/module2.js","fs":"node_modules/parcel-bundler/src/builtins/_empty.js"}]
,{}]},{},["node_modules/parcel-bundler/src/builtins/hmr-runtime.js","src/index.js"], null)
//# sourceMappingURL=/src.a2b27638.map

可以看到代碼被拼接成了對象的形式,接收參數(shù) module, require 用來模塊導(dǎo)入導(dǎo)出,實(shí)現(xiàn)了 commonjs 的模塊加載機(jī)制,一個更加簡化版:

parcelRequire = (function (modules, cache, entry, globalName) {
  function newRequire(id){
    if(!cache[id]){
      let module = cache[id] = { exports: {} }
      modules[id][0].call(module.exports, newRequire, module, module.exports, this);
    }
    return cache[id]
  }
  for (var i = 0; i < entry.length; i++) {
    newRequire(entry[i]);
  }
  return newRequire;
})()

代碼被拼接起來:

`(function(modules){
  //...newRequire
})({` +
  asset.id +
    ':[function(require,module,exports) {\n' +
        asset.generated.js +
      '\n},' +
'})'
(function(modules){
  //...newRequire
})({
  "src/index.js":[function(require,module,exports){
    // code
  }]
})

hmr-runtime

上面打包的 js 中還有個 hmr-runtime.js 太長被我省略了
hmr-runtime.js 創(chuàng)建一個 WebSocket 監(jiān)聽服務(wù)端消息
修改文件觸發(fā) onChange 方法,onChange 將改變的資源 buildQueue.add 加入構(gòu)建隊(duì)列,重新調(diào)用 bundle 方法,打包資源,并調(diào)用 emitUpdate 通知瀏覽器更新
當(dāng)瀏覽器接收到服務(wù)端有新資源更新消息時(shí)
新的資源就會設(shè)置或覆蓋之前的模塊
modules[asset.id] = new Function('require', 'module', 'exports', asset.generated.js)
對模塊進(jìn)行更新:

function hmrAccept(id){
  // dispose 回調(diào)
  cached.hot._disposeCallbacks.forEach(function (cb) {
    cb(bundle.hotData);
  });

  delete bundle.cache[id]; // 刪除之前緩存
  newRequire(id); // 重新此加載

  // accept 回調(diào)
  cached.hot._acceptCallbacks.forEach(function (cb) {
    cb();
  });

  // 遞歸父模塊 進(jìn)行更新
  getParents(global.parcelRequire, id).some(function (id) {
    return hmrAccept(global.parcelRequire, id);
  });
}

至此整個打包流程結(jié)束

總結(jié)

parcle index.html
進(jìn)入 cli,啟動Server調(diào)用 bundle,初始化配置(Plugins, env, HMRServer, Watcher, WorkerFarm),從入口資源開始,遞歸編譯(babel, posthtml, postcss, vue-template-compiler等),編譯完設(shè)置緩存,構(gòu)建 Bundle 樹,進(jìn)行打包
如果沒有 watch 監(jiān)聽,結(jié)束關(guān)閉 Watcher, Worker, HMR
有 watch 監(jiān)聽:
文件修改,觸發(fā) onChange,將修改的資源加入構(gòu)建隊(duì)列,遞歸編譯,查找緩存(這一步緩存的作用就提醒出來了),編譯完設(shè)置新緩存,構(gòu)建 Bundle 樹,進(jìn)行打包,將 change 的資源發(fā)送給瀏覽器,瀏覽器接收 hmr 更新資源

看完了這篇文章,相信你對Parcel源碼的案例分析有了一定的了解,想了解更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI