前面介紹了webpack的基本配置,本文將詳細介紹webpack中關於代碼優化的配置php
CommonsChunkPlugin 插件,是一個可選的用於創建一個獨立文件(又稱做 chunk)的功能,這個文件包括多個入口 chunk 的公共模塊。經過將公共模塊拆出來,最終合成的文件可以在最開始的時候加載一次,便存到緩存中供後續使用。這會帶來速度上的提高,由於瀏覽器會迅速將公共的代碼從緩存中取出來,而不是每次訪問一個新頁面時,再去加載一個更大的文件css
new webpack.optimize.CommonsChunkPlugin(options)
【配置項】html
{ name: string, // or names: string[], // common chunk 的名稱
filename: string, // common chunk 的文件名模板。能夠包含與 `output.filename` 相同的佔位符 minChunks: number|Infinity|function(module, count) -> boolean, // 在傳入公共chunk(commons chunk) 以前所須要包含的最少數量的 chunks 。 // 數量必須大於等於2,或者少於等於 chunks的數量 chunks: string[], // 經過 chunk name 去選擇 chunks 的來源。chunk 必須是 公共chunk 的子模塊。
children: boolean, // 若是設置爲 `true`,全部公共chunk 的子模塊都會被選擇 deepChildren: boolean, // If `true` all descendants of the commons chunk are selected async: boolean|string, // 若是設置爲 `true`,一個異步的公共chunk 會做爲 `options.name` 的子模塊,和 `options.chunks` 的兄弟模塊被建立。 minSize: number, // 在 公共chunk 被建立立以前,全部公共模塊 (common module) 的最少大小。 }
【提取公共代碼】node
new webpack.optimize.CommonsChunkPlugin({ name: "commons", // ( 公共chunk(commnons chunk) 的名稱) filename: "commons.js", // ( 公共chunk 的文件名) })
【明確第三方庫chunk】react
entry: { vendor: ["jquery", "other-lib"], app: "./entry" }, plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: "vendor", minChunks: Infinity, }) ]
【將公共模塊打包進父 chunk】jquery
new webpack.optimize.CommonsChunkPlugin({ children: true, })
【額外的異步公共chunk】webpack
new webpack.optimize.CommonsChunkPlugin({ name: "app", // or names: ["app", "subPageA"] children: true, async: true, minChunks: 3, })
【wepack4】web
webpack 4 將移除 CommonsChunkPlugin, 取而代之的是兩個新的配置項 optimization.splitChunks 和 optimization.runtimeChunknpm
經過設置 optimization.splitChunks.chunks: "all" 來啓動默認的代碼分割配置項瀏覽器
當知足以下條件時,webpack 會自動打包 chunks:
當前模塊是公共模塊(多處引用)或者模塊來自 node_modules 當前模塊大小大於 30kb 若是此模塊是按需加載,並行請求的最大數量小於等於 5 若是此模塊在初始頁面加載,並行請求的最大數量小於等於 3
經過設置 optimization.runtimeChunk: true 來爲每個入口默認添加一個只包含 runtime 的 chunk
上面介紹的CommonsChunkPlugin能夠去重和分離chunk。而本節介紹的動態導入,則是經過模塊的內聯函數調用來分離代碼
webpack 提供了兩個相似的技術。對於動態導入,第一種,也是優先選擇的方式是,使用符合 ECMAScript 提案 的 import() 語法。第二種,則是使用 webpack 特定的 require.ensure
下面來使用import()語法來進行動態導入
const path = require('path'); const HTMLWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: { index: './src/index.js' }, plugins: [ new HTMLWebpackPlugin({ title: 'Code Splitting' }) ], output: { filename: '[name].bundle.js', chunkFilename: '[name].bundle.js', path: path.resolve(__dirname, 'dist') } };
下面來動態導入loadsh
function getComponent() {
return import(/* webpackChunkName: "lodash" */ 'lodash').then(_ => { var element = document.createElement('div'); element.innerHTML = _.join(['Hello', 'webpack'], ' '); return element; }).catch(error => 'An error occurred while loading the component'); } getComponent().then(component => { document.body.appendChild(component); })
在註釋中使用了 webpackChunkName
。這樣作會致使bundle 被命名爲 lodash.bundle.js
,而不是 [id].bundle.js
懶加載或者按需加載,是一種很好的優化網頁或應用的方式。這種方式其實是先把你的代碼在一些邏輯斷點處分離開,而後在一些代碼塊中完成某些操做後,當即引用或即將引用另一些新的代碼塊。這樣加快了應用的初始加載速度,減輕了它的整體體積,由於某些代碼塊可能永遠不會被加載
上面經過動態導入的loadsh確實會在腳本運行的時候產生一個分離的代碼塊 lodash.bundle.js
,在技術概念上「懶加載」它。問題是加載這個包並不須要用戶的交互 -- 意思是每次加載頁面的時候都會請求它
下面來增長一個交互,當用戶點擊按鈕的時候用 console 打印一些文字。可是會等到第一次交互的時候再加載那個代碼塊(print.js
)
//print.js console.log('The print.js module has loaded! See the network tab in dev tools...'); export default () => { console.log('Button Clicked: Here\'s "some text"!'); }
//index.js import _ from 'lodash'; function component() { var element = document.createElement('div'); var button = document.createElement('button'); var br = document.createElement('br'); button.innerHTML = 'Click me and look at the console!'; element.innerHTML = _.join(['Hello', 'webpack'], ' '); element.appendChild(br); element.appendChild(button); button.onclick = e => import(/* webpackChunkName: "print" */ './print').then(module => { var print = module.default; print(); }); return element; } document.body.appendChild(component());
tree shaking 是一個術語,一般用於描述移除 JavaScript 上下文中的未引用代碼(dead-code)。它依賴於 ES2015 模塊系統中的靜態結構特性,例如 import 和 export。這個術語和概念其實是興起於 ES2015 模塊打包工具 rollup
【JS】
JS的tree shaking主要經過uglifyjs插件來完成
npm install --save-dev uglifyjs-webpack-plugin
const path = require('path'); const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }, plugins: [ new UglifyJSPlugin() ] };
【CSS】
CSS的tree shaking主要經過purify CSS來實現的
npm i -D purifycss-webpack purify-css
const path = require('path'); const glob = require('glob'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const PurifyCSSPlugin = require('purifycss-webpack'); module.exports = { entry: {...}, output: {...}, module: { rules: [ { test: /\.css$/, loader: ExtractTextPlugin.extract({ fallbackLoader: 'style-loader', loader: 'css-loader' }) } ] }, plugins: [ new ExtractTextPlugin('[name].[contenthash].css'), // Make sure this is after ExtractTextPlugin! new PurifyCSSPlugin({ // Give paths to parse for rules. These should be absolute! paths: glob.sync(path.join(__dirname, 'app/*.html')), }) ] };
若是要設置多路徑,則須要將glob換成glob-all
const glob = require('glob-all');
paths: glob.sync([ path.join(__dirname, '.php'), path.join(__dirname, 'partials/.php') ])
模塊熱更新,又稱爲模塊熱替換,HMR(Hot Module Replacement)。在應用程序運行過程當中替換、添加或刪除模塊,無需從新加載頁面,極大地加速了開發時間
啓用此功能實際上至關簡單。而咱們要作的,就是更新 webpack-dev-server 的配置,和使用 webpack 內置的 HMR 插件。此外,還添加了NamedModulesPlugin
,以便更容易查看要修補(patch)的依賴
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const webpack = require('webpack'); module.exports = { entry: { app: './src/index.js' }, devtool: 'inline-source-map', devServer: { contentBase: './dist', hot: true }, plugins: [ new CleanWebpackPlugin(['dist']), new HtmlWebpackPlugin({ title: 'Hot Module Replacement' }), new webpack.NamedModulesPlugin(), new webpack.HotModuleReplacementPlugin() ], output: { filename: '[name].bundle.js', path: path.resolve(__dirname, 'dist') } };
【使用chunkhash】
將hash替換爲chunkhash,這樣當chunk不變時,緩存依然有效
const path = require('path'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: './src/index.js', plugins: [ new CleanWebpackPlugin(['dist']), new HtmlWebpackPlugin({ title: 'Caching' }) ], output: { filename: '[name].[chunkhash].js', path: path.resolve(__dirname, 'dist') } };
【提取模板】
CommonsChunkPlugin 能夠用於將模塊分離到單獨的文件中。還可以在每次修改後的構建結果中,將 webpack 的樣板(boilerplate)和 manifest 提取出來。經過指定 entry 配置中未用到的名稱,此插件會自動將咱們須要的內容提取到單獨的包中
const path = require('path'); const webpack = require('webpack'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: './src/index.js', plugins: [ new CleanWebpackPlugin(['dist']), new HtmlWebpackPlugin({ title: 'Caching' }), new webpack.optimize.CommonsChunkPlugin({ name: 'manifest' }) ], output: { filename: '[name].[chunkhash].js', path: path.resolve(__dirname, 'dist') } };
將第三方庫(library)(例如 lodash 或 react)提取到單獨的 vendor chunk 文件中,是比較推薦的作法,這是由於,它們不多像本地的源代碼那樣頻繁修改。所以經過實現以上步驟,利用客戶端的長效緩存機制,能夠經過命中緩存來消除請求,並減小向服務器獲取資源,同時還能保證客戶端代碼和服務器端代碼版本一致。這能夠經過使用新的 entry(入口) 起點,以及再額外配置一個 CommonsChunkPlugin 實例的組合方式來實現
var path = require('path'); const webpack = require('webpack'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: { main: './src/index.js', vendor: [ 'lodash' ] }, plugins: [ new CleanWebpackPlugin(['dist']), new HtmlWebpackPlugin({ title: 'Caching' }), new webpack.optimize.CommonsChunkPlugin({ name: 'vendor' }), new webpack.optimize.CommonsChunkPlugin({ name: 'manifest' }) ], output: { filename: '[name].[chunkhash].js', path: path.resolve(__dirname, 'dist') } };
[注意]CommonsChunkPlugin
的 'vendor'
實例,必須在 'manifest'
實例以前引入
【使用Name而不是id】
每一個 module.id 會基於默認的解析順序(resolve order)進行增量。也就是說,當解析順序發生變化,ID 也會隨之改變
下面來使用兩個插件解決這個問題。第一個插件是 NamedModulesPlugin,將使用模塊的路徑,而不是數字標識符。雖然此插件有助於在開發過程當中輸出結果的可讀性,然而執行時間會長一些。第二個選擇是使用 HashedModuleIdsPlugin,推薦用於生產環境構建
const path = require('path'); const webpack = require('webpack'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: { main: './src/index.js', vendor: [ 'lodash' ] }, plugins: [ new CleanWebpackPlugin(['dist']), new HtmlWebpackPlugin({ title: 'Caching' }), new webpack.HashedModuleIdsPlugin(), new webpack.optimize.CommonsChunkPlugin({ name: 'vendor' }), new webpack.optimize.CommonsChunkPlugin({ name: 'manifest' }) ], output: { filename: '[name].[chunkhash].js', path: path.resolve(__dirname, 'dist') } };
使用html-webpack-inline-chunk-plugin插件將mainfest.js內聯到html文件中
$ npm install html-webpack-inline-chunk-plugin --save-dev
const path = require('path'); const webpack = require('webpack'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const InlineChunkWebpackPlugin = require('html-webpack-inline-chunk-plugin'); module.exports = { entry: { main: './src/index.js', vendor: [ 'lodash' ] }, plugins: [ new CleanWebpackPlugin(['dist']), new HtmlWebpackPlugin({ title: 'Caching', inlineSource: '.(js|css)$' }), new webpack.HashedModuleIdsPlugin(), new webpack.optimize.CommonsChunkPlugin({ name: 'vendor' }), new webpack.optimize.CommonsChunkPlugin({ name: 'manifest' }), new InlineChunkWebpackPlugin({ inlineChunks: ['manifest'] }) ], output: { filename: '[name].[chunkhash].js', path: path.resolve(__dirname, 'dist') } };