优化打包构建速度
优化代码运行的性能
if(module.hot){
// 此方法会监听 print.js 文件的变化,一旦发生变化,其他模块不会重新打包构建,会执行后面的回到函数
module.hot.accept('./print.js',function () {
print();
})
}
entry: ['./src/js/index.js','./src/index.html'],
一种提供源代码到构建后代码映射关系的技术(如果构建后代码出错了,通过映射关系可以追踪源代码错误)
devtool:'eval-source-map',
正常来说,一个文件会被所有的loader过滤处理一遍,如果我有100个loader配置,那么我一个文件就要被100个loader匹配,而使用oneOf后,而如果放在oneOf中的loader规则有一个匹配到了,oneOf中的其他规则就不会再对这文件进行匹配
注意:oneOf中不能有两个loader规则配置处理同一种文件,否则只能生效一个 例如:对于js进行eslint检测后再进行babel转换
解决:将eslint抽出到外部,然后优先执行,这样在外部检测完后oneOf内部配置就会再进行检测匹配
以下loader只会匹配一个
注意: 不能有两个配置处理同一个文件
oneOf:[
{
test:/\.css$/,
use:[
// 此loader会将css代码以标签的形式整合进js代码中
// 'style-loader',
...commonCssLoader
]
},
{
test:/\.less$/,
use:[
...commonCssLoader,
'less-loader'
]
},
/**
* 正常来讲,一个文件只能被一个loader处理。
* 当一个文件要被多个loader处理时,那么一定要指定loader的先后顺序
* 先执行 eslint,再执行babel
*/
// 在package.json中的eslintConfig ---> airbnb规则
// 对js做兼容性处理
{
test:/\.js$/,
exclude:/node_module/,
loader:'babel-loader',
options:{
presets:[
// 转译新的es6语法
'@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,
}
},
{
tset:/\.html$/,
loader:'html-loader'
},
{
exclude:/\.(js|html|less|jpg|png|gif|css)/,
loader: 'file-loader',
options: {
outputPath: 'media',
}
}
]
cacheDirectory: true
让第二次打包构建速度更快
hash: 每次wepack构建时会生成一个唯一的hash值。
问题: 因为js和css同时使用一个hash值。
(可能我却只改动一个文件)如果重新打包,会导致所有缓存失效。
根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样
问题: js和css的hash值还是一样的
因为css是在js中被引入的,所以同属于一个chunk
chunk:从一个入口文件引入的其他依赖和其他文件,都属于同一个chunk文件
根据文件的内容生成hash值。不同文件hash值一定不一样
让代码上线运行缓存更好使用
指的就是当我引入一个模块的时候,我不引入这个模块的所有代码,我只引入我需要的代码,这就需要借助 webpack 里面自带的 Tree Shaking 这个功能来帮我们实现。
官方有标准的说法:Tree-shaking的本质是消除无用的js代码。无用代码消除在广泛存在于传统的编程语言编译器中,编译器可以判断出某些代码根本不影响输出,然后消除这些代码,这个称之为DCE(dead code elimination)
在 webpack 项目中,有一个入口文件,相当于一棵树的主干,入口文件有很多依赖的模块,相当于树枝。实际情况中,虽然依赖了某个模块,但其实只使用其中的某些功能。通过 Tree-Shaking,将没有使用的模块摇掉,这样来达到删除无用代码的目的。
在production 模式下不用在webpack.config.js中配置
前提: 1. 必须使用ES6模块化 2. 开启production环境
满足这两个前提会自动开启 tree shaking
作用: 减少代码体积
在package.json中配置
"sideEffects": false 即所有代码都没有副作用(都可以进行tree shaking)
问题: 可能会将css/ @babel/ployfill (副作用)文件干掉
"sideEffects":["*.css"] 将css资源标记为不会进行tree shaking的资源
将打包输出的一个 chunk 分割成多个 chunk,加载时候可以并行加载等等,加快加载速度,还可实现按需加载等等
主要是对js代码进行分割
// 单入口 一般对应单页面应用
// entry: './src/js/index.js',
entry: {
// 多入口:有一个入口,最终输出就有一个bundle
index: './src/js/index.js',
test: './src/js/test.js'
},
output: {
// [name]:取文件名
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build')
},
optimization:{
splitChunks:{
chunks:'all'
}
},
// ES10 的 import语法
//通过注释,可以让js生成的打包文件带上这个名字
import(/* webpackChunkName: 'test' */'./test')
.then(({ mul, count }) => {
// 文件加载成功~
// eslint-disable-next-line
console.log(mul(2, 5));
})
.catch(() => {
// eslint-disable-next-line
console.log('文件加载失败~');
});
// eslint-disable-next-line
console.log(sum(1, 2, 3, 4));
应用场景:当我们模块很多时,导入的js太多,或者说有的js只有使用的时候才有用,而我一开始便加载,就可能造成一些不必要的性能浪费
1、懒加载:当文件需要使用时才加载
可能的问题:当用户第一次使用时,如果js文件过大,可能造成加载时间过长(有延迟),但是第二次就不会了,因为懒加载第二次是从缓存中读取文件
2、预加载 prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载
正常加载可以认为时并行加载(同一时间加载多个文件,但是同一时间有上限)
就例如下面例子,有预加载的代码运行效果,是页面刷新后,但是还未进行使用时,该文件其实已经加载好了
注意:预加载虽然性能很不错,但是需要浏览器版本较高,兼容性较差,慎用预加载
console.log('index.js文件被加载了~');
// import { mul } from './test';
//懒加载
document.getElementById('btn').onclick = function() {
//懒加载其实也是需要前面Ⅵ代码分割功能,将我的需要加载的文件打包成单独文件
import(/* webpackChunkName: 'test'*/'./test').then(({ mul }) => {
console.log(mul(4, 5));
});
};
//预加载
//在注释参数上添加 webpackPrefetch: true
import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {
console.log(mul(4, 5));
});
PWA: 渐进式网络开发应用程序(离线可访问) workbox -->下载依赖: workbox-webpack-plugin
1、在配置中使用该插件 :① 帮助serviceworker快速启动 ② 删除旧的 serviceworker
2、在入口文件js中添加代码
3、eslint不认识 window、navigator全局变量
解决:需要修改package.json中eslintConfig配置
4、代码必须运行在服务器上才有效果
① node.js
② npm i serve -g -->serve -s build 启动服务器,将build目录下所有资源作为静态资源暴露出去
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
plugins: [
new WorkboxWebpackPlugin.GenerateSW({
/*生成一个 serviceworker 配置文件~*/
//1. 帮助serviceworker快速启动
clientsClaim: true,
//2. 删除旧的 serviceworker
skipWaiting: true
})
],
// 注册serviceWorker
// 处理兼容性问题
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker
.register('/service-worker.js')
.then(() => {
console.log('sw注册成功了~');
})
.catch(() => {
console.log('sw注册失败了~');
});
});
}
"eslintConfig": {
"extends": "airbnb-base",
"env": {
"browser": true //开启为eslint支持浏览器端的bian'l,比如 window
}
},
1、下载thread-loader依赖
2、使用loader: 'thread-loader’开启多线程打包
注意点:进程启动大约为600ms,进程通信也有开销,只有工作消耗时间较长,才需要多进程打包 比如:babel转换可以使用多线程
const { resolve } = require('path');
module.exports = {
module: {
rules: [
oneOf: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
/*
开启多进程打包。
进程启动大概为600ms,进程通信也有开销。
只有工作消耗时间比较长,才需要多进程打包
*/
{
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
}
}
]
}
]
}
]
}
};
当你使用外部引入代码时:如CDN引入,不想他将我引入的模块也打包,就需要添加这个配置
即:声明哪些库是不进行打包的
–>externals: {}
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
mode: 'production',
externals: {
// 拒绝jQuery被打包进来
jquery: 'jQuery'
}
};
使用dll技术,对某些库(第三方库:jquery、react、vue…)进行单独打包
作用:如果不是cdn引入,而是使用第三方库,想要打包后暴露出去,使用该方法
1、首先你需要写一个新的配置文件,因为使用dll技术,所以命名为webpack.dll.js
当你运行 webpack 时,默认查找 webpack.config.js 配置文件 需求:需要先运行 webpack.dll.js 文件
–> webpack --config webpack.dll.js 在这个文件中进行对某些库的单独打包
2、在webpack.config.js中,需要告诉webpack哪些库不需要再次打包(即在dll.js中打包后生成的文件)
3、这里需要使用到add-asset-html-webpack-plugin与webpack插件
4、运行webpack.dll.js对第三方库进行单独打包后,除非你要加新的库,不然不用再重新打包这个,直接webpack打包其他的即可
const { resolve } = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
// 最终打包生成的[name] --> jquery
// ['jquery'] --> 要打包的库是jquery
jquery: ['jquery'],
// react:['react','react-dom' ]
},
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: 'production'
};
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
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'
};