2018.3.1更:
有贊·微商城(base杭州)部門招前端啦,最近的前端hc有十多個,跪求大佬扔簡歷,我直接進行內推實時反饋進度,有興趣的郵件 lvdada#youzan.com,或直接微信勾搭我 wsldd225 瞭解跟多javascript
有贊開源組件庫·zanUIcss
動機
如今的網站都向webapp進化:html
結論是在瀏覽器端存在大量的代碼。前端
大段的代碼須要被組織,模塊系統提供了這個機會把代碼分割成模塊。java
有許多中引入依賴導出值的標準:node
<script>
標籤風格(沒有模塊系統)<script>
標籤風格若是沒有使用模塊系統你將會按以下方式處理模塊化的代碼jquery
<script src="module1.js"></script> <script src="module2.js"></script> <script src="libraryA.js"></script> <script src="module3.js"></script>
各個模塊把接口暴露給全局對象,好比window
對象。各個模塊之間能夠經過全局對象進行訪問互相依賴的接口。webpack
廣泛的問題:git
require
這種風格使用同步的require
方法來加載依賴和返回暴露的接口。一個模塊能夠經過給exports
對象添加屬性,或者設置module.exports
的值來描述暴露對象。es6
require("module"); require("../file.js"); exports.doStuff = function() {}; module.exports = someValue;
CommonJs規範也在服務端nodejs中使用。
利:
弊:
require
異步的模塊定義
其餘一些模塊系統(針對瀏覽器)對同步的require
有問題,引出了異步的版本(一種定義模塊暴露值的方式)
require(["module", "../file"], function(module, file) { /* ... */ }); define("mymodule", ["dep1", "dep2"], function(d1, d2) { return someExportedValue; });
利:
弊:
實踐:
ecmascript6添加了一些語言結構。
import "jquery"; export function doStuff() {} module "localModule" {}
利:
弊:
開發者能夠選擇本身的模塊風格,確保現存的代碼和包能工做。添加習慣的模塊風格是方便的。
模塊要在瀏覽器端運行,因此他們必需要從後端傳輸到瀏覽器端。
傳輸模塊會出現兩個極端:
這兩種使用方法都是瘋狂的,但並不理想:
一個模塊對應一個請求
一個請求包含全部模塊
一個更靈活的傳輸方式會更好,兩個極端方式的折中方案在大多數例子中會更好。
當編譯全部模塊時,把一系列模塊分割成許多更小的塊。
這就容許更多更小更快的請求,初始階段不須要的模塊能夠經過命令加載,這樣就能夠加速初始加載,當你實際須要代碼時也能加載相應的代碼塊。
「分割點」取決於開發者
爲何模塊系統只能幫助開發者解決javascript的模塊問題?還有許多其餘的資源須要處理:
編譯:
使用起來應該跟下列同樣方便:
require("./style.less"); require("./template.jade"); require("./image.png");
當編譯這些模塊時,靜態分析嘗試尋找模塊的全部依賴。
傳統上靜態分析尋找只能填寫字符串(不帶變量),可是require('./template/' + templateName + '.jade')
是很廣泛的結構。
許多第三方庫都有不一樣的書寫風格,一些很是詭異。
一個聰明強大的解析器容許幾乎全部現存的代碼運行,可是開發者寫了一些奇怪的代碼,這須要找到最合適的解決方法。
什麼是webpack
webpack是一個模塊打包工具
webpack把有依賴的模塊產出表明這些模塊的靜態資源。
現存的模塊打包工具不適用大型項目(大型的單頁應用)。開發新的模塊打包工具最使人激動的緣由就是代碼分割,而且靜態資源跟模塊化也是無縫對接的。
我嘗試過拓展示有的模塊打包工具,可是沒法實現全部目標。
webpack有兩種依賴類型:同步和異步。異步的依賴充當分割的點並造成新的塊。當塊的樹被優化,一個文件就發射每個塊。
webpack默認處理javascript,可是loaders用來把資源轉變爲javascript,這樣作每一個資源都能構成模塊。
強大的解析能力
webpack有一個很是強大的解析器用來處理幾乎全部第三方的類庫。它甚至容許在依賴引入時使用表達式require("./templates/" + name + ".jade")
。webpack能處理廣泛的模塊風格: CommonJs和AMD
webpack產出了豐富的插件系統。多數內部特徵都在插件系統基礎上開發,這容許你根據本身需求自定義webpack,同時也支持貢獻通用的開源插件。
使用loaders
loaders是應用在app中靜態資源的轉換程序。他們是一些能把資源文件中的源代碼做爲參數而且返回出新的源代碼的函數。
舉個例子:你可使用loaders告訴webpack去加載coffeescript或者jsx
loaders與模塊有想同的解析方式。一個loader模塊預計暴露出一個函數而且使用js在nodejs中編寫。一般狀況下你在npm中管理loaders,但你也能夠在app中管理loaders文件。
按照約定(並不強制)loaders一般命名爲xxx-loader
,xxx是一名字。好比json-loader
。
你能夠經過全名引用loaders(json-loader),或者經過縮略名(json)
loader的名字的約定和搜索優先順序由webpack配置api中的resolveLoader.moduleTemplates
定義。
loader名字的約定是方便的,尤爲是用require()
進行loader引用的時候。看下列使用方法。
npm install xxx-loader --save-dev
有許多中在app中使用loaders的方法:
注意: 儘量的避免使用require,若是你打算把scripts標籤放入環境無關的環境。對特定的loaders使用配置約定。
用require語句指定loaders是可行的,只須要使用!
把loaders跟資源分隔開,每一部分都相對於當前目錄進行解析。
require("./loader!./dir/file.txt"); // => uses the file "loader.js" in the current directory to transform // "file.txt" in the folder "dir". require("jade!./template.jade"); // => uses the "jade-loader" (that is installed from npm to "node_modules") // to transform the file "template.jade" // If configuration has some transforms bound to the file, they will still be applied. require("!style!css!less!bootstrap/less/bootstrap.less"); // => the file "bootstrap.less" in the folder "less" in the "bootstrap" // module (that is installed from github to "node_modules") is // transformed by the "less-loader". The result is transformed by the // "css-loader" and then by the "style-loader". // If configuration has some transforms bound to the file, they will not be applied.
你能夠把loaders經過配置與正則綁定:
{ module: { loaders: [ { test: /\.jade$/, loader: "jade" }, // => "jade" loader is used for ".jade" files { test: /\.css$/, loader: "style!css" }, // => "style" and "css" loader is used for ".css" files // Alternative syntax: { test: /\.css$/, loaders: ["style", "css"] }, ] } }
你能夠經過cli的拓展綁定loaders:
$ webpack --module-bind jade --module-bind 'css=style!css'
jade loader處理.jade文件,style cssloader處理.css文件
loaders穿衣經過查詢字符串傳遞查詢參數(跟web同樣)。查詢字符串使用?
添加到loader,相似url-loader?mimetype=image/png
注意:查詢字符串的形式取決於loader。查詢loader的文檔。大多數loaders接受常規形式的參數(?key=value&key2=value2
)或者json對象(?{"key":"value","key2":"value2"}
)
require
require("url-loader?mimetype=image/png!./file.png");
配置
{ test: /\.png$/, loader: "url-loader?mimetype=image/png" }
或者
{ test: /\.png$/, loader: "url-loader", query: { mimetype: "image/png" } }
cli
webpack --module-bind "png=url-loader?mimetype=image/png"
使用插件
使用插件添加與webpack打包有關的典型功能。舉個例子,BellOnBundlerErrorPlugin插件能在打包器工做的進程中輸出錯誤信息。
經過使用webpack的插件屬性。
// webpack should be in the node_modules directory, install if not. var webpack = require("webpack"); module.exports = { plugins: [ new webpack.ResolverPlugin([ new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin("bower.json", ["main"]) ], ["normal", "loader"]) ] };
非內置插件能夠經過npm下載或者其餘途徑。
npm install component-webpack-plugin
var ComponentPlugin = require("component-webpack-plugin"); module.exports = { plugins: [ new ComponentPlugin() ] }
當經過npm安裝第三方插件時咱們建議使用這個工具: https://www.npmjs.com/package...
這會檢查全部安裝在依賴中的第三方插件而後在須要的時候進行懶加載。
這個小教程帶你過一遍簡單的例子
你會學到:
你須要先安裝nodejs
npm install webpack -g
這使得webpack全局命令可用
從全新的目錄開始。
webpack會分析入口文件中的依賴文件。這些文件(也稱模塊)也會包含在
bundle.js
中。webpack會給每一個模塊一個獨立的id而後把全部憑id訪問的模塊存到bundle.js中。只有入口模塊在初始化時會被執行。一個小型的運行時會提供一個require函數,當須要時會執行依賴的資源。
咱們想要給應用添加css文件。
webpack默認只支持javascript,因此咱們須要css-loader來處理css文件。
執行npm install css-loader style-loader
下載安裝loaders(他們須要安裝在本地,不帶-g)這會建立一個loaders存放的node_modules
文件夾。
開始使用他們:
require("!style!css!./style.css"); document.write(require("./content.js"))
在引入模塊時添加loader的前綴,模塊會經歷一個loader管道,這些loaders將模塊內容以特定的方式進行改變。在全部改變完成後,最後的結果是一個javascript模塊。
咱們不想寫如此長的引用require("!style!css!./style.css")
咱們能夠給loaders綁定拓展因此咱們只用寫require("./style.css")
require("./style.css"); document.write(require("./content.js"))
跑命令
webpack ./entry.js bundle.js --module-bind 'css=style!css'
咱們想把配置信息移到一個配置文件中:
添加配置文件webpack.config.js:
module.exports = { entry: "./entry.js", output: { path: __dirname, filename: "bundle.js" }, module: { loaders: [ { test: /\.css$/, loader: "style!css" } ] } };
若是項目擴大了應用編譯會變慢,因此咱們想增長如下進度條,而且帶有顏色。。
webpack --progress --colors
咱們不想每次改動代碼都手動進行編譯。。
webpack --progress --colors --watch
webpack能緩存未變化的模塊,在每次編譯後都能產出文件。
當使用監控模式,webpack對全部文件在編譯過程都安裝了文件監聽器。若是任何改動被捕捉到了,webpack會再次編譯。當緩存開啓了,webpack會把每一個模塊都存在內存裏,若是沒有變更就會重複利用。
開發服務器是更棒的選擇。
npm install webpack-dev-server -g
webpack-dev-server --progress --colors
這綁定了一個小型express服務器在localhost:8080。做爲靜態資源和bundle的服務器。當從新編譯時瀏覽器會從新更新。打開 http://localhost:8080/webpack-dev-server/bundle 。
dev 服務器會使用webpack的監控模型,這能防止webpack釋放結果文件到硬盤上,安裝它保持結果文件都是從內存中讀取的。
/
commonjs
CommonJS團隊經過確保每一個模塊都在本身的命名空間中執行定義了一種解決javascript做用域問題的模塊形式。
commonjs經過強制模塊輸出正確的想要暴露在全局的變量,同時定義好正確工做所需的其餘模塊。
爲了實現這些commonjs
提供了兩個工具:
必須來個hello world
例子:
這有個不使用commonjs的例子:
在salute.js文件中定義
var MySalute = "Hello";
而後在第二個文件world.js中取值
console.log(MySalute) // hello
實際上,MySalute由於沒有定義world.js不會正常工做,咱們須要把每一個script定義成模塊。
// salute.js var MySalute = "Hello"; module.exports = MySalute;
// world.js var Result = MySalute + "world!"; module.exports = Result;
這裏咱們使用了特殊的對象module而後把變量引用賦值給了module.exports,因此commonjs模塊系統知道這事咱們想要暴露給世界的模塊內的對象。salute.js暴露了MySalute,world.js暴露了Result
咱們離成功就差了一步:依賴定義。咱們早就把每一個script定義成了獨立的模塊,可是world.js還須要知道誰定義了MySalute
// world.js var MySalute = require("./salute"); var Result = MySalute + "world!"; module.exports = Result;
AMD
AMD(異步模塊系統)爲了適應那些以爲commonjs模塊系統還沒在browser準備好的人產出的,由於他們以爲commonjs的本質是同步的。
AMD提出了現代js的標準,以致於模塊能異步的加載依賴,解決了同步加載的問題。
模塊有defined函數來定義
define
define函數用於使用AMD定義一個模塊。這只是一個帶有簽名的函數。
define(id?: String, dependencies?: String[], factory: Function|Object);
id
指定了模塊的名字,可選。
dependencies
這個參數定義了被定義的模塊所依賴的模塊。是一個包含了模塊標識符的數組,可選項。可是若是省略,默認設置成[「require」, 「exports」, 「module」].
factory
最後一個參數定義了模塊內容,能夠是個函數(立馬執行),或者對象。若是factory是個函數,返回值會變成模塊的導出值。
定義一個名爲myModule依賴jQuery的模塊
define('myModule', ['jquery'], function($) { // $ is the export of the jquery module. $('body').text('hello world'); }); // and use it require(['myModule'], function(myModule) {});
注意:在webpack中命名模塊只在本地使用,在require.js中命名模塊是全局的。
define(['jquery'], function($) { $('body').text('hello world'); });
define(['jquery', './math.js'], function($, math) { // $ and math are the exports of the jquery module. $('body').text('hello world'); });
define(['jquery'], function($) { var HelloWorldize = function(selector){ $(selector).text('hello world'); }; return HelloWorldize; });
code Splitting 代碼分割
對於大型apps而言將全部代碼都放在一個文件中是不高效的。尤爲是一些代碼塊只要某些狀況下才須要加載。webpack有一個特性能夠將代碼分割成可根據命令加載的塊。其餘一些打包器稱呼爲「layers」「rollups」「fragments」。這個特性叫作「code splitting」
這是一個可選擇的特性,你能夠在代碼中定義分割點。webpack處理依賴,導出文件以及運行時。
聲明一個常見的誤解:代碼分割不僅是提取通用代碼放入可分享的塊。更顯著的特性是能夠將代碼分割成按需加載的塊。這能夠控制初始下載更小的代碼而後當應用須要的時候下載其餘代碼。
AMD和commonjs定義了不一樣的方法按需加載代碼。他們都被支持而且充當分割的點。
require.ensure
require.ensure(dependencies, callback)
require.ensure
方法確保當回調調用的時候dependencies
裏的每一個依賴都會被同步的引入。require
做爲參數傳遞到回調函數中。
require.ensure(["module-a", "module-b"], function(require) { var a = require("module-a"); // ... });
注意:require.ensure
只加載模塊,並不執行。
require
AMD定義了一個異步的require
方法。
reuqire(dependices, callback)
當調用的時候,全部依賴都被加載,callback函數中能獲得依賴暴露出來的對象。
例子:
require(["module-a", "module-b"], function (a, b) {// ...})
note: AMDrequire
加載並執行模塊。在webpack中模塊由左向右執行
note: 省略回調函數是容許的。
webpack不支持es6模塊,根據你的編譯器建立的格式直接使用require.ensure
或者require
。
webpack1.x.x
(2.0.0支持)原生不支持es6模塊。可是你可使用編譯器獲得,好比:babel。把es6的import語法編譯成commonjs和amd模塊。這個方法是有效的可是也有一個重要的動態加載警告。
模塊額外的語法(import x from 'foo'
)是特地被設計爲可靜態分析的。這就意味着你不能動態的import。
// 這是非法的 ['lodash', 'backbone'].forEach(function (item) {import item})
幸運的是,有個js api 「loader」用來處理動態使用例子:System.load
(或者)System.import
。這個API跟require變量的做用同樣。然而多數編譯器不支持轉變System.load
調用require.ensure
。因此你想使用代碼分割你能夠直接使用require。
//static imports import _ from 'lodash' // dynamic imports require.ensure([], function(require) { let contacts = require('./contacts') })
全部在代碼分割點引用的依賴會進入新的塊中,其中的依賴也會遞歸增長。
若是傳入了一個函數表達式(或者綁定了一個函數表達式)做爲分割點的回調函數。webpack會將全部依賴都放到塊中。
若是兩個塊包含相同的模塊,他們會被合併成一個塊。
根據配置選項target
一個用於塊加載的運行時會被添加到打包文件bundle
中。舉個例子,將target設置成web
塊會經過jsonp被加載。一個塊只會被加載一次,而且平行的請求會被合併成一個。運行時會檢查已經加載的塊是否知足其餘塊。
入口塊
一個入口塊包含了運行時外加一系列的模塊。若是塊包含了模塊0,運行時會執行它。若是沒有運行時會等待包含模塊0的塊而後執行。
普通的塊
普通塊不包含運行時,這隻包含一系列的模塊。結構取決於塊加載的算法。舉個例子,jsonp中模塊會被包裝在jsonp的回調函數中。塊也會包含一系列知足的id。
初始塊(不是入口)
初始的塊是個普通的塊。惟一的區別在於優化機制認爲初始塊更重要由於初始塊計算初始的時間。這種塊的類型會出如今commonsChunkPlugin
插件的結合中。
分割app和vendor代碼
把你的app分割成兩個文件,叫作app.js和vendor.js。你能夠在vendor.js中依賴vendor類型的文件,而後傳遞這些名字到commonsChunkPlugin
中。
var webpack = require("webpack"); module.exports = { entry: { app: "./app.js", vendor: ["jquery", "underscore", ...], }, output: { filename: "bundle.js" }, plugins: [ new webpack.optimize.CommonsChunkPlugin(/* chunkName= */"vendor", /* filename= */"vendor.bundle.js") ] };
這會從app塊中移除全部vendor塊。bundle.js將會保留app的代碼,沒有任何依賴。這些移除的代碼將會留在vendor.bundle.js中。在你的html中加載
<script src="vendor.bundle.js"></script> <script src="bundle.js"></script>
設置多入口的點產出多入口的塊是可行的。入口塊包含一個運行時且當前頁面只有一個運行時。(固然也有例外)
使用commonChunkPlugin
插件運行時會被移到通用的塊中。入口點如今在初始的塊中。然而只有一個初始塊會被加載,多個入口塊會被加載。這就顯示了在單頁面執行多個入口點的可能性。
var webpack = require("webpack"); module.exports = { entry: { a: "./a", b: "./b" }, output: { filename: "[name].js" }, plugins: [ new webpack.optimize.CommonsChunkPlugin("init.js") ] }
<script src="init.js"></script> <script src="a.js"></script> <script src="b.js"></script>
CommonsChunkPlugin
會把多入口的塊移到一個新的入口塊(通用塊),運行時也會被移到通用的塊。這意味着老的入口塊如今變成了初始塊。
如下優化插件能夠根據特定條件合併塊。
LimitChunkCountPlugin MinChunkSizePlugin AggressiveMergingPlugin
require.ensure函數接受額外的第三個參數。這個參數必定是一個字符串。若是兩個分割點傳遞了相同的字符串他們會使用相同的塊。
require.include
require.include是一個webpack特定的函數用來給當前塊添加一個模塊,可是不執行。(表達式會從bundle中移除。)
例子:
require.ensure(["./file"], function(require) { require("./file2"); }); // is equal to require.ensure([], function(require) { require.include("./file"); require("./file2"); });
若是在多子塊的狀況下require.include是好用的,require.include在父塊中會引入模塊,在子塊中該模塊的實例會消失。
例子:
可執行的demo能夠在devTools中查看網絡
stylesheets
經過使用style-loader
和css-loader
將樣式文件內嵌到webpack js打包文件中。經過這種方式你能夠將你的樣式文件和其餘模塊同樣處理。引入樣式以下方式require("./stylesheet.css")
從npm中安裝loaders
npm install style-loader css-loader --save-dev
下面介紹一個使require()
正常工做的配置例子
{ module: { loaders: [ {test: /\.css$/, loader: "style-loader!css-loader"} ] } }
杜宇預編譯的css語言能夠查詢對應的loader的配置例子,你能夠在module中加入
請牢記管理modules的執行順序是困難的,因此請自行設計好樣式文件(你也能夠依賴同一份css文件中的順序)
// 在模塊中直接引用樣式文件 // 但這有一個反作用會在dom中添加額外的`style`標籤 require("./stylesheet.css")
結合extract-text-webpack-plugin就能夠產出獨立的css文件。
結合代碼分割技術咱們可使用兩種不一樣的方式:
推薦第一種方法主要是由於對於初始加載時間是最優的。在多入口文件的小型app中推薦第二種方法是由於考慮HTTP請求併發上限以及緩存。
從npm中安裝插件
npm install extract-text-webpack-plugin --save
爲了使用插件你須要改變配置,使用特殊的loader將樣式輸出到css文件。在代碼編寫後webpack優化階段插件會檢查哪一個相關的模塊須要被抽離(在第一種方法中只有初始塊)。這些模塊經過nodejs執行獲得內容,另外模塊被會從新編譯到原先的包中代替空的模塊。
爲被抽離的模塊建立了新的內容。
這個例子展現了多入口,可是也一樣適合但入口。
// webpack.config.js var ExtractTextPlugin = require("extract-text-webpack-plugin"); module.exports = { // The standard entry point and output config entry: { posts: "./posts", post: "./post", about: "./about" }, output: { filename: "[name].js", chunkFilename: "[id].js" }, module: { loaders: [ // Extract css files { test: /\.css$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader") }, // Optionally extract less files // or any other compile-to-css language { test: /\.less$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader!less-loader") } // You could also use other loaders the same way. I. e. the autoprefixer-loader ] }, // Use the plugin to specify the resulting filename (and add needed behavior to the compiler) plugins: [ new ExtractTextPlugin("[name].css") ] }
使用第二種方法你只用設置allChunks
成true
。
// ... module.exports = { // ... plugins: [ new ExtractTextPlugin("style.css", { allChunks: true }) ] }
你能夠結合CommonChunkPlugin使用獨立的css文件。
// ... module.exports = { // ... plugins: [ new webpack.optimize.CommonsChunkPlugin("commons", "commons.js"), new ExtractTextPlugin("[name].css") ] }
優化
去最小化你的腳本(若是你使用css-loader還有css),webpack支持一個簡單的選項:
--optimize-minize
或者 new webpack.optimize.UglifyJsPlugin()
這是個最簡單可是最有效的優化你的webapp的方法。
正如你所知道的(若是你有持續閱讀文檔)webpack會給模塊和塊id
去標識他們。webpack能夠改變ids的分配去獲得最短的id長度做用於經常使用的ids:
--optimize-occurence-order
或者new webpack.optimize.OccurenceOrderPlugin()
入口塊對於文件大小有更高的優先級。
若是你使用一些有不少依賴的第三方庫,這可能會發生一些文件會相同。webpack會發現這些文件並刪除他們。這會防止你的包裏包含相同的代碼,相反的會在運行時應用一個函數的引用。這不影響語義,你能夠這樣開啓:
--optimize-dedupe
或者new webpack.optimize.DedupePlugin()
這個特性在入口文件中添加了一些開銷。
在書寫代碼的時候,你可能早已經添加了許多代碼分割點來按需加載代碼。當編譯後你可能會注意到這麼多的塊對於http的開銷來講是仍是過小體量的。幸運的是,webpack能夠經過後處理的方式去合併他們。你能夠提供兩種選項:
--optimize-max-chunks 15 new webpack.optimize.LimitChunkCountPlugin({maxChunks: 15})
--optimize-min-chunk-size 10000 new webpack.optimize.MinChunkSizePlugin({minChunkSize: 10000})
webpack經過合併chunk來解決這個優化問題(webpack更傾向於合併有相同模塊的chunk)。沒有東西會被合併到入口chunk,因此不會影響頁面加載時間。
webpack就是被設計優化像單頁應用這種web應用的。
你能夠將app中的代碼分割成多個chunk,由路由判斷來加載。入口chunk只包含路由和一些第三方資源,沒有內容。當你的用戶在app中操做時這會工做順利,可是對於不一樣路由的初始加載你可能須要一個來回:第一步獲取路由第二步獲取當前頁內容。
若是你使用HTML5 history的API跳轉當前頁面,經過客戶端代碼服務器能知道哪一個頁面被請求。爲了節省桐鄉服務器的來回路程你能夠包含內容塊到返回中。直接添加script標籤是可行的。瀏覽器會加載平行的chunks。
<script src="entry-chunk.js" type="text/javascript" charset="utf-8"></script> <script src="3.chunk.js" type="text/javascript" charset="utf-8"></script>
你能夠從stats中提取文件名(stats-webpack-plugin能用來導出構建後的stats)
當你編譯多頁面的app,你想在多頁面之間共享相同的代碼。事實上結合webpack這很是容易:只須要結合多個入口點進行編譯:
module.exports = { entry: { p1: "./page1", p2: "./page2", p3: "./page3" }, output: { filename: "[name].entry.chunk.js" } }
這會生成多個入口chunk:p1.entry.chunk.js
,p2.entry.chunk.js
和p3.entry.chunk.js
可是其餘的chunk能經過他們分享。
若是你的入口chunks有一些相同的模塊,這有個很好用的插件。CommonsChunkPlugin
識別相同的模塊而後將他們放入一個通用chunk。你須要在頁面中加入兩個script標籤。一個是通用的chunk,一個是入口chunk。
var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin"); module.exports = { entry: { p1: "./page1", p2: "./page2", p3: "./page3" }, output: { filename: "[name].entry.chunk.js" }, plugins: [ new CommonsChunkPlugin("commons.chunk.js") ] }
這會造成多個入口chunkp1.entry.chunk.js
,p2.entry.chunk.js
和p3.entry.chunk.js
。加上一個common.chunk.js
,首先加載common.chunk.js
而後加載xx.entry.chunk.js
你也能夠經過選擇入口chunks造成多個通用chunks,你也能夠嵌套通用chunks
var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin"); module.exports = { entry: { p1: "./page1", p2: "./page2", p3: "./page3", ap1: "./admin/page1", ap2: "./admin/page2" }, output: { filename: "[name].js" }, plugins: [ new CommonsChunkPlugin("admin-commons.js", ["ap1", "ap2"]), new CommonsChunkPlugin("commons.js", ["p1", "p2", "admin-commons.js"]) ] }; // <script>s required: // page1.html: commons.js, p1.js // page2.html: commons.js, p2.js // page3.html: p3.js // admin-page1.html: commons.js, admin-commons.js, ap1.js // admin-page2.html: commons.js, admin-commons.js, ap2.js
高級用法: 你能夠在通用chunk中運行代碼
var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin"); module.exports = { entry: { p1: "./page1", p2: "./page2", commons: "./entry-for-the-commons-chunk" }, plugins: [ new CommonsChunkPlugin("commons", "commons.js") ] };
看multiple-entry-points example和advanced multiple-commons-chunks example
長期緩存
爲了有效的緩存文件,文件須要帶有hash或者版本號的URL。你能夠人爲的修改產出的文件的版本號v.1.3
可是這樣不是很方便。額外的人工才操做以及沒有改變的文件不從緩存中加載。
webpack能夠根據文件名給文件添加hash值。處理文件的loaders(worker-loader,file-loader)早已經實現,對於chunks你須要開啓它,有兩種等級:
選擇1經過爲文件名配置hash選項來開啓
webpack ./entry output/[hash].bundle.js
{ output: { path: path.join(__dirname, "assets", "[hash]"), publicPath: "assets/[hash]/", filename: "output.[hash].bundle.js", chunkFilename: "[id].[hash].bundle.js" } }
選項2經過添加[chunkhash]到chunkFilename配置選項來開啓。
--output-chunk-file [chunkhash].js
output: { chunkFilename: "[chunkhash].bundle.js" }
記得你須要在html中引用帶有hash的入口chunk。你可能想從stats提取hash或者文件名。結合熱替換你必須使用選項1,但不是在publicPath
配置項中。
你可能想獲得最後的文件名字嵌入你的html中。這個信息在webpack的stats中是能夠獲取的。若是你是使用CLI你能夠運行--json
去獲得以json形式輸出的stats。
你能夠在配置文件中加入assets-webpack-plugin插件來容許你獲得stats對象。接下來是一個將此寫入文件的例子。
plugins: [ function() { this.plugin("done", function(stats) { require("fs").writeFileSync( path.join(__dirname, "..", "stats.json"), JSON.stringify(stats.toJson())); }); } ]
stats json包含了好用的對象--assetsByChunkName
,這個對象包含了以chunk爲鍵名文件名爲鍵值的對象。
多入口的點
要求:代碼分割
若是你須要多個打包文件給多個html頁面使用,你可使用「多入口點」特性。這會同時生成多個打包文件。額外的chunks能夠被這些入口chunks共享,模塊只會被構建一次。
提示: 若是你想從模塊中開始一個入口chunk,這是個錯誤的想法。使用代碼分割!
每個入口chunk都包含了webpack運行時,因此你只用在每一個頁面加載一個入口chunk(提示:可使用commonsChunkPlugin插件去繞過這個限制將運行時放入單個chunk中。)
爲了使用多入口點你能夠往entry選項中傳入一個對象。鍵名錶明瞭入口點的名字,鍵值表明了入口點。
當應用多入口點時必須改寫默認的output.filename
選項。否則每一個入口點都會寫入相同的文件。使用[name]
獲得入口點的名字。
{ entry: { a: "./a", b: "./b", c: ["./c", "./d"] }, output: { path: path.join(__dirname, "dist"), filename: "[name].entry.js" } }
第三方庫和拓展
你開發了一個第三方庫而後要分到編譯/打包後的版本(除了模塊化的版本)。你想要容許用戶在script標籤或者amd加載器中使用。或者你想取決於不一樣的預編譯器而不限制用戶,把這個模塊做爲普通的commonjs模塊。
webpack有三個跟這個狀況相關的配置選項:output.library
,output.libraryTarget
,externals
output.libraryTarget
容許你控制輸出的類型,舉例:commonjs,amd,script中使用。
output.library
容許你指定第三方庫的名字。
externals
容許你指定第三方庫的不須要通過webpack處理的依賴。可是是輸出文件的依賴。這也代表了他們是在運行時環境中輸入的。
編譯在script中使用的第三方庫。
var jQuery = require("jquery"); var math = require("math-library"); function Foo() {} // ... module.exports = Foo;
推薦的配置(與之相關)
{ output: { // export itself to a global var libraryTarget: "var", // name of the global var: "Foo" library: "Foo" }, externals: { // require("jquery") is external and available // on the global var jQuery "jquery": "jQuery" } }
打包結果
var Foo = (/* ... webpack bootstrap ... */ { 0: function(...) { var jQuery = require(1); /* ... */ }, 1: function(...) { module.exports = jQuery; }, /* ... */ });
你也可使用externals
選項嚮應用導出一個存在的接口。舉個例子,你想使用cdn資源script引用的jquery,但又明確聲明經過require('jquery')
做爲依賴,你能夠像這樣把他指定爲外部資源:{externals: {jquery: "jQuery"}}
外部資源在分解請求以前執行,這意味着你須要指定沒有分解的請求。externals中不能應用loaders,因此你須要用loader具體化一個請求。require("bundle!jquery") { externals: {"bundle!jquery": "bundledJQuery"}}
墊板模塊(shim)
不是全部js文件均可以直接使用webpack。此文件多是webpack不支持的文件,甚至沒有任何模塊形式。
webpack提供一些loaders使這些文件能夠跟webpack一塊兒工做。
下面的例子使用require保持簡潔。你一般都會在webpack中配置他們。
若是一個文件的依賴不是經過require()引入的,你可使用如下loader中的一種。
imports-loader
import loader容許你根據不一樣的全局變量去使用模塊。
對於依賴像$或者this的第三方模塊這是很方便的。imports loader會添加必要的require('whatever')
調用。因此這些模塊能夠跟webpack工做。
例子:
file.js 須要一個全局的$變量,你也有一個應該被使用的jquery模塊。
require("imports?$=jquery!./file.js")
file.js須要一個全局的配置變量xConfig
,你但願是{value: 123}
require("imports?xConfig=>{value:123}!./file.js")
file.js須要一個全局的this對象。
require("imports?this=>window!./file.js") or require("imports?this=>global!./file.js")
這個插件使得一個模塊在任何模塊中能稱爲一個變量。只有當你使用這個變量的時候纔會依賴這個模塊。
例子: 不須要寫require("jquery")
就能在任何模塊中使用$和jquery變量。
new webpack.ProvidePlugin({ $: "jquery", jQuery: "jquery", "window.jQuery": "jquery" })
不暴露值的文件。
exports-loader
這個loader暴露文件的內部變量。
例子:
這個文件在全局上下文定義了一個變量var XModule = ...
var XModule = require("exports?XModule!./file.js")
這個文件在全局上下文定義了多個變量 var XParser, Minimizer
var XModule = require("exports?Parser=XParser&Minimizer!./file.js"); XModule.Parser; XModule.Minimizer
這個文件設置了一個全局變量 XModule = ....
require("imports?XModule=>undefined!exports?XModule!./file.js") (import to not leak to the global context)
這個文件在window對象下設置了屬性 window.XModule = ...
require("imports?window=>{}!exports?window.XModule!./file.js")
有些模塊使用了錯誤的模塊風格。你想去修復而且告訴webpack不要使用這種風格。
例子:
require("imports?define=>false!./file.js")
require("imports?require=>false!./file.js")
module.noParse
webpack會使解析失效,所以你不能使用依賴,這對已經打包好的第三方庫比較實用。
例子:
{ module: { noParse: [ /XModule[\\\/]file\.js$/, path.join(__dirname, "web_modules", "XModule2") ] } }
exports 和 module任然是可用的,你可使用imports-loader使他們失效。
script-loader
這個loader在全局上下文中評估代碼,就跟你在script中添加代碼同樣。這種方式每個第三方庫都能正常工做,require、module等都會失效。
注意:此文件會被當作字符串加入到bundle中,不會被webpack壓縮,因此咱們須要使用壓縮版本。也沒有做用於這種loader加載的第三方庫的開發者工具。
有些狀況你想一個模塊暴露出本身。
除非必須否則少用(providePlugin更好)
expose-loader
這個loader將模塊暴露到全局上下文中。
例子:
將file.js暴露到全局上下文中的XModule變量。
require("expose?XModule!./file.js")
另外一個例子:
require('expose?$!expose?jQuery!jquery'); ... $(document).ready(function() { console.log("hey"); })
經過jquery文件暴露到全局上下文,你能夠在項目中的任何地方使用jquery。同理你想使用bootstrap也能夠經過這種方法。
注意: 使用太多全局變量會使你的app缺乏效率,若是你想使用大量命名空間,考慮在項目中加入Babel runtime
在很是小的應用場景下你須要應用不僅一個配置,你須要使用正確的loader順序。內嵌:expose!imports!exports
,配置項:expose before imports before exports.。
測試
有兩種方式能夠測試web應用:
mocha-loader使用mocha框架執行你的代碼。若是執行代碼你會在瀏覽器看到測試結果。
提示:當在bash命令行使用!
時,你須要使用\
轉義。
webpack 'mocha!./test.js' testBundle.js <!--index.html is a HTML page which loads testBundle.js--> open index.html
webpack-dev-server會自動的建立加載腳本的HTML頁面。當文件改變時會從新執行。
webpack-dev-server 'mocha!./test.js' --hot --inline --output-filename test.js open http://localhost:8080/test
提示:使用--hot
服務器只會在該文件或該文件的依賴有變化時從新執行測試。
你能夠將webpack與karma一塊兒使用,將webpack做爲預處理器加進karma的配置中
打包性能
若是你在尋找加速webpack打包的方法,你可能要經過如下幾種方法去更加深刻的提升你配置的webpack的打包性能。
確保每次打包的時候不會所有從新打包。webpack有一個強大的緩存層容許你使用內存中早已編譯好的模塊,如下幾種方法幫助使用:
watch: true
有緩存,可是緩存到硬盤,性能通常。使用noParse
能夠在解析時排除大的第三方庫,可是會中斷。
有個分析工具能夠提供詳細的分析和一些能夠幫助你優化打包文件大小和性能的有用信息。
從內部表現生成源文件代價是高的。只有當這個chunk內部沒有任何改變時,chunk都由本身緩存。大多數chunk取決於自身包含的模塊,可是入口chunk不一樣,若是額外的chunk名字改變了,入口塊一樣會被認爲是髒的,即被修改過的。因此使用在文件名中使用[hash]或者[chunkhash]
時,入口chunk幾乎會在每次修改中都從新構建。
使用HMR入口chunk會嵌入編譯的hash因此每次改變也會被認爲是髒的。
優秀的sourceMaps會減慢build
devtool: "source-map"
不會緩存模塊的sourcemaps並且會從新生成chunk的sourcemaps。這是給生產環境用的。
devtool: "eval-source-map"
跟上一種異樣好,可是會緩存模塊的sourcemaps,對於重複構建速度會快。
devtool: "eval-cheap-module-source-map"
只提供行的sourcemaps,速度更快
devtool: "eval-cheap-source-map"
不會生成模塊的sourcemaps,例如jsx向js的mapping
devtool: "eval"
性能最好,但只生產模塊的sourcemaps,在一些狀況下這是足夠的。使用output.pathinfo: true
編譯
UglifyJsPlugin插件使用sourcemaps生成對應源代碼的錯誤信息,可是sourcemaps是慢的,在生產環境使用是ok的,若是構建時速度太慢(甚至不能完成),你須要關閉這個功能。new UglifyJsPlugin({ sourceMap: false })
只在嵌套路徑下使用resolve.modulesDirectories,大多數路徑使用resolve.root,這能夠給出性能展現 看討論
只在生產環境使用優化用的插件
若是你有大量不多改變的模塊(好比說vendor),chunking不會帶來多大的性能提高(commonChunkPlugin),這有兩個插件能夠在分隔的大包進程中建立一個打包文件,可是也會在appbundle中引用。
提早建立DLL包你須要使用Dllplugin,這是例子。這會觸發公共的打包文件和私有的清單文件。
從APP打包文件中引用DLL打包文件,你須要使用DllRefencePlugin
這是例子,在找到Dll打包文件以前會阻止依賴app的文件。
熱加載
注意模塊熱替換機制目前還處於試驗階段。
模塊熱替換(HMR)會在應用運行時替換、增長、移除模塊而不用從新加載頁面。
webpack在構建打包的過程當中增長一個小型的HMR運行時到打包文件中。當構建完成時,webpack不會退出並保持活躍。觀察源文件的變化。若是webpack檢測到文件的變化,他會從新構建變化的模塊。取決於webpack的設置,webpack會給HMR運行時發送一個信號,或者HMR運行時會查詢webpack的變化。無論哪一種形式,改變的模塊會被髮送到HMR運行時,而後應用熱更新。首先HMR運行時會檢查更新的模塊能不能自我accept,若是不會那麼會檢查依賴這些變化模塊的模塊。若是這些模塊也不會accept,則會冒泡到下一個層級,知道能執行accept方法,或者到達app的入口文件,這也意味着熱更新是失敗的。
app代碼請求HMR檢查更新,HMR運行時異步的下載更新而後告知app代碼更新是可用的。而後app代碼告知HMR運行時應用這些更新,而後HMR同步的應用更新。app代碼要不要依賴用戶的輸入取決於開發者本身。
除了普通的資源編譯器還須要觸發「update」以容許從以前的版本更新到當前版本,「update」包含兩部分:
清單包含新的hash值和更新的chunk。
更新的chunks包含了全部模塊。
編譯器額外確保模塊和chunk的id在build之間是不變的。他使用「record」json文件去存儲或者在內存中存儲。
HMR是一個可選的特性,因此這隻會影響包含HMR代碼的模塊。文檔描述的API在模塊中都是可用的。一般狀況下模塊開發者須要寫一個handles,這個handles在這個模塊的依賴更新時會被觸發。他也能寫一個當此模塊更新時就會觸發的handle。在大多數狀況下在每一個模塊中寫HMR的代碼不是強制的。若是一個模塊沒有HMRhandles那麼更新會傳遞到下一個層級。這就意味了一個handle能沿着模塊樹處理一個更新。若是一個模塊更新了,整個模塊樹都會重載(只重載不轉移)
對於模塊系統來講運行時是額外的代碼用來觸發追蹤父模塊和子模塊。
在管理方面運行時支持兩個方法: check
和apply
check
從更新清單發起http請求,若是請求失敗則沒有更新的內容。否則請求來的chunks列表會跟當前已經加載的chunks進行對比。每個已加載的chunk與之對應的更新chunk會被下載。全部的模塊更新在更新的同時都會存到運行時中。此時runtime進入「ready」狀態,意味着更新已經下載等待被應用。
在ready狀態下每一個新的chunk請求也會被下載。
apply
方法標記全部更新的模塊爲無效。對於每個無效的模塊須要一個updatehandle或者在其父模塊上須要update handle。否則無效打包文件會將全部父級都標記爲無效。這個過程一直持續到沒有冒泡發生。若是冒泡到入口chunk則熱替換失敗。
如今全部無效模塊都被處理了可是沒有加載。而後當前的hash更新全部的accept被調用。runtime從新回到「idle」狀態。
你能夠在開發環境當成livereload去使用它。事實上webpack-dev-server支持熱替換模式,嘗試在從新加載整個頁面以前用HMR替換。你只須要添加webpack/hot/dev-server
入口點而後用--hot
開啓開發服務器。
webpack/hot/dev-server
當HMR更細失敗後會從新加載整個頁面。若是你想用你本身的方式重載頁面,你能夠添加在入口點webpack/hot/only-dev-server
。
你也當作更新機制在生產環境使用。你須要寫相關的代碼與HMR交互。
一些loaders生產的模塊已是能夠熱更新的。style-loader
能改變樣式表。你不須要作其餘特殊的東西。
一個模塊只有你accept纔會更新。因此你須要寫module.hot.accept
在模塊的父級及以上。舉例:router是個好地方。
若是你只是想與webpack-dev-server一塊兒使用,只用加webpack/hot/dev-server
當作入口點。否則你須要使用能調用check
和apply
HMR代碼。
你須要開啓編譯器的record去跟蹤編譯過程當中的id(watch方式和webpack-dev-server會將records保存到內存,因此在開發過程當中不須要)
你須要在編譯器開啓HMR而且添加HMR運行時。
if(module.not)
包裹下)配合webpack使用熱替換你須要4件事:
module.hot.accept
module.hot.check, module.hot.apply
小栗子:
/* style.css */ body { background: red; }
/* entry.js */ require("./style.css"); document.write("<input type='text' />");
而後就可使用dev-server使用熱替換
npm install webpack webpack-dev-server -g npm install webpack css-loader style-loader webpack-dev-server ./entry --hot --inline --module-bind "css=style\!css"
dev-server提供內存records,這對於開發是很好地。
--hot
開關開啓代碼熱替換。
這會添加HotModuleReplacementPlugin,確保不麼添加
--hot
,不麼就在webpack.config.js裏添加HotModuleReplacementPlugin,可是永遠不要同一時間兩個都加。HMR插件會添加兩次。
有個特殊的控制代碼webpack/hot/dev-server
,會被--inline
自動添加。(你不用手動添加在webpack.config.js)
style-loader
已經包含熱替換代碼。
閱讀更多關於如何寫熱替換代碼hot module replacement
聯繫做者微博