【翻譯向】webpack2 指南(上)

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

前置定義

Bundle 代碼包
Chunk 代碼塊css

安裝

npm install webpack --save-devhtml

代碼分割

代碼分割是 webpack 中最引人注目的功能之一。它容許你把代碼分割成各類能夠根據需求載入的代碼包,就像一個用戶瀏覽器去匹配路由同樣,或者一個用戶發出的事件。這容許你小的模塊,容許你控制資源的載入優先級,若是使用得當的話,能夠大大影響(下降)你應用的加載時間。java

緩存和並行加載的資源分割

第三方代碼分割

一個典型的應用會依賴不少第三方的框架和庫文件。不像應用代碼自己,這些第三方代碼的變動很是頻繁。
若是咱們保持這些庫在它自己的代碼包中,從應用代碼自己分離出來,那麼咱們就可使用瀏覽器的緩存策略去在一個長時間內把這些代碼緩存到最終用戶的機器上。node

爲了達到這個效果,第三方代碼的 verndor 包的 hash 部分必須保持不變,無論應用代碼如何變化。學習 如何經過 CommonsChunkPlugin 來分割 verndor/libray 代碼webpack

CSS 分割

你可能也想把樣式文件分割成爲一個單獨的包,從應用邏輯總獨立出來。這能夠加強樣式文件的可緩存性,而且容許瀏覽器在加載應用代碼時並行加載你的樣式文件,所以也能夠避免 FOUC (一個無樣式內容的閃屏)。
學習 如何去分割 CSS 經過使用 ExtractTextWebpackPlugin.git

按需代碼分割

雖然前面的資源分割須要用戶在配置文件中預先指定分割點,可是也能夠在應用代碼中建立動態的分割點。es6

這個功能在有不少細微顆粒代碼塊時會頗有用,舉個例子,每個應用的路由或者按照用戶的預測行爲。這可使用戶按需加載須要的資源。github

經過 require.ensure() 來分割代碼

require.ensure 是一個 CommonJS 風格的方式去異步加載資源。經過添加 require.ensure([<fileurl>]) , 咱們能夠在代碼中定義一個分割點。 Webpack 能夠建立一個包含在這個分割點中的全部代碼的代碼包。學習 如何分割代碼 經過使用 require.ensure().web

TODO System.import()

代碼分割 - CSS

在 webpack 中,當你使用 css-loader 而且在 JavaScript 中引入 CSS 文件,那麼 CSS 文件會被打包在你的 JavaScript 文件中。這有一個很差的地方,就是你沒法使用瀏覽器異步並行加載 CSS 的能力。相反,你的頁面會等到整個 JavaScript 文件加載完成,才完成了樣式文件的加載。Webpack 能夠經過使用 extract-text-webpack-plugin 和 css-loader 來把樣式文件分離出來去解決這個問題。

使用 css-loader

引入 css 到你的 JavaScript 中,須要使用 css-loader 去配置 webpack 的配置文件。

//webpack.config.js

modules.exports = function(env){
    entry: '..',
    ...
    module: {
        rules: [{
            test: /\.css$/,
            exclude: /node_modules/,
            loader: 'css-loader'
        }]
    }
    ...
}

使用 extract-text-webpack-plugin - ExtractTextPlugin

安裝:

npm I --save-dev extract-text-webpack-plugin

要使用這個 ExtractTextPlugin,須要經過兩個步驟配置在 webpack.config.js 中。

在 lodaer 裏面

從以前的 css-loader 中適配,咱們應該以下添加 ExtractTextPlugin.

loader: ExtractTextPlugin.extract('css-loader?sourceMap') //Can be used without sourcemaps too.

在 plugin 裏面

new ExtractTextPlugin({ filename: 'bundle.css', disable: false, allChunks: true })

經過這兩步,就能夠生成一個新的包含全部 CSS 模塊的代碼包,而後把他們添加到 index.htmlheade 中去。能夠經過 ExtractTextPlugin 去了解關於它 api 的更多信息。

完整的配置文件以下:

var ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = function () {
    return {
        entry: './main.js',
        output: {
            path: './dist',
            filename: 'bundle.js'
        },
        module: {
            rules: [{
                test: /\.css$/,
                exclude: /node_modules/,
                loader: ExtractTextPlugin.extract({
                    loader: 'css-loader?sourceMap'
                })
            }]
        },
        devtool: 'source-map',
        plugins: [
            new ExtractTextPlugin({ filename: 'bundle.css', disable: false, allChunks: true })
        ]
    }
}

代碼分割-庫文件

一個典型的應用會依賴不少第三方來提供框架或功能支持。項目中使用的固定版本的庫/框架文件的代碼通常不會有變更,然而應用自己的業務邏輯代碼卻常常會有變更。

