构建配置抽离成npm包的意义
通用性
- 业务开发和无需关注构建配置
- 统一团队构建脚本
可维护性
- 构建配置合理的拆分
- README文档、changeLog文档等
质量
- 冒烟测试、单元测试、测试覆盖率
- 持续集成
构建配置管理的可选方案
- 通过多个配置文件管理不同的环境,
webpack --config
参数进行控制 - 将构建配置设计成一个库,比如
hjs-webpack
、Neutrino
、webpack-blocks
- 抽成一个工具进行管理,比如
create-react-app
,kyt
,nwb
- 将所有的配置放在一个文件,通过
--env
参数控制分支选择
构建配置包设计
- 通过多个配置文件管理不同环境的webpack配置。
- 基础配置
webpack.base.config.js
- 开发环境
webpack.dev.config.js
- 生产环境
webpack.prod.config.js
- SSR环境
webpack.ssr.config.js
通过
webpack-merge
组合配置
- 抽离成一个npm包统一管理
- 规范: git commit 日志、README、 ESlint规范、 Semver规范
- 质量: 冒烟测试、单元测试、测试覆盖率和 CLI
冒烟测试
冒烟测试是指对提交测试的软件在进行详细深入的测试之前而进行的预测试,这种预测试的主要目的 是暴露导致软件需重新发布的基本功能失效等严重问题。
执行
- 构建是否成功
- 每次构建完成build目录是否有内容输出
- 是否有JS、CSS等静态资源文件
- 是否有HTML文件
git提交
- 提交格式要求
<type>(<scope>):<subject>
<blank line>
<body>
<blank line>
<footer>
type
代表每次提交的类型。所有type类型如下:
- feat: 新增feature
- fix :修复bug
- docs:仅仅修改了文档, 比如
README
,CHANGELOG
,CONTRIBUTE
等等 - style: 仅仅修改了空格、格式缩进、逗号等等,不改变代码逻辑
- refactor: 代码重构,没有加新功能或者修复bug
- perf: 优化相关,比如提升性能、体验
- test: 测试用例, 包括单元测试、集成测试等
- chore: 改变构建流程或者增加依赖库 工具等
- revert: 回滚到上一个版本
版本规范
-
遵守semver规范的优势
- 避免出现循环依赖
- 依赖冲突减少
-
语义化版本规范格式
- 主版本号: 做了不兼容的api修改
- 次版本号:做了向下兼容的功能性新增
- 修订号: 做了向下兼容的问题修正
-
先行版本号
先行版本号可以作为发布正式版之前的版本,格式是在修订版本号后面加上一个连接号(-),再加上一连串以(.)分割的标识符,
标识符可以由英文、数字和连接号[0-9A-Za-z-]
组成。- alpha: 是内部测试版,一般不向外部发布,会有很多bug。一般只有测试人员使用。
- beta : 也是测试版,这个阶段的版本会一直加入新的功能。 在alpha版之后推出。
- rc :
Release Candidate
系统平台上就是发行候选版本。RC版不会再 加入新的功能了,主要着重于除错。
webpack构建速度和体积优化策略
stats
-
初级分析: 使用webpack内置的
stats
:构建的统计信息 -
package.json中使用stats。
"scripts":{ "build:stats":"webpack --env production --json > stats.json", ... }
-
nodejs中使用
const webpack = require("webpack");
const congfig = require("./webpack.config.js")("production");
webpack(config,(err,stats)=> {
if(err){
return console.error(err)
}
if(stats.hasErrors()){
return console.error(stats.toString("errors-only"));
}
console.log(stats);
})
- 缺点:颗粒度太粗,看不出问题所在。
speed-measure-webpack-plugin
:速度分析
- 代码示例:
const SpeedMeasureWebpackPlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasureWebpackPlugin();
const webpackConfig = smp.wrap({
plugins:[
new MyPlugin(),
new MyOtherPlugin()
]
})
//可以看到每个loader和插件执行耗时
webpack-bundle-analyzer
:分析体积
- 代码示例:
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins:[
new BundleAnalyzerPlugin()
]
}
//构建完成后会在8888端口展示大小
- 可以分析哪些问题?
- 依赖的第三方模块的大小
- 业务里面组件代码的大小
使用高版本的webpack和nodejs。
- 高版本的webpack和nodejs, 构建时间降低了 60%- 98%。
- 使用webpack4:优化原因
- v8带来的优化(for...of代替forEach, Map和 Set替代Object、includes替代indexOf)
- 默认使用更快的md4 hash算法
- webpack AST 可以直接从 loader传递给 AST,减少解析时间
- 使用字符串方法代替正则表达式
多进程/多实例构建
- 资源并行解析可选方案
- thread-loader :官方
- parallel-webpack
- HappyPack
- 使用HappyPack解析资源
- 原理:每次webpack解析一个模块,HappyPack会将它及它的依赖分配给worker线程中
- 代码示例
exports.plugins = {
new HappyPack({
id:'jsx',
threads:4,
loaders:['babel-loader']
}),
new HappyPack({
id:'styles',
threads:2,
loaders: ['style-loader','css-loader','less-loader']
})
}
- 使用thread-loader解析资源
- 每次webpack解析一个模块,thread-loader会将它及它的依赖分配给worker线程中
- 代码示例
module.exports = {
...,
module:{
rules:[
{
test:/.js$/,
use:[
{
loader:'thread-loader',
options:{
workers:3
}
},
'babel-loader'
]
}
]
}
}
并行压缩
- 使用
parallel-uglify-plugin
插件
- 代码示例
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
module.exports = {
plugins:[
new ParallelUglifyPlugin({
uglifyJS:{
output:{
beautify:false,
comments:false
},
compress:{
warning: false,
drop_console: true,
collapse_vars: true,
reduce_vars: true
}
}
})
]
}
- 使用
uglify-webpack-plugin
开启 parallele参数。
- 代码示例:
const UglifyPlugin = require('uglify-webpack-plugin');
module.exports = {
plugins:[
new UglifyPlugin({
uglifyOptions:{
warning:false,
parse:{},
compress:{},
mangle:true,
output:null,
toplevel: false,
nameCache:null,
ie8:false,
keep_fnames: false
},
parallel:true
})
]
}
terser-webpack-plugin
开启parallel参数。
- 代码示例
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
optimization:{
minimizer:[
new TerserPlugin({
parallel:4
})
]
}
}
分包:设置externals
- 思路:将react,react-dom基础包通过cdn引入,不打入bundle中。
- 方法:使用 html-webpack-externals-plugin
- 代码示例:
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');
plugins:[
new HtmlWebpackExternalsPlugin({
externals:[
{
module:'react',
entry:'//11.url.cn/now/lib/15.1.0/react-width-addons.min.js?_bid=3123',
global:'React'
},
{
module:'react-dom',
entry:'//11.url.cn/now/lib/15.1.0/react-dom.min.js?_bid=3123',
global:'ReactDOM'
}
]
})
]
进一步分包:预编译资源模块
- 思路:将react,react-dom,redux, react-redux基础包和业务基础包打包成一个文件
- 方法:使用DLLOPlugin 进行分包,DllReferencePlugin对 manifest.json引用。
- 使用DLLPlugin进行分包
const path = reqire('path');
const webpack = require('webpack');
module.exports = {
context:process.cwd(),
resolve:{
extensions:['.js','.jsx','.json','.less','.css'],
modules:[__dirname,'node_modules']
},
entry:{
library:[
'react',
'react-dom',
'redux',
'react-redux'
]
},
output:{
filename:'[name].dll.js',
path:path.resolve(__dirname,'./build/library'),
library:'[name]'
},
plugins:[
new webpack.DllPlugin({
name:'[name]',
path:'./build/library/[name].json'
})
]
}
- 使用DllReferencePlugin引用manifest.json【在
webpack.config.js
中使用】
module.exports = {
plugins:[
new webpack.DllReferencePlugin({
manifest:require('./build/library/manifest.json')
})
]
}