这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战
找了张翔哥的照片,给奥运健儿加油打气!!!🤞
进入正题,进入正题!
1. 自动清理构建目录
- 避免反复的手动或者命令去删除dist文件
plugins: [
new MiniCssExtractPlugin({
filename: "[name]_[contenthash:8].css",
}),
new OptimizeCss({
assetNameRegExp: /\.css$/g,
cssProcessor: require("cssnano"),
}),
// 默认 删除output指定的输出目录
new CleanWebpackPlugin(),
]
2. 资源内联
- 代码层面
- 页面框架的初始化脚本
- 上报相关打点
- css内联避免页面闪动
- 请求层面:减少http网络请求
- 小图片或者字体内联(url-load)
<head>
// raw-loader 内联html
// 使用 0.5.1版本
<%= require('raw-loader!./meta.html') %>
<title>Document</title>
// raw-loader 内联js
<script>
<%= require('raw-loader!babel-loader!../../node_modules/lib-flexible/flexible.js') %>
</script>
</head>
cs
3. 多页面应用打包
- 页面解耦
- 对seo更加友好
- 新增或删除页面是无需重复配置
const glob = require("glob");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const setMPA = () => {
const entry = {};
const htmlWebpackPlugin = [];
// 读取匹配 src下面的目录 entryFiles为入口文件的目录数组
const entryFiles = glob.sync(path.join(__dirname, "./src/*/index.js"));
Object.keys(entryFiles).map((index) => {
const entryFile = entryFiles[index];
// 正则匹配
const match = entryFile.match(/src\/(.*)\/index\.js/);
const pageName = match && match[1];
entry[pageName] = entryFile;
htmlWebpackPlugin.push(
new HtmlWebpackPlugin({
template: path.join(__dirname, `src/${pageName}/index.html`),
filename: `${pageName}.html`,
// 打包出的 js css文件自动注入html
inject: true,
// 所生成的html使用哪些chunk
chunks: [pageName],
minify: {
html5: true,
collapseWhitespace: true,
preserveLineBreaks: false,
minifyCSS: true,
minifyJS: true,
removeComments: false,
},
})
);
});
return {
entry,
htmlWebpackPlugin,
};
};
const { entry, htmlWebpackPlugin } = setMPA();
module.exports = {
entry: entry, // 单入口位字符串 多入口为对象 键值对写入
plugins: [].concat(htmlWebpackPlugin),
};
4. sourcemap
- 通过sourcemap 定位源代码
- 开发环境开启,线上环境关闭
- 线上排查问题的时候可以将sourcemap上传到错误监控系统
- 科普链接: www.ruanyifeng.com/blog/2013/0…
- source map关键字
- eval: 使用eval 包裹代码块
- source map: 产生.map文件
- cheap: 不包含列信息
- inline: 将.map作为DataURL嵌入,不单独生成,map文件
- module: 包括loader的sourcemap
// 接上一篇
devtool:'source-map'
5. 提取页面公共资源
- 基础库分离 cdn引入
const HtmlWebpackExternalPlugin = require("html-webpack-externals-plugin");
plugins: [
new HtmlWebpackExternalPlugin({
externals: [
{
module: "react",
entry: "https://unpkg.com/react@16/umd/react.production.min.js",
global: "React",
},
{
module: "react-dom",
entry:
"https://unpkg.com/react-dom@16/umd/react-dom.production.min.js",
global: "ReactDOM",
},
],
}),
]
- splitChunksPlugin
- webpack4内置
- chunks参数说明
- async:异步引入的库进行分离(默认)
- initial:同步引入的库进行分离
- all:所有引入的库进行分离(推荐)
- webpack 地址:webpack.docschina.org/plugins/spl…
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async',
// 抽离公共包最小体积
minSize: 20000,
minRemainingSize: 0,
// 使用次数
minChunks: 1,
// 浏览器请求异步资源的次数
maxAsyncRequests: 30,
maxInitialRequests: 30,
enforceSizeThreshold: 50000,
cacheGroups: {
commons: {
test: /(react|react-dom)/,
// 这里的name 要同步在 htmlWebpackPlugin的chunks上使用
name: "vendors",
chunks: "all",
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
};
// 使用
htmlWebpackPlugin.push(
new HtmlWebpackPlugin({
template: path.join(__dirname, `src/${pageName}/index.html`),
filename: `${pageName}.html`,
// 打包出的 js css文件自动注入html
inject: true,
// 所生成的html使用哪些chunk
// 对于上面的name
chunks: [pageName, 'vendors'],
minify: {
html5: true,
collapseWhitespace: true,
preserveLineBreaks: false,
minifyCSS: true,
minifyJS: true,
removeComments: false,
},
})
);
6. tree shaking (摇树优化)
- 模块可能有多个方法,只有用到的方法才会被打包进bundle文件,tree shaking就是 把只用到的方法打入到bundle,没用到的会在uglify阶段被擦除
- webpack4默认支持,
- mode: production的情况默认开启
- 要求:必须是es6的语法,cjs的方式不支持(动态引入)
- 代码编写有副作用的情况下 tree shaking 失效
DCE(Elimination)
- 代码不会被执行,不可到达
- 代码执行结果不会被用到
- 代码只会影响死变量(只写不读)
tree shaking (原理)
- 利用es6模块特点
- 只能作为模块顶层的语句
- import的模块名只能是字符串变量
- import binding 是 immutable 的
- 代码擦除
-
uglify阶段删除无用代码
-
7. scope hoisting
现象
- 构建后的存在大量的闭包代码 导致问题
- 大量函数闭包包裹代码,导致体积增大(模块一如越多越明显)
- 运行代码是创建的函数作用域变多,内存开销变大 模块转换分析
结论
- 被webpack转换后的模块会带上一层包裹
- import会被转换成_webpack_require 进一步分析webpack的模块机制
scope hoisting(原理)
- 将所以模块的代码按照引用顺序放在一个函数作用域里面,然后适当的重命名一些变量以防止变量名冲突
- 减少函数声明代码和内存开销
- webpack4-production 默认支持
- 要求:必须是es6的语法,cjs的方式不支持(动态引入)
8. 代码分割
意义
- 对于大的Web应用来说,将所有的代码都放在一个文件中显然是不够有效的,特别是当你的某些代码模块实在某些特殊的时候才会被用到时.webpack有一个功能就是将你的代码分割成chunks(语块),当代码运行到需要他们的时候在进行加载 适用的场景
- 抽离相同代码到一个共享块
- 脚本懒加载,使得初始下载的代码更小
懒加载js脚本的方式
- CommonJS: require.ensure
- ES6: 动态import (目前还没有原生支持,需要babel转换)
// .babelrc 文件
{
"plugins": ["@babel/plugin-syntax-dynamic-import"]
}
// index.js 文件
loadComponent = () => {
import("./text.js").then((Text) => {
this.setState({
Text: Text.default,
});
});
};
// loadComponent执行时,text.js加载
// import("./text.js") 为promise对象
9.ESLint
遵循原则(建议)
-
不重复造轮子,基于eslint:recommend配置并改进
-
能够帮助发现代码错误的规则,全部开启
-
帮助保持团队的代码风格统一,而不是限制开发体验 ESLint执行落地
-
和CI/CD系统集成
- 和webpack集成
- 使用eslint-loader
rules: [
{
test: /.js$/,
// include: path.resolve("src"),
use: [
// {
// loader: "thread-loader",
// options: {
// // 3个进程
// workers: 3,
// },
// },
"babel-loader",
// "happypack/loader",
"eslint-loader"
],
exclude: /node_modules/,
},
]
// 新建.eslintrc.js文件
module.exports = {
// 指定解析器
"parser": "babel-eslint",
// 继承 安装的框架 多个数组写入
"extends": "airbnb",
// 自定义规则
"rules": {
"semi": "error"
},
// 生效的环境
"env": {
"browser": true,
"node": true
}
}
// 详细配置参考 eslint 官网 https://eslint.org/