摘要:本篇文章是對(duì)的源碼解析,代碼基本架構(gòu)與執(zhí)行流程,帶你了解打包工具的內(nèi)部原理,在這之前你如果對(duì)不熟悉可以先到官網(wǎng)了解介紹下面是偷懶從官網(wǎng)抄下來(lái)的介紹極速零配置應(yīng)用打包工具極速打包使用進(jìn)程去啟用多核編譯。
本篇文章是對(duì) Parce 的源碼解析,代碼基本架構(gòu)與執(zhí)行流程,帶你了解打包工具的內(nèi)部原理,在這之前你如果對(duì) parcel 不熟悉可以先到 Parcel官網(wǎng) 了解
介紹下面是偷懶從官網(wǎng)抄下來(lái)的介紹:
極速零配置Web應(yīng)用打包工具
極速打包
Parcel 使用 worker 進(jìn)程去啟用多核編譯。同時(shí)有文件系統(tǒng)緩存,即使在重啟構(gòu)建后也能快速再編譯。
將你所有的資源打包
Parcel 具備開箱即用的對(duì) JS, CSS, HTML, 文件 及更多的支持,而且不需要插件。
自動(dòng)轉(zhuǎn)換
如若有需要,Babel, PostCSS, 和 PostHTML 甚至 node_modules 包會(huì)被用于自動(dòng)轉(zhuǎn)換代碼.
零配置代碼分拆
使用動(dòng)態(tài) import() 語(yǔ)法, Parcel 將你的輸出文件束(bundles)分拆,因此你只需要在初次加載時(shí)加載你所需要的代碼。
熱模塊替換
Parcel 無(wú)需配置,在開發(fā)環(huán)境的時(shí)候會(huì)自動(dòng)在瀏覽器內(nèi)隨著你的代碼更改而去更新模塊。
友好的錯(cuò)誤日志
當(dāng)遇到錯(cuò)誤時(shí),Parcel 會(huì)輸出 語(yǔ)法高亮的代碼片段,幫助你定位問(wèn)題。
打包工具 | 時(shí)間 |
---|---|
browserify | 22.98s |
webpack | 20.71s |
parcel | 9.98s |
parcel - with cache | 2.64s |
我們常用的打包工具大致功能:
模塊化(代碼的拆分, 合并, Tree-Shaking 等)
編譯(es6,7,8 sass typescript 等)
壓縮 (js, css, html包括圖片的壓縮)
HMR (熱替換)
versionparcel-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ò)展名獲取對(duì)應(yīng) Asset |-- Pipeline.js 多線程執(zhí)行方法 |-- Resolver.js 解析模塊路徑 |-- Server.js 靜態(tài)資源服務(wù)器 |-- SourceMap.js SourceMap |-- cli.js cli入口 解析命令行參數(shù) |-- worker.js 多線程入口流程 說(shuō)明
Parcel是面向資源的,JavaScript,CSS,HTML 這些都是資源,并不是 webpack 中 js 是一等公民,Parcel 會(huì)自動(dòng)的從入口文件開始分析這些文件 和 模塊中的依賴,然后構(gòu)建一個(gè) bundle 樹,并對(duì)其進(jìn)行打包輸出到指定目錄
一個(gè)簡(jiǎn)單的例子我們從一個(gè)簡(jiǎ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個(gè)子模塊
執(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) {...啟動(dòng)自動(dòng)打開瀏覽器} } else { bundler.bundle(); } }
在 cli.js 中利用 commander 解析命令行并調(diào)用 bundle 方法
有 serve, watch, build 3個(gè)命令來(lái)調(diào)用 bundle 函數(shù),執(zhí)行 pracel index.html 默認(rèn)為 serve,所以調(diào)用的是 bundler.serve 方法
進(jìn)入 Bundler.js
bundler.serveasync 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 起了一個(gè)靜態(tài)服務(wù)指向 最終打包的文件夾
下面就是重要的 bundle 方法
async bundle() { // 加載插件 設(shè)置env 啟動(dòng)多線程 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)榻酉聛?lái)要構(gòu)建 Bundle 樹,先對(duì)上一次的 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); } // 將改變的資源通過(guò)websocket發(fā)送到瀏覽器 if (this.hmr && !isInitialBundle) { this.hmr.emitUpdate(changedAssets); } // 對(duì)資源打包 this.bundleHashes = await this.mainBundle.package( this, this.bundleHashes ); // 將獨(dú)立的資源刪除 this.unloadOrphanedAssets(); return this.mainBundle; }
我們一步步先從 this.start 看
startif (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í)行,也就是說(shuō) this.start 只會(huì)執(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.env
watch 初始化監(jiān)聽文件并綁定 change 回調(diào)函數(shù),內(nèi)部 child_process.fork 起一個(gè)子進(jìn)程,使用 chokidar 包來(lái)監(jiān)聽文件改變
hmr 起一個(gè)服務(wù),WebSocket 向?yàn)g覽器發(fā)送更改的資源
farm 初始化多進(jìn)程并指定 werker 工作文件,開啟多個(gè) child_process 去解析編譯資源
接下來(lái)回到 bundle,isInitialBundle 是一個(gè)判斷是否是第一次構(gòu)建
fs.mkdirp 創(chuàng)建輸出文件夾
遍歷入口文件,通過(guò) resolveAsset,內(nèi)部調(diào)用 resolver 解析路徑,并 getAsset 獲取到對(duì)應(yīng)的 asset(這里我們?nèi)肟谑?index.html,根據(jù)擴(kuò)展名獲取到的是 HTMLAsset)
將 asset 添加進(jìn)隊(duì)列
然后啟動(dòng) this.buildQueue.run() 對(duì)資源從入口遞歸開始打包
這里 buildQueue 是一個(gè) PromiseQueue 異步隊(duì)列
PromiseQueue 在初始化的時(shí)候傳入一個(gè)回調(diào)函數(shù) callback,內(nèi)部維護(hù)一個(gè)參數(shù)隊(duì)列 queue,add 往隊(duì)列里 push 一個(gè)參數(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)行的重建,如果是重建則對(duì)資源的屬性重置,并使其緩存失效
之后調(diào)用 loadAsset 加載資源編譯資源
async loadAsset(asset) { if (asset.processed) { return; } // Mark the asset processed so we don"t load it twice asset.processed = true; // 先嘗試讀緩存,緩存沒有在后臺(tái)加載和編譯 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 在開始有個(gè)判斷防止重復(fù)編譯
之后去讀緩存,讀取失敗就調(diào)用 this.farm.run 在多進(jìn)程里編譯資源
編譯完就去加載并編譯依賴的文件
最后如果是新的資源沒有用到緩存,就重新設(shè)置一下緩存
下面說(shuō)一下這里嗎涉及的兩個(gè)東西:緩存 FSCache 和 多進(jìn)程 WorkerFarm
read 讀取緩存,并判斷最后修改時(shí)間和緩存的修改時(shí)間
write 寫入緩存
緩存目錄為了加速讀取,避免將所有的緩存文件放在一個(gè)文件夾里,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 里有兩個(gè)函數(shù),init 和 run
WorkerFarm.getShared 初始化的時(shí)候會(huì)創(chuàng)建一個(gè) new WorkerFarm ,調(diào)用 worker.js 的 init 方法,根據(jù) cpu 獲取最大的 Worker 數(shù),并啟動(dòng)一半的子進(jìn)程
farm.run 會(huì)通知子進(jìn)程執(zhí)行 worker.js 的 run 方法,如果進(jìn)程數(shù)沒有達(dá)到最大會(huì)再次開啟一個(gè)新的子進(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 這個(gè)generated 不一定是最終代碼 ,像 html里內(nèi)聯(lián)的 script ,vue 的 html, js, css,都會(huì)進(jìn)行二次或多次遞歸處理,最終調(diào)用 asset.postProcess 生成代碼
Asset下面說(shuō)幾個(gè)實(shí)現(xiàn)
HTMLAsset:
pretransform 調(diào)用 posthtml 將 html 解析為 PostHTMLTree(如果沒有設(shè)置posthtmlrc之類的不會(huì)走)
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" 語(yǔ)法,CallExpression 找到 require調(diào)用,import 被標(biāo)記為 dynamic 動(dòng)態(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 對(duì) .vue 文件進(jìn)行解析
generate 對(duì) html, js, css 處理,就像上面說(shuō)到會(huì)對(duì)其分別調(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() 就是上面說(shuō)到的PromiseQueue 和 WorkerFarm 結(jié)合起來(lái):buildQueue.run —> processAsset -> loadAsset -> farm.run -> worker.run -> pipeline.process -> pipeline.processAsset -> asset.process,執(zhí)行之后所有資源編譯完畢,并返回入口資源loadedAssets就是 index.html 對(duì)應(yīng)的 HTMLAsset 資源
之后是 let changedAssets = [...this.findOrphanAssets(), ...loadedAssets] 獲取到改變的資源
findOrphanAssets 是從所有資源中查找沒有 parentBundle 的資源,也就是獨(dú)立的資源,這個(gè) parentBundle 會(huì)在等會(huì)的構(gòu)建 Bundle 樹中被賦值,第一次構(gòu)建都沒有 parentBundle,所以這里會(huì)重復(fù)入口文件,這里的 findOrphanAssets 的作用是在第一次構(gòu)建之后,文件change的時(shí)候,在這個(gè)文件 import了新的一個(gè)文件,因?yàn)樾挛募]有被構(gòu)建過(guò) Bundle 樹,所以沒有 parentBundle,這個(gè)新文件也被標(biāo)記物 change
invalidateBundle 因?yàn)榻酉聛?lái)要構(gòu)建新的樹所以調(diào)用重置所有資源上一次樹的屬性
createBundleTree 構(gòu)建 Bundle 樹:
首先一個(gè)入口資源會(huì)被創(chuàng)建成一個(gè) bundle,然后動(dòng)態(tài)的 import() 會(huì)被創(chuàng)建成子 bundle ,這引發(fā)了代碼的拆分。
當(dāng)不同類型的文件資源被引入,兄弟 bundle 就會(huì)被創(chuàng)建。例如你在 JavaScript 中引入了 CSS 文件,那它會(huì)被放置在一個(gè)與 JavaScript 文件對(duì)應(yīng)的兄弟 bundle 中。
如果資源被多于一個(gè) bundle 引用,它會(huì)被提升到 bundle 樹中最近的公共祖先中,這樣該資源就不會(huì)被多次打包。
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
offsets:所有 bundle 中資源位置的映射 Map
我們的例子會(huì)被構(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 同級(jí),map 因?yàn)轭愋筒煌环诺搅?子bundle
一個(gè)復(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)橐獙?duì) css 熱更新,所以新增了 css-loader.js, bundle-url.js 兩個(gè) js
replaceBundleNames替換引用:生成樹之后將代碼中的文件引用替換為最終打包的文件名,如果是生產(chǎn)環(huán)境會(huì)替換為 contentHash 根據(jù)內(nèi)容生成 hash
hmr更新: 判斷啟用 hmr 并且不是第一次構(gòu)建的情況,調(diào)用 hmr.emitUpdate 將改變的資源發(fā)送給瀏覽器
Bundle.package 打包
unloadOrphanedAssets 將獨(dú)立的資源刪除
packagepackage 將generated 寫入到文件
有6種打包:
CSSPackager,HTMLPackager,SourceMapPackager,JSPackager,JSConcatPackager,RawPackager
當(dāng)開啟 scopeHoist 時(shí)用 JSConcatPackager 否則 JSPackager
圖片等資源用 RawPackager
最終我們的例子被打包成 index.html, src.[hash].js, src.[hash].map 3個(gè)文件
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
可以看到代碼被拼接成了對(duì)象的形式,接收參數(shù) module, require 用來(lái)模塊導(dǎo)入導(dǎo)出,實(shí)現(xiàn)了 commonjs 的模塊加載機(jī)制,一個(gè)更加簡(jiǎn)化版:
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; })()
代碼被拼接起來(lái):
`(function(modules){ //...newRequire })({` + asset.id + ":[function(require,module,exports) { " + asset.generated.js + " }," + "})"
(function(modules){ //...newRequire })({ "src/index.js":[function(require,module,exports){ // code }] })hmr-runtime
上面打包的 js 中還有個(gè) hmr-runtime.js 太長(zhǎng)被我省略了
hmr-runtime.js 創(chuàng)建一個(gè) WebSocket 監(jiān)聽服務(wù)端消息
修改文件觸發(fā) onChange 方法,onChange 將改變的資源 buildQueue.add 加入構(gòu)建隊(duì)列,重新調(diào)用 bundle 方法,打包資源,并調(diào)用 emitUpdate 通知瀏覽器更新
當(dāng)瀏覽器接收到服務(wù)端有新資源更新消息時(shí)
新的資源就會(huì)設(shè)置或覆蓋之前的模塊
modules[asset.id] = new Function("require", "module", "exports", asset.generated.js)
對(duì)模塊進(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); }); }
至此整個(gè)打包流程結(jié)束
總結(jié)parcle index.html
進(jìn)入 cli,啟動(dòng)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ì)列,遞歸編譯,查找緩存(這一步緩存的作用就提醒出來(lái)了),編譯完設(shè)置新緩存,構(gòu)建 Bundle 樹,進(jìn)行打包,將 change 的資源發(fā)送給瀏覽器,瀏覽器接收 hmr 更新資源
通過(guò)此文章希望你對(duì) parcel 的大致流程,打包工具原理有更深的了解
了解更多請(qǐng)關(guān)注專欄,后續(xù) 深入Parcel 同系列文章,對(duì) Asset,Packager,Worker,HMR,scopeHoist,FSCache,SourceMap,import 更加 詳細(xì)講解與代碼實(shí)現(xiàn)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/102629.html
摘要:的另一個(gè)核心特性,蘋果表示也正在開發(fā)中,按開發(fā)進(jìn)度可能幾個(gè)月后就能與我們見面。是基于的本地化數(shù)據(jù)庫(kù),支持以及瀏覽器環(huán)境。 前端每周清單專注前端領(lǐng)域內(nèi)容,以對(duì)外文資料的搜集為主,幫助開發(fā)者了解一周前端熱點(diǎn);分為新聞熱點(diǎn)、開發(fā)教程、工程實(shí)踐、深度閱讀、開源項(xiàng)目、巔峰人生等欄目。歡迎關(guān)注【前端之巔】微信公眾號(hào)(ID: frontshow),及時(shí)獲取前端每周清單。 本期是 2017 年的最后一...
摘要:楊冀龍是安全焦點(diǎn)民間白帽黑客組織核心成員,被浪潮之巔評(píng)為中國(guó)新一代黑客領(lǐng)軍人物之一他在本文中依次分享了對(duì)于黑客的定義如何從黑客成為一名安全創(chuàng)業(yè)者技術(shù)創(chuàng)業(yè)踩過(guò)的坑給技術(shù)創(chuàng)業(yè)者建議等內(nèi)容。 showImg(https://segmentfault.com/img/remote/1460000012377230?w=1240&h=796); 前端每周清單專注前端領(lǐng)域內(nèi)容,以對(duì)外文資料的搜集為...
摘要:前言在前三篇文章中我們?cè)敿?xì)分析了對(duì)棧幀的修改,以及它是如何在修改之后的棧幀中實(shí)現(xiàn)變量級(jí)污點(diǎn)跟蹤方法級(jí)跟蹤。總結(jié)的污點(diǎn)跟蹤粒度是變量粒度的,因此大大提高了污點(diǎn)傳播的精準(zhǔn)度。下一步繼續(xù)分析下級(jí)污點(diǎn)傳播。 前言 在前三篇文章中我們?cè)敿?xì)分析了TaintDroid對(duì)DVM棧幀的修改,以及它是如何在修改之后的棧幀中實(shí)現(xiàn)DVM變量級(jí)污點(diǎn)跟蹤、Native方法級(jí)跟蹤。本篇文章我們來(lái)分析下IPC級(jí)污點(diǎn)傳...
閱讀 2645·2021-11-18 10:07
閱讀 1095·2021-08-03 14:04
閱讀 737·2019-08-30 13:08
閱讀 2591·2019-08-29 15:33
閱讀 1105·2019-08-29 14:07
閱讀 3005·2019-08-29 14:04
閱讀 1450·2019-08-29 11:19
閱讀 1158·2019-08-29 10:59