前端網紅集結號,傳遞一線全棧技術,帶你穿越前端時空。
公眾號「前端時空」最近推出每日一題活動,
回覆「0」進入交流群,
回覆「1」看每日一題,
回覆「2」看答案解析
目錄
- 開發環境性能優化
- 生產環境性能優化
開發環境性能優化
- 優化打包構建速度
- HMR
- 優化代碼調試
- source-map
HMR
概念:「HMR:」 hot module replacement 熱模塊替換 / 模塊熱替換
作用:一個模塊發生變化,只會重新打包這一個模塊,而不是打包所有模塊,極大的提升了構建速度
- 樣式文件:可以使用 HMR 功能:因為 style-loader 內部已經實現
- js 文件:默認不能使用 HMR 功能:開啟需要添加支持 HMR 功能的 js 代碼,且只能處理 「非入口 js 文件」(入口文件將其它文件全部引入,若添加,會導致全部重新加載)
- html 文件:默認不能使用 HMR 功能,同時會導致 「html 文件不能熱更新」了
解決:修改 「entry」 入口,將 html 文件引入
<code>module.exports = {
// 引入html,解決熱更新的問題
entry: ['./src/js/index.js', './src/index.html'],
devServer: {
// 開啟 HMR 功能
// 當修改了 webpack 配置,新配置要想生效,必須重啟服務
hot: true
}
}
/<code>
source-map
概念:一種提供源代碼構建後代碼映射技術(如果構建後代碼出錯了,可以通過映射追蹤到源代碼錯誤)
- 參數:[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
- source-map:外部(錯誤代碼的準確信息 和 位置)
- inline-source-map:內聯(只生成一個內聯 source-map)(錯誤代碼的準確信息 和 位置)
- hidden-source-map:外部(直接生成 .map 文件)(不能追蹤源代碼錯誤,只能提示到構建後代碼的錯誤位置)
- eval-source-map:內聯(每一個文件都生成對應的 source-map,都在 eval)(錯誤代碼的準確信息 和 位置)
- nosources-source-map:外部(錯誤代碼的準確信息,沒有源代碼信息)
- cheap-source-map:外部(錯誤代碼的準確信息 和 位置,但只能精確到行)
- cheap-module-source-map:外部(錯誤代碼的準確信息 和 位置,會將 loader 的 source-map 加入)
- 開發環境:速度快,調試更友好。eval-source-map / eval-cheap-module-source-map
(vue 和 react 腳手架中默認使用:eval-source-map) - 速度快:(eval > inline > cheap > ...)
eval-cheap-source-map、
eval-source-map、 - 調試友好:
source-map、
cheap-module-source-map、
cheap-source-map - 生產環境:源代碼要不要隱藏?調試要不要更友好?source-map / cheap-module-source-map
內聯會讓代碼體積變大,所以在生產環境中只會只用 「外部 source-map」
nosources-source-map、
hidden-source-map
<code>module.exports = {
mode: 'development', // 'production'
devtool: 'eval-source-map' // 'source-map'
}/<code>
生產環境性能優化
- 優化打包構建速度
- oneOf
- babel 緩存
- 多進程打包
- externals
- dll
- 優化代碼運行的性能
- 緩存(hash -> chunkhash -> contenthash)
- tree shaking
- code split
- 懶加載/預加載
- PWA
oneOf
oneOf:避免了每一個文件都要被 loader 過一次 注:不能有兩個配置處理同一種類型文件
<code>module.exports = {
module: {
rules: [
{
test: /\\.js$/,
exclude: /node_modules/,
//優先執行
enforce: 'pre',
loader: 'eslint-loader',
options: {
fix: true
}
},
{
// 以下 loader 只會匹配一個
oneOf: [
...,
{},
{}
]
}
]
}
}/<code>
cache(緩存)
- babel 緩存
讓第二次打包構建速度更快
<code>module.exports = {
module: {
rules: [
{
test: /\\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
// 開啟 babel 緩存
// 第二次構建時,會讀取之前的緩存
cacheDirectory: true
}
}
]
}
}
/<code>
- 文件資源緩存
讓代碼上線運行緩存更好使用
- hash:每次 webpack 構建時會生成一個唯一的 hash 值
問題:因為 js 和 css 是同時使用一個 hash 值(如果重新打包會導致所有文件緩存都失效) - chunkhash:根據 chunk 生成 hash 值。如果打包來源於同一個 chunk,那麼 hash 值就一樣
問題:js 和 css 的 hash 值還是一樣的
因為 css 是在 js 文件中被引入的,所以同屬於一個 chunk - 「contenthash」 :根據文件的內容生成 hash 值。不同文件的 hash 值移動不一樣
tree shaking(搖樹)
❝
tree shaking:去除無用的代碼
❞
前提:1. 必須使用 ES6 模塊化;2. 開啟 production 環境
作用:減少代碼體積
在 package.json 中配置:
- "sideEffects": false 所有的代碼都沒有副作用(都可以進行 tree shaking)
問題:可能會把 css / @babel/polyfill (副作用)文件幹掉 - sideEffects: ["*.css", "*.less"]
code split(代碼分割)
- 多入口形式:
<code>module.exports = {
entry: {
// 多入口
main: './src/js/index.js',
test: './src/js/test.js'
},
output: {
// [name]: 取文件名
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build')
},
mode: 'production'
}
/<code>
- optimization:
<code>module.exports = {
entry: './src/js/index.js',,
output: {
filename: 'js/built.[contenthash:10].js',
path: resolve(__dirname, 'build')
},
// 可以將 node_modules 中的代碼單獨打包一個chunk最終輸出
optimization: {
splitChunks: {
chunks: 'all'
}
}
mode: 'production'
}
/<code>
- 「某個文件的單獨打包」
<code>// import動態導入語法:能將某個文件單獨打包成一個 chunk
// 此處的註釋可以命名打包後文件名
import(/* webpackChunkName: 'test' */ './test.js')
.then(() => {})
.catch(() => {})/<code>
懶加載 和 預加載
懶加載:當文件需要時才加載
預加載 prefetch:會在使用前,提前加載 js 文件。等其他資源加載完畢,瀏覽器空閒了,在偷偷加載資源
正常加載可以認為是並行加載(同一時間加載多個文件)
<code>// import動態導入語法:能將某個文件單獨打包成一個 chunk
// webpackChunkName 此處的註釋可以命名打包後文件名,webpackPrefetch 預加載
import(/* webpackChunkName: 'test', webpackPrefetch: true */ './test.js')
.then(() => {})
.catch(() => {})/<code>
PWA
PWA:漸進式網絡開發應用程序(離線可訪問)
插件:workbox --> npm i workbox-webpack-plugin -D
<code>module.exports = {
plugins: [
new WorkboxWebpackPlugin.GenerateSW({
// 1. 幫助 serviceworker 快速啟動
// 2. 刪除舊的 serviceworker
// 生成一個 serviceworker 配置文件
clientsClaim: true,
skipWaiting: true
})
]
}
/<code>
註冊 serviceworker,並處理兼容性問題
- eslint 不認識 window、navigator 等全局變量
解決:需要修改 package.json 中 eslintConfig 配置
<code>"env": {
"browser": true
}
/<code>
- sw 代碼必須運行在服務器上
<code>// 註冊 serviceworker,並處理兼容性問題
if ('serviceworker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceworker
.register('/service-worker.js')
.then(() => {
console.log('sw註冊成功了')
})
.catch(() => {
console.log('sw註冊失敗了')
})
})
}/<code>
多進程打包
插件:npm i thread-loader -D
進程啟動大概為 600ms,進程通信也有開銷。只有工作消耗品時間比較長,才需要多進程打包。
<code>module.exports = {
module: {
rules: [
{
test: /\\.js$/,
exclude: /node_modules/,
use: [
// 開啟多進程打包
'thread-loader',
{
loader: 'babel-loader',
options: {
presets: []
}
}
]
}
]
}
}/<code>
externals
- 設置拒絕打包的庫
<code>module.exports = {
externals: {
// 忽略庫名 --> npm包名
jquery: 'jQuery'
}
}
/<code>
- 在入口 index.html 引入 CDN
dll
對代碼進行單獨打包,(第三方庫:jQuery,react,vue ...),第二次以後打包時不再打包第三方庫。
webpack.dll.js 文件:
注:運行 webpack 時,默認查找 webpack.config.js,需要運行 webpack.dll.js 文件時,可以通過運行 webpack --config webpack.dll.js 實現運行
<code>const { resolve } = require('path')
const webpack = require('webpack')
module.exports = {
entry: {
// 最終打包生成的[name] --> jquery
// ['jquery'] --> 要打包的庫是 jquery
jquery: ['jquery']
},
output: {
filename: '[name].js',
path: resolve(__dirname, 'dll'),
library: '[name]_[hash]' // 打包的庫裡面向外暴露的內容的名字
},
plugins: [
// 打包生成一個 manifest.json --> 提供和 jQuery 映射
new webpack.DllPlugin({
name: '[name]_[hash]', // 映射庫的暴露的內容名稱
path: resolve(__dirname, 'dll/manifest.json')
})
],
mode: 'produciton'
}
/<code>
<code>const { resolve } = require('path')
const webpack = require('webpack')
module.exports = {
plugins: [
// 告訴webpack哪些庫不參與打包,同時使用名稱改變
new webpack.DllReferencePlugin({
path: resolve(__dirname, 'dll/manifest.json')
}),
// 將某個文件打包輸出,並在html中自動引入
new AddAssetHtmlWebpackPlugin({
filepath: resolve(__dirname, 'dll/jquery.js')
})
],
mode: 'produciton'
}/<code>
閱讀更多 前端時空 的文章