優化方向
熱更新
概念
/**
- hmr: hot module replacement 熱模塊替換 / 模塊熱更新
- 作用: 一個模塊發生改變,只會重新打包這一個模塊(而不是打包所有模塊),極大的提升了構建速度
- 樣式文件: 可以使用hmr功能: style-loader內部實現了熱更新功能
- js文件:默認沒有這個功能(會全量刷新)—> 修改js代碼,只能處理非入口文件
- html文件: 默認沒有這個功能(會全量刷新)因為項目中只有一個html文件,所以我們不需要做熱更新
*/
實現
因為我們在開發環境使用的是webpack-dev-server,所以不需要額外的添加代碼,hot默認值為true,自動會熱更新。但是對于js我們需要額外的處理。
// 在入口文件中寫如下代碼
if (module.hot) {// 一旦 module.hot為true, 說明開啟了熱更新功能,--》讓hmr功能代碼生效module.hot.accept('./print.js',function() {// 方法會監聽到print.js文件的變化,一旦發生變化,其他模塊不會重新打包構建// 會執行回調函數print();})
}
source-map
是一種提供源代碼到構建后代碼的一種映射技術(如果構建后代碼出錯了,通過映射關系,可以追蹤到源代碼的錯誤)。
- 配置
/*** 是一種提供源代碼到構建后代碼的一種映射技術(如果構建后代碼出錯了,通過映射關系,可以追蹤到源代碼的錯誤)。* [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map* source-map: 外聯 錯誤代碼的準確信息和源代碼的錯誤位置* inline-source-map: 內聯 (只生成一個source-map) 錯誤代碼的準確信息和源代碼的錯誤位置* hidden-source-map: 外聯 錯誤代碼的原因,但沒有錯誤代碼的位置,不能追蹤源代碼的錯誤,只能提示構建代碼的位置 (防止代碼泄露)* eval-source-map: 內聯 (每個文件都生成一個source-map,都在eval中) 錯誤代碼的準確信息和源代碼的錯誤位置* nosources-source-map 外聯 錯誤代碼準確信息,但沒有任何源代碼信息 * cheap-source-map 外聯 錯誤代碼的準確信息和源代碼的錯誤位置 只能精確到行,不能精確到列* cheap-module-source-map 外聯 錯誤代碼的準確信息和源代碼的錯誤位置 module會將loader的source-map加入* * 內聯和外聯的區別:1. 外聯生成了文件,內聯沒有 2.內聯構建速度更快* * 使用:* 開發環境:* 速度快, (eval > inline > cheap) eval-cheap-source-map:速度最快 * 調試更友好 * source-map* cheap-module-source-map* cheap-source-map* * ----> 開發環境使用:eval-source-map / eval-cheap-module-source-map* 生產環境:源代碼要不要隱藏,調試要不要更友好* 內聯會讓包的體積非常大,所以生產環境一般不用內聯* * 隱藏: * nosources-source-map (全部隱藏)* hidden-source-map (部分隱藏)* 生產環境使用:source-map / cheap-module-source-map* */ devtool: 'source-map'
oneOf
- 使用
module:{rules:[{oneOf: [// loader ]}]
}
正常情況中我們每個文件都會被所有的loader“處理一下”,但是在oneOf中的loader,他匹配到之后就不會接著執行下面的loader了,所以oneOf,**可以提升構建速度 **,當一類文件,配置了多個的情況下,可以將其他的放到oneOf外面處理
緩存
- babel緩存
就是要在babel下,添加 cacheDirectory: true, 個人實驗沒啥效果(構建速度提升不明顯)
{// 1. js兼容性處理: babel-loader @babel/core @babel/preset-env// 問題:babel只能轉換基礎的語法,如promise則不能進行轉換// 2. 全部兼容 @babel/polyfill// 問題:可以解決兼容性問題,但是引入了所有的兼容性代碼,體積太大// 3. 需要做兼容性的處理就ok,按需加載---> core-jstest: /\.js$/,exclude: /node_modules/,loader: 'babel-loader',options: {// 預設: 指示babel做怎樣的兼容性處理presets: [['@babel/preset-env',{useBuiltIns: 'usage', //按需加載corejs: {version: 3, // 指定core-js版本},targets: { // 指定兼容瀏覽器版本chrome: '60',firefox: '60',ie: '9',safari: '10',edge: '17'}}]],// 開啟babel緩存,第二次構建,會讀取之前的緩存cacheDirectory: true,}},
- 全局加cache ,構建速度顯著提升(從2778ms–>419ms)
module.export = {cache: {type: 'filesystem',allowCollectingMemory: true,},
}
- 文件資源緩存,提升線上訪問速度
// 通過修改文件名來實現緩存的效果,輸出文件全部加上[contenthash:10]/*** 緩存* babel緩存:cacheDirectory:true --》讓代碼第二次構建打包速度更快* 文件資源緩存* hash: 每次webpack構建時會生成一個唯一的hash值* 問題: js和css同時使用一個hash值* 如何重新打包,會導致所有的緩存失效* chunkhash: 根據chunk生成的hansh值,如果打包來源于同一個chunk,那么hash值就一樣 chunkid modeid* contenthash: 文件內容不變,hash值不變 ---》讓代碼線上運行緩存更好使用* */
例如:output: {filename: 'budle.[contenthash:10].js',path: resolve(__dirname,'build')},
tree sharking
去掉沒有用的代碼,只留下引用了的代碼,減小代碼體積。
/*** tree shaking 去除無用代碼* 前提: 1. 必須使用es6模塊化(使用import export 導入導出) 2. 開啟production環境* 在package.json中配置* "sideEffects": false 所有代碼都沒有副作用(度可以進行tree shaking)* 問題: 可能會把css / @babel/polyfill 文件干掉* 解決:"sideEffects": ["*.css"]* 在sideEffects配置不需要刪除的代碼* */
code split
- 多入口
適合多頁面開發
entry: {main: './src/index.js',test: './src/print.js'
}
output: {filename: '[name].[contenthash:10].js', // 這里的【name】,在入口文件中配置了, main test將成為文件名path: resolve(__dirname,'build');
}
- chunk
// 將node_modules中代碼單獨打包成一個chunk最終輸出 // 自動分析多入口chunk中有沒有公共的文件,如果有會打包成單獨一個chunkoptimization: {splitChunks: {chunks: 'all'}}
- import
注意:!!! 如果你配置了eslint,可能會報錯,因為eslint不能夠解析import動態引入的語法,不報錯的話就不需要使用babel-eslint。
// 通過js代碼,讓某個文件單獨被打包成一個chunk
// import動態導入
import(/* webpackChunkName: 'test' */'./print').then(({print,mul}) => {console.log(3, 6);
}).catch(e) {console.log("文件加載失敗",e)
}
下載
npm i -D babel-eslint
在package.json文件中加入parser配置
"eslintConfig": {"extends": "airbnb-base","parser": "babel-eslint","parserOptions": {"sourceType": "module", "allowImportExportEverywhere": true }},
懶加載
- 將import動態引入放入異步函數中
document.querySelector('#btn').onclick = function() {import(/*webpackChunkName: 'test'*/'./print').then(({mul}) => {console.log(mul(4, 5));}).catch((err) => {console.log(err)})
}
預加載
document.querySelector('#btn').onclick = function() {
// 懶加載: 當文件需要使用才加載
// prefetch: 預加載,提前加載js文件 等其他資源加載完畢,瀏覽器空閑了,在偷偷加載資源
// 正常加載:可以認為是并行加載(同一時間加載多個js文件)
import(/* webpackChunkName: 'test',webpackPrefetch: true */'./print')
.then(({ mul }) => {console.log(mul(3, 9));
})
.catch((e) => {console.log('文件加載失敗',e);
})
}
PWA
- 下載插件
npm i -D workbox-webpack-plugin
- 代碼 webpack.config.js中
/*** PWA: 漸進式網絡開發應用程序(離線可訪問)* workbox --> workbox-webpack-plugin*/
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
plugins: [// 生成一個serviceworker配置文件new WorkboxWebpackPlugin.GenerateSW({clientsClaim: true, // 幫助serviceworker快速啟動skipWaiting: true, // 刪除舊的 serviceworker })
]
- 入口文件
// 注冊serviceworker
// 處理兼容性問題
// eslint不認識window navigator全局變量
// 解決: 修改package.json中的eslintConfig配置
// "env": {
// "browser": true
// }
// servicework代碼必須構建在服務器上
if ('serviceWorker' in navigator) {window.addEventListener('load',() => {navigator.serviceWorker.register('/service-worker.js').then(() => {console.log('serviceWorker注冊成功')}).catch(() => {console.log('serviceWorker注冊失敗~')})})
}
- package.json文件中
"eslintConfig": {..."env": {"browser": true}},
多進程開發
- 下載
npm i thread-loader -D
- 代碼
/*** 開啟多進程打包 * 進程啟動大概600ms,進程通信也有開銷 * 只有工作消耗時間比較長,才需要多進程打包 * 一般用來處理js文件消耗比較大的時候使用* */
{loader: 'thread-loader',options: {workers: 3,}
},
externals
讓一些額外的包,不打包到build.js中
externals: {jquery: '$', // 外部可以通過$直接訪問,不需要引入了
},
index.html中引入jquery文件
<scriptsrc="https://code.jquery.com/jquery-3.1.0.js"integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk="crossorigin="anonymous"></script>
dll
- 根目錄下創建webpack.dll.js文件
/*** 使用dll技術,對某些庫(第三方庫: jquery, react, vue...)進行單獨打包* 當運行webpack時默認查找webpack.config.js配置文件* 需求: 我們需要運行webpack.dll.js文件 webpack --config ./webpack.dll.js* */
const { resolve } = require('path');
const webapck = require('webpack');module.exports = {entry: {// 最終打包生成的[name] ---> jqueryjquery: ['jquery']},output: {filename: '[name].js',path: resolve(__dirname, 'dll'),library: '[name]_[hash:10]', //打包的庫里面向外暴露出去的內容名字},plugins: [// 打包生成一個 mainfest.json ---> 提供和jquery映射關系new webapck.DllPlugin({name: '[name]_[hash:10]', // 映射庫暴露的內容名稱path: resolve(__dirname, 'dll/mainfest.json') // 輸出文件路徑})],mode: 'production'
}
- 執行該文件
webpack -c ./webpack.dll.js -c是 --config縮寫
- 下載包
npm i -D add-asset-html-webpack-plugin
- 在webpack.config.js中加入如下代碼
plugins: [// 告訴webpack那些庫不參與打包,同時使用時的名稱也得改變new webpack.DllReferencePlugin({manifest: resolve(__dirname,'dll/mainfest.json')}),// 將某個文件打包輸出去,并在html中自動引入該資源new AddAssetHtmlWebpackPlugin({filepath: resolve(__dirname, 'dll/jquery.js'),outputPath: 'auto', // 生成的index.html文件中多了一層auto目錄}),
]