久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

好程序員課堂:跟你講講webpack 的流程(收藏版)

 西北望msm66g9f 2016-10-31

目前,,幾乎所有業(yè)務(wù)的開(kāi)發(fā)構(gòu)建都會(huì)用到 webpack ,。的確,,作為模塊加載和打包神器,只需配置幾個(gè)文件,,加載各種 loader 就可以享受無(wú)痛流程化開(kāi)發(fā),。但對(duì)于 webpack 這樣一個(gè)復(fù)雜度較高的插件集合,它的整體流程及思想對(duì)我們來(lái)說(shuō)還是很透明的,。那么接下來(lái)我會(huì)帶你了解 webpack 這樣一個(gè)構(gòu)建黑盒,,首先來(lái)談?wù)勊牧鞒獭?/span>



首先是準(zhǔn)備工作



1. webstorm 中配置 webpack-webstorm-debugger-script


在開(kāi)始了解之前,必須要能對(duì) webpack 整個(gè)流程進(jìn)行 debug ,,配置過(guò)程比較簡(jiǎn)單,。


先將 webpack-webstorm-debugger-script 中的 webstorm-debugger.js 置于webpack.config.js 的同一目錄下,搭建好你的腳手架后就可以直接 Debug 這個(gè) webstorm-debugger.js 文件了,。


2. webpack.config.js 配置


估計(jì)大家對(duì) webpack.config.js 的配置也嘗試過(guò)不少次了,,這里就大致對(duì)這個(gè)配置文件進(jìn)行個(gè)分析。


var path = require('path');

var node_modules = path.resolve(__dirname, 'node_modules');

var pathToReact = path.resolve(node_modules, 'react/dist/react.min.js');

 

module.exports = {

  // 入口文件,是模塊構(gòu)建的起點(diǎn),,同時(shí)每一個(gè)入口文件對(duì)應(yīng)最后生成的一個(gè) chunk,。

  entry: {

    bundle: [

      'webpack/hot/dev-server',

      'webpack-dev-server/client?http://localhost:8080',

      path.resolve(__dirname, 'app/app.js')

    ]

  },

  // 文件路徑指向(可加快打包過(guò)程)。

  resolve: {

    alias: {

      'react': pathToReact

    }

  },

  // 生成文件,,是模塊構(gòu)建的終點(diǎn),,包括輸出文件與輸出路徑。

  output: {

    path: path.resolve(__dirname, 'build'),

    filename: '[name].js'

  },

  // 這里配置了處理各模塊的 loader ,,包括 css 預(yù)處理 loader ,,es6 編譯 loader,圖片處理 loader,。

  module: {

    loaders: [

      {

        test: /\.js$/,

        loader: 'babel',

        query: {

          presets: ['es2015', 'react']

        }

      }

    ],

    noParse: [pathToReact]

  },

  // webpack 各插件對(duì)象,,在 webpack 的事件流中執(zhí)行對(duì)應(yīng)的方法。

  plugins: [

    new webpack.HotModuleReplacementPlugin()

  ]

};


除此之外再大致介紹下 webpack 的一些核心概念:


loader:能轉(zhuǎn)換各類資源,,并處理成對(duì)應(yīng)模塊的加載器,。loader 間可以串行使用。

chunk:code splitting 后的產(chǎn)物,,也就是按需加載的分塊,,裝載了不同的 module。

對(duì)于 module 和 chunk 的關(guān)系可以參照 webpack 官方的這張圖:



plugin:webpack 的插件實(shí)體,,這里以 UglifyJsPlugin 為例,。


function UglifyJsPlugin(options) {

  this.options = options;

}

 

module.exports = UglifyJsPlugin;

 

UglifyJsPlugin.prototype.apply = function(compiler) {

  compiler.plugin('compilation', function(compilation) {

    compilation.plugin('build-module', function(module) {

    });

    compilation.plugin('optimize-chunk-assets', function(chunks, callback) {

      // Uglify 邏輯

    });

    compilation.plugin('normal-module-loader', function(context) {

    });

  });

};


