webpack
成熟的工具,重点在于配置、使用、性能优化,原理不是高优
为什么需要webpack? webpack作用
- 优化打包效率
- 优化打包后代码: 压缩代码,整合代码,以网页加载更快
- 工程化,构建流程
🤔思考问题
- module chunk bundle 分别是什么意思?有何区别?
- loader和plugin的区别?
- webpack如何实现懒加载
- babel-runtime 和 babel-polyfill的区别
关于webpack5
- webpack5主要是内部效率的优化
- 对比webpack4没有太多的使用上的改动
- 升级webpack5的同时注意升级周边插件
基本配置
安装配置
初始化安装
npm install webpack webpack-cli html-webpack-plugin webpack-dev-server -D
打包命令
- webpack
拆分配置和merge
- webpack.common.js 公共配置
- webpack.dev.js 开发环境
- webpack.prod.js 生产环境
启动本地服务 dev-server 命令
- webpack-dev-server(webpack4)
- webpack serve(webpack5)
Module
rule
module: {
rules: [
{
test: /\.js$/,//Include all modules that pass test assertion.
use: ['babel-loader'],
include: srcPath,
exclude: /node_modules/
}
]
},
解析ES6 -> 编译为ES5
修改配置文件.babelrc
{
"presets": ["@babel/preset-env"]
}
@babel/preset-env 是 babel 7 架構下的一組 preset,能讓你用最新的 JavaScript 語法寫程式,並且智慧地根據瀏覽器的環境引入需要的 polyfill,節省手動管理 syntax transform 的時間,還能夠減少 bundle 檔案大小
devServer
- compress
- 启动gzip compressinon
- hot
- 启用 webpack 的 热模块替换 特性
- open
- 告诉 dev-server 在服务器已经启动后打开浏览器。
- port
- 指定监听请求的端口号
- proxy
- 代理 解决开发时的跨域问题
- 代理 解决开发时的跨域问题
解析图片文件
// import img
function insertImgElem(imgFile) {
const img = new Image();
img.src = imgFile;
document.body.appendChild(img)
}
import imgFile1 from './img/1.png';
insertImgElem(imgFile1);
import imgFile2 from './img/2.jpeg';
insertImgElem(imgFile2);
Image() 函数将会创建一个新的HTMLImageElement实例。
它的功能等价于 document.createElement('img')
webpack配置
npm i -D file-loader
npm i -D url-loader
- webpack.dev.js
module: {
rules: [
{
test: /\.(png|jpeg|jpg|git)$/,
use: 'file-loader'
}
]
}
- webpack.prod.js
mode: 'production',
module: {
rules: [
{
test: /\.(png|jpeg|jpg|git)$/,
use: {
loader: 'url-loader',
options: {
// 小于 5kb 的图片 base64 格式产出
// 否侧 依然用 file-loader 的形式,产出 url 格式
limit: 5 * 1024,
// 打包到 img 目錄下
outputPath: '/img1',
// 設置圖片的cdn地址
// publicPath: 'https://cdn.abc.com'
}
}
}
]
},
效果
好处:图片小 用base64 减少一次http请求 减少耗时
常见loader和plugin
处理样式文件
- postcss-loader
- less-loader
- css-loader
- style-loader
npm i -D postcss-loader less-loader css-loader style-loader
配置 webpack.common.js
module: {
rules: [
{
test: /\.css$/,
// loader 的执行顺序是: 从后往前
use: ['style-loader', 'css-loader', 'postcss-loader']// 加了postcss
},
{
test: /\.less$/,
// 增加 ‘less-loader',注意顺序
use: ['style-loader', 'css-loader', 'less-loader']
}
]
},
postcss-loader中配置自动生成前缀postcss.config.js
module.exports = {
plugins: [require('autoprefixer')]
}
transform: rotate(-45deg) 自动添加前缀的效果
高级配置
配置多入口
虽然说现在的项目都是SPA(单页面应用),但项目有时候需要产出多个页面
plugins: [
// 多入口 - 生成 index.html
new HtmlWebpackPlugin({
template: path.join(srcPath, 'index.html'),
filename: 'index.html',
// chunks 表示该页面要引用哪些 chunk (即上面的 index 和 other),默认全部引用
chunks: ['index'] // 只引用 index.js
}),
// 多入口 - 生成 other.html
new HtmlWebpackPlugin({
template: path.join(srcPath, 'other.html'),
filename: 'other.html',
chunks: ['other'] // 只引用 other.js
})
]
打包输出
抽离和压缩CSS(重要)
抽离公共代码(重要)
-
为什么需要抽离公共代码和第三方代码?
- 当引入第三方库时,如果不独立打包,每次修改业务逻辑代码,都会重新打包第三方库,导致加载慢。
- 两个文件引入了同一个模块,会打包两次
-
配置为独立的chunks
webpack如何实现异步加载JS(懒加载)(重要)
默认支持的语法,不用配置
setTimeout(() => {
import ('./danymic.js').then(res => {// 打包时会独立成一个chunk
console.log('res :>> ', res.default.message);
})
}, 1500)
处理React和Vue
-
处理React(解析JSX语法)
安装
npm install --save-dev @babel/preset-react.babelrc
注意:
env参数可能很快将被废弃{ "presets": ["@babel/preset-react"] }配置完webpack中配置了'babel-loader',babel-loader会使用@babel/preset-react 去解析JSX语法
-
处理vue(vue-loader)
参考:www.babeljs.cn/docs/babel-…
- 处理Vue
module chunk bundle的区别
-
module-各个源码文件,webpack中一切皆模块 (能被引入的文件都是模块,不管是什么类型,比如css js 图片)
-
chunk-多模块合并成的,如entry、import() 、splitChunk都可以生成chunk
-
bundle-最终的输出文件
优化构建速度
让你聊聊webpack的时候,不要跟背书式回答问题,要通过分析问题的思路来回答问题
优化babel-loader(一般用于开发环境)
module: {
rules: [
// 优化: 1. 开启缓存 2. include exclude明确范围,写其中一个即可以
{
test: /\.js$/,
loader: ['babel-loader?cacheDirectory'], // 开启缓存
include: srcPath,
// exclude: /node_modules/
},
]
}
开启缓存: 用cacheDirectory,只要ES6代码没变,就不会重新编译,会缓存下来 第二次编译时,没改的部分使用缓存
IgnorePlugin(可用于生产环境)
避免一些模块引入。如果不用IgnorePlugin,可能会出现一些问题,比如打包的体积太大,打包慢
例如:moment会支持多语言,如何只引入中文模块?
// 忽略 moment 下的全部 /locale目录
new webpack.IgnorePlugin(/\.\/locale/, /moment/)
按需引入
// 业务代码中动态引入语言包
import 'moment/locale/zh-cn'
noParse(忽略大型的 library 可以提高构建性能)(可用于生产环境)
noParse避免重复打包 module: { noParse: [/react.min.js$/] }
IgnorePlugin和noParse区别:
- IgnorePlugin直接不引入,代码中没有
- noParse(类似vue.min.js已经模块化处理过)引入,但不打包
happyPack 多进程打包(可用于生产环境)
JS单线程,开启多进程打包 提高构建速度(特别是多核CPU)
ParallelUglifyPlugin优化压缩(可用于生产环境)
必须用于生产环境,压缩代码
- webpack内置Uglify工具压缩JS
- JS单线程,开启多线程压缩更快
- 和happypack同理
// 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
plugins: [
new ParallelUglifyPlugin({
// 传递给 UglifyJS 的参数
// (还是使用 UglifyJS 压缩,只不过帮助开启了多进程)
uglifyJS: {
output: {
beautify: false, // 最紧凑的输出
comments: false, // 删除所有的注释
},
compress: {
// 删除所有的 `console` 语句,可以兼容ie浏览器
drop_console: true,
// 内嵌定义了但是只用到一次的变量
collapse_vars: true,
// 提取出出现多次但是没有定义成变量去引用的静态值
reduce_vars: true,
}
}
})
]
- 关于是否需要开启多进程
- 项目较大的时候,开启会加快打包
- 项目较小的时候,开启会变慢,因为多进程开销
自动刷新(不可用于生产环境)
一般不用自我配置,因为开发的时候一般会用devServer,会自动开启自动刷新
// 一般不用自我配置,因为开发的时候一般会用devServer,会自动开启自动刷新
// watch: true, // 开启监听,默认为 false
// // 开启监听后,webpack-dev-server 会自动开启刷新浏览器
// // 监听配置
// watchOptions: {
// ignored: /node_modules/, // 忽略哪些
// // 监听到变化发生后会等300ms再去执行动作,防止文件更新太快导致重新编译频率太高
// // 默认为 300ms
// aggregateTimeout: 300,
// // 判断文件是否发生变化是通过不停的去询问系统指定文件有没有变化实现的
// // 默认每隔1000毫秒询问一次
// poll: 1000
// }
热更新(不可用于生产环境)
区别
- 自动刷新:整个网页全部刷新,速度慢,状态会丢失(比如填写表单时,表单内容会丢失,或者路由跳转了很多层,网页刷新后会跳到首页)
- 热更新:新代码生效,网页不刷新,状态不丢失
开启热更新配置
配置哪些模块需要热更新
DllPlugin拆分 bundles(不可用于生产环境)
-
前端框架如vue React 体积大,构建慢
-
较稳定,不常升级
-
同一个版本只需要构建一次,不用每次都重新构建
-
webpack已内置DllPlugin支持
-
DllPlugin 打包出dll文件
- 打包后产出文件
- react.dll.js 打包后的内容
- react.manifest.json 索引 配置参考
- 打包后产出文件
webpack.dll.js
const path = require('path')
const DllPlugin = require('webpack/lib/DllPlugin')
const { srcPath, distPath } = require('./paths')
module.exports = {
mode: 'development',
// JS 执行入口文件
entry: {
// 把 React 相关模块的放到一个单独的动态链接库
react: ['react', 'react-dom']
},
output: {
// 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称,
// 也就是 entry 中配置的 react 和 polyfill
filename: '[name].dll.js',
// 输出的文件都放到 dist 目录下
path: distPath,
// 存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react
// 之所以在前面加上 _dll_ 是为了防止全局变量冲突
library: '_dll_[name]',
},
plugins: [
// 接入 DllPlugin
new DllPlugin({
// 动态链接库的全局变量名称,需要和 output.library 中保持一致
// 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值
// 例如 react.manifest.json 中就有 "name": "_dll_react"
name: '_dll_[name]',
// 描述动态链接库的 manifest.json 文件输出时的文件名称
path: path.join(distPath, '[name].manifest.json'),
}),
],
}
- DllReferencePlugin 使用dll文件 配置
优化产出代码
- 体积更小
- 合理分包,不重复加载
- 速度更快,内存使用更少
使用生产环境
mode: 'production',打包生产环境代码
- 自动开启代码压缩
- Vue React等框架会自动删掉调试代码(如开发环境的warning)
- 自动开启Tree-Shaking(摇晃树,把没有用的东西摇掉)
什么是Tree-Shaking
- js和css都可以treeShaking
- ES6 Module才能让tree-shaking生效,commonjs不可以。因为ES6 Module是静态引入,Commonjs是动态引入
tree-shaking会把一些没有用到的函数删除
小图片使用base64编码
module: {
rules: [
// 图片 - 考虑 base64 编码的情况
{
test: /\.(png|jpg|jpeg|gif)$/,
use: {
loader: 'url-loader',
options: {
// 小于 5kb 的图片用 base64 格式产出
// 否则,依然延用 file-loader 的形式,产出 url 格式
limit: 5 * 1024,
// 打包到 img 目录下
outputPath: '/img1/',
// 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)
// publicPath: 'http://cdn.abc.com'
}
}
},
]
}
bundle加hash
提取公共代码
splitChunks
懒加载
使用CDN加速
webpack的scope hosting(作用域提升)
默认情况不开启scope hosting
- 产生多个函数,每个函数都有一个作用域,对js代码执行和内存消耗非常不友好
开启后
- 多个函数合并为一个函数,作用域减少
- 代码体积更小
- 创建函数作用域更少
- 代码可读性更好
来自:深入浅出webpack
同时,考虑到 Scope Hoisting 依赖源码需采用 ES6 模块化语法,还需要配置
mainFields。 原因在 4-10 使用 TreeShaking 中提到过:因为大部分 Npm 中的第三方库采用了 CommonJS 语法,但部分库会同时提供 ES6 模块化的代码,为了充分发挥 Scope Hoisting 的作用,需要增加以下配置:
module.exports = {
resolve: {
// 针对 Npm 中的第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法的文件
mainFields: ['jsnext:main', 'browser', 'main']
},
};
webpack4升级 webpack5 以及周边插件后
Babel
Babel作用
ES6模块化,浏览器并不完全支持,需要通过Babel来编译成ES5
例如:将let const 编译为var;将class编译为function
环境搭建和基本配置
package.json
{
"devDependencies": {
"@babel/cli": "^7.7.5",
"@babel/core": "^7.7.5",
"@babel/plugin-transform-runtime": "^7.7.5",
"@babel/preset-env": "^7.7.5"
},
"dependencies": {
"@babel/polyfill": "^7.7.0",
"@babel/runtime": "^7.7.5"
}
}
.babelrc 基本配置
{
"presets": [
[
"@babel/preset-env"
]
],
"plugins": [
]
}
presets
- @babel/preset-env是很多plugin的一个集合,可以转换ES6 7 8语法
- 是一个babel插件的集合,预设置,代替我们写很多的plugins
- @babel/preset-flow
- @babel/preset-react(转换jsx语法)
- @babel/preset-typescript(转换ts语法)
babel和babel-loader的区别
- babel是编译ES6的核心工具
- babel-loader是用babel封装后,将babel用于webpack打包流程。类似less和less-loader
babel-polyfill
什么是babel-polyfill?
对一些浏览器不支持的函数做补丁或者兼容
- babel只负责解析语法,不处理API,也不管模块化(webpack处理模块化)
- webpack把babel-polyfill引入进来,babel处理完交给webpack。polyfill解析Promise和includes语法,API就可以在浏览器正常运行
@babel/polyfill与core-js关系
@babel/polyfill可以看作是:core-js加regenerator-runtime。
regenerator-runtime是generator以及async/await的运行时依赖
单独使用@babel/polyfill会将core-js全量导入,造成项目打包体积过大。
从Babel v7.4.0[5]开始,
@babel/polyfill被废弃了,可以直接引用core-js与regenerator-runtime替代
为了解决全量引入core-js造成打包体积过大的问题,我们需要配合使用@babel/preset-env。
babel-polyfill如何按需引入
为什么要按需引入
babel-polyfill文件较大,如果只使用一部分功能,无需全部引入。(已废弃原因)
babel-runtime
babel-polyfill的问题
- 会污染全局环境。如果做一个独立的系统,则没问题,如果做一个第三方库,就会可能产生与使用方命名冲突问题。
babel-runtime: 避免自行引入polyfill时导致的污染全局命名空间的问题
@babel/plugin-transform-runtime 的作用是将 helper 和 polyfill 都改为从一个统一的地方引入,并且引入的对象和全局变量是完全隔离的,这样解决了上面的两个问题。
参考
思考
前端为什么需要打包构建
代码层面
- 打包体积会更小(tree-shaking,代码压缩,合并),加载速度更快
- 编译高级语言语法(TS,ES6,less,scss,模块化)
- 兼容性和错误提示(babel-polyfill、postcss、eslint)
打包流程、前端工程化方面
- 統一、高效的开发环境
- 统一的构建流程和产出标准
- 集成公司的构建规范(提测、上线等)
module chunk bundle的区别
loader和plugin的区别
- loader模块转换器 如less -> css
- plugin扩展插件 如HtmlWebpackPlugin
常见的loader和plugin有哪些
babel和webpack的区别
- babel-js新语法编译工具,不关心模块化
- webpack是打包构建工具,是多个loader plugin的集合
如何产出一个lib
babel-polyfill和babel-runtime的区别
- babel-polyfill会污染全局
- babel-runtime不会污染全局
- 开发第三方lib要用babel-runtime,避免与使用方产生冲突
webpack如何使用懒加载
- import()
- 结合Vue React 异步组件
- 结合Vue-router React-router异步加载路由
为何Proxy不能被Polyfill
因为(function callback是es5语法)
- Class可以用function模拟
- Promise可以用callback来模拟
- 但Proxy的功能用Object.defineProperty无法模拟
Polyfill 是一块代码(通常是 Web 上的 JavaScript),用来为旧浏览器提供它没有原生支持的较新的功能。 proxy没有任何现成的语法可以模拟到,所以无法Polyfill
常见性能优化方法
从构建速度和产出代码两个方面分析
优化构建速度
总结
- 可用于生产环境的
- 优化babel-loader
- IgnorePlugin
- noParse
- happyPack多进程打包
- ParallelUgifyPlugin优化压缩
- 不能用于生产环境
- 自动更新
- 热更新
- DllPlugin
特别强调:热更新万万不能用于生产环境,在代码中写的热更新范围,在生产打包时一定要删掉,否则生产环境会有问题!
优化产出代码
- 小图片通过base64方式
- bundle加hash
- 懒加载
- 提取公共代码
- 使用cdn加速
- IgnorePlugin
- 使用Production模式
- 开启Scope Hosting