Webpack是什么
Webpack是一种前端资源构建工具,一个静态模块打包器(module bundle)
在Webpack看来,前端的所有资源文件(js/json/img/less/....)都会作为模块处理
它将根据模块的依赖进行静态分析,打包生成对应的静态资源
Webpack五个核心概念
1、Entry
入口提示Webpack一那个文件为入口起点开始打包,分析构建内部依赖图
2、output
输出指示Webpack打包后的资源bundles输出到哪里去,以及如何命名
3、loader
loader让Webpack能够去处理那些非js文件(webpack自身只能理解js)
4、plugins
插件可以用于执行范围更广的任务,插件的范围包括,从打包和压缩,一直到重新定义环境中的变量符
5、mode
webpack构建流程
确定入口:根据配置中的entry找到所有入口文件
编译模块:从入口文件开始,调用所有配置的loader对模块进行翻译,然后递归所有依赖的模块,重复编译,得到每个模块翻译后的最终内容以及它们之间的依赖关系。
输出资源:根据入口和模块的依赖关系,组装成一个个包含多个模块的chunk,然后将chunk转换成一个单独的文件加入输出列表
输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,将文件的内容写入文件系统上。
有哪些常用的loader
image-loader:加载并且压缩图片文件
babel-loader:把ES6转换成ES5
css-loader:加载CSS,支持模块化,压缩,文件导入等特性;css-loader 是解释 `@import` 和 `url()`
style-loader:把CSS代码注入到JS中,通过DOM操作去加载CSS,最终是以style标签的形式嵌入到Html代码中
eslint-loader:通过ESLint检查JS代码
file-loader:把文件移动到输出文件夹中,在代码中通过相对URL区引用输出的文件
url-loader:和file-loader类似,通过设置limit能在文件很小的情况下以base64的方式把文件内容注入到代码中去,主要是用于处理css中的url()
html-loader:处理html中的图片,注意的是:url-loader使用的是es6解析模块,html-loader使用的是es5解析模块,需要将esModule:false
source-map-loader:加载额外的Source map文件,方便断点调试
postcss-loader:css兼容性处理,另外要搭配 postcss-preset-env
有哪些常见的Plugin?他们是解决什么问题的?
define-plugin:定义环境变量
commons-chunk-plugin:提取公共代码
uglifyjs-webpack-plugin:通过UglifyES压缩ES6代码
MiniCssExtractPlugin:提取js中的css成单独文件 (注:需要再use中使用MiniCssExtractPlugin。loader)
HtmlWebpackPlugin:默认会一个空的HTML,自动引入打包输出的所有资源,通过配置template的文件路径,可以将文件内容进行复制
OptimizeCssAssetsWebpackPlugin:压缩css
压缩CSS
optimize-css-assets-webpack-plugin
用法:引入插件,使用插件new OptimizeCssAssetsWebpackPlugin()
压缩Html
plugins:[
new HtmlWebpackPlugin({
template:'./index.html',
//压缩处理
minify:{
// 移除空格
collapseWhitespace:true,
// 移除注释
removeComments:true
}
}),
],
压缩JS
//为什么要使用 ParallelUglifyPlugin
//webpack默认提供了UglifyJS插件来压缩JS代码,但是它使用的是单线程压缩代码,也就是说多个js文件需要被压缩,它需要一个个文件进行压缩。所以说在正式环境打包压缩代码速度非常慢(因为压缩JS代码需要先把代码解析成用Object抽象表示的AST语法树,再去应用各种规则分析和处理AST,导致这个过程耗时非常大)。
//当webpack有多个JS文件需要输出和压缩时候,原来会使用UglifyJS去一个个压缩并且输出,但是ParallelUglifyPlugin插件则会开启多个子进程,把对多个文件压缩的工作分别给多个子进程去完成,但是每个子进程还是通过UglifyJS去压缩代码。无非就是变成了并行处理该压缩了,并行处理多个子任务,效率会更加的提高。
npm i -D webpack-parallel-uglify-plugin
{
plugins: [
new ParallelUglifyPlugin({
cacheDir: '.cache/',
uglifyJS:{
output: {
comments: false
},
warnings: false,
compress: {
drop_debugger: true,
drop_console: true
}
}
}),
],
}
webpack-parallel-uglify-plugin配置参数说明
test: 使用正则去匹配哪些文件需要被 ParallelUglifyPlugin 压缩,默认是 /.js$/.
include: 使用正则去包含被 ParallelUglifyPlugin 压缩的文件,默认为 [].
exclude: 使用正则去不包含被 ParallelUglifyPlugin 压缩的文件,默认为 [].
cacheDir: 缓存压缩后的结果,下次遇到一样的输入时直接从缓存中获取压缩后的结果并返回,cacheDir 用于配置缓存存放的目录路径。默认不会缓存,想开启缓存请设置一个目录路径。
workerCount:开启几个子进程去并发的执行压缩。默认是当前运行电脑的 CPU 核数减去1。
sourceMap:是否为压缩后的代码生成对应的Source Map, 默认不生成,开启后耗时会大大增加,一般不会将压缩后的代码的
sourceMap发送给网站用户的浏览器。
uglifyJS:用于压缩 ES5 代码时的配置,Object 类型,直接透传给 UglifyJS 的参数。
uglifyES:用于压缩 ES6 代码时的配置,Object 类型,直接透传给 UglifyES 的参数。
eslint-loader用法(webpack<V5.0)
首先需要安装
npm i eslint eslint-loader eslint-plugin-import eslint-config-airbnb-base -D
//语法检查eslint-loader eslint,检测规则在package.json的eslintConfig中
{
test:/\.js$/,
exclude:/node_modules/, //只检查自己的源代码,不检查别人的源代码
loader:'eslint-loader',
options:{
fix:true //自动修复
}
}
//package.json中
"eslintConfig":{
"extends":"airbnb-base"
}
eslint-webpack-plugin用法
首先安装插件和eslint
npm i eslint eslint-webpack-plugin eslint-config-airbnb-base eslint-plugin-import -D
const ESLintPlugin = require('eslint-webpack-plugin')
plugins:[
new ESLintPlugin({
extensions: ['js'],
context: path.resolve(__dirname,'src'),
exclude: '/node_modules',
fix:true
})
],
//在package.json中配置
"eslintConfig":{
"extends":"airbnb-base"
}
JS兼容性处理:babel-loader用法
//安装 npm i babel-loader @babel/preset-env @babel/core
//1.但是只能转化基本语法,如promise不能被转化
//2.全部js兼容性处理-->@babel/polyfill 需要先安装@babel/polyfill ,这样子会导致包太大了,因为做了所有兼容处理
//3.按需加载 --->corejs
//转换基本语法
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'], // 预设 指示babel做怎么样的兼容性处理
plugins: ['@babel/plugin-proposal-object-rest-spread']
}
}
}
]
}
//按需加载
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'], // 预设 指示babel做怎么样的兼容性处理
plugins: [
[
"@babel/preset-env",
{
//按需加载
useBuiltIns:'usage',
// 指定版本
corejs:{
version:3
},
targets:{
chrome:'60',
firefox:'60',
ie:'9',
safari:'10',
edge:'17'
}
}
]
]
}
}
}
]
}
CSS兼容性处理:postcss
postcss-loader:用于处理css兼容性问题
1、首先需要安装 postcss-loader,postcss
2、在use中使用postcss-loader,同时需要配置postcss-preset-env,postcss-preset-env就是帮助postcss-loader在package.json中 寻找borwserslist
//package.json
"browserslist": {
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
"production": [
">0.1%",
"not dead",
"not op_mini all"
]
},
method-1
// webpack.config.js
{
test:/\.css$/,
use:[
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: ["postcss-preset-env"]
}
}
}
]
}
method-2
{
test:/\.css$/,
use:[
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader' //Loader 将会自动搜索配置文件。
]
}
//然后再 postcss.config.js中
module.exports = {
plugins:[
[
'postcss-preset-env'
]
]
}
source-map追踪
作用:提供源代码到构建后代码的映射技术,如果构建代码出错,通过映射可以追踪源代码错误
source-map:外部,
错误代码准确信息和源代码的错误位置
inline-source-map:内联(只生成一个source-map)
错误代码准确信息和源代码的错误位置
eval-source-map:内联(每个文件都会生成一个source-map进行追踪)
错误代码准确信息和源代码的错误位置
hidden-source-map:外部
错误代码准确信息和源代码的错误位置
不能追踪源代码错误,智能提示构建后代码的位置错误
nosources-source-map:外部
错误代码准确信息,但是没有源代码的任何信息
cheap-source-map:外部
错误代码准确信息和源代码的错误位置
只能精确到行
cheap-module-source-map:外部
错误代码准确信息和源代码的错误位置
module会将loader的source map加入
如何选择:
开发环境:速度快,调试友好
生产环境:源码是否要隐藏,调试要不要友好
webpack性能优化
开发环境:优化打包构建速度,优化代码调试,
生产环境:优化打包构建速度,优化代码运行的性能
1.减少不必要的模块依赖
module.noParse字段可用于配置不需要解析的模块。对于类似jquery和lodash这些大型的第三方库,本身就是兼容性非常好的,不需要在进行解析。可以忽略以提升构建速度。
module.exports = {
// ...
module: {
noParse: /jquery|lodash/, // 正则表达式
}
}
2.减少不必要的loader处理
使用loader的时候尽可能考虑不需要处理的模块,以提升构建速度。
通过module.rule.exclude或module.rule.include排除或仅包含需要应用loader的场景。
rules: [
{
test: /\.js$/,
exclude: /node_modules/, //不匹配
loader: "eslint-loader",
},
{
test: /\.js$/,
include: /src/, //匹配
loader: "babel-loader",
},
]
3.通过配置区分生产环境和开发环境的plugin,不要混用,导致影响构建速度。
4.使用cache-loader进行缓存文件。
对于一些比较消耗性能的模块,并且打包后文件内容没有改动可以使用cache-loader进行缓存。因为缓存是直接保存在硬盘,所以存取缓存都是要消耗一定的性能。小文件没必要缓存。下次就可以不使用loader而使用缓存。
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'cache-loader',
options: {
cacheDirectory: path.resolve('.cache') //还能进行配置
}
},
'babel-loader'
],
include: path.resolve('src')
}
]
}
}
5.开启多线程构建
webpack是基于node.js运行的,而node.js是单线程的,所以webpack处理文件是一个个处理的,如果需要处理的文件一多的话,处理速度会很慢,因此在需要解析大量文件的项目中,可以通过开启多线程来处理文件,以加快构建速度。
如thread-loader会开启一个线程池,线程池中包含适量的线程,利用多线程同时开启多个loader的处理
module.exports = {
mode: "development",
module: {
rules: [
{
test: /\.js$/,
use: [
"thread-loader",
"babel-loader"
]
}
]
}
};
可以使用happypack是一个开启多线程解析和构建文件的plugin。
var HappyPack = require('happypack')
var happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length })
module.exports = {
mode: "development",
plugins: [
new HappyPack({
id: 'js',
loaders: ['babel-loader'],
// 用于检索工作线程的预定义线程池
threadPool: happyThreadPool,
// 启用此选项可将状态消息从HappyPack记录到STDOUT
verbose: true
})
]
如何优化打包后的文件:
打包后的文件最优的结果就是文件尽可能少、文件尽可能小,所以只需要根据这两个方向去优化。
1.模块分离
模块分离就是将一个整体的代码,分布到不同的打包文件中去,目的就是为了减少公共代码,降低打包文件总体积,特别是对于一些大型的第三方库,使用.模块分离还能充分利用浏览器缓存去加载这些公共模块,不用每次都去请求,利于传输速度。当多个chunk引入了公共模块的时候,webpack默认是不分离模块的,这样就会造成代码重复,所以当多个chunk引入了公共模块或者公共模块体积比较大,或者公共模块较少变动的时候,可以使用分包。
分包有两种方式:手动分离和自动分离
手动分离
手动分包就是开发人员通过根据分析模块的依赖关系,把公共模块通过打包或手动放到一个公共文件夹里,被打包后的index.html文件引入到页面。
大致步骤:
(1)提取公共模块到公共文件夹,并生成资源清单列表(可以手动创建,也可以使用webpack内置插件DllPlugin生成)
(2)在html页面引入公共模块
(3)使用DllReferencePlugin控制chunk打包结果,资源清单里面有的模块,不在打包进入bundle里面。
自动分离
自动分离相对于手动分离会方便一点,只需要配置要分离策略。
webpack提供了optimization配置项,用于配置一些优化信息
其中splitChunks是分离模块策略的配置
module.exports = {
optimization: {
splitChunks: {
// 分包策略
chunks: 'all',
}
}
}
使用以上chunks为all,就可以满足大部分的场景了。
附上博客:
https://blog.csdn.net/qq_38888512/article/details/116203201?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163907556216780261972559%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=163907556216780261972559&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~baidu_landing_v2~default-7-116203201.pc_search_result_control_group&utm_term=webpack%E6%80%8E%E4%B9%88%E4%BC%98%E5%8C%96&spm=1018.2226.3001.4187
优化开发环境打包构建速度-->HMR模块热替换
作用:一个模块发生变化,只会重新打包这一个模块,极大提升构建速度
1、css文件:可以使用HRM功能,因为style-loader内部实现了
2、js文件:默认不能使用HMR
3、html文件:默认不能使用HMR,解决方案:修改entry入口,将html添加到入口; entry:['./src/js/index.js','./src/index/html'],但其实html可以不用做HMR功能
//解决js不能热模块更替问题
import acss from '../css/a.css'
import index from '../css/index.css'
import bjs from './b'
bjs()
const a = (x, y) => x + y;
console.log(a(1, 2));
console.log(2);
if(module.hot){ //如果module.hot为true,说明开启了HMR功能
module.hot.accept('./b.js',function(){//监听b.js变化,执行回调
bjs()
})
}
Tree shaking
作用:去除无用代码,减少打包体积
前提:必须使用ES6模块化,必须开启生产环境
在package.json中配置
"sideEffects":false
//表示所有代码都没有副作用(都可以进行tree shaking),但是会把css / @babel/polyfill文件干掉
"sideEffects":['*.css']
//不会进行tree shaking
或者在启动webpack时追加参数--optimize-minimize来实现
代码分割 code split
const path = require('path');
module.exports = {
mode: 'production',
entry: {
index: './src/js/index.js',
base: './src/js/base.js'
},
output: {
filename: 'js/[name][contenthash:8].js',
path: path.resolve(__dirname, 'bulid'),
},
optimization:{
//被注释的代码不是必须的,可以根据自己需求开启
splitChunks:{
chunks: 'all',
},
//将当前模块记录其他模块的hash单独打包为一个文件runtime
runtimeChunk:{
name: entrypoint => `runtime-${entrypoint.name}`
},
}
}
预加载和懒加载
懒加载:文件被需要时才加载
document.getElementById('btn').onclick = function () {
// 懒加载
import(/* webpackChunkName: 'test' */"./test.js")
.then(({multi}) => {
console.log(multi)
console.log('multi=', multi(3, 3))
}).catch(() => {
console.log('test文件加载失败!')
})
}
预加载(prefetch):在其他资源加载完毕后,再加载资源
document.getElementById('btn').onclick = function () {
// 预加载
import(/* webpackChunkName: 'test', webpackPrefetch: true */ "./test.js")
.then(({multi, sum}) => {
console.log(multi, sum)
console.log('multi=', multi(3, 3))
}).catch(() => {
console.log('test文件加载失败!')
})
}
PWA技术
渐进式网络开发应用进程(离线可访问)
插件:workbox--> workbox-webpack-plugin
new WorkboxWebpackPlugin({
//1.帮助苏serviceworker快速启动 ;2.删除旧的serviceworker
clientClaim:true,
skipWaiting:true
})
多进程打包
安装thread-loader
开启多进程打包,进程启动需要600ms左右,进程通信也有开销,只有打包工作比较长的才需要多进程打包
use:[
// 开启多进程打包
{
loader:'thread-loader',
options:{
workers:2 //进程2
}
},
{
loader:'babel-loader',
options:{
// 预设 指示babel做怎么样的兼容性处理
presets:[
[
"@babel/preset-env",
{
//按需加载
useBuiltIns:'usage',
// 指定版本
corejs:{
version:3
},
// 指定兼容性做到哪个版本的浏览器
targets:{
chrome:'60',
firefox:'60',
ie:'9',
safari:'10',
edge:'17'
}
}
]
]
}
}
]
externals
//打包的时候忽略jquery,同时要配置jquery的CDN
externals:{
jquery:"jQuery"
}