實踐結果
package.json
{
"name": "webpack-dep-test-better",
"version": "1.0.0",
"devDependencies": {
"@babel/core": "^7.8.7",
"@babel/preset-env": "^7.8.7",
"add-asset-html-webpack-plugin": "^3.1.3",
"babel-loader": "^8.0.6",
"core-js": "^3.6.4",
"css-loader": "^3.4.2",
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.1.0",
"eslint-loader": "^3.0.3",
"eslint-plugin-import": "^2.20.1",
"file-loader": "^5.1.0",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"jquery": "^3.4.1",
"less": "^3.11.1",
"less-loader": "^5.0.0",
"mini-css-extract-plugin": "^0.9.0",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"postcss-loader": "^3.0.0",
"postcss-preset-env": "^6.7.0",
"style-loader": "^1.1.3",
"thread-loader": "^2.1.3",
"url-loader": "^3.0.0",
"webpack": "^4.42.0",
"webpack-cli": "^3.3.11"
},
"browserslist": {
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
"production": [
">0.2%",
"not dead",
"not op_mini all"
]
},
"eslintConfig": {
"extends": "airbnb-base",
"env": {
"browser": true
}
},
"sideEffects": [
"*.css",
"*.less"
],
"dependencies": {}
}
webpack.config.js
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
process.env.NODE_ENV = 'development';
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
module.exports = {
entry:"./src/js/entry.js",
output:{
path:resolve(__dirname,'build'),
//filename:'js/built.js'
filename:'js/built.[contenthash:10].js'
},
module:{
rules:[
// {
// test: /\.js$/,
// exclude: /node_modules/,
// enforce: 'pre',
// loader: 'eslint-loader',
// options: {
// fix: true
// }
//},
{
oneOf:[
{
test:/\.less$/,
use:[MiniCssExtractPlugin.loader,'css-loader',{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [require('postcss-preset-env')()]
}
},'less-loader']
},
{
test:/\.css$/,
use:[MiniCssExtractPlugin.loader,'css-loader',{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [require('postcss-preset-env')()]
}
}]
},
{
test: /\.js$/,
exclude: /node_modules/,
use:[
'thread-loader',
{
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {
version: 3
},
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17'
}
}
]
],
cacheDirectory: true
}
}
]
},
{
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
esModule:false,
outputPath: 'images'
}
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
exclude: /\.(html|js|css|less|jpg|png|gif)/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]',
outputPath: 'media'
}
}]
}
]
},
plugins:[
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
}),
new MiniCssExtractPlugin({
filename: 'css/built.[contenthash:10].css'
}),
new OptimizeCssAssetsWebpackPlugin(),
// 作用:模塊掃描時,不打包dll/manifest.json中說明的模塊。
new webpack.DllReferencePlugin({
manifest: resolve(__dirname, 'dll/manifest.json')
}),
// 作用:將dll/bundle.js打包輸出到build/bundle.js,並在html中自動引入該資源
new AddAssetHtmlWebpackPlugin({
filepath: resolve(__dirname, 'dll/jquery.js')
})
],
mode: 'production',
devtool:'source-map',
externals: {
jquery: '$'
},
optimization: {
splitChunks: {
chunks: 'all'
}
}
};
實踐準備
創建項目:webpack_dep_test_better
初始化
npm init
npm i webpack webpack-cli -D
// 上篇博客 生產環境打包 涉及到的所有依賴,可複製執行以下命令下載相關庫。
// 或者複製上篇博客產出的package.json更改名字後執行npm i
npm i @babel/core @babel/preset-env babel-loader core-js css-loader eslint eslint-config-airbnb-base eslint-loader eslint-plugin-import file-loader html-loader html-webpack-plugin less less-loader mini-css-extract-plugin optimize-css-assets-webpack-plugin postcss-loader postcss-preset-env style-loader url-loader -D
src/js/entry.js:空入口文件
webpack.config.js:複製上一篇博客實踐產生的webpack.config.js文件
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
process.env.NODE_ENV = 'development';
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
entry:"./src/js/entry.js",
output:{
path:resolve(__dirname,'build'),
filename:'js/built.js'
},
module:{
rules:[
{
test:/\.less$/,
use:[MiniCssExtractPlugin.loader,'css-loader',{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [require('postcss-preset-env')()]
}
},'less-loader']
},
{
test:/\.css$/,
use:[MiniCssExtractPlugin.loader,'css-loader',{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [require('postcss-preset-env')()]
}
}]
},
{
test: /\.js$/,
exclude: /node_modules/,
enforce: 'pre',
loader: 'eslint-loader',
options: {
fix: true
}
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {
version: 3
},
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17'
}
}
]
]
}
},
{
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
esModule:false,
outputPath: 'images'
}
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
exclude: /\.(html|js|css|less|jpg|png|gif)/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]',
outputPath: 'media'
}
}
]
},
plugins:[
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
}),
new MiniCssExtractPlugin({
filename: 'css/built.css'
}),
new OptimizeCssAssetsWebpackPlugin()
],
mode: 'production'
};
實踐過程
一:oneOf【優化構建】
1.優化思路
- module的rule匹配時,除js文件需匹配兩個rule(eslint-loader,babel-loader)之外,其它文件只需匹配一個rule,所以這些文件一旦匹配成功就無需再往下匹配。
2.配置代碼的結構:使用oneOf之前
3.配置代碼的結構:使用oneOf之後
二:babel緩存【優化構建】
1.優化思路
- 一個js模塊發生變化,只需要使用babel對這一個js文件進行再編譯,而無需對其它js文件進行再編譯處理。
2.配置示例
- babel-loader的配置加上cacheDirectory: true
三:多進程打包【優化構建】
可選:新的進程啓動和通信都存在開銷,使用不當不但無法優化構建速度,還會拖慢構建速度。
1.優化思路
- nodejs默認是單線程執行的。
- 合理使用多進程進行打包可以加快構建速度。(當某個loader要處理的文件及其內容很多導致運行時間很長時使用,如babel-loader)
2.配置示例
- 下載thread-loader
npm i thread-loader -D
- 配置thread-loader
四:無需打包(externals)【優化構建】
注意:親測與eslint-loader共同使用時會報錯import/no-unresolved並打包失敗。
1.優化思路
- 部分第三方庫可使用CDN做外部加載,無需打包進bundle之中。
2.配置示例
- src/index.html:手動引入該庫
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>webpack_dep_test_better</title>
</head>
<body>
<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
</body>
</html>
- webpack.config.js:配置模塊與庫的映射
module.exports = {
...,
externals: {
// 建立映射關係
// 鍵爲模塊名,如entry.js中import $ from 'jquery'引入的jquery模塊
// 值爲庫對象,如<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>方式引入在全局暴露的jQuery/$對象。
jquery: '$'// 值爲'jQuery'也可
}
}
3.與eslint的兼容問題
- 待處理…
五:支線打包(dll)【優化構建】
1.優化思路
- 依賴的第三方庫/基本不變的代碼可打包得到一個獨立的bundle,打包一次即可,沒有必要每次構建都重新打包。
2.優化後的目錄結構
3.配置示例
- webpack.dll.js:webpack支線配置文件,用於生成dll/bundle.js與dll/manifest.json文件。
const { resolve } = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
// 鍵:dll/bundle的名稱
// 值:要打包的庫數組
jquery: ['./src/js/jquery.js']
},
output: {
// 輸出dll/bundle的具體文件名
filename: '[name].js',
path: resolve(__dirname, 'dll'),
library: '[name]_[hash]' // 聲明爲window的屬性,var [name]_[hash] = function....
},
plugins: [
// 生成manifest.json文件
new webpack.DllPlugin({
name: '[name]_[hash]', // 該庫聲明在window的屬性:var [name]_[hash] = function....
path: resolve(__dirname, 'dll/manifest.json') // 輸出文件路徑
})
],
mode: 'production'
};
// webpack指定配置文件運行(默認webpack.config.js)
webpack --config webpack.dll.js
- webpack.config.js:webpack主線配置文件,忽略該支線所有庫的打包(讀取dll/manifest)並將dll/bundle打包到build/bundle(同時在build/index.html中引入)。
npm i add-asset-html-webpack-plugin -D
...,
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
module.exports = {
plugins:[
...,
// 作用:讀取manifest.json,content鍵的值告訴webpack哪些庫不參與打包。
new webpack.DllReferencePlugin({
manifest: resolve(__dirname, 'dll/manifest.json')
}),
// 作用:將dll/bundle.js打包輸出到build/bundle.js,並在build/index.html中引入該資源
new AddAssetHtmlWebpackPlugin({
filepath: resolve(__dirname, 'dll/jquery.js')
})
]
}
- entry.js中引入該模塊’./jquery’:主線每次打包都忽略此模塊,僅在支線打包一次。
import $ from './jquery'// './jquery'主線每次打包都忽略此模塊,在支線打包一次。
// eslint-disable-next-line
console.log(jquery_891268e901abb8a8c479);// mainfest.json中的name值
console.log($);
六:文件緩存問題【優化上線】
1.優化思路
- 瀏覽器從服務端下載的文件資源帶有過期時間,文件在過期之前不會向服務端發送獲取該文件的http請求。
- 只要每次構建後build/index.html中引入的bundle命名不一致(帶上hash值),就可以避免使用同名的本地緩存文件。
- 合理利用瀏覽器的本地緩存,只請求服務端有更新的文件,不請求服務端沒有更新的文件。
2.配置示例
本地緩存是否合理利用,取決於命名時不同hash值的選擇。如:更新built.css後,只需要built.css的最終文件名發生變化,不需要built.js的最終文件名發生變化。
- 【棄用】hash:webpack構建時產生的hash值。(如:built.css與built.js同一次構建,hash值一致,每次構建後文件名一起變化。)
- 【棄用】chunkhash:chunk的hash值。(如:css由entry.js引入,built.css、built.js同屬一個chunk,chunkhash值一致,每次構建後文件名一起變化)
- 【選用】contenthash:根據文件內容生成的hash值。(built.css與built.js內容不一致,只有更新文件的contenthash以及最終文件名發生變化)
module.exports = {
entry:"./src/js/entry.js",
// 1.bundle.js的命名
output:{
path:resolve(__dirname,'build'),
//filename:'js/built.js'
filename:'js/built.[contenthash:10].js'
},
...,
// 2.bundle.css的命名
plugins:[
...,
new MiniCssExtractPlugin({
//filename: 'css/built.css'
filename: 'css/built.[contenthash:10].css'
}),
...
]
}
3.目錄結構:打包後
七:去除無使用代碼 / 模塊(tree shaking)【優化上線】:
1.優化思路
- 打包時去除引入而不被使用的模塊。
- 打包時去除引入模塊中不被使用的代碼。
2.試驗1
前提:模塊使用import引入、mode爲production
- src/js/test.js:被搖的模塊
// eslint-disable-next-line
console.log("test");
function fn(){
// eslint-disable-next-line
console.log("test");
}
export default {
fn
};
- src/js/entry.js:引入被搖的模塊
import test from './test'
import '../css/test.css'
//test.fn();
- build/js/built.js:test.js模塊被部分打包
... function(e,t,n){"use strict";n.r(t),console.log("test");n(0)}]);
- 試驗結果:css打包成功、console一個test
2.試驗2
- 配置不被搖的模塊:package.json(json文件內不允許註釋)
"sideEffects": [
"*.css",
"*.less"
]
- src/js/entry.js:引入被搖的模塊
import test from './test'
import '../css/test.css'
//test.fn();
- build/js/built.js:test.js模塊不被打包
2.試驗3
- 配置不被搖的模塊:package.json(json文件內不允許註釋)
"sideEffects": [
"*.css",
"*.less"
]
- src/js/entry.js:引入被搖的模塊
import test from './test'
import '../css/test.css'
test.fn();
- build/js/built.js:fn以及console.log都被打包
... function(e,t,n){"use strict";n.r(t),console.log("test");var r={fn:function(){console.log("test")}};n(0);r.fn()}]);
3.試驗結論
- 試驗結果表明,不管是否做sideEffects配置,css打包都有效。
- 模塊內有效的代碼會被留下(如test.js中的console.log(“test”),此行代碼被視爲有效與配置有關),模塊內無效的代碼會被過濾(如如test.js中的fn方法)。
- 模塊內沒有有效的代碼,那麼整個模塊都不會被打包。
4. 對比試驗123,神奇的現象??
- 未配置sideEffects之前,test.js中的console.log(“test”)被視爲有效代碼,模塊被部分過濾。
- 配置sideEffects之後,test.js中的console.log(“test”)被視爲無效代碼,模塊全部過濾。
- 配置sideEffects之後,同時test.js的fn方法被調用,test.js中的console.log(“test”)被視爲有效代碼。
八:代碼分割(code split)【優化上線】
1.優化思路
- 把一個大的bundle文件(built.js)拆分爲多個小的bundle文件以支持bundle的並行加載與懶加載。
2.配置示例
- 1.第三方庫(node_module)的拆分
module.exports = {
...,
optimization: {
splitChunks: {
chunks: 'all'
}
}
}
- 2.懶加載和預加載文件的拆分(見九)
- 3.其它入口的拆分(多entry形成多chunk,打包產生多bundle)
九:js的懶加載和預加載【優化上線】
注意:預加載有瀏覽器兼容問題,慎用。
1.優化思路
- 懶加載可以實現延後加載、按需加載,以更合理、更高效的方式加快瀏覽器的加載速度。
2.代碼示例
// 在按鈕點擊的回調函數中對纔對test.js模塊進行加載
document.getElementById('btn').onclick = function() {
import(/* webpackChunkName: 'test' */'./test.js').then(({fn}) => {
fn();
});
};
- 低版本webstorm會對import報錯(錯誤提示)