在 webpack 中你經(jīng)常可以看到 compilation.plugin(‘xxx’, callback) ,,你可以把它當(dāng)作是一個(gè)事件的綁定,,這些事件在打包時(shí)由 webpack 來(lái)觸發(fā)。


3. 流程總覽


在具體流程學(xué)習(xí)前,,可以先通過(guò)這幅 webpack 整體流程圖 了解一下大致流程(建議保存下來(lái)查看),。



shell 與 config 解析


每次在命令行輸入 webpack 后,操作系統(tǒng)都會(huì)去調(diào)用 ./node_modules/.bin/webpack 這個(gè) shell 腳本,。這個(gè)腳本會(huì)去調(diào)用 ./node_modules/webpack/bin/webpack.js 并追加輸入的參數(shù),,如 -p , -w 。(圖中 webpack.js 是 webpack 的啟動(dòng)文件,,而 $@ 是后綴參數(shù))



在 webpack.js 這個(gè)文件中 webpack 通過(guò) optimist 將用戶配置的 webpack.config.js 和 shell 腳本傳過(guò)來(lái)的參數(shù)整合成 options 對(duì)象傳到了下一個(gè)流程的控制對(duì)象中,。


1. optimist


和 commander 一樣,optimist 實(shí)現(xiàn)了 node 命令行的解析,,其 API 調(diào)用非常方便,。


var optimist = require('optimist');

 

optimist

  .boolean('json').alias('json', 'j').describe('json')

  .boolean('colors').alias('colors', 'c').describe('colors')

  .boolean('watch').alias('watch', 'w').describe('watch')

  ...


獲取到后綴參數(shù)后,optimist 分析參數(shù)并以鍵值對(duì)的形式把參數(shù)對(duì)象保存在 optimist.argv 中,,來(lái)看看 argv 究竟有什么,?


// webpack --hot -w

{

  hot: true,

  profile: false,

  watch: true,

  ...

}


2. config 合并與插件加載


在加載插件之前,webpack 將 webpack.config.js 中的各個(gè)配置項(xiàng)拷貝到 options 對(duì)象中,并加載用戶配置在 webpack.config.js 的 plugins ,。接著 optimist.argv 會(huì)被傳入到./node_modules/webpack/bin/convert-argv.js 中,,通過(guò)判斷 argv 中參數(shù)的值決定是否去加載對(duì)應(yīng)插件。(至于 webpack 插件運(yùn)行機(jī)制,,在之后的運(yùn)行機(jī)制篇會(huì)提到)


ifBooleanArg('hot', function() {

  ensureArray(options, 'plugins');

  var HotModuleReplacementPlugin = require('../lib/HotModuleReplacementPlugin');

  options.plugins.push(new HotModuleReplacementPlugin());

});

...

return options;


options 作為最后返回結(jié)果,,包含了之后構(gòu)建階段所需的重要信息。


{

  entry: {},//入口配置

  output: {}, //輸出配置

  plugins: [], //插件集合(配置文件 + shell指令)

  module: { loaders: [ [Object] ] }, //模塊配置

  context: //工程路徑

  ...

}


這和 webpack.config.js 的配置非常相似,,只是多了一些經(jīng) shell 傳入的插件對(duì)象,。插件對(duì)象一初始化完畢, options 也就傳入到了下個(gè)流程中,。


var webpack = require('../lib/webpack.js');

var compiler = webpack(options);


編譯與構(gòu)建流程


在加載配置文件和 shell 后綴參數(shù)申明的插件,,并傳入構(gòu)建信息 options 對(duì)象后,,開(kāi)始整個(gè) webpack 打包最漫長(zhǎng)的一步,。而這個(gè)時(shí)候,真正的 webpack 對(duì)象才剛被初始化,,具體的初始化邏輯在 lib/webpack.js 中,,如下:


function webpack(options) {

  var compiler = new Compiler();

  ...// 檢查options,若watch字段為true,則開(kāi)啟watch線程

  return compiler;

}

...