把應用代碼和庫文件的代碼打包在一塊兒是一件很是低效的事情。這是由於瀏覽器能夠根據緩存頭緩存這些資源文件到本地而不用每次都去服務端或者 cdn 上去發請求從新獲取,若是文件內容沒有變更的話。爲了可以享受這個好處,咱們須要保持第三方文件的 hash 不變,不管應用自己的代碼如何變化。

咱們只有把應用代碼和第三方代碼分離開才能夠達到這樣的效果。

咱們考慮一個一個簡單的應用,它使用了 momentjs ,一個一般用來時間格式化的庫。

安裝 moment

npm install --save moment

Index 文件會引用 moment 做爲一個依賴而且打印當前的時間:

Index.js

var moment = require('moment');
console.log(moment().format());

咱們能夠經過以下這個配置文件來打包這個應用

Webapck.config.js

module.exports = function(env) {
  return {
    entry: './index.js',
    output: {
      filename: '[chunkhash].[name].js',
      path: './dist'
    }
  }
}

當運行 webapck 命令的時候,若是你檢查打包後的文件,你會發現 momentindex.js 都被打包在了 bundle.js 中。

這不是一個很好的解決方案。若是 index.js 修改了,那麼這打包文件會從新構建,瀏覽器就須要從新去加載這個文件,即便 moment.js 文件並無任何改動。

多個入口

讓咱們緩和這個問題,咱們給 moment 添加一個新的入口命名爲 vendors.

Webpack.config.js

module.exports = function(env) {
  return {
    entry: {
      main: './index.js',
      vendor: 'moment'
    },
    output: {
      filename: '[chunkhash].[name].js',
      path: './dist'
    }
  }
}

如今執行 webpack 命令,咱們會看到兩個打包後的文件。若是你檢查裏面代碼的話,你會看到 moment 的代碼同時出如今兩個代碼包中。

爲了解決這個問題,咱們須要使用 CommonsChunkPlugin.

CommonsChunksPlugin

這是一個至關複雜的插件。它從根本上容許你從不一樣的代碼包中提取出全部的相同模塊而且把它們加入到共同的代碼包中。若是這個相同的代碼包不存在,那麼就建立一個新的。

咱們能夠修改 webpack 的配置文件來使用這個 CommonsCunksPlugin

Webpack.config.js

var webpack = require('webpack');
module.exports = function(env) {
  return {
    entry: {
      main: './index.js',
      vendor: 'moment'
    },
    output: {
      filename: '[chunkhash].[name].js',
      path: './dist'
    },
    plugins: [
      new webpack.optimize.CommonsChunkPlugin({
        name: 'vendor' // Specify the common bundle's name.
      })
    ]
  }
}

這樣的話, moment 的代碼就只會出如今 vendor 代碼包中了。

清單文件(Manifest File)

可是,若是咱們能夠修改應用的代碼而且再次執行 webpack 命令,咱們看到 vendors 文件的 hash 仍是變化了。即便咱們已經分離了 vendormain 代碼包,可是當應用代碼發生修改的時候 vendor 仍是變化了。 這意味着咱們依舊不能享受瀏覽器緩存帶來的好處,由於每一次從新編譯都會修改 vendors 的 hash 值。

這個問題是由於每一次編譯,webpack 生成一些 webpack 運行時代碼,用來幫助 webpack 來作它的工做。當那裏存在一個單獨的代碼包,運行時會駐留在其中。但當多個代碼包被生成的時候,運行時代碼會被提取到公共的模塊中,就是這個 vendor 文件。

爲了阻止這個,咱們須要提取出運行時到一個分離的清單文件(Manifest File)。雖然咱們又多建立另外一個代碼包,但它的開銷也被咱們在 vendor 文件上得到的長期緩存所帶來的好處所抵消了。

Webpack.config.js

var webpack = require('webpack');
module.exports = function(env) {
  return {
    entry: {
      main: './index.js',
      vendor: 'moment'
    },
    output: {
      filename: '[chunkhash].[name].js',
      path: './dist'
    },
    plugins: [
      new webpack.optimize.CommonsChunkPlugin({
        names: ['vendor', 'manifest'] // Specify the common bundle's name.
      })
    ]
  }
};

經過上面這個配置文件,咱們會看到三個代碼包被生成。vendor,mainmanifest. 這樣當應用代碼修改的時候,從新打包後,修改的就只有 mainmanifest 了。 manifest 被修改是由於裏面有對生成文件 hash 值的引用。

代碼分割-使用 RequireJS

在這個章節,咱們討論 webpack 如何經過 require.ensure() 分割代碼。

require.ensure()

Webpack 靜態分析給 require.ensure() 在代碼中當構建和添加模塊到分離的代碼塊中。這個新的代碼塊會被 webpack 在須要的時候經過 jsonp 引入。

它的語法以下:

