webpack优化配置
HMR热模块替换
HMR:hot moddule replacement 热模块替换
作用:一个模块发生变化,只会重新打包这个模块,极大的提升了构建速度
样式文件:可以使用HMR,style-loader内部实现
js文件:默认没有HMR功能,需要修改js代码,添加支持该功能的代码
HMR功能对js的处理,只能处理非入口文件的js文件
html文件: 默认没有HMR功能,同时会导致js文件不能热更新了(不需要HMR)
解决(热更新):修改entry,将HTML文件引入 entry: ['./src/index.js', './src/index.html']
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: ['./src/index.js', './src/index.html'],
output: {
filename: 'build.js',
path: resolve(__dirname, 'build'),
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
linit: 8 * 1024,
name: '[hash:10].[ext]',
exModule: false, // 关闭es6模块化
},
// outputPath:'img' 输出到build/img文件夹
},
{
// 处理html中的img
test: /\.html$/,
loader: 'html-loader',
},
{
exclude: /\.(html|css|js|jpg|png|gif|less)$/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]',
},
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
}),
],
mode: 'development',
devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true, // 优化
port: 3000,
open: true,
hot: true //开启HMR功能
// 当修改了webpack配置,新配置想要生效必须重启webpack服务
},
};
js文件的修改:
if (module.hot) {
// 一旦module.hot为true 说明开启了HMR功能 -- 让HMR代码生效
module.hot.accept('./print.js', () => {
// 方法会监听print.js文件的变化,一旦发生变化,其他模块不会重新打包构建,
// 会执行后面的回调函数
print();
});
}
懒加载
懒加载lazy-loading:当文件需要使用时才加载
预加载prefetch:会在使用前提前加载js文件(兼容性差,慎用)
正常加载可以认为是并行加载(同一时间加载多个文件),预加载则是等其他资源加载完毕,浏览器空闲了,再加载资源
console.log('index.js is loaded')
document.getElementById('btn').onclick = () => {
//webpackChunkName: 'test'设置模块名字
import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test')
.then(({mul}) => {
console.log(mul(4, 5));
})
}
source-map
一种提供源代码构建后代码映射技术(如果构建后代码出错,通过映射关系可以追踪代码错误位置)
[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map (顺序不能弄错)
source-map:外部,错误代码的准确信息和源代码的错误位置
inline-source-map:内联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.内联构建速度更快
module.exports = {
entry: ['./src/index.js', './src/index.html'],
output: {
filename: 'build.js',
path: resolve(__dirname, 'build'),
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
linit: 8 * 1024,
name: '[hash:10].[ext]',
exModule: false, // 关闭es6模块化
},
// outputPath:'img' 输出到build/img文件夹
},
{
// 处理html中的img
test: /\.html$/,
loader: 'html-loader',
},
{
exclude: /\.(html|css|js|jpg|png|gif|less)$/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]',
},
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
}),
],
mode: 'development',
devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true, // 优化
port: 3000,
open: true,
hot: true //开启HMR功能
// 当修改了webpack配置,新配置想要生效必须重启webpack服务
},
devtool: 'source-map',
};
开发环境:速度快,调试更友好
速度快(eval>inline>cheap>...)
参考:
eval-cheap-source-map
eval-source-map
调试更友好:
source-map
cheap-moudle-source-map
cheap-source-map
-->eval-source-map / eval-cheap-module-source-map
生产环境:源代码要不要隐藏,调试更友好
内联会让代码体积变大,所以生产环境不用内联
隐藏:
nosource-source-map 全部隐藏
hidden-source-map 只隐藏源代码,会提示构建后代码错误
--> source-map / cheap-module-source-map
oneof
一般情况下,一个文件只能被一个loader处理,当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序:先执行eslint再执行babel
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/build.js',
path: resolve(__dirname, 'build'),
},
module: {
rules: [
{
// 在package.josn中eslintConfig --> airbnb
test: /\.js$/,
exclude: /node_module/,
// 优先执行
enforce: 'pre',
loader: 'eslint-loader',
options: {
fix: true,
},
},
{
// 一下loader只会匹配一个,提升构建速度
// 注意:不能有两项配置处理同一类型的文件
oneOf: [
{
test: /\.css$/,
use: [
...commonCssLoader,
],
}, {
test: /\.less$/,
use: [
...commonCssLoader,
'less-loader',
],
}, {
// 在package.josn中eslintConfig --> airbnb
test: /\.js$/,
exclude: /node_module/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {
version: 3,
},
targets: {
chrome: '60',
firefox: '50',
},
},
],
],
},
}, {
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
outputPath: 'imgs',
esModule: false,
},
}, {
test: /\.html$/,
loader: 'html-loader',
}, {
exclude: /\.(js|css|less|html|jpg|png|gif)$/,
loader: 'file-loader',
options: {
outputPath: 'media',
},
},
],
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/build.css',
}),
new OptimizeCssAssetsWebpackPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true,
},
}),
],
mode: 'production',
};
缓存
babel缓存:CacheDirectory:true --让第二次打包构建速度更快
文件资源缓存:让代码上线运行缓存更好使用
hash:每次webpack构建时会生成一个唯一的hash值
问题:因为js和css同时使用一个hash值
如果重新打包,会导致所有缓存失败(可能只改动了一个文件)
chunkhash:根据chunk生成的hash值,如果打包来源于同一个chunk,那么hash值就一样
问题:js和css的hash值还是一样
因为css是在js中被引入,所以属于同一chunk(所有根据入口文件生成的东西都会成为一个chunk)
contenthash:根据文件的内容生成hash值,不同的文件hash一定不一样
const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
// 定义node环境变量,决定使用browserslist的哪个环境
process.env.NODE_ENV = 'production';
// 复用loader
const commonCssLoader = [
MiniCssExtractPlugin.loader,
'css-loader', {
// 还需在package.json中定义browserslist
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => {
require('postcss-preset-env')();
},
},
},
];
module.exports = {
entry: './src/index.js',
output: {
filename: 'js/build.[contenthash:10].js',
path: resolve(__dirname, 'build'),
},
module: {
rules: [
{
// 在package.josn中eslintConfig --> airbnb
test: /\.js$/,
exclude: /node_module/,
// 优先执行
enforce: 'pre',
loader: 'eslint-loader',
options: {
fix: true,
},
},
{
oneOf: [
{
test: /\.css$/,
use: [
...commonCssLoader,
],
}, {
test: /\.less$/,
use: [
...commonCssLoader,
'less-loader',
],
}, {
// 在package.josn中eslintConfig --> airbnb
test: /\.js$/,
exclude: /node_module/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {
version: 3,
},
targets: {
chrome: '60',
firefox: '50',
},
}
]
],
// 开启babel缓存
// 第二次构建时,会读取之前的缓存
cacheDirectory: true,
},
},
}, {
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
outputPath: 'imgs',
esModule: false,
},
}, {
test: /\.html$/,
loader: 'html-loader',
}, {
exclude: /\.(js|css|less|html|jpg|png|gif)$/,
loader: 'file-loader',
options: {
outputPath: 'media',
},
},
],
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/build.[contenthash:10].css',
}),
new OptimizeCssAssetsWebpackPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true,
},
}),
],
mode: 'production',
devtool: 'source-map',
};
服务器代码:
安装express:
cnpm install express--save
cnpm install body-parser cookie-parser multer --save
启动服务器指令:
npm i nodemon -g
nodemon server.js
//-------or-------
node server.js
访问服务器地址:http://localhost:3000
const express = require('express');
const app = express();
app.use(express.static('build', { maxAge: 1000 * 3600 }));//maxAge:有效期
app.listen(3000);
tree shaking
去除无用代码:减少代码体积
前提:
-
必须使用ES6
-
开启production环境
满足以上两个条件就会开启tree shaking
在package.json中配置:
"sideEffects": false 所有代码都没有副作用(都可以进行tree shaking),默认值时true,若不指定其他值,就默认所有文件都有副作用,无法进行tree shaking。
设置为false的问题:可能会把css、@babel、polyfill(副作用)文件去掉
"sideEffects": [".css", ".less"] 不会把css文件处理
code-split
方法一:通过多入口拆分代码
const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// 多入口:有几个入口,最终输出就有几个bundle
entry: {
main: './src/index.js',
test: './src/test.js'
},
output: {
// [name]:取文件名
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build'),
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true,
},
}),
],
mode: 'production'
};
方法二:
- 可将node_modules中代码单独打包一个chunk输出
- 自动分析多入口chunk中,有没有公共的文件,若有会打包成单独一个chunk
module.exports = {
entry: {
main: './src/index.js',
test: './src/test.js'
},
output: {
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build'),
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true,
},
}),
],
/*
1.可以将node_modules中代码单独打包一个chunk输出
2.自动分析多入口chunk中,有没有公共的文件,若有会打包成单独一个chunk
*/
optimization: {
splitChunks: {
chunks: 'all'
}
},
mode: 'production'
};
方法三:
通过js代码,让某个文件被单独打包成一个chunk
import动态导入语法:能将某个文件单独打包
import (/* webpackChunkName: 'test' */'./test')
.then(({mul, count}) => {
// 文件加载成功
console.log(mul(2, 5));
})
.catch(() => {
console.log('文件加载失败');
})
PWA
渐进式网络开发应用程序(离线可访问)
workbox --> workbox-webpack-plugin
const WorkboxWebpackPlugin = require('workbox-webpack-plugin')
plugins: [
new MiniCssExtractPlugin({
filename: 'css/build.[contenthash:10].css',
}),
new OptimizeCssAssetsWebpackPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true,
},
}),
new WorkboxWebpackPlugin.GenerateSW({
/*
1.帮助serviceworker快速启动
2.删除旧的serviceworker
生成一个serviceworker配置文件
*/
clientsClaim: true,
skipWaiting: true
}),
],
js文件
/*
1.eslint 不认识window,navigator全局变量
解决:需要修改package.json中的eslintConfig配置
"env": {
"browser": true
}
2.sw必须运行在服务器上
--> node.js
--> npm i serve -g
serve -s build 启动服务器,将build目录下所有资源作为静态资源暴露出去
*/
// 注册serviceworker
// 处理兼容性问题
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js')
.then(() => {
console.log('sw registed');
})
.catch(() => {
console.log('sw registe failed');
});
});
}
多进程打包
开启多进程打包:thread-loader
进程开启大概需要600ms,进程通信也有开销,只有工作消耗时间长,才需要多进程打包(慎用)
const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
process.env.NODE_ENV = 'production';
module.exports = {
entry: './src/index.js',
output: {
filename: 'js/build.[contenthash:10].js',
path: resolve(__dirname, 'build'),
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_module/,
enforce: 'pre',
loader: 'eslint-loader',
options: {
fix: true,
},
},
{
oneOf: [
{
// 在package.josn中eslintConfig --> airbnb
test: /\.js$/,
exclude: /node_module/,
use: [
{
loader: 'thread-loader',
options: {
workers: 2 //2个进程
}
},
{
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {
version: 3,
},
targets: {
chrome: '60',
firefox: '50',
},
},
],
],
// 开启babel缓存
// 第二次构建时,会读取之前的缓存
cacheDirectory: true,
},
},
],
}
],
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/build.[contenthash:10].css',
}),
new OptimizeCssAssetsWebpackPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true,
},
}),
],
mode: 'production',
devtool: 'source-map',
};
外部扩展externals
让webpack不对一些不怎么需要更新的第三方 库打包,减少打包时间。可通过CMD,AMD,global等方式引入第三方库。
const {resolve} = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry:'./src/index.js',
output:{
filename:'js/build.js',
path:resolve(__dirname, 'build')
},
module:{
rules:[
]
},
plugins:[
new HtmlWebpackPlugin({
template: './src/index.html'
}
)
],
mode: 'production',
externals: {
// 忽略库名--npm包
// 拒绝jQuery被打包,通过cdn引入,提高加载速度
jquery: 'jQuery'
}
}
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
dll
使用dll技术,对一些第三方库(如:jquery,react等)进行单独打包(不重复打包)。
当运行webpack时,默认查找webpack.config.js文件,使用dll技术需要运行webpack.dll.js文件,运行:
webpack --config webpack.dll.js
//webpack.dll.js
const webpack = require('webpack')
const {resolve} = require('path')
module.exports = {
entry: {
// 最终打包生成[name] --> jquery
// ['jquery'] --> 要打包的库是jquery
jquery: ['jquery']
},
output: {
filename: '[name].js',
path:resolve(__dirname, 'dll'),
library: '[name]_[hash]' //打包库里向外暴露出去的内容叫什么名字
},
plugins: [
new webpack.DllPlugin({
name: '[name]_[hash]', //映射库的暴露内容名称
path: resolve(__dirname, 'dll/manifest.json') //输出文件路径
})
]
}
//webpack.config.js
const {resolve} = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack') //使用dll的插件
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')
module.exports = {
entry:'./src/index.js',
output:{
filename:'build.js',
path:resolve(__dirname, 'build')
},
module:{
rules:[
]
},
plugins:[
new HtmlWebpackPlugin({
template: './src/index.html'
}
),
// 告诉webpack哪些库不参与打包,同时使用时名称也得变
new webpack.DllReferencePlugin({
manifest: resolve(__dirname, 'dll/manifest.json')
}),
// 将某个文件打包输出,并在 html中自动引入该资源(不用手动引入)
new AddAssetHtmlWebpackPlugin({
filepath: resolve(__dirname, 'dll/jquery.js')
})
],
mode: 'production'
}
与externals的区别:
externals:彻底不需要打包,可用cdn引入
dll:需要打包,只用打包一次