实践结果
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报错(错误提示)