webpack 知识点梳理 基本配置 拆分配置和 merge 拆分配置为 webpack.common.js、webpack.dev.js、webpack.prod.js
webpack.dev.js、webpack.prod.js 通过 webpack-merge 的 smart 继承 webpack.common.js
1 import { smart } from 'webpack-merge'
在 webpack5 中,更改为
1 import merge from 'webpack-merge'
dev、prod 区别
mode、plugin: webpack.DefinePlugin、devServer
启动本地服务 安装 webpack-dev-server,通过 –config 指定 config 文件
proxy 端口代理
处理 es6 对 js 文件处理,使用 babel-loader
使用 babelrc 文件进行配置 preset & plugin
处理样式 css:
Loader: [‘style-loader’, ‘css-loader’, ‘postcss-loader’]
postcss-loader: 配置文件 postcss.config.js 其内容 plugins: [require(‘autoprefix’)]
less:
Loader: [‘style-loader’, ‘css-loader’, ‘less-loader’]
loader 执行顺序从后往前
处理图片 url-loader: 小于指定 limit 大小的图片使用 base64,可以通过指定 output 把文件统一放在某个目录下
url-loader file-loader 区别
它们都可以用于载入静态资源文件,url-loader 比 file-loader 的优势在于可以将文件转为 base64
they are both deprecated in webpack v5,使用 asset module 代替
url-loader file-loader 原理:
loader 本质上是一个函数,它们遇到需要解析的文件,会使用 webpack loader 的 api emitFile 将文件移动到指定的路径,并将文件改写为以下内容
1 export default '/absolute/path'
or
1 export default 'data:imgae/jpeg:base64....'
这样的内容。
output 1 2 3 output : { filename : 'bundle.[currentHash:8].js' }
使用 currentHash 的好处:当内容不变时,hash 值不变,浏览器可以使用缓存。
模块化 TODO: 为什么可以使用 import output 的语法?
高级配置 多入口
需要配置多个 entry
1 2 3 4 5 entry : { index : path.join (srcPath, 'index.js' ), other : path.join (srcPath, 'other.js' ), }
对应的 output 需要需改
1 2 3 4 5 output : { filename : 'bundle.[currentHash:8].js' , path : distPath, }
使用 currentHash 的好处:当内容不变时,hash 值不变,浏览器可以使用缓存。
HtmlWebpackPlugin 需要多个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 plugins : [ new HtmlWebpackPlugin ({ template : path.join (srcPath, 'index.html' ), filename : 'index.html' , chunks : ['index' ] }), new HtmlWebpackPlugin ({ template : path.join (srcPath, 'other.html' ), filename : 'other.html' , chunks : ['other' ] }) ]
其他
CleanWebpackPlugin: 会清空 output.path 指定的路径
抽离压缩 css 文件 使用 MiniCssExtractPlugin
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 rules : [ { test : '/\.less$/' , loader : [ MiniCssExtractPlugin .loader , 'css-loader' , 'less-loader' , 'postcss-loader' ] } ], plugin : [ new MiniCssExtractPlugin : { filename : 'css/main.[contenthash:8].css' } ], optimization : { minimizer : [new TerserJsPlugin ({}), new OptimizeCssAssetPlugin ()] }
抽离公共代码 公用代码和第三方代码不会经常变,如果不抽离,每次都需要被重新打包,因为每次的hash值都发生变化,所以不能使用缓存,浏览量每次都会加载很慢。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 plugins : [ new HtmlWebpackPlugin ({ template : path.join (srcPath, 'index.html' ), filename : 'index.html' , chunks : ['index' , 'vender' , 'common' ] }), new HtmlWebpackPlugin ({ template : path.join (srcPath, 'other.html' ), filename : 'other.html' , chunks : ['other' , 'common' ] }) ], optimization : { splitChunks : { chunks : 'all' , cacheGroups : { vendor : { vendor : { name : 'vender' , priotrity : 1 , test : /node_modules/ , minSize : 10 * 1024 , minChunks : 1 }, common : { name : 'common' , priotrity : 0 , test : /node_modules/ , minSize : 10 * 1024 , minChunks : 2 } } } } }
怎么懒加载 使用方式:
1 2 3 4 5 6 7 setTimeout (() => { import ( './dynamic-data.js' ).then (res => { console .log (res.default .message ) }) }, 5000 )
处理 jsx 和 vue
react 使用 @babel/preset-react
1 2 3 4 5 // .babelrc { "presets": ["@babel/preset-react"], "plugins": [] }
vue 使用 vue-loader
1 2 3 4 5 6 7 8 9 module : { rules : [ { test : '/\.vue$/' , loader : ['vue-loader' ], include : srcPath } ] }
性能优化 - 优化打包效率 1. 优化 babel-loader 1 2 3 4 5 6 7 { test : /\.js$/ , loader : ['babel-loader?cacheDirectory' ], include : srcPath, exclude : /node_module/ }
2. IgnorePlugin 避免引用无用模块
例子:
1 2 3 4 5 plugins : [ new webpack.IgnorePlugin (/\.\/local/ , /moment/ ) ]
使用的地方
1 2 3 import moment from 'moment' import 'moment/locale/zh-cn' moment.locale ('zh-cn' )
3. noParse 1 2 3 4 module : { noParse : [/react\.min\.js$/ ] }
IgnorePlugin 直接不引于,代码中没有(同时优化产出代码)
noParse 引入,但不打包
4. HappyPack 多进程打包 JS 是单线程的,要开启多进程打包
提高构建速度,特别是对于多核 CPU
1 2 const HappyPack = require ('happypack' )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 module : { rules : [ { test : /\.js$/ , use : ['happypack/loader?id=babel' ], include : srcPath, } ] }, plugins : [ new HappyPack ({ id : 'babel' , loaders : ['babel-loader?cacheDirectory' ] }) ]
5. ParallelUglifyPlugin 多进程压缩代码 使用多进程压缩代码
1 2 const ParallelUglifyPlugin = require ('ParallelUglifyPlugin' )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 plugins : [ new ParallelUglifyPlugin ({ uglifyJS : { output : { beautify : false , comments : false , }, compress : { drop_console : true , collapse_vars : true , reduce_vars : true , } } }) ]
PS:
项目较大,打包较慢,开启多进程能提高速度
项目较小,打包很开,开启多进程返回会降低速度(进程开销)
按需使用
6. webpack 配置自动刷新和热更新 自动刷新 vs. 热更新
自动刷新: 整个网页全部刷新,速度慢,状态丢失
热更新: 新代码生效,页面不刷新,速度快,状态不丢失
wepack 有自带的配置字段 watch 配置自动刷新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 module .exports = { entry : { index : [ 'webpack-dev-server/client?http://localhost:8080/' , 'webpack/hot/dev-server' , path.join (srcPath, 'index.js' ), ], other : path.join (srcPath, 'other.js' ), }, devServer : { port : 8080 , progress : true , contentBase : distPath, open : true , compress : true , hot : true , proxy : { '/api' : 'http://localhost:3000' , '/api2' : { target : 'http://localhost:3000' , pathRewrite : { '/api2' : '' , }, }, }, }, };
使用的地方
1 2 3 4 5 6 7 8 9 10 11 12 import { sum } from './math' const sumRes = sum (10 , 20 )console .log ('sumRes' , sumRes)if (module .hot ) { module .hot .accept (['./math' ], () => { const sumRes = sum (10 , 30 ); console .log ('sumRes in hot' , sumRes); }); }
7. 使用 DllPlugin 动态链接库插件
前端框架如 vue、react 体积大、构建慢
比较稳定,不常升级版本
同一个版本只构建一次即可,不用每次都重新构建
webpack 已经内置 DllPlugin 支持
步骤:
通过 DllPlugin 打包出 dll 文件
通过 DllReferencePlugin 引用 dll 文件
性能优化 - 优化产出代码 好处:
体积更小
合理分包,不重复加载
速度更快、内存使用更少
1. 小图片 base64 编码 使用 url-loader 配置 limit
2. bundle 使用 hash 可以使用缓存
3. 懒加载 1 import ('./dynamic-data.js' ).then ()
4. 提取公共代码 optimization.splitChunks
5. IgnorePlugin IgnorePlugin 直接不引于,代码中没有无用的代码
6. 使用 cdn 加速 1 2 3 4 5 output : { filename : '[name].[contentHash:8].js' , path : distPath, publicPath : 'http://cdn.abc.com' },
output 中配置 publicPath,以及静态资源配置 publicPath
上传静态文件
7. 使用 mode production(包括 Tree-Shaking)
自动开启代码压缩
Vue React 等会自动删掉调试代码(如开发环境的 warning)
启用 Tree-Shaking,只有 ES6 Module 才能让 tree-shaking 生效
Tree-Shaking 举例
1 2 3 4 export const add = (a, b ) => a + b;export const multi = (a, b ) => a * b
在实际使用的地方只有使用到 add,那么 multi 就不会被打包
ES6 Module vs. Commonjs
ES6 Module 静态引入,编译时引入
commonjs 动态引入,执行时引入
只有 ES6 Module 才能静态分析,实现 Tree-shaking
8. Scope Hoisting 打包出来的代码中,每个文件一个函数会合并成一个函数
好处:
1 2 3 4 5 6 7 8 9 10 11 12 const ModuleConcatenationPlugin = require ('webpack/lib/optimize/ModuleConcatenationPlugin' )module 。exports = { resolve : { mainFields : ['jsnext:main' , 'browser' , 'main' ] }, plugins : [ new ModuleConcatenationPlugin () ] }
babel babel 只对语法进行转换,对于新的 api 不进行转换,例如 Promise 或者 [].includes
新的语法需要使用 babel-polyfill 进行转换
babel 也不管模块化,所以需要配合 webpack 使用
环境搭建 & 基本配置 .babelrc
1 2 3 4 5 6 { "presets" : [ [ "@babel/preset-env" ] ] , "plugins" : [ ] }
使用 babel 转换 js 文件
babel-polyfill polyfill 即布丁,babel-polyfill = core-js + regenerator,在 babel 7.4 之后弃用,推荐直接使用 core-js & regenerator
core-js & regenerator core-js 是一系列 polyfill 的合集
regenerator 对 generator 语法进行补充支持
配置按需引入 .babelrc
1 2 3 4 5 6 7 8 9 10 11 12 { "presets" : [ [ "@babel/preset-env" , { "useBuiltIns" : 'usage', "corejs" : 3 , } ] ] , "plugins" : [ ] }
babel-runtime babel-polyfill 会污染全局环境,
使用 babel-runtime 不会去污染全局环境
如果开发第三方库,必须使用 babel-runtime
构建流程概述 webpack 5 主要是内部效率优化
面试真题 1. 前端为何要进行打包和构建
代码层面
体积更小(tree-shaking、压缩、合并),加载更快
编译高级语言和语法(TS、ES6、模块化、scss)
兼容性和错误检查(polyfill、postcss、eslint)
研发流程方面
统一高效的开发环境
统一的构建流程和产出标准
集成公司的构建规范(提测、上线等)
2. module chunk bundle 的区别
module 各个源码文件,webpack 中一切皆模块
chunk 多模块合并成的,来源:entry、import()、splitChunk
bundle 最终的输入文件,一般一个 chunk 对应一个 bundle
chunk 是偏抽象的一个概念,bundle 是 chunk 的输出实体
3. loader vs. plugin loader: 模块转换器 如:less => css
plugin: 扩展插件,如 HtmlWebpackPlugin
4. 常见的 loader 和 plugin 有哪些 略
5. babel vs. webpack
babel 只关心语法,不关心 api 不关心模块
webpack 打包构建工具,是多个 loader plugin 的集合,通常会结合 babel 使用
6. 如何产出一个 lib 配置 output.library
7. babel-polyfill vs. babel-runtime 略
8. webpack 如何实现懒加载 & vue react 异步加载路由 import()
9. 为啥 proxy 不能被 polyfill 如 class 可以用 function 模拟
如 Promise 可以用 callback 模拟
但是 proxy 的功能无法用 Object.defineProperty 模拟
10. webpack 优化构建速度 略