網上已經有很多Webpack教程入門教程了。 本文記錄了我以個人方式方法、思路認識瞭解Webpack。從官方的Tutorial入手,不斷提出問題、解決,一步一步認識Webpack。javascript
從早期的本身寫腳本,到如今的各類構建工具,前端工程化已經發展到新的階段了。css
早先在百度地圖的時候,地圖代碼用PHP進行簡單粗放的處理。這個階段算是最原始的本身寫腳本處理。後來我用Ruby寫了一套集合了開發、動態合併、mock數據、一鍵build的工具。這算是更進了一步。html
如今基於Nodejs的任務管理工具Grunt、Glup都提供了代碼合併、壓縮、各類JS Transpiler、CSS預處理、各類前端模板的處理。前端
在Grunt、Gulp中是經過第三方庫進行編譯的。 在Webpack中也是相似的,只不過是增長了Loader的概念。經過一系列「Loader」完成處理。 處理以後統一輸出爲JS代碼。java
初步認識
在深刻以前,你須要先照着官方教程實踐一下。有個感性的認識。 完成教程的getting started部分後,你能夠初步得出如下結論:node
- Webpack是一個用來打包js工程的工具。官方定義爲
Module bundler
- Webpack命令行參數能夠配置到名爲
webpack.config.js
(或webpackfile
)的配置文件中 - Webpack提供一個能夠檢測文件變化並編譯而後刷新瀏覽器的
webpack-dev-server
那麼問題來了: 對於如Grunt、Webpack這種經過配置工做的工具來講,有哪些配置可用,配置的行爲、配置的可選值,須要完備的文檔纔好會用。 幸虧Webpack官網提供了詳盡的文檔react
下面是主要配置項的簡要說明webpack
context 工程目錄,必須是絕對路徑
entry 打包生成的bundle。能夠是多個
output 生成的文件配置選項
output.filename 生成的文件名模板,好比 "[name].bundle.js" output.path 生成的文件目錄,絕對路徑 output.publicPath 線上靜態資源目錄 output.chunkFilename 代碼塊文件名模板 output.sourceMapFilename source-map文件名模板。默認是[file].map output.devtoolModuleFilenameTemplate output.devtoolFallbackModuleFilenameTemplate output.devtoolLineToLine output.hotUpdateChunkFilename output.hotUpdateMainFilename output.jsonpFunction JSONP異步加載代碼塊(chunk)時JSONP函數名,默認是webpackJsonp output.hotUpdateFunction JSONP異步熱更新代碼塊時JSONP函數名,默認是webpackHotUpdate output.pathinfo 是否以註釋形式在require中增長模塊path信息 output.library bundle做爲庫輸出,值爲庫名 output.libraryTarget 輸出庫的格式。好比可選amd,umd,commonjs等 output.umdNamedDefine output.sourcePrefix output.crossOriginLoading module module.loaders Loader配置 module.preLoaders, module.postLoaders preLoader和postLoader配置 module.noParse 不須要loader編譯的文件 resolve 模塊決議配置 resolve.alias 模塊別名 resolve.root 模塊根目錄,絕對路徑 resolve.modulesDirectories 模塊目錄,工做方式相似node_modules。默認值是["web_modules", "node_modules"] resolve.fallback 若是在root和modulesDirectories都找不到,會在這裏搜索 resolve.extensions 用於模塊查找的擴展名。 resolve.packageMains resolve.packageAlias resolve.unsafeCache resolveLoader 與resolve相似,不過是給loader模塊決議使用的配置 resolveLoader.moduleTemplates externals target 目標環境,代碼是用於web仍是node仍是electron環境等等 bail profile 每一個模塊的時間打點信息 cache 是否開啓編譯緩存以提升性能。watch模式默認開啓 debug 設置loaders爲debug模式 devtool 用於方便調試的開發工具選項。好比source-map方便調試混淆後的代碼 devServer 傳給webpack-dev-server的參數 node 傳遞給node做爲polyfills和mocks的參數 amd require.mad和define.amd對應的值。好比{jQuery:true} loader 提供給loader的額外信息 recordsPath, recordsInputPath, recordsOutputPath plugins 插件配置
Loaders
咱們最關心的是有哪些loader能夠用呢? 經過在 https://github.com/webpack 搜索項目名中包含-loader
。我找到了這些官方提供的loader:git
#裸數據 raw-loader #腳本代碼 coffee-loader script-loader #樣式相關 css-loader style-loader less-loader #html相關 html-loader jade-loader #json相關 json-loader json5-loader #其餘 worker-loader-loader imports-loader exports-loader source-map-loader coffee-redux-loader multi-loader react-proxy-loader expose-loader url-loader node-loader bundle-loader val-loader transform-loader jshint-loader null-loader coverjs-loader
咦,爲何有一個css-loader還有一個style-loader?css-loader是用來加載css文件的 style-loader是用來應用已經加載的css中的樣式的。github
在配置文件中,配置須要使用的loader。test
用來對文件名進行匹配測試,匹配成功的文件會用對應的loader處理。
module: { loaders: [ { test: /\.coffee$/, loader: "coffee-loader" }, { test: /\.js$/, loader: "jsx-loader" } ] },
每一個loader都有本身獨特的配置,須要參考對應文檔。 全部loader均可以配置一下項目:
test 用來對文件名進行匹配測試
exclude 被排除的文件名 include 包含的文件名 loader 歎號分割的loaders loaders loader數組
好比babel的配置就有query、cacheDirectory配置項。
能夠想象,loader要作的工做無非就是拿到源碼,根據參數配置進行變換,返回變換後的結果。看一下less-loder
的源代碼:
/** * 簡化後的僞代碼 */ var less = require("less"); var loaderUtils = require("loader-utils"); module.exports = function(source){ //解析loader的query string var query = loaderUtils.parseQuery(this.query); //默認less編譯配置 var config = { filename: this.resource, compress: !!this.minimize }; //將query中的配置merge到默認配置中 Object.keys(query).forEach(function(attr){ config[attr] = query[attr] }); //編譯less var cb = this.callback; less.render(source, config, function(e, result){ cb(null, result.css, result.map); }); };
基本上就是從query讀取配置,調用less編譯器編譯源碼。
官網編寫loader的教程驗證了上述想法。同時也指出了編寫loader時要注意的一些問題。
參考:官方給出的已有loader列表
Plugins
有哪些plugin呢? 經過在 https://github.com/webpack 搜索項目名中包含-plugin
我找到了這些官方提供的plugin:
extract-text-webpack-plugin compression-webpack-plugin i18n-webpack-plugin component-webpack-plugin
感受不對啊,那個不少教程中常見的UglifyJsPlugin
都沒有看到啊!那麼只有一個可能,這些plugin都是內置的。在源代碼中必定能找到。clone下來webpack的代碼。打開lib,滿眼都是XXXPlugin。在optimize目錄下能夠找到UglifyJsPlugin
。大體看一下這些代碼能夠發現,每一個Plugin的原型上都有一個apply函數:
/** * 從UglifyJsPlugin.js簡化而來的僞代碼 */ ... var uglify = require("uglify-js"); ... UglifyJsPlugin.prototype.apply = function(compiler) { ... compiler.plugin("compilation", function (module) { ... var input = asset.source(); var ast = uglify.parse(input); //壓縮 if (options.compress !== false) { var compress = uglify.Compressor(options.comrpess); ast = ast.transform(compress); } //混淆 if (options.mangle !== false) { ast.mangle_names(); uglify.mangle_properties(ast); } //從新從ast生成代碼 var result = uglify.OutputStream(); ast.print(result); }); };
能夠想象,webpack會根據配置文件中plugins數組中的插件實例,調用其apply函數。 在apply函數中,插件對感興趣的事件(官方叫作stage)註冊處理函數(plugin
)。好比UglifyJsPlugin
就是在compilation
事件觸發時,對源代碼進行壓縮混淆。
經過官網閱讀how-to-write-a-plugin能夠驗證了上面的想法。
既然有compilation
事件,那確定還有其餘事件嘍。在lib目錄下搜索源代碼中的compile.plugin
調用
$ ack -Q compiler.plugin( | grep plugin|gawk "{print($2)}"|sort|uniq compiler.plugin("additional-pass", compiler.plugin("after-compile", compiler.plugin("after-environment", compiler.plugin("after-resolvers", compiler.plugin("compilation", compiler.plugin("compile", compiler.plugin("context-module-factory", compiler.plugin("done", compiler.plugin("emit", compiler.plugin("entry-option", compiler.plugin("environment", compiler.plugin("invalid", compiler.plugin("make", compiler.plugin("normal-module-factory", compiler.plugin("run", compiler.plugin("should-emit", compiler.plugin("this-compilation", compiler.plugin("watch-run",
一共有18個事件。官方教程中只介紹了done,compilation,emit
三個。 用一樣的方法,咱們還能夠查出compilation
支持的事件:
$ ack -Q compilation.plugin( | grep plugin|gawk "{print($2,$3)}"|sort|uniq compilation.plugin("additional-assets", function(callback) compilation.plugin("additional-chunk-assets", function() compilation.plugin("after-hash", function() compilation.plugin("after-optimize-chunk-assets", function(chunks) compilation.plugin("after-optimize-tree", function(chunks, compilation.plugin("before-module-ids", function(modules) compilation.plugin("build-module", function(module) compilation.plugin("chunk-hash", function(chunk, compilation.plugin("failed-module", moduleDone); compilation.plugin("need-additional-pass", function() compilation.plugin("normal-module-loader", function(context, compilation.plugin("normal-module-loader", function(loaderContext) compilation.plugin("normal-module-loader", function(loaderContext, compilation.plugin("optimize-assets", function(assets, compilation.plugin("optimize-chunk-assets", function(chunks, compilation.plugin("optimize-chunk-ids", function(chunks) compilation.plugin("optimize-chunk-order", function(chunks) compilation.plugin("optimize-chunks-advanced", function(chunks) compilation.plugin("optimize-chunks-basic", function(chunks) compilation.plugin("optimize-module-order", function(modules) compilation.plugin("optimize-modules-advanced", function(modules) compilation.plugin("optimize-tree", function(chunks, compilation.plugin("record", function(compilation, compilation.plugin("record-chunks", function(chunks, compilation.plugin("record-modules", function(modules, compilation.plugin("revive-chunks", function(chunks, compilation.plugin("revive-modules", function(modules, compilation.plugin("seal", function() compilation.plugin("should-generate-chunk-assets", function() compilation.plugin("should-record", function() compilation.plugin("succeed-module", moduleDone); compilation.plugin(["optimize-chunks", "optimize-extracted-chunks"], compilation.plugin(["optimize-chunks-basic", "optimize-extracted-chunks-basic"],
有了這兩個列表,在本身編寫插件就能夠有的放矢地參考源代碼了。
參考:官方給出的已有plugin的列表
Webpack-dev-server
Webpack提供一個小巧的基於express的開發服務器。支持自動刷新、模塊熱替換。還有代理。具體如何配置在這裏。
代理(proxy
)在開發是仍是頗有用的。你能夠將動態請求映射到後端的開發機,方便聯調。
總結
如今照着官方教程你已經能夠簡單地使用Webpack了。下一步要作的是
- 瞭解webpack.config.js中如何配置,有哪些要注意的(好比路徑)
- 實踐經常使用的Loader和Plugin
- 實踐webpack的衆多配置項
- 實踐使用webpack-dev-server進行開發
須要時能夠更進一步:
- 學習如何編寫Loader和Plugin
- 閱讀已有Loader和Plugin的源碼
- 在源碼中瞭解上面列出的stages的含義
更新:Webpack、Browserify和Gulp三者之間究竟是怎樣的關係?
下面是我在知乎的回答:
Task Runner
Gulp、Grunt和Make(常見於c/cpp)、Ant、Maven、Gradle(Java/Android)、Rake、Thor(Ruby)同樣,都是是Task Runner。用來將一些繁瑣的task自動化並處理任務的依賴關係。 其中有些是基於配置描述的,描述邏輯比較費勁,好比Ant基於xml。還有些就是代碼,比較靈活,我的偏好這種。好比Rake、Thor、Gulp、Gradle。對於Gradle來講也有些蛋疼。由於它自己是Groovy的DSL。若是要深刻使用,你還得學一下Groovy語言。其餘就好多了Rake、Thor就是寫Ruby;Gulp就是JavaScript。相對門檻低不少。
模塊化解決方案
Browserify It provides a way to bundle CommonJS modules together, adheres to the Unix philosophy(小工具協做), is in fact a good alternative to Webpack. Webpack takes a more monolithic(總體解決、大而全) approach than Browserify... is relies on configuration.
上面這些工具在功能上有交集:代碼的Minify、Concat;資源預處理等;
其實每一個工具的官網上都有對工具的設計思想、要解決的問題、與其餘工具的對比。本身摘抄下來,作個表格對比一下。高亮出每一個工具獨特的特性。這樣你就知道何時須要用哪一個工具了。 好比,你的工程模塊依賴很簡單,不須要把js或各類資源打包,只須要簡單的合併、壓縮,在頁面中引用就行了。那就不須要Browserify、Webpack。Gulp就夠用了。
反過來,若是你的工程龐大,頁面中使用了不少庫(SPA很容易出現這種狀況),那就能夠選擇某種模塊化方案。至因而用Browserify仍是Webpack就須要根據其餘因素來判斷了。好比團隊已經在使用了某種方案,你們都比較熟悉了。再好比,你喜歡Unix小工具協做的方式,那就Browserify。
充分了解各類工具、方案,選擇合適的和本身須要的。沒有絕對的好。優勢換了場景也會變成缺點。
UPDATE
下面是閒耘™用Makefile管理前端工程任務的例子: https://github.com/hotoo/pinyin/blob/master/Makefile
更多資料: