最近在寫一下對本身的思考,發現還有兩篇草稿沒發,記得應該是去年年初的時候寫的,後來公司一直忙,就不多來社區了,今天先發一篇了,這篇記得當時還找到做者,加了微信css
webpack 版本不一樣,配置也會有一些地方不同的,這裏是 webpack 4html
爲了儘量少的讓文件被 Loader 處理,能夠經過 include 去命中只有哪些文件須要被處理。
複製代碼
用過 Windows 系統的人應該會常常看到以 .dll 爲後綴的文件,這些文件稱爲動態連接庫,
在一個動態連接庫中能夠包含給其餘模塊調用的函數和數據。vue
要給 Web 項目構建接入動態連接庫的思想,須要完成如下事情:node
爲何給 Web 項目構建接入動態連接庫的思想後,會大大提高構建速度呢? 緣由在於包含大量複用模塊的動態連接庫只須要編譯一次,
在以後的構建過程當中被動態連接庫包含的模塊將不會在從新編譯,
而是直接使用動態連接庫中的代碼react
分解任務和管理線程的事情 HappyPack 都會幫你作好webpack
用過 UglifyJS 的你必定會發如今構建用於開發環境的代碼時很快就能完成,
但在構建用於線上的代碼時構建一直卡在一個時間點遲遲沒有反應,其實卡住的這個時候就是在進行代碼壓縮。git
因爲壓縮 JavaScript 代碼須要先把代碼解析成用 Object 抽象表示的 AST 語法樹,
再去應用各類規則分析和處理 AST,致使這個過程計算量巨大,耗時很是多。github
爲何不把在4-3 使用 HappyPack中介紹過的多進程並行處理的思想也引入到代碼壓縮中呢?web
ParallelUglifyPlugin 就作了這個事情。
當 Webpack 有多個 JavaScript 文件須要輸出和壓縮時,本來會使用 UglifyJS 去一個個挨着壓縮再輸出,
可是 ParallelUglifyPlugin 則會開啓多個子進程,把對多個文件的壓縮工做分配給多個子進程去完成,
每一個子進程其實仍是經過 UglifyJS 去壓縮代碼,可是變成了並行執行。
因此 ParallelUglifyPlugin 能更快的完成對多個文件的壓縮工做。正則表達式
使用 ParallelUglifyPlugin 也很是簡單,把原來 Webpack 配置文件中內置的 UglifyJsPlugin 去掉後,再替換成 ParallelUglifyPlugin,
不過看到 GitHub 上說是支持並行的,uglifyjs-webpack-plugin/#parallel
要讓 Webpack 開啓監聽模式,有兩種方式: 在配置文件
webpack.*.config.js
中設置watch: true
。 在執行啓動Webpack
命令時,帶上--watch
參數,完整命令是webpack --watch
文件監聽工做原理: 在 Webpack 中監聽一個文件發生變化的原理是定時的去獲取這個文件的最後編輯時間,
每次都存下最新的最後編輯時間,若是發現當前獲取的和最後一次保存的最後編輯時間不一致,就認爲該文件發生了變化。
配置項中的 watchOptions.poll 就是用於控制定時檢查的週期,具體含義是每隔多少毫秒檢查一次。
watchOptions: {
// 不監聽的 node_modules 目錄下的文件
ignored: /node_modules/,
}
複製代碼
webpack 內置插件
HotModuleReplacementPlugin
,
配置 devServer
// these devServer options should be customized in /config/index.js devServer: { clientLogLevel: 'warning', historyApiFallback: { rewrites: [ { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, ], }, hot: true, 複製代碼
要優化模塊熱替換的構建性能,思路和在4-5 使用自動刷新中提到的很相似:
監聽更少的文件,忽略掉 node_modules 目錄下的文件。
可是其中提到的關閉默認的 inline 模式手動注入代理客戶端的優化方法不能用於在使用模塊熱替換的狀況下,
緣由在於模塊熱替換的運行依賴在每一個 Chunk 中都包含代理客戶端的代碼。
要在 Webpack 中接入 UglifyJS 須要經過插件的形式,目前有兩個成熟的插件,分別是:
UglifyJsPlugin:經過封裝 UglifyJS 實現壓縮。
ParallelUglifyPlugin:多進程並行處理壓縮
把 cssnano 接入到 Webpack 中也很是簡單,由於 css-loader 已經將其內置了,
要開啓 cssnano 去壓縮代碼只須要開啓 css-loader 的 minimize 選項
const path = require('path'); const {WebPlugin} = require('web-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { module: { rules: [ { test: /\.css$/,// 增長對 CSS 文件的支持 // 提取出 Chunk 中的 CSS 代碼到單獨的文件中 use: ExtractTextPlugin.extract({ // 經過 minimize 選項壓縮 CSS 代碼 use: ['css-loader?minimize'] }), }, ] }, plugins: [ // 用 WebPlugin 生成對應的 HTML 文件 new WebPlugin({ template: './template.html', // HTML 模版文件所在的文件路徑 filename: 'index.html' // 輸出的 HTML 的文件名稱 }), new ExtractTextPlugin({ filename: `[name]_[contenthash:8].css`,// 給輸出的 CSS 文件名稱加上 Hash 值 }), ], } 複製代碼
以前的相對路徑,都變成了絕對的指向 CDN 服務的 URL 地址,配置中的path 也須要換成 CDN 地址前綴
Tree Shaking 能夠用來剔除 JavaScript 中用不上的死代碼。它依賴靜態的 ES6 模塊化語法
Webpack 內置了專門用於提取多個 Chunk 中公共部分的插件 CommonsChunkPlugin,CommonsChunkPlugin 大體使用方法以下:
const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin'); new CommonsChunkPlugin({ // 從哪些 Chunk 中提取 chunks: ['a', 'b'], // 提取出的公共部分造成一個新的 Chunk,這個新 Chunk 的名稱 name: 'common' }) 複製代碼
router 按需加載
在前面的優化方法中提到了代碼壓縮和分塊,這些都是在網絡加載層面的優化,
除此以外還能夠優化代碼在運行時的效率,Prepack 就是爲此而生。
Prepack 由 Facebook 開源,它採用較爲激進的方法:
在保持運行結果一致的狀況下,改變源代碼的運行邏輯,輸出性能更高的 JavaScript 代碼。
實際上 Prepack 就是一個部分求值器,編譯代碼時提早將計算結果放到編譯後的代碼中,而不是在代碼運行時纔去求值。
Prepack 經過在編譯階段預先執行了源碼獲得執行結果,再直接把運行結果輸出來以提高性能
Prepack 的工做原理和流程大體以下:
經過 Babel 把 JavaScript 源碼解析成抽象語法樹(AST),以方便更細粒度地分析源碼;
Prepack 實現了一個 JavaScript 解釋器,用於執行源碼。
藉助這個解釋器 Prepack 才能掌握源碼具體是如何執行的,並把執行過程當中的結果返回到輸出中。
從表面上看去這彷佛很是美好,但實際上 Prepack 還不夠成熟與完善。
Prepack 目前還處於初期的開發階段,侷限性也很大,例如:
接入 Webpack
const PrepackWebpackPlugin = require('prepack-webpack-plugin').default; module.exports = { plugins: [ new PrepackWebpackPlugin() ] }; 複製代碼
Scope Hoisting 可讓 Webpack 打包出來的代碼文件更小、運行的更快, 它又譯做 "做用域提高",
是在 Webpack3 中新推出的功能
好處是:
代碼體積更小,由於函數申明語句會產生大量代碼;
代碼在運行時由於建立的函數做用域更少了,內存開銷也隨之變小。
Scope Hoisting 的實現原理其實很簡單: 分析出模塊之間的依賴關係,儘量的把打散的模塊合併到一個函數中去,但前提是不能形成代碼冗餘。
所以只有那些被引用了一次的模塊才能被合併。
因爲 Scope Hoisting 須要分析出模塊之間的依賴關係,所以源碼必須採用 ES6 模塊化語句,否則它將沒法生效。
緣由和4-10 使用 TreeShaking 中介紹的相似。
爲了更簡單直觀的分析輸出結果,社區中出現了許多可視化的分析工具。
這些工具以圖形的方式把結果更加直觀的展現出來,讓你快速看到問題所在。
兩種分析工具:
stats.json
在啓動 Webpack 時帶上以上兩個參數,啓動命令以下:
webpack --profile --json > stats.json,
複製代碼
若是沒有問題,你會發現項目中多出了一個 stats.json 文件。
這個 stats.json 文件是給後面介紹的可視化分析工具使用的。
但是我在 vue 項目中使用時出現了一個問題
web>webpack --profile --json > stats.json No configuration file found and no output filename configured via CLI option. A configuration file could be named 'webpack.config.js' in the current directory . Use --help to display the CLI options. 複製代碼
webpack --config ./build/webpack.dev.conf.js --json > stats.json
複製代碼
webpack --profile --json 會輸出字符串形式的 JSON,
stats.json 是 UNIX/Linux 系統中的管道命令,
含義是把 webpack --profile --json 輸出的內容經過管道輸出到 stats.json 文件中。
打開 Webpack Analyse 連接的網頁後,你就會看到一個彈窗提示你上傳 JSON 文件,
也就是須要上傳上面講到的 stats.json 文件
webpack-bundle-analyzer
發現 vue-cli 2 版本中 webpack.prod.conf.js 裏面有關因而否開啓 webpack-bundle-analyzer 配置; 也就是說
npm run build --report
的時候,BundleAnalyzerPlugin
能以可視化的方式展現打包結果;
若是單獨使用 webpack-bundle-analyzer:
按照開發環境和線上環境爲該項目配置了兩份文件,下面是使用
webpack4
版本
const path = require('path'); const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin'); const {AutoWebPlugin} = require('web-webpack-plugin'); const HappyPack = require('happypack'); // 自動尋找 pages 目錄下的全部目錄,把每個目錄當作一個單頁應用 const autoWebPlugin = new AutoWebPlugin('./src/pages', { // HTML 模版文件所在的文件路徑 template: './template.html', // 提取出全部頁面公共的代碼 commonsChunk: { // 提取出公共代碼 Chunk 的名稱 name: 'common', }, }); module.exports = { // AutoWebPlugin 會找爲尋找到的全部單頁應用,生成對應的入口配置, // autoWebPlugin.entry 方法能夠獲取到生成入口配置 entry: autoWebPlugin.entry({ // 這裏能夠加入你額外須要的 Chunk 入口 base: './src/base.js', }), output: { filename: '[name].js', }, resolve: { // 使用絕對路徑指明第三方模塊存放的位置,以減小搜索步驟 // 其中 __dirname 表示當前工做目錄,也就是項目根目錄 modules: [path.resolve(__dirname, 'node_modules')], // 針對 Npm 中的第三方模塊優先採用 jsnext:main 中指向的 ES6 模塊化語法的文件,使用 Tree Shaking 優化 // 只採用 main 字段做爲入口文件描述字段,以減小搜索步驟 mainFields: ['jsnext:main', 'main'], }, module: { rules: [ { // 若是項目源碼中只有 js 文件就不要寫成 /\.jsx?$/,提高正則表達式性能 test: /\.js$/, // 使用 HappyPack 加速構建 use: ['happypack/loader?id=babel'], // 只對項目根目錄下的 src 目錄中的文件採用 babel-loader include: path.resolve(__dirname, 'src'), }, { test: /\.js$/, use: ['happypack/loader?id=ui-component'], include: path.resolve(__dirname, 'src'), }, { // 增長對 CSS 文件的支持 test: /\.css$/, use: ['happypack/loader?id=css'], }, ] }, plugins: [ autoWebPlugin, // 使用 HappyPack 加速構建 new HappyPack({ id: 'babel', // babel-loader 支持緩存轉換出的結果,經過 cacheDirectory 選項開啓 loaders: ['babel-loader?cacheDirectory'], }), new HappyPack({ // UI 組件加載拆分 id: 'ui-component', loaders: [{ loader: 'ui-component-loader', options: { lib: 'antd', style: 'style/index.css', camel2: '-' } }], }), new HappyPack({ id: 'css', // 如何處理 .css 文件,用法和 Loader 配置中同樣 loaders: ['style-loader', 'css-loader'], }), // 4-11提取公共代碼 new CommonsChunkPlugin({ // 從 common 和 base 兩個現成的 Chunk 中提取公共的部分 chunks: ['common', 'base'], // 把公共的部分放到 base 中 name: 'base' }), ], watchOptions: { // 4-5使用自動刷新:不監聽的 node_modules 目錄下的文件 ignored: /node_modules/, } }; 複製代碼
const path = require('path'); const DefinePlugin = require('webpack/lib/DefinePlugin'); const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin'); const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const {AutoWebPlugin} = require('web-webpack-plugin'); const HappyPack = require('happypack'); const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin'); // 自動尋找 pages 目錄下的全部目錄,把每個目錄當作一個單頁應用 const autoWebPlugin = new AutoWebPlugin('./src/pages', { // HTML 模版文件所在的文件路徑 template: './template.html', // 提取出全部頁面公共的代碼 commonsChunk: { // 提取出公共代碼 Chunk 的名稱 name: 'common', }, // 指定存放 CSS 文件的 CDN 目錄 URL stylePublicPath: '//css.cdn.com/id/', }); module.exports = { // AutoWebPlugin 會找爲尋找到的全部單頁應用,生成對應的入口配置, // autoWebPlugin.entry 方法能夠獲取到生成入口配置 entry: autoWebPlugin.entry({ // 這裏能夠加入你額外須要的 Chunk 入口 base: './src/base.js', }), output: { // 給輸出的文件名稱加上 Hash 值 filename: '[name]_[chunkhash:8].js', path: path.resolve(__dirname, './dist'), // 指定存放 JavaScript 文件的 CDN 目錄 URL publicPath: '//js.cdn.com/id/', }, resolve: { // 使用絕對路徑指明第三方模塊存放的位置,以減小搜索步驟 // 其中 __dirname 表示當前工做目錄,也就是項目根目錄 modules: [path.resolve(__dirname, 'node_modules')], // 只採用 main 字段做爲入口文件描述字段,以減小搜索步驟 mainFields: ['jsnext:main', 'main'], }, module: { rules: [ { // 若是項目源碼中只有 js 文件就不要寫成 /\.jsx?$/,提高正則表達式性能 test: /\.js$/, // 使用 HappyPack 加速構建 use: ['happypack/loader?id=babel'], // 只對項目根目錄下的 src 目錄中的文件採用 babel-loader include: path.resolve(__dirname, 'src'), }, { test: /\.js$/, use: ['happypack/loader?id=ui-component'], include: path.resolve(__dirname, 'src'), }, { // 增長對 CSS 文件的支持 test: /\.css$/, // 提取出 Chunk 中的 CSS 代碼到單獨的文件中 use: ExtractTextPlugin.extract({ use: ['happypack/loader?id=css'], // 指定存放 CSS 中導入的資源(例如圖片)的 CDN 目錄 URL publicPath: '//img.cdn.com/id/' }), }, ] }, plugins: [ autoWebPlugin, // 4-14開啓ScopeHoisting new ModuleConcatenationPlugin(), // 4-3使用HappyPack new HappyPack({ // 用惟一的標識符 id 來表明當前的 HappyPack 是用來處理一類特定的文件 id: 'babel', // babel-loader 支持緩存轉換出的結果,經過 cacheDirectory 選項開啓 loaders: ['babel-loader?cacheDirectory'], }), new HappyPack({ // UI 組件加載拆分 id: 'ui-component', loaders: [{ loader: 'ui-component-loader', options: { lib: 'antd', style: 'style/index.css', camel2: '-' } }], }), new HappyPack({ id: 'css', // 如何處理 .css 文件,用法和 Loader 配置中同樣 // 經過 minimize 選項壓縮 CSS 代碼 loaders: ['css-loader?minimize'], }), new ExtractTextPlugin({ // 給輸出的 CSS 文件名稱加上 Hash 值 filename: `[name]_[contenthash:8].css`, }), // 4-11提取公共代碼 new CommonsChunkPlugin({ // 從 common 和 base 兩個現成的 Chunk 中提取公共的部分 chunks: ['common', 'base'], // 把公共的部分放到 base 中 name: 'base' }), new DefinePlugin({ // 定義 NODE_ENV 環境變量爲 production 去除 react 代碼中的開發時才須要的部分 'process.env': { NODE_ENV: JSON.stringify('production') } }), // 使用 ParallelUglifyPlugin 並行壓縮輸出的 JS 代碼 new ParallelUglifyPlugin({ // 傳遞給 UglifyJS 的參數 uglifyJS: { output: { // 最緊湊的輸出 beautify: false, // 刪除全部的註釋 comments: false, }, compress: { // 在UglifyJs刪除沒有用到的代碼時不輸出警告 warnings: false, // 刪除全部的 `console` 語句,能夠兼容ie瀏覽器 drop_console: true, // 內嵌定義了可是隻用到一次的變量 collapse_vars: true, // 提取出出現屢次可是沒有定義成變量去引用的靜態值 reduce_vars: true, } }, }), ] }; 複製代碼
吳浩麟擁有本書的著做權。
其它人不能將本書用於商用用途,不能轉載,不能以任何形式發行,違者將追究法律責任。