【翻譯向】webpack2 指南(下)

原文發佈與抹橋的博客-【翻譯向】webpack2 指南(下)javascript

緩存(Caching)

爲了可以使 webpack 處理後的靜態資源可以長期緩存下來,須要:css

  1. 使用 [chunkhash] 給每個文件建立基於內容變化的緩存標識html

  2. 在 HTML 文件中引入文件時使用編譯狀態來拿到文件名稱java

  3. 在載入資源以前生成 chunk-manifest JSON 文件並寫入到 HTML 頁面中jquery

  4. 確保包含啓動代碼的入口代碼塊的 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]

但存在的問題是,每次咱們從新編譯,全部的文件名都會變化,這會致使客戶端每次都從新把整個應用的代碼從新下載一遍。那麼咱們如何才能時客戶端每次只下載有變更的文件。

給每一個文件生成獨特的 hash

當一個文件的內容沒有變化的時候,如何保證它的文件名不會在每次編譯中變化。好比,一個用來放全部的咱們的全部依賴庫文件的代碼包。

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 文件:

{
  "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. 可使用下面這兩個插件來完成:

肯定的 hashes (Deterministic hashes)

爲了壓縮生成文件的大小,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)將不會變化,除非你修改它的代碼或者依賴。

墊片(Shimming)

webpack 做爲了個模塊打包工具,能夠支持的模塊系統包括 ES2015 modules, CommonJs 和 AMD. 可是不少狀況下,當咱們使用第三方庫的時候,咱們看到他們會依賴一個全局變量好比 $ 或者說 jquery. 它們也可能建立一些須要暴露出來的新的全局變量。咱們來看幾種不一樣的方式來使 webpack 可以理解這些非模塊(broken modules)的文件。

最好使用在 dist 文件下沒有打包壓縮過的 CommonJs/AMD 文件(Prefer unminified CommonJS/AMD files over bundled dist versions.)
大多數模塊會在 package.jsonmain 字段中指定它們的 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 風格的全局變量。好比,爲了暴露 filefilehelpers.parseparse:

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 庫的做者,正在尋找精簡打包代碼的流程,那麼這個章節的內容會對你頗有幫助。

編寫一個庫(Author a Library)

咱們這有一個精簡的包裝庫來把數字 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.

配置 webpack

那麼接下來的事情就是打包這個庫

• 不打包 lodash,可是會被它的消費者引入
• 命名這個庫爲 `webpack-numbers`, 而且變量爲 `webpackNumbers`
• 庫能夠經過 `import webapckNumbers from 'webpack-numbers'` 或者 `require('webpack-numbers')` 來引入
• 當經過 `script` 標籤引入的時候,能夠經過全局變量 `webpackNumbers` 來訪問
• 能夠在 Node.js 中使用

添加 webpack

添加基礎 webpack 配置。

webpack.config.js

module.exports = {
  entry: './src/index.js',
  output: {
    path: './dist',
    filename: 'webpack-numbers.js'
  }
};

這將添加一個基礎配置來打包這個庫。

添加 Loaders

可是若是沒有對應 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 裏面向你的用戶傳播了。

相關文章
相關標籤/搜索