原文發佈與抹橋的博客-【翻譯向】webpack2 指南(下)javascript
爲了可以使 webpack 處理後的靜態資源可以長期緩存下來,須要:css
使用 [chunkhash]
給每個文件建立基於內容變化的緩存標識html
在 HTML 文件中引入文件時使用編譯狀態來拿到文件名稱java
在載入資源以前生成 chunk-manifest JSON 文件並寫入到 HTML 頁面中jquery
確保包含啓動代碼的入口代碼塊的 hash 值不會被修改,當它的依賴沒有變化的時候webpack
每當咱們代碼中有一些東西須要被更新的時候,它須要在服務上部署而後由客戶端從新下載這些文件。當網絡狀情況不太好的時候,這是意見很是低效的事情。這也是爲何瀏覽器要緩存靜態資源的緣由。git
這會致使一個陷阱:當咱們發佈一個新的版本的時候不去更新的文件名,這會讓瀏覽器認爲文件沒有變化,致使客戶端拿不到最新的資源。github
一個簡單解決問題的方式就是告訴瀏覽器一個新的文件名。在沒有 webpack 的時候咱們會使用構建版原本標識這次的更新:web
application.js?build=1 application.css?build=1
在 webpack 中這樣作也很簡單:每一次 webpack 的構建都會生成一個能夠用來構成文件名的獨一無二的 hash 值。下面這個配置文件會生成兩個帶有 hash 值的文件名:npm
// webpack.config.js const path = require('path'); module.exports = { entry: { vendor: './src/vendor.js', main: './src/index.js' }, output: { path: path.join(__dirname, 'build'), filename: '[name].[hash].js' } };
執行 webpack 命令會獲得如下輸出:
Hash: 55e783391098c2496a8f Version: webpack 1.10.1 Time: 58ms Asset Size Chunks Chunk Names main.55e783391098c2496a8f.js 1.43 kB 0 [emitted] main vendor.55e783391098c2496a8f.js 1.43 kB 1 [emitted] vendor [0] ./src/index.js 46 bytes {0} [built] [0] ./src/vendor.js 40 bytes {1} [built]
但存在的問題是,每次咱們從新編譯,全部的文件名都會變化,這會致使客戶端每次都從新把整個應用的代碼從新下載一遍。那麼咱們如何才能時客戶端每次只下載有變更的文件。
當一個文件的內容沒有變化的時候,如何保證它的文件名不會在每次編譯中變化。好比,一個用來放全部的咱們的全部依賴庫文件的代碼包。
Webpack 容許根據文件的內容生成 hash 值。這是更新後的配置:
// webpack.config.js module.exports = { /*...*/ output: { /*...*/ filename: '[name].[chunkhash].js' } };
這個配置一樣會生成兩個文件,可是每一個文件都擁有本身的 hash 值:
main.155567618f4367cd1cb8.js 1.43 kB 0 [emitted] main vendor.c2330c22cd2decb5da5a.js 1.43 kB 1 [emitted] vendor
不要在開發環境中使用 [chunkhash],這會致使每次的編譯時間邊長。把開發環境和生成環境的配置文件分開,在開發環境使用 [name].js ,在生產環境中使用 [name].[chunkhash].js
從 webpack 編譯狀態(compilation stats)中獲取文件名
在開發環境中,你只須要在 HTML 中引入你的文件就能夠了。
<script src="main.js"></srcipt>
然而,在生產環境中,咱們每次都會獲得一個不一樣的文件名:
<script src="main.12312123t234.js"></srcipt>
爲了可以在 HTML 中引入正確的文件,咱們須要瞭解一些構建的信息。這能夠經過下面這個插件來從 webpack 編譯狀態(compliation stats) 中提取出來。
// webpack.config.js const path = require('path'); module.exports = { /*...*/ plugins: [ function() { this.plugin("done", function(stats) { require("fs").writeFileSync( path.join(__dirname, "…", "stats.json"), JSON.stringify(stats.toJson())); }); } ] };
或者,經過下面這些插件來暴露 JSON 文件:
https://www.npmjs.com/package/assets-webpack-plugin
一個簡單的經過 webpack-manifest-plugin 輸出的文件以下:
{ "main.js": "main.155567618f4367cd1cb8.js", "vendor.js": "vendor.c2330c22cd2decb5da5a.js" }
接下來的事情就由你的服務來決定了。這有一個很不錯的例子 [walk through for Rails-based projects](
walk through for Rails-based projects). 使用 Node.js 的服務端渲染的話可使用 webpack-isomorphic-tools. 若是你的應用不須要依賴服務端渲染的話,那徹底能夠直接生成一個 index.html
. 可使用下面這兩個插件來完成:
爲了壓縮生成文件的大小,webpack 使用 id 代替名字來識別模塊。再編譯過程當中,id 已經被生成,映射到代碼塊的名字而且放到一個 JavaScript 對象中,被叫作代碼塊清單( chunk manifest)。它會被放到入口文件中,確保打包後的文件可以正常工做。
這會有和以前同樣的問題:不管修改任何地方的文件,即便其它地方都沒有修改,更新後的入口須要包含清單文件。這會生成一個新的 hash 值,致使問們的入口文件名修改,沒法享受長期緩存帶來的好處。
爲了解決這個問題,咱們應該使用 https://github.com/diurnalist/chunk-manifest-webpack-plugin ,一個能夠把清單文件提取出來單獨放到一個 JSON 文件中。這是更新後的配置文件,會生成 chunk-manifest.json 並放到咱們打包後的文件夾下面:
// webpack.config.js var ChunkManifestPlugin = require('chunk-manifest-webpack-plugin'); module.exports = { // your config values here plugins: [ new ChunkManifestPlugin({ filename: "chunk-manifest.json", manifestVariable: "webpackManifest" }) ] };
當咱們從入口文件中移除掉清單文件後,那麼咱們就須要手動把這個文件提供給 webapck 使用。在上面的例子中你可能注意到 manifestVariable
這個選項。這是一個全局變量,一個供 webpack 來查找清單 JSON 文件,這也是爲何咱們須要在代碼包前面引入到 HTML 中。在 HTML 中寫入 JSON 文件的內容是很容易,那麼咱們的 HTML 文件的 head 部分就會向下面這樣:
<html> <head> <script> //<![CDATA[ window.webpackManifest = {"0":"main.3d038f325b02fdee5724.js","1":"1.c4116058de00860e5aa8.js"} //]]> </script> </head> <body> </body> </html>
最終的 webpack.config.js 文件以下:
var path = require('path'); var webpack = require('webpack'); var ManifestPlugin = require('webpack-manifest-plugin'); var ChunkManifestPlugin = require('chunk-manifest-webpack-plugin'); var WebpackMd5Hash = require('webpack-md5-hash'); module.exports = { entry: { vendor: './src/vendor.js', main: './src/index.js' }, output: { path: path.join(__dirname, 'build'), filename: '[name].[chunkhash].js', chunkFilename: '[name].[chunkhash].js' }, plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: "vendor", minChunks: Infinity, }), new WebpackMd5Hash(), new ManifestPlugin(), new ChunkManifestPlugin({ filename: "chunk-manifest.json", manifestVariable: "webpackManifest" }) ] };
若是使用 webpack-html-plugin ,那麼你可使用 inline-manifest-webpack-plugin 來作這件事。
使用這個配置,那麼第三方代碼塊(vendors chunkd)將不會變化,除非你修改它的代碼或者依賴。
webpack 做爲了個模塊打包工具,能夠支持的模塊系統包括 ES2015 modules, CommonJs 和 AMD. 可是不少狀況下,當咱們使用第三方庫的時候,咱們看到他們會依賴一個全局變量好比 $
或者說 jquery
. 它們也可能建立一些須要暴露出來的新的全局變量。咱們來看幾種不一樣的方式來使 webpack 可以理解這些非模塊(broken modules)的文件。
最好使用在 dist
文件下沒有打包壓縮過的 CommonJs/AMD 文件(Prefer unminified CommonJS/AMD files over bundled dist versions.)
大多數模塊會在 package.json
的 main
字段中指定它們的 dist
版本。這對大多數開發者來講都是很是有用的,對 webpack 來講最好設置一個別名到它們的 src
目錄下面,這樣可以使 webpack 更好的優化依賴。可是,在不少狀況下使用 dist
版本也不會有什麼大的問題。
// webpack.config.js module.exports = { ... resolve: { alias: { jquery: "jquery/src/jquery" } } };
provide-plugin
經過使用 [provide-plugin](https://webpack.js.org/plugins/provide-plugin)
使這個模塊在全部經過 webpack
引用的模塊中做爲一個可用的變量。只有當你使用了這個變量後,對應的模塊纔會被引用進來。不少古老的模塊經過使用特定的全局變量,好比 jQuery 的 $
和或者 jQuery
. 在這個場景下,你能夠提早在 webpack 中配置爲 var $=requrei('jquery')
,在每一次遇到全局 $
標識符。
module.exports = { plugins: [ new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery' }) ] };
imports-loader
[imports-loader](https://webpack.js.org/loaders/imports-loader/)
將必需要的全局變量插入到傳統模塊中。好比,一些傳統模塊依賴 this
指向 window
對象。這會致使一個問題,當模塊被執行在 CommonJS 上下文的時候, this
指向爲 module.exports
.在這種狀況下,你能夠經過 imports-loader
重寫 this
.
webpack.config.js
module.exports = { module: { rules: [{ test: require.resolve("some-module"), use: 'imports-loader?this=>window' }] } };
它支持不一樣的模塊類型,好比 AMD,CommonJS 同時也支持傳統模塊。但是,一般狀況下它會去檢查 define
變量,而後使用一些奇怪(quirky)的代碼去暴露這些屬性。在這種狀況下,經過設置 define = false
來強制CommonJS 路徑可能會有一些幫助。
webpack.config.js
module.exports = { module: { rules: [{ test: require.resolve("some-module"), use: 'imports-loader?define=>false' }] } };
exports-loader
假設一個庫文件建立了一個全局變量,期待它的消費者去使用。在這種狀況下,咱們應該使用 [exports-loader](https://webpack.js.org/loaders/exports-loader/)
, 來暴露一個 CommonJS 風格的全局變量。好比,爲了暴露 file
爲 file
,helpers.parse
爲 parse
:
webpack.config.js
module.exports = { module: { rules: [{ test: require.resolve("some-module"), use: 'exports-loader?file,parse=helpers.parse' // adds below code the the file's source: // exports["file"] = file; // exports["parse"] = helpers.parse; }] } };
script-loader
[script-loader](https://webpack.js.org/loaders/script-loader/)
會在全局上下文裏面解析代碼,就和你在 HTML 中添加了一個 script
標籤同樣。在這種狀況下,理論上全部的模塊都應該正常的運行。
這個文件會被做爲字符串打包在代碼中。不會被
webpack
壓縮,因此請使用壓縮後的版本。一樣這種狀況沒法使用 webpack 提供的開發工具。
假設你有一個 legacy.js
文件包含:
GLOBAL_CONFIG = {}
script-loader
require('script-loader!legacy.js')
通常會獲得這樣的結果:
eval('GLOBAL_CONFIG = {}')
noParse
選項當沒有 AMD/CommonJS 風格的模塊,同時你須要在 dist
中引入,你能夠把這個模塊標識爲 [noParse](https://webpack.js.org/configuration/module/#module-noparse)
. 這樣 webpack
就只會引入這個模塊可是不會去作任何處理,這樣也能夠減小構建的時間。
任何須要 AST 支持的,好比
ProvidePlugin
, 都是不會工做的。
module.exports = { module: { noParse: /jquery|backbone/ } };
webpack 是一個工具,能夠用來打包應用代碼,一樣也能夠用來打包庫代碼。若是你是一個 JavaScript 庫的做者,正在尋找精簡打包代碼的流程,那麼這個章節的內容會對你頗有幫助。
咱們這有一個精簡的包裝庫來把數字 1 到 5 轉換到對應的單詞,反之亦然。 看起來多是這樣的:
src/index.js
import _ from 'lodash'; import numRef from './ref.json'; export function numToWord(num) { return _.reduce(numRef, (accum, ref) => { return ref.num === num ? ref.word : accum; }, ''); }; export function wordToNum(word) { return _.reduce(numRef, (accum, ref) => { return ref.word === word && word.toLowerCase() ? ref.num : accum; }, -1); };
庫的使用規範以下:
//ES2015modules import*aswebpackNumbersfrom'webpack-numbers'; ... webpackNumbers.wordToNum('Two')//outputis2 ... //CommonJSmodules varwebpackNumbers=require('webpack-numbers'); ... webpackNumbers.numToWord(3);//outputisThree ... //Asascripttag <html> ... <scriptsrc="https://unpkg.com/webpack-numbers"></script> <script> ... /*webpackNumbersisavailableasaglobalvariable*/ webpackNumbers.wordToNum('Five')//outputis5 ... </script> </html>
完整的庫配置和代碼放在這裏 webpack-library-example.
那麼接下來的事情就是打包這個庫
• 不打包 lodash,可是會被它的消費者引入 • 命名這個庫爲 `webpack-numbers`, 而且變量爲 `webpackNumbers` • 庫能夠經過 `import webapckNumbers from 'webpack-numbers'` 或者 `require('webpack-numbers')` 來引入 • 當經過 `script` 標籤引入的時候,能夠經過全局變量 `webpackNumbers` 來訪問 • 能夠在 Node.js 中使用
添加基礎 webpack 配置。
webpack.config.js
module.exports = { entry: './src/index.js', output: { path: './dist', filename: 'webpack-numbers.js' } };
這將添加一個基礎配置來打包這個庫。
可是若是沒有對應 loaders 去解析代碼是沒有辦法工做的。這裏,咱們添加 json-loader
來添加對 json 文件的解析。
webpack.config.js
module.exports = { // ... module: { rules: [{ test: /.json$/, use: 'json-loader' }] } };
externals
如今,若是執行 webpack
命令,你會發現一個提交較大的代碼包被生成。若是你去檢查代碼,會發現 ladash 被打包到了代碼包中。對於你的庫來講把 lodash
打包在一塊兒使徹底沒有必要的。
能夠經過 externals
配置:
webpack.config.js
module.exports = { ... externals: { "lodash": { commonjs: "lodash", commonjs2: "lodash", amd: "lodash", root: "_" } } ... };
這意味着在使用者的環境下你的庫會指望依賴 lodash
。
libraryTarget
爲了這個庫可以被普遍的使用,咱們須要讓它在不一樣的環境下有相同的表現。好比, CommonJS,AMD,Node.js 或者做爲一個全局變量。
爲了達到這個目的,須要在 webpack 配置中添加 library
屬性。
webpack.config.js
module.exports = { ... output: { ... library: 'webpackNumbers' } ... };
這可以你的庫被引入的時候能夠做爲一個全局變量被訪問。爲了可以在其它狀況下使用,在配置中繼續添加 libraryTarget
的值:
webpack.config.js
module.exports = { ... output: { ... library: 'webpackNumbers', libraryTarget:'umd' // Possible value - amd, commonjs, commonjs2, commonjs-module, this, var } ... };
若是 library
設置了,可是 libraryTarget
沒有配置,那麼 libraryTarget
默認爲 var
就像在 config reference 中指定的同樣。
將打包後的文件添加到 package.json
中指定的字段裏面。
package.json
{ ... "main": "dist/webpack-numbers.js", "module": "src/index.js", // To add as standard module as per https://github.com/dherman/defense-of-dot-js/blob/master/proposal.md#typical-usage ... }
如今你能夠把它做爲一個 npm 模塊發佈了,而且在 unpkg.com 裏面向你的用戶傳播了。