entry(入口)
单入口:entry是一个字符串
module.exports={
entry:'./path/file.js'
}
多入口:entry是一个对象
module.exports={
entry:{
app:'./src/app.js',
adminApp:'./src/adminApp.js'
}
}
output(输出)
单入口配置
module.exports={
output:{
filename:'bundle.js',
path:_dirname+'/dist'
}
}
多入口配置
通过占位符确保文件名称的唯一
module.exports={
output:{
filename:'[name].js',
path:_dirname+'/dist'
}
}
loaders
webpack开箱即用只支持JS和JSON两种文件类型,通过Loaders去支持其他文件类型并且把它们转化成有效的模块,并且可以添加到依赖图中。
本身是一个函数,接受源文件作为参数,返回转换的结果。
test对应匹配规则,use匹配loader
module.exports={
module:{
rules:[
{test:/\.txt$/,use:'raw-loader'}
]
}
}
Plugins
插件用于bundle文件的优化,资源管理和环境变量注入。作用于整个构建过程
module.exports={
plugins:[new HtmlWebpackPlugin({template:'./src/index.html'})]
}
module
指定当前的构建环境是:production、development还是none(什么都不做),
设置mode可以使用webpack内置的函数,默认值为production
热更新:webpack-dev-server
WDS不刷新浏览器
WDS不输出文件,而是放在内存中
使用的是HotModuleReplacementPlugin插件,设置hot:true之后会自动引用,不需要在plugin中添加
文件指纹
文件指纹就是打包后输出的文件名的后缀
Hash:和整个项目的构建相关,只要项目文件有修改,整个项目构建的hash值就会修改
Chunkhash:和webpack打包的chunk有关,不同的entry会生成不同的chunkhash值
contenthash:根据文件内容来定义hash,文件内容不变,则contenthash不变
推荐使用contenthash
js、css、html压缩
js压缩:webpack4内置uglifyjs-webpack-plugin
css压缩:mini-css-extract-plugin + cssnano
html压缩:html-webpack-plugin
资源内联
使用场景:
- 页面初始化需要做的的初始化脚本
- css内联避免页面闪动
- 减少网络请求(小图片或者字体内联)
html和js内联使用raw-loader
css内联使用:
- style-loader
- html-inline-css-webpack-plugin
多页面打包通用方案
动态获取entry和设置html-webpack-plugin数量
利用glob.sync
提取页面公共资源
- 基础库分离(vue、react)
将基础包通过cdn引入,不打入bundle中,使用html-webpack-externals-plugin - 公共脚本分离(utils中的工具) 利用webpack内置的SplitChunksPlugin进行公共脚本分离
tree shaking(摇树优化)
- tree shaking只能作用于ES6模块,Babel的预置默认把任何模块转译成CommonJS模块,你可以简单设置modules: false来解决此问题。
- production mode的情况下默认开启
- 在具有副作用的代码中tree-shaking会失效,可以使用webpack-deep-scope-plugin插件来优化,但是如果使用了babel+代码具有副作用的情况下,这个插件还是会失效
scope hoisting(打包后代码的优化)
production模式默认开启
打包后有大量函数闭包包裹代码,导致体积增大,运行代码时创建的函数作用域变多,内存开销变大。
import会被转换成__webopack_require
通过scope hoisting插件可以解决这个问题,webpack mode为production时默认开启,必须是ES6语法
plugins:[
new webpack.optimize.ModuleConcatenationPlugin()
]
动态分割和动态import
适用场景:
- 抽离相同代码到一个共享块
- 脚本懒加载,使用初始下载的代码更小(首屏优化) 懒加载js脚本的方式:
- conmmonJS:require.ensure
- ES6:动态import(目前还没有原生支持,需要babel转换)
如何使用动态import? - 安装babel插件
npm i @babel/plugin-syntax-dynamic-import --save-dev
- 配置.babelrc文件
"plugin":["@babel/plugin-syntax-dynamic-import"]
打包组件库和基础库
- 在生成开发版文件和min版文件时,配置mode:'none'来避免打包后文件都为压缩状态的情况
const TerserPlugin=require('terser-webpack-plugin')
module.exports={
mode:"none",
entry:{
'large-number':'./src/index.js', // 测试环境代码
'large-number.min':'./src/index.js' // 线上环境代码
},
output:{
filename:'[name].js',
library:'largeNumber',
libraryTarget:'umd', // 用于多环境引入
libraryExport:'default' // 去除引用时额外的.default
},
optimization:{
minimize:true,
minimizer:[
new TerserPlugin({ // TerserPlugin插件支持压缩ES6代码
include:/\.min\.js$/, // 匹配只压缩.min.js文件
})
]
}
}
构建日志
统计信息stats
Preset | Alternative | Description |
---|---|---|
'errors-only' | none | 只在发生错误时输出 |
minimal | none | 只在发生错误或有新的编译时输出 |
none | false | 没有输出 |
normal | true | 标准输出 |
verbose | none | 全部输出 |
生产环境配置
module.exports = {
stats:'errors-only' // 推荐
}
开发环境配置
devServer: {
contentBase: './dist',
hot: true,
stats: 'errors-only'
},
优化命令行的构建日志,使用friendly-errors-webpack-plugin
plugins:[
new FriendlyErrorsWebpackPlugin()
],
stats:'errors-only' // 需要搭配这条命令使用
构建配置管理的可选方案
- 通过多个配置文件管理不同环境的构建,webpack --config 参数进行控制
- 将构建配置设计成一个库
- 抽成一个工具进行管理,比如:create-react-app
- 将所有的配置放在一个文件,通过--env参数控制分支选择
冒烟测试
- 判断是否构建成功
- 检测是否有内容输出:
- 是否有js、css等静态资源文件
- 是否有HTML文件
持续集成
优点:
- 快速发现错误
- 防止分支大幅偏离主干
核心措施是,代码集成到主干之前,必须通过自动化测试。只要有一个测试用例失败,就不能集成。
接入Travis CI
- https://travis-ci.org/ 使用gitHub账号登录
- 在https://travis-ci.org/account/repositories 为项目开启
- 项目根目录下新增.travis.yml
travis.yml文件内容
- install安装项目依赖
- script运行测试用例
编写好的构建包发布到npm上
添加用户:npm adduser
升级版本
- 升级补丁版本号:npm version patch
- 升级小版本号:npm version minor
- 升级大版本号:npm version major
发布版本:npm publish
git规范和Changelog生成
良好的Git commit规范优势:
- 加快Code Review的流程
- 根据Git Commit的元数据生成Changelog
- 后续维护者可以知道Feature被修改的原因
技术方案
提交格式要求
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
type代表某次提交的类型,比如是修复一个bug还是增加一个新的feature.所有的type类型如下:
- build:主要目的是修改项目构建系统(例如 glup,webpack,rollup 的配置等)的提交
- ci:主要目的是修改项目继续集成流程(例如 Travis,Jenkins,GitLab CI,Circle等)的提交,
- docs:仅仅修改了文档,比如README,CHANGELOG,CONTRIBUTE等等
- feat:新增feature
- fix:修复bug
- pref:优化相关,比如提升性能、体验
- refactor:代码重构,没有加新功能或者修复bug
- revert:回滚到上一个版本
- style:仅仅修改了空格、格式缩进、逗号等等,不改变代码逻辑
- test:测试用例,包括单元测试、集成测试等
- chore:不属于以上类型的其他类型
安装
npm install husky --save-dev
npm install --save-dev @commitlint/{cli,config-conventional}
配置commitlint.config.js
module.exports = {extends: ['@commitlint/config-conventional']};
配置package.json
{
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
}
}
速度分析:使用speed-measure-webpack-plugin
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');
const smp = new SpeedMeasureWebpackPlugin();
module.exports = smp.wrap({ // 用smp.wrap包裹配置文件
})
体积分析:使用webpack-bundle-analyzer
const BundleAnalyzerPlugin=require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports={
plugin:[
new BundleAnalyzerPlugin()
]
}
构建完成后会在8888端口展示
加快构建速度:升级webpack和node版本
使用webpack4:优化原因
v8带来的优化(for of替代forEach、Map和Set替代Object、includes替代indexOf)
默认使用更快的md4 hash算法
wepacks AST 可以直接从loader传递给AST,减少解析时间
使用字符串方法替代正则表达式
加快构建速度:多进程/多实例构建
使用webpack官方维护插件:thread-loader解析资源
原理:每次webpack解析一个模块,thread-loader会将它及它的依赖分配给worker线程中
npm install --save-dev thread-loader
module.exports = {
module: {
rules: [
{
test: /\.js$/,
include: path.resolve("src"),
use: [
"thread-loader", // thread-loader要写在loader最前面
// your expensive loader (e.g babel-loader)
]
}
]
}
}
加快构建速度:多进程/多实例并行压缩代码
使用terser-webppack-plugin 开启parallel参数
const TerserPlugin=require('terser-webpack-plugin');
module.exports={
optimization:{
minimizer:[
new TerserPlugin({
parallel:4
})
]
}
}
加快构建速度:进一步分包-预编译资源模块
使用html-webpack-externals-plugin的缺点:每一个基础包都要引入并配置,splitchunk也会再次解析
思路:将reat\react-dom\redux\react-redux基础包和业务基础包打包成一个文件
方法:使用DLLPlugin进行分包,DllReferencePlugin对manifest.json引用
一般需要单独创建webpack.dll.js配置文件
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
library: [
'react',
'react-dom'
]
},
output: {
filename: '[name]_[chunkhash].dll.js',
path: path.join(__dirname, 'build/library'),
library: '[name]'
},
plugins: [
new webpack.DllPlugin({
name: '[name]_[hash]',
path: path.join(__dirname, 'build/library/[name].json')
})
]
};
在webpack中引用
module.exports={
plugins:[
new webpack.DllReferencePlugin({
manifest:require('./build/library/manifest.json')
})
]
}
在webpack4中性能方面提升不大,但在分包作用中还是有用的
加快构建速度:缓存
目的:提升二次构建速度
缓存思路:
- babel-loader开启缓存
- terser-webpack-plugin 开启缓存
- 使用cache-loader或者hard-source-webpack-plugin(推荐hard-source-webpack-plugin,配置更简单)
加快构建速度:缩小构建目标
目的:尽可能的少构建模块
比如:babel-loader不解析node_modules (各种exclude和include)
减少文件搜索范围
- 优化resolve.modules配置(减少模块搜索层级)
- 优化resolve.mainFields配置
- 优化resolve.extensions配置
- 合理使用alias
modules.exports={
resolve:{
alias:{
react:path.resolve(__dirname,'./node_modules/react/dist/react.min.js'),
},
modules:[path.resolve(__dirname,'node_modules')],
extensions:['.js'],
mainFields:['main']
}
}
treeshaking-删除无用的js和css
无用的CSS如何删除掉?
PurifyCSS:遍历代码,识别已经用到的CSS class
uncss:HTML需要通过jsdom加载,所有的样式通过PostCSS解析,通过document.querySelector来识别在html文件里面不存在的选择器
在webpack中如何使用PurifyCSS?
PurifyCSS-webpack-plugin不再维护所以使用:
purgecss-webpack-plugin+mini-css-extract-plugin配合使用
const PurgecssPlugin = require('purgecss-webpack-plugin');
const PATHS={
src:path.join(__dirname,'src')
}
plugins:[
new PurgecssPlugin({
paths:glob.sync(`${PATHS.src}/**/*`,nodir:true) // 插件要求路径必须是绝对路径
})
]
图片压缩
使用image-webpack-loader
Imagemin的优点分析
- 有很多定制选项
- 可以引入更多第三方优化插件,例如pngquant
- 可以处理多种图片格式
Imagemin的压缩原理
pngquant:是一款PNG压缩器,通过将图像转换为具有alpha通道(通常比24/32位PNG文件小60-80%)的更高效的8位PNG格式,可显著减少文件大小。
pngcrush: 其主要目的是通过尝试不同的压缩级别和PNG过滤方法来降低PNG IDAT数据流的大小。
optipng:其设计灵感来自于pngcrush。optipng可将图像文件重新压缩为更小尺寸,而不会丢失任何信息。
tinypng:也是将24位png文件转化为更小有索引的8位图片,同时所有非必要的metadata也会被剥离掉
npm i image-webpack-loader -D
rules: [{
test: /\.(gif|png|jpe?g|svg)$/i,
use: [
'file-loader',
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 65
},
// optipng.enabled: false will disable optipng
optipng: {
enabled: false,
},
pngquant: {
quality: [0.65, 0.90],
speed: 4
},
gifsicle: {
interlaced: false,
},
// the webp option will enable WEBP
webp: {
quality: 75
}
}
},
],
}]
构建体积优化:动态polyfill
使用Polyfill Service
原理:识别User Agent,下发不同的Polyfill
通过插入https://polyfill.io/v3/polyfill.min.js
来实现动态polyfill
国内可以使用阿里的服务http://polyfill.alicdn.com/polyfill.min.js
,速度更快
使用方法:
<script src="https://polyfill.io/v3/polyfill.min.js"></script>
配置
px自动转换成rem
需要在html文件中内联引入lib-flexible,一起使用,可以添加exclude去掉不想转换的库,也可以添加/no/的语法去设置某一行样式不进行转换
{
loader: 'px2rem-loader',
options: {
remUnit: 75, // 如果是750的设计稿
remPrecision: 8
}
}
自动添加css前缀
{
loader: 'postcss-loader',
options: {
plugins: () => [
require('autoprefixer')({
browsers: ['last 2 version', '>1%', 'ios 7']
})
]
}
自动清除构建目录产物
const CleanWebpackPlugin = require('clean-webpack-plugin');
plugins: [
new webpack.HotModuleReplacementPlugin(),
new CleanWebpackPlugin(),
new FriendlyErrorsWebpackPlugin()
].concat(htmlWebpackPlugins)
文件监听
webpack开启监听模式有两种方式:
- 启动webpack命令时,带上 --watch 参数
- 再配置webpack.config.js 中设置watch:true
文件监听的原理分析: 轮询判断文件的最后编辑时间是否变化 某个文件发生了变化,并不会立刻告诉监听者,而是先缓存起来,等aggregateTimeout
module.export={
// 默认false,
watch:true,
// 只有开启监听模式时,watchOptions才有意义
watchOptions:{
// 默认为空,忽略文件夹,支持正则,支持数组
ignored:/node_modules/,
// 监听到变化发生后会等300ms再去执行,默认300ms
aggregateTimeout:300,
// 轮询间隔,默认每秒检查一次变动
poll:1000
}
}
热更新
需要webpack-dev-server
,配置webpack文件
devServer: {
hot: true
}
devtool分类
这么多模式,到底试用哪个?
- 开发环境推荐:
cheap-module-eval-source-map
- 生产环境推荐:
cheap-module-source-map
- 相关解释:
- 大部分情况我们调试并不关心列信息,而且就算 sourcemap 没有列,有些浏览器引擎(例如 v8) 也会给出列信息,所以我们使用 cheap 模式可以大幅提高 souremap 生成的效率。
- 使用 module 可支持 babel 这种预编译工具(在 webpack 里做为 loader 使用)。
- 使用 eval 方式可大幅提高持续构建效率,参考webapck devtool速度对比列表,这对经常需要边改边调的前端开发而言非常重要
- 直接将sourceMap放入打包后的文件,会明显增大文件的大小,不利于静态文件的快速加载;而外联.map时,.map文件只会在F12开启时进行下载(sourceMap主要服务于调试),故推荐使用外联.map的形式。