webpack 的實(shí)際入口是 Compiler 中的 run 方法,run 一旦執(zhí)行后,,就開(kāi)始了編譯和構(gòu)建流程 ,,其中有幾個(gè)比較關(guān)鍵的 webpack 事件節(jié)點(diǎn)。


  • compile 開(kāi)始編譯

  • make 從入口點(diǎn)分析模塊及其依賴的模塊,,創(chuàng)建這些模塊對(duì)象

  • build-module 構(gòu)建模塊

  • after-compile 完成構(gòu)建

  • seal 封裝構(gòu)建結(jié)果

  • emit 把各個(gè)chunk輸出到結(jié)果文件

  • after-emit 完成輸出


1. 核心對(duì)象 Compilation


compiler.run 后首先會(huì)觸發(fā) compile ,,這一步會(huì)構(gòu)建出 Compilation 對(duì)象:



這個(gè)對(duì)象有兩個(gè)作用,一是負(fù)責(zé)組織整個(gè)打包過(guò)程,,包含了每個(gè)構(gòu)建環(huán)節(jié)及輸出環(huán)節(jié)所對(duì)應(yīng)的方法,,可以從圖中看到比較關(guān)鍵的步驟,如 addEntry() , _addModuleChain() ,buildModule() , seal() , createChunkAssets() (在每一個(gè)節(jié)點(diǎn)都會(huì)觸發(fā) webpack 事件去調(diào)用各插件),。二是該對(duì)象內(nèi)部存放著所有 module ,,chunk,生成的 asset 以及用來(lái)生成最后打包文件的 template 的信息,。


2. 編譯與構(gòu)建主流程


在創(chuàng)建 module 之前,,Compiler 會(huì)觸發(fā) make,并調(diào)用 Compilation.addEntry 方法,,通過(guò) options 對(duì)象的 entry 字段找到我們的入口js文件,。之后,在 addEntry 中調(diào)用私有方法_addModuleChain ,,這個(gè)方法主要做了兩件事情,。一是根據(jù)模塊的類型獲取對(duì)應(yīng)的模塊工廠并創(chuàng)建模塊,二是構(gòu)建模塊。


而構(gòu)建模塊作為最耗時(shí)的一步,,又可細(xì)化為三步:


調(diào)用各 loader 處理模塊之間的依賴webpack 提供的一個(gè)很大的便利就是能將所有資源都整合成模塊,,不僅僅是 js 文件。所以需要一些 loader ,,比如 url-loader ,, jsx-loader , css-loader 等等來(lái)讓我們可以直接在源文件中引用各類資源,。webpack 調(diào)用 doBuild() ,,對(duì)每一個(gè) require() 用對(duì)應(yīng)的 loader 進(jìn)行加工,最后生成一個(gè) js module,。


Compilation.prototype._addModuleChain = function process(context, dependency, onModule, callback) {

  var start = this.profile && +new Date();

  ...

  // 根據(jù)模塊的類型獲取對(duì)應(yīng)的模塊工廠并創(chuàng)建模塊

  var moduleFactory = this.dependencyFactories.get(dependency.constructor);

  ...

  moduleFactory.create(context, dependency, function(err, module) {

    var result = this.addModule(module);

    ...

    this.buildModule(module, function(err) {

      ...

      // 構(gòu)建模塊,,添加依賴模塊

    }.bind(this));

  }.bind(this));

};


調(diào)用 acorn 解析經(jīng) loader 處理后的源文件生成抽象語(yǔ)法樹(shù) AST


Parser.prototype.parse = function parse(source, initialState) {

  var ast;

  if (!ast) {

    // acorn以es6的語(yǔ)法進(jìn)行解析

    ast = acorn.parse(source, {

      ranges: true,

      locations: true,

      ecmaVersion: 6,

      sourceType: 'module'

    });

  }

  ...

};


遍歷 AST,構(gòu)建該模塊所依賴的模塊對(duì)于當(dāng)前模塊,,或許存在著多個(gè)依賴模塊,。當(dāng)前模塊會(huì)開(kāi)辟一個(gè)依賴模塊的數(shù)組,在遍歷 AST 時(shí),,將 require() 中的模塊通過(guò) addDependency() 添加到數(shù)組中,。當(dāng)前模塊構(gòu)建完成后,webpack 調(diào)用 processModuleDependencies 開(kāi)始遞歸處理依賴的 module,,接著就會(huì)重復(fù)之前的構(gòu)建步驟,。


