国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

深入 Parcel--架構(gòu)與流程篇

yuxue / 1643人閱讀

摘要:本篇文章是對的源碼解析,代碼基本架構(gòu)與執(zhí)行流程,帶你了解打包工具的內(nèi)部原理,在這之前你如果對不熟悉可以先到官網(wǎng)了解介紹下面是偷懶從官網(wǎng)抄下來的介紹極速零配置應(yīng)用打包工具極速打包使用進(jìn)程去啟用多核編譯。

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

介紹

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

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

極速打包

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

將你所有的資源打包

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

自動轉(zhuǎn)換

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

零配置代碼分拆

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

熱模塊替換

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

友好的錯誤日志

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

打包工具 時間
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 (熱替換)

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 這些都是資源,并不是 webpackjs 是一等公民,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-bundlerpackage.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);
      }
    }

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

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

    // 因為接下來要構(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
    );

    // 將獨立的資源刪除
    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, devDependenciesparcel-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 起一個子進(jìn)程,使用 chokidar 包來監(jiān)聽文件改變

hmr 起一個服務(wù),WebSocket 向瀏覽器發(fā)送更改的資源

farm 初始化多進(jìn)程并指定 werker 工作文件,開啟多個 child_process 去解析編譯資源

接下來回到 bundleisInitialBundle 是一個判斷是否是第一次構(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)隊列
然后啟動 this.buildQueue.run() 對資源從入口遞歸開始打包

PromiseQueue

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

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)前資源的依賴項
    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 讀取緩存,并判斷最后修改時間和緩存的修改時間
write 寫入緩存

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

WorkerFarm

在上面 start 里初始化 farm 的時候,workerPath 指向了 worker.js 文件,worker.js 里有兩個函數(shù),init run
WorkerFarm.getShared 初始化的時候會創(chuàng)建一個 new WorkerFarm ,調(diào)用 worker.jsinit 方法,根據(jù) cpu 獲取最大的 Worker 數(shù),并啟動一半的子進(jìn)程
farm.run 會通知子進(jìn)程執(zhí)行 worker.jsrun 方法,如果進(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 ,vuehtml, js, css,都會進(jìn)行二次或多次遞歸處理,最終調(diào)用 asset.postProcess 生成代碼

Asset

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

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

parse 調(diào)用 posthtml-parserhtml 解析為 PostHTMLTree

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

transform htmlnano 壓縮代碼

generate 處理內(nèi)聯(lián)的 scriptcss

postProcess posthtml-render 生成 html 代碼

JSAsset

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

parse 調(diào)用 @babel/parserjs 解析為 AST

collectDependencies 用 babylon-walk 遍歷 ast, 如 ImportDeclarationimport 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)換為 commonjsterser 壓縮代碼

generate @babel/generator 獲取 jssourceMap 代碼

VueAsset

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

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

postProcess component-compiler-utilscompileTemplate, compileStyle處理 html,cssvue-hot-reload-api HMR處理,壓縮代碼

回到 bundle 方法:

let loadedAssets = await this.buildQueue.run() 就是上面說到的PromiseQueueWorkerFarm 結(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 的資源,也就是獨立的資源,這個 parentBundle 會在等會的構(gòu)建 Bundle 樹中被賦值,第一次構(gòu)建都沒有 parentBundle,所以這里會重復(fù)入口文件,這里的 findOrphanAssets 的作用是在第一次構(gòu)建之后,文件change的時候,在這個文件 import了新的一個文件,因為新文件沒有被構(gòu)建過 Bundle 樹,所以沒有 parentBundle,這個新文件也被標(biāo)記物 change

invalidateBundle 因為接下來要構(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

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

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

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

module1.jsmodule2.js 被提到了與 index.js 同級,map 因為類型不同被放到了 子bundle

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

// 資源樹
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 )

因為要對 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 將獨立的資源刪除

package

packagegenerated 寫入到文件
有6種打包:
CSSPackagerHTMLPackagerSourceMapPackagerJSPackagerJSConcatPackagerRawPackager
當(dāng)開啟 scopeHoist 時用 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)出,實現(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) {
" +
        asset.generated.js +
      "
}," +
"})"
(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)建隊列,重新調(diào)用 bundle 方法,打包資源,并調(diào)用 emitUpdate 通知瀏覽器更新
當(dāng)瀏覽器接收到服務(wù)端有新資源更新消息時
新的資源就會設(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)建隊列,遞歸編譯,查找緩存(這一步緩存的作用就提醒出來了),編譯完設(shè)置新緩存,構(gòu)建 Bundle 樹,進(jìn)行打包,將 change 的資源發(fā)送給瀏覽器,瀏覽器接收 hmr 更新資源

最后

通過此文章希望你對 parcel 的大致流程,打包工具原理有更深的了解
了解更多請關(guān)注專欄,后續(xù) 深入Parcel 同系列文章,對 AssetPackagerWorkerHMRscopeHoistFSCacheSourceMapimport 更加 詳細(xì)講解代碼實現(xiàn)

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/102629.html

相關(guān)文章

  • 前端每周清單第 45 期: Safari 支持 Service Worker, Parcel 完整教

    摘要:的另一個核心特性,蘋果表示也正在開發(fā)中,按開發(fā)進(jìn)度可能幾個月后就能與我們見面。是基于的本地化數(shù)據(jù)庫,支持以及瀏覽器環(huán)境。 前端每周清單專注前端領(lǐng)域內(nèi)容,以對外文資料的搜集為主,幫助開發(fā)者了解一周前端熱點;分為新聞熱點、開發(fā)教程、工程實踐、深度閱讀、開源項目、巔峰人生等欄目。歡迎關(guān)注【前端之巔】微信公眾號(ID: frontshow),及時獲取前端每周清單。 本期是 2017 年的最后一...

    趙春朋 評論0 收藏0
  • 前端每周清單第 43 期:2017 JavaScript 回顧、Rust WebAssembly

    摘要:楊冀龍是安全焦點民間白帽黑客組織核心成員,被浪潮之巔評為中國新一代黑客領(lǐng)軍人物之一他在本文中依次分享了對于黑客的定義如何從黑客成為一名安全創(chuàng)業(yè)者技術(shù)創(chuàng)業(yè)踩過的坑給技術(shù)創(chuàng)業(yè)者建議等內(nèi)容。 showImg(https://segmentfault.com/img/remote/1460000012377230?w=1240&h=796); 前端每周清單專注前端領(lǐng)域內(nèi)容,以對外文資料的搜集為...

    xorpay 評論0 收藏0
  • TaintDroid剖析之IPC級污點傳播

    摘要:前言在前三篇文章中我們詳細(xì)分析了對棧幀的修改,以及它是如何在修改之后的棧幀中實現(xiàn)變量級污點跟蹤方法級跟蹤。總結(jié)的污點跟蹤粒度是變量粒度的,因此大大提高了污點傳播的精準(zhǔn)度。下一步繼續(xù)分析下級污點傳播。 前言 在前三篇文章中我們詳細(xì)分析了TaintDroid對DVM棧幀的修改,以及它是如何在修改之后的棧幀中實現(xiàn)DVM變量級污點跟蹤、Native方法級跟蹤。本篇文章我們來分析下IPC級污點傳...

    baishancloud 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<