require.ensure(dependencies: String[], callback: function(require), chunkName: String)

依賴(dependencies)

這是一個字符串數組,用來聲明全部須要在執行回掉函數以前就須要預先加載好且可用的模塊。

回調函數(callback)

一個回調函數會被 webpack 執行一次當全部依賴(dependencies)都被加載之後。Require 對象的實現做爲一個參數傳遞給這個回調函數。這樣,咱們能夠更進一步 require 須要的依賴(dependencies)和其餘須要執行的模塊。

代碼塊名字(chunkName)

代碼塊名字是一個用來命名經過 require.ensrue() 建立的代碼塊。經過給不一樣的 require.ensure() 建立的代碼分割點分割出來的代碼塊一個相同的名字,咱們能夠確保全部的依賴都被打包到同一個代碼塊中。

咱們來看一下以下結構的一個項目

\\ file structure
    |
    js --|
    |    |-- entry.js
    |    |-- a.js
    |    |-- b.js
    webpack.config.js
    |
    dist
// entry.js

require('a');
require.ensure([], function(require){
  require('b');
});

// a.js
console.log('***** I AM a *****');

// b.js
console.log('***** I AM b *****');
// webpack.config.js

module.exports = function(env) {
  return {
    entry: './js/entry.js',
    output: {
      filename: 'bundle.js',
      path: './dist'
    }
  }
}

當運行 webpack 命令的時候,咱們發現 webpack 建立了兩個新的代碼包,bundle.js0.bundle.js.

entry.jsa.js 被打包到了 bundle.js 中。

b.js 被打包到了 0.bundle.js

require.ensure() 的陷阱

空數組做爲一個參數

require.ensure([], function(require){
  require('./a.js');
});

上面的代碼確保一個分割點被建立, a.js 會被 webpack 單獨的打包成一個文件。

依賴做爲參數

require.ensure(['./a.js'], function(require) {
  require('./b.js');
});

上面的代碼,a.jsb.js 會被一塊兒打包而且從主代碼包中分離出來。可是隻有 b.js 的內容被執行了。 a.js 的內容只是是可用的但並無被執行。爲了執行 a.js, 咱們須要 require 它做爲一個同步的方式好比 require('./a.js) ,這樣 JavaScript 就能夠執行它了。

依賴管理

Ø es6 module
Ø Commonjs
Ø Amd

表達式依賴(require with expression)

當你經過表達式去引入一個模塊的時候,就會建立一個上下文,因此當編譯的時候咱們並不知道準確的模塊是哪一個。

例:

require("./template/" + name + ".ejs");

Webpack 解析 require() 的調用,而且提取出來一些信息:

Directory:./template
Regularexpression:/^.*\.ejs$/

上下文模塊(context module)

一個上下文模塊被生成。它包含了在這個文件夾下全部能夠被上面的正則匹配所匹配到的模塊的引用。上下文模塊包含了一個把請求解釋到模塊 id 的 map.
例:

{
  "./table.ejs": 42,
  "./table-row.ejs": 43,
  "./directory/folder.ejs": 44
}

上下文模塊一樣包含了一些運行時代碼用來訪問這個 map.

這意味着動態的引用能夠被支持,可是會致使全部可能被用到的模塊都被打包到了最終的代碼包中。

require.context

你能夠經過 require.context() 方法建立你本身的上下文。它容許你傳入一個用來查詢的文件夾,一個用來決定是否遞歸查找子文件夾的標識,還有一個用來匹配文件的正則表達式。

Webpack 會在代碼打包的時候解析 require.context()

它的語法以下:

require.context(directory, useSubdirectories = false, regExp = /^\.\//)

例:

require.context("./test", false, /\.test\.js$/);
// a context with files from the test directory that can be required with a request endings with `.test.js`.
require.context("../", true, /\.stories\.js$/);
// a context with all files in the parent folder and descending folders ending with `.stories.js`.

上下文模塊API(context module API)

一個上下文模塊暴露一個方法,它接收一個參數:請求的內容。
暴露出來的函數有三個屬性:resolve,key,id

• `resolve` 是一個函數,執行後返回解析後的請求內容的模塊 id
• `keys`是一個函數,執行後返回一個數組,包含全部可能被上下文模塊所請求的全部的模塊的 id
當你想要經過正則匹配引入一個文件夾下全部模塊時,這會很是有用:
function importAll (r) {
  r.keys().forEach(r);
}
importAll(require.context('../components/', true, /\.js$/))
var cache = {};
function importAll (r) {
  r.keys().forEach(key => cache[key] = r(key));
}
importAll(require.context('../components/', true, /\.js$/));
// At build-time cache will be polulated with all required modules.
• `id` 是上下文模塊生成的模塊 id. 當使用 `module.hot.accept` 時,這會很是有用。
相關文章
相關標籤/搜索