Compilation.prototype.addModuleDependencies = function(module, dependencies, bail, cacheGroup, recursive, callback) {

  // 根據(jù)依賴數(shù)組(dependencies)創(chuàng)建依賴模塊對(duì)象

  var factories = [];

  for (var i = 0; i <>dependencies.length; i++) {

    var factory = _this.dependencyFactories.get(dependencies[i][0].constructor);

    factories[i] = [factory, dependencies[i]];

  }

  ...

  // 與當(dāng)前模塊構(gòu)建步驟相同

}


3. 構(gòu)建細(xì)節(jié)


module 是 webpack 構(gòu)建的核心實(shí)體,也是所有 module 的 父類,,它有幾種不同子類:NormalModule , MultiModule , ContextModule , DelegatedModule 等,。但這些核心實(shí)體都是在構(gòu)建中都會(huì)去調(diào)用對(duì)應(yīng)方法,也就是 build() ,。來(lái)看看其中具體做了什么:


// 初始化module信息,,如context,id,chunks,dependencies等。

NormalModule.prototype.build = function build(options, compilation, resolver, fs, callback) {

  this.buildTimestamp = new Date().getTime(); // 構(gòu)建計(jì)時(shí)

  this.built = true;

  return this.doBuild(options, compilation, resolver, fs, function(err) {

    // 指定模塊引用,,不經(jīng)acorn解析

    if (options.module && options.module.noParse) {

      if (Array.isArray(options.module.noParse)) {

        if (options.module.noParse.some(function(regExp) {

            return typeof regExp === 'string' ?

            this.request.indexOf(regExp) === 0 :

              regExp.test(this.request);

          }, this)) {

          return callback();

        }

      } else if (typeof options.module.noParse === 'string' ?

        this.request.indexOf(options.module.noParse) === 0 :

          options.module.noParse.test(this.request)) {

        return callback();

      }

    }

    // 由acorn解析生成ast

    try {

      this.parser.parse(this._source.source(), {

        current: this,

        module: this,

        compilation: compilation,

        options: options

      });

    } catch (e) {

      var source = this._source.source();

      this._source = null;

      return callback(new ModuleParseError(this, source, e));

    }

    return callback();

  }.bind(this));

};


對(duì)于每一個(gè) module ,,它都會(huì)有這樣一個(gè)構(gòu)建方法。當(dāng)然,,它還包括了從構(gòu)建到輸出的一系列的有關(guān) module 生命周期的函數(shù),,我們通過(guò) module 父類類圖其子類類圖(這里以 NormalModule 為例)來(lái)觀察其真實(shí)形態(tài):



可以看到無(wú)論是構(gòu)建流程,處理依賴流程,,包括后面的封裝流程都是與 module 密切相關(guān)的,。


打包輸出


在所有模塊及其依賴模塊 build 完成后,webpack 會(huì)監(jiān)聽(tīng) seal 事件調(diào)用各插件對(duì)構(gòu)建后的結(jié)果進(jìn)行封裝,,要逐次對(duì)每個(gè) module 和 chunk 進(jìn)行整理,,生成編譯后的源碼,,合并,拆分,,生成 hash ,。 同時(shí)這是我們?cè)陂_(kāi)發(fā)時(shí)進(jìn)行代碼優(yōu)化和功能添加的關(guān)鍵環(huán)節(jié)。


Compilation.prototype.seal = function seal(callback) {

  this.applyPlugins('seal'); // 觸發(fā)插件的seal事件

  this.preparedChunks.sort(function(a, b) {

    if (a.name <>b.name) {

      return -1;

    }

    if (a.name > b.name) {

      return 1;

    }

    return 0;

  });

  this.preparedChunks.forEach(function(preparedChunk) {

    var module = preparedChunk.module;

    var chunk = this.addChunk(preparedChunk.name, module);

    chunk.initial = chunk.entry = true;

    // 整理每個(gè)Module和chunk,,每個(gè)chunk對(duì)應(yīng)一個(gè)輸出文件,。

    chunk.addModule(module);

    module.addChunk(chunk);

  }, this);

  this.applyPluginsAsync('optimize-tree', this.chunks, this.modules, function(err) {

    if (err) {

      return callback(err);

    }

    ... // 觸發(fā)插件的事件

    this.createChunkAssets(); // 生成最終assets

    ... // 觸發(fā)插件的事件

  }.bind(this));

};


