基础
1.打包
在开发中使用CSS预处理器,ES6语法等,浏览器不能识别,而打包则将其翻译成浏览器能识别的资源
2.构建工具
项目开发中需要不同打包工具,比较麻烦,而构建工具则将其集合起来。
3.打包资源
4.配置文件 webpack.config.js
所有配置文件都是基于nodejs平台运行,模块化采用commonjs
位置:根目录,和src同级
内容:五个概念entry / output / loader(module.rules) / plugins / mode
//resolve用于拼接绝对路径const { resolve } = require('path')module.exports = {entry : './src/index.js',output : {//输出路径多层可以建立文件夹,hash用于实际用途:区别filename : 'js/build.[contenthash:10].js',path:resolve(__dirname, 'build')},module : {rules : []},plugins : [],mode : 'production'}
5.打包样式资源:style-loader / css-loader
/*test表示匹配文件规则use表示处理该类文件的loader,处理顺序:从右到左(从下到上)less-loader:把less文件编译成css文件css-loader:把css文件编译成commonjs模块加载到js中,内容为样式字符串style-loader:创建style标签,插入js中的样式资源,添加到head中*/module : {rules : [{test:/\.css$/use:['style-loader','css-loader']},{test:/\.less$/use:['style-loader','css-loader','less-loader']}]}
6.打包html资源:html-webpack-plugin
//引入类const HtmlWebpackPlugin = require('html-webpack-plugin')plugins = [创建实例:默认创建空的html文件,并自动引入打包输出的所有资源(JS/CSS)new HtmlWebpackPlugin({//template属性指定复制的html文件,并自动引入打包输出的所有资源(JS/CSS)template: './src/index.html'})]
7.打包图片资源:url-loader
module : {rules : [//不能处理html中img,仅能处理url()引入的{test:/\.(jpg|png|gif)$/,use:'url-loader', //url-loader依赖file-loader//配置loaderoptions:{limit : 8 * 1024, //小于则采用base64编码,减少请求数量但增大体积esModule:false, //关闭es6模块,html-loader默认commonjs模块name:'img/[hash:10].[ext]' //取哈希值前10位重命名,[ext]取原扩展名}},//处理html中的img图片{test:/\.html$/,use:'html-loader'}]}
file-loader 能让你指定从什么地方拷贝资源文件以及发布后放到哪个目录去,并能让你使用版本哈希码来重命名发布后的文件来实现增量更新和更好的缓存策略。
url-loader 能根据你指定的文件大小阈值,来判断一个文件是转换成内联的 base-64 码(如果该文件尺寸小于该阈值)还是使用file-loader来降级处理。小文件 base-64 化能有效减少 HTTP 请求数。
8.打包其他资源 file-loader
module : {rules : [exclude: /\.(js|css|less|html|jpg|png|gif)$/,use:'file-loader',options:{name:'[hash:10].[ext]'}]}
9.dev-Server(在webpack.config.js中配置)
运行指令:npx webpack-dev-server
module.exports = {/* entry output module plugin mode *//*开发服务器devServer:自动化,只在内存中编译打包,没有输出指令:npx webpack serve*/devServer:{contentBase:resolve(__dirname,'build'),//启用gzip压缩compress:true,port:3000,//自动打开浏览器open:true}}
10.汇总(练习)
*新增options的属性outputPath 用于指定输出的文件分类
打包完是没有css打包文件的,css和js在一起了
const { resolve } = require('path')const HtmlWebpackPlugin = require('html-webpack-plugin')module.exports = {entry:'./src/index.js',output:{filename:'js/built.js'path:resolve(__dirname,'build')},module:{rules:[{test:/\.css$/,use:['style-loader','css-loader']},{test:/\.less$/,use:['style-loader','css-loader','less-loader']},{test:/\.(jpg|png|gif)$/,use:'url-loader',options:{limit:8*1024,name:'[hash:10].[ext]',esModule:falseoutputPath:'imgs'}},{test:/\.html$/,use:'html-loader'},{exclude:/\.(js|css|less|html|jpg|png|gif)$/,use:'file-loader',options:{name:'[hash:10].[ext]',outputPath:'media'}}]},plugins:[new HtmlWebpackPlugin({template: './src/index.html'})],mode:'development',devServer:{contentBase:resolve(__dirname,'build'),compress:true,port:3000,open:true}}
进阶
12.提取CSS为单独文件:mini-css-extract-plugin
提取后是link标签引入,不是style标签引入,解析更快且不会闪屏
const { MiniCssExtractPlugin } = require('mini-css-extract-plugin')module:{rules:[{test:/\.css$/,use:[MiniCssExtractPlugin.loader, //取代style-loader,作用:提取js中的css为单独文件'css-loader']}]}plugins:[new HtmlWebpackPlugin({template: './src/index.html'}),new MiniCssExtractPlugin({//重命名且重新分配位置pathname: './css/built.css'})]
13.CSS兼容性处理:postcss-loader / postcss-preset-env
postcss —-> postcss-loader postcss-presrt-env
需要更改loader的配置,以对象的形式引入
//postcss默认按browserslist生产环境的配置打包css//需要设置nodejs环境变量(临时),以便postcss按开发环境配置打包cssprocess.env.NODE_ENV = 'development'module:{rules:[{test:/\.css$/,use:[MiniCssExtractPlugin.loader,'css-loader',{loader:'postcss-loader',options:{ident:'postcss',//loader的插件plugins: [require('postcss-preset-env')() //帮助postcss找到package.json中browserslist的配置,并加载指定css兼容性样式]}}]}]}
14.CSS压缩:css-minimizer-webpack-plugin
15.js检查:eslint-loader
需要用到esline-loader(其依赖eslint)
规则是airbnb,需要用到eslint-config-airbnb-base / eslint-plugin-import / eslint
新增规则:use数组里仅一个自定义loader,可少use的一层
/*package.json*/"eslintConfig":{"extends":"airbnb-base"}/*webpack.config.js*/module:{rules:[{loader:eslint-loader,exclude:/node_modules/, //不检查三方库enforce:'pre', //优先执行options:{fix:true //自动修复不符合规范的代码}}]}/*文件中使用,用于忽略下一行的检查*///eslint-disable-next-line
16.js兼容性处理:babel-loader / @babel/core
/*js兼容性处理:babel-loader @babel/core1.基本js兼容性处理: @babel/preset-env2.全部js兼容性畜栏里 @babel/polyfill3.按需加载 core-js*/module:{rules:[{test:/\.js$/,exclude:/node_modules/,loader:babel-loader,options:{//预设:指示babel做怎么样的兼容性处理presets:['@babel/presets-env',{//按需加载useBuiltIns:'usage',//指定core-js版本corejs:{version:3},//指定浏览器版本targets:{chrome:'60',firefox:'60',ie:'9',safari:'10',edge:'17'}}]}}]}
17.js和html的压缩
无html的兼容
module.exports = {/*...*/plugins:[//html代码压缩利用到HtmlWebpackPlugin的minify选项new HtmlWebPackPlugin({template:'./src/index.html',minify:{collapseWhitespace:true,removeComments:true}})],//js代码压缩mode:'production'}
18.汇总(练习)
性能优化
19.性能优化
性能优化分为开发环境优化和生产环境优化
开发环境优化:优化打包构建速度、优化代码调试
生产环境优化:优化打包构建速度、优化代码运行性能
开发环境:
20.HMR
hot module replacement 热模块替换,作用:一个模块发生变化,只会重新打包这一个模块,而不是打包所有模块,极大提升构建速度
样式文件:可以使用HMR功能,因为style-loader内部实现了
js文件:默认不能使用HMR功能,需要修改js代码,添加支持HMR功能的代码,但只支持非入口文件
html文件:默认不能使用HMR功能,开启HMR后需要修改entry入口,将html文件引入,解决不更新的问题,但 html始终没有热更新也不需要热更新。
/*webpack.config.js*/const { resolve } = require('path')module.exports = {entry:['./index.js','./index.html'],/*...*/dev-server:{contentBase:resolve(__dirname,'build')compress:true,port:3000,open:true,//开启HMRhot:true}}/*index.js*/if(module.hot){//module.hot为true说明开启了HMR//accept方法接收两个参数,第一个为监听的对象,第二个为监听的回调module.hot.accept('./print.js',()=>{})}
21.source-map
一种提供源代码到构建后代码映射的技术(如果构建后代码出错了,通过映射可以追踪代码错误)
module.export = {entry:'',/*...*/dev-server:{},devtool:'source-map'}
source-map的配置选项:
[inline- | hidden- | eval-][nosources-][cheap-[module-]]-source-map
1.默认生成外部map文件
inline- 入口内联。提示错误信息和错误位置。
hidden- 外部map文件,隐藏部分代码。仅提示错误信息。
eval- 组件分别内联。提示错误信息和错误位置(hash值)。
2.默认不隐藏
nosources- 隐藏全部代码。仅提示错误信息,没有任何源代码信息。
3.
cheap- 只能精确到行。提示错误信息和错误位置(精确到行)。
cheap-module在cheap-的基础上加上loader的source map
构建速度(eval>inline>cheap)
应用场景:
开发环境:构建速度快,调试更友好。eval- cheap-可以选用
生产环境:隐藏源代码,代码体积。nosources- hidden-可以选用,inline-不能选用
开发环境:eval-source-map / eval-cheap-module-source-map
生产环境:source-map / cheap-module-source-map
生产环境:
22.oneOf
module.export = {/*...*/module:{rules:[//eslint-loader,注意要enforce:'pre'{},{oneOf:[//babel-loader在里面{},{},{}]}]}}/*后续eslint使用eslint-webpack-plugin,避免这类问题*/
oneOf是一个数组,每个元素是一个loader规则配置对象,每种类型的文件只会匹配里面的一个loader,优化构建速度。
23.缓存
babel缓存:让第二次打包构建速度更快
文件资源缓存:让代码上线的缓存更好使
hash:每次webpack构建都会生成唯一的hash值,重复构建时即使文件未修改也生成新的hash
问题:js和css同时使用一个hash值,任意一个文件修改都会导致缓存同时失效(缓存雪崩)
chunkhash:根据chunk生成hash值,打包来源于同一个chunk,那么hash值也一样
问题:js和css来源于同一个chunk,同一个chunkhash值,缓存雪崩
contenthash:根据文件内容生成hash值,不同文件内容hash值不一样
const resolve = require('path')module.exports = {entry:'./src/index.js',output:{filename:'js/[contenthash].js', //filename使用contenthash命名,强缓存也可以重刷新path:resolve(__dirname,'build')},module:{rules:[//eslint-loader{},{oneOf:[//babel-loader{test:/\.js$/,exclude:/node_modules/,loader:'babel-loader',presets:[],//开启babel缓存,第二次构建时会读取之前的缓存cacheDirectory:true}]}]}}
24.tree shaking
tree shaking:去除无用代码
前提:1.必须使用ES6模块化 2.开启production环境(无需额外配置)
作用:减少代码体积
在package.json中配置:"sideEffects":false 所有代码都没有副作用(都可以tree shaking)
问题:可能会把css/ @babel/polyfill文件不打包(因为仅引入没有使用)"sideEffects":["*.css","*.less"] 表示忽略css/less文件
25.代码分割
代码分割可用于并行加载、按需加载。
方法一:多入口
多个入口文件打包成多个bundle,但多个文件都用同个三方库时,三方库的代码会存在于多个bundle,需要配置optimizaiton
const resolve = require('path')module.exports = {entry:{index:'./src/index.js',test:'./src/test.js'},output:{filename:'js/[name].[contenthash:10].js',path:resolve(__dirname,'build')},/* ... */}
方法二:配置optimization
可以将node_modules中代码单独打包一个chunk最终输出
自动分析多入口chunk中的公共文件,公共文件打包成单独的chunk
module.exports = {/*entry output module plugins*/optimization:{splitChunks:{chunks:'all'}}}
**方法三:单入口+动态引入 +optimizaiton配置(常用)
js文件的动态引入:import函数返回一个promise对象,then回调默认接收一个对象,对象包含export的内容,所以可以解构赋值直接使用其内暴露的内容,默认暴露则解构赋值使用default
import函数接收的路径前加上/ webpackChunkName:’xxx’ */ 可作为bundle的name属性,用于自定义命名bundle文件。
import(/* webpackChunkName:'test' */'./test').then(({mul})=>{console.log(mul(2,5))}).catch((err)=>{console.log(err)})
26.懒加载和预加载
正常加载可以认为是并行加载(同一时间加载多个多个文件)。(JS引擎线程和GUI渲染线程互斥,导致JS线程运行时页面不会渲染,同步加载越多用户等待时间越长)
预加载(prefetch):在并行加载后,浏览器空闲再偷偷加载资源,使用时直接从缓存读取。
懒加载:代码被需要时才发送请求读取。
懒加载实现:在异步任务中实现动态import即可
预加载实现:在懒加载之上,动态import时,路径前加上/ prefetch:true /
27.PWA
PWA(progressive web application):渐进式网络开发应用程序(离线可访问),借助workbox(workbox-webapck-plugin)实现:在有网络时将资源存到serviceworker中,离线时读取serviceworker的资源。
/*webpack.config.js中生成serviceworker*/const workboxWebpackPlugin = require('workbox-webpack-plugin')module.exports = {/*entry output module...*/plugins:[//生成一个serviceworker配置文件new workboxWebpackPlugin.GenerateSW({//1.帮助serviceworker快速启动;2.删除旧的serviceworkerclientsClaim:true,skipWaiting:true})]}/*在需要离线访问的js文件中注册serviceworker,处理兼容性问题*/if('serviceWorker' in navigator){window.addEventListener('load',()=>{//注册serviceworker,文件是servicebox-webpack-plugin生成的navigator.serviceworker.register('/service-worker.js').then(()=>{console.log('sw注册成功')}).catch(()=>{console.log('sw注册失败')})})}
问题1:eslint不认识window、navigator全局变量,需要修改package.json中的eslintConfig配置
"env":{"browser":true //支持浏览器全局变量}
问题2:sw代码必须运行在服务器上
28.多进程打包
需要安装thread-loader,写在babel里。线程启动需要约600ms,线程通信也需要消耗,只有工作时长较长才需要多进程打包。
module.exports = {/*entry output*/module = {rules:[{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'}}]}}]}]}}
29.external
external可以不打包三方库,三方库可以从cdn引入,而非自己打包。(面试题-为什么cdn可以加快速度:因为http协议限制同源的最大连接数是有限的,所以把三方库从自己的节点转自cdn节点)
module.exports = {/* entry output module plugin mode*/externals:{jquery:'jQuery'}}
30.dll
dll:事先单独打包一次三方库,打包index.js时不打包三方库,且三方库可以打包成多个bundle。区别于:
external:免打包三方库
代码分割:每次打包index.js时都会打包三方库,三方库打包成一个bundle
dll分三步:
1.配置webpack.dll.js,用于指明单独打包的三方库,并生成映射文件manifast.json(webpack.DllPlugin)
2.webpack.config.js中配置webpack.DllReferencePlugin,用于指示映射关系,指示免打包的三方库
3.webpack.config.js中配置AddAssetHtmlWebpackPlugin,用于自动引入html
/*webpack.dll.js运行指令:webpack --config webpack.dll.js*/const { resolve } = require('path')const webpack = require('webpack')module.exports = {entry:{//最终打包生成的[name]是jquery,['jquery']是要打包的库//多对属性多个bundle,数组内多个元素表示多个三方库打包成一个bundlejquery:['jquery']},output:{filename:'[name].js',path:resovle(__dirname,'dll'),library:'[name]_[hash]' //库中向外暴露的变量名称},plugins:[//输出manifest文件,该文件提供jquery打包文件和三方库的映射关系new webpack.DllPlugin({name:'[name]_[hash]', //映射库的暴露的名称内容,与上述保持一致path:resolve(__dirname,'dll/manifest.json')})]}/* webpack.config.js */const { resolve } = require('path')const webpack = require('webpack')const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')module.exports = {/* entry output module*/plugins:[//通过映射文件,指示webpack不需要打包的库且更改其名称,new webpack.DllReferencePlugin({mainfest:resolve(__dirname,'dll/manifest.json')}),//指示webpack在html中自动引入在映射关系中的三方库new AddAssetHtmlWebpackPlugin({filepath:resolve(__dirname,'dll/jquery.js')})]}
31.性能优化汇总
开发环境性能优化:
优化打包构建速度
HMR
优化代码调试
source-map
生产环境性能优化:
优化打包构建速度
oneOf
babel缓存
多进程打包
externals
dll
优化代码运行性能
缓存(hash-chunkhash-contenthash)
tree shaking
code split
懒加载/预加载
pwa
33.详细配置:entry
1.string
单入口,打包形成一个chunk,输出一个bundle,此时chunk名称默认是main
2.array
多入口,数组中所有元素形成一个chunk,输出一个bundle,此时chunk名称默认是main。
用途:在HMR功能中让html热更新生效
3.object
多入口,生成多个chunk,输出多个bundle,此时chunk的[name]是key
34.详细配置:output
output配置对象的属性
1.filename 指定文件名称和目录
2.path 输出文件目录(所有资源输出的公共目录)
3.pubilcPath 所有资源引入公共路径前缀
不添加,如’imgs/a.jpg’则在当前目录下寻找
添加’/‘,如’/imgs/a.jpg’则在根目录寻找
4.chunkFilename 非入口chunk的名称(react脚手架有配置)
5.library 打包文件向外暴露的变量名(不设置则为IIFE)
6.libraryTarget 变量名添加到哪个对象上
const { resolve } = require('path')module.exports = {output: {filename:'js/[contenthash:10].js',path:resolve(__dirname,'build'),publicPath:'/',chunkFilename:'js/[name].[contenthash:10].js',//library:[name],//libraryTarget:window, browser//libraryTarget:global, node}}
35.详细配置:module
1.多个loader使用use数组,单个loader写成loader
2.enforce ‘pre’优先执行 ‘post’延后执行
3.exclude排除(需要排除/node_modules/) include只检查(resolve(__dirname,’src’))
36.详细配置:resolve
resolve用于解析模板的规则
引入文件时,引入自己的文件使用相对路径或绝对路径找到文件,引入三方库时则只需要使用包名即可,原理是webpack自动去node_module寻找,且webpack会逐级往上找。
const { resolve } = require('path')module.exports = {/* entry output module plugin mode*/resolve:{//配置解析模板路径别名,方便但无提示alias:{$css:resolve(__dirname,'src/css')//之后可以在任意地方使用src/css都可以避免复杂的相对路径},//配置省略文件后缀名,前面的优先级高extensions:['js','json','jsx'],//配置第三方库的路径(可避免webpack自行逐渐往上找node_modules)modules:[resolve(__dirname,'../../node_modules')'node_modules']}}
37.详细配置:devServer
module.exports = {/* entry output module plugins mode*///运行代码的目录contentBase:resolve(__dirname,'build'),//监视contentBase目录下的所有文件,一旦文件变化就会reload(无需重启)watchContentBase:true,watchOptions:{//忽略文件ignored:/node_modules/},//gzipcompress:true,port:5000,open:true,hot:true,//不显示启动服务器日志信息clientLogLevel:'none',//除基本信息外均不显示quiet:true,//禁止全屏报错ovelay:false,//解决开发环境的跨域问题proxy:{'/api':{target:'http://localhost:3000',pathRewrite:{'/^api/':''},changeOrigin:true}}}
38.详细配置:optimizaiton
index.js引入a.js,其中index.js文件内容中记录着a.js的hash值,所以当a文件内容变化时,a的hash值变化,导致index.js的内容变化,导致index.js的hash值也变化。
解决方案:把index.js中保存其他模块hash值的内容提取为单独的runtime文件
const TerserWebpackPlugin = require('terser-webpack-plugin')module.exports = {/* entry output module plugins mode*/optimizaiton:{splitChunks:{chunks:'all',//以下为默认配置miniSize:30*1024,maxSize:0, //最大没有限制minCHunks:1, //要限制的chunk最少被引用1次maxAsynbcRequests:5, //按需加载时并行加载的文件的最大数量maxInitialRequests:3, //入口js文件最大并行请求数量automaticNameDelimiter:'~', //名称连接符name:true, //跨域使用命名规则cacheGroups:{//分割chunk的组,node_modules文件会被打包到vendors组的chunk中 ==>vendors~xxx.jsvendors:{test:/[\\/]node_modules[\\/]/,priority:-10},default:{miniChunk:2, //要限制的chunk最少被引用2次,覆盖上述priority:-20,reuseExistingChunk:true //如果要打包的模板是之前已提取的则复用}}},//解决a文件修改导致b文件的contenthash变化runtimeChunks:{name:entrypoint => `runtime-${entrypoint.name}`},minimizer:[//配置生产环境的压缩方案:js和css (不再用ugily,而是terser)new TerserWebpackPlugin({cache:true,parallel:true, //多线程打包sourceMap:true})]}}