1. 生成最終 assets


在封裝過(guò)程中,webpack 會(huì)調(diào)用 Compilation 中的 createChunkAssets 方法進(jìn)行打包后代碼的生成,。 createChunkAssets 流程如下:



不同的 Template從上圖可以看出通過(guò)判斷是入口 js 還是需要異步加載的 js 來(lái)選擇不同的模板對(duì)象進(jìn)行封裝,,入口 js 會(huì)采用 webpack 事件流的 render 事件來(lái)觸發(fā) Template類 中的renderChunkModules() (異步加載的 js 會(huì)調(diào)用 chunkTemplate 中的 render 方法)。


if(chunk.entry) {

  source = this.mainTemplate.render(this.hash, chunk, this.moduleTemplate, this.dependencyTemplates);

} else {

  source = this.chunkTemplate.render(chunk, this.moduleTemplate, this.dependencyTemplates);

}


在 webpack 中有四個(gè) Template 的子類,,分別是 MainTemplate.js ,, ChunkTemplate.js,ModuleTemplate.js ,, HotUpdateChunkTemplate.js ,,前兩者先前已大致有介紹,而 ModuleTemplate 是對(duì)所有模塊進(jìn)行一個(gè)代碼生成,,HotUpdateChunkTemplate 是對(duì)熱替換模塊的一個(gè)處理,。

模塊封裝模塊在封裝的時(shí)候和它在構(gòu)建時(shí)一樣,,都是調(diào)用各模塊類中的方法,。封裝通過(guò)調(diào)用module.source() 來(lái)進(jìn)行各操作,比如說(shuō) require() 的替換,。


MainTemplate.prototype.requireFn = '__webpack_require__';

MainTemplate.prototype.render = function(hash, chunk, moduleTemplate, dependencyTemplates) {

    var buf = [];

    // 每一個(gè)module都有一個(gè)moduleId,在最后會(huì)替換,。

    buf.push('function ' + this.requireFn + '(moduleId) {');

    buf.push(this.indent(this.applyPluginsWaterfall('require', '', chunk, hash)));

    buf.push('}');

    buf.push('');

    ... // 其余封裝操作

};


生成 assets各模塊進(jìn)行 doBlock 后,把 module 的最終代碼循環(huán)添加到 source 中,。一個(gè) source 對(duì)應(yīng)著一個(gè) asset 對(duì)象,,該對(duì)象保存了單個(gè)文件的文件名( name )和最終代碼( value )。


2. 輸出


最后一步,,webpack 調(diào)用 Compiler 中的 emitAssets() ,,按照 output 中的配置項(xiàng)將文件輸出到了對(duì)應(yīng)的 path 中,從而 webpack 整個(gè)打包過(guò)程結(jié)束,。要注意的是,,若想對(duì)結(jié)果進(jìn)行處理,則需要在 emit 觸發(fā)后對(duì)自定義插件進(jìn)行擴(kuò)展,。


最后總結(jié)


webpack 的整體流程主要還是依賴于 compilation 和 module 這兩個(gè)對(duì)象,,但其思想遠(yuǎn)不止這么簡(jiǎn)單。最開(kāi)始也說(shuō)過(guò),,webpack 本質(zhì)是個(gè)插件集合,,并且由 tapable 控制各插件在 webpack 事件流上運(yùn)行,,至于具體的思想和細(xì)節(jié),將會(huì)在后一篇文章中提到,。同時(shí),,在業(yè)務(wù)開(kāi)發(fā)中,無(wú)論是為了提升構(gòu)建效率,,或是減小打包文件大小,,我們都可以通過(guò)編寫(xiě) webpack 插件來(lái)進(jìn)行流程上的控制,這個(gè)也會(huì)在之后提到,。


-文章結(jié)束-


    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn),。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多