自动清理构建目录产物
每次构建的时候不清理目录,造成构建的输出目录output文件越来越多
通过npm script清理构建目录
rm -rf ./dist && webpack
rimraf ./dist && webpack
自动清理构建目录
通过clean-webpack-plugin
默认删除outpput指定的输出目录
plugins: [new CleanWebpackPlugin()]
PostCSS插件autoprefixer自动补齐CSS3前缀
IE(Trident)- -ms
火狐(Geko)- -moz
谷歌(Webkit)- -webkit
欧朋(Presto)- -o
module: {rules: [{test: /.less$/,use: [MiniCssExtractPlugin.loader,'css-loader','less-loader',{loader: 'postcss-loader',options: {plugins: () => {require('autoprefixer')({browser: ['last 2 version', '>1%', 'ios7']})}}}]}]}
移动端CSS px自动转换成rem
rem是什么
W3C对rem的定义:font-size of the root element
rem和px的对比
rem是相对单位
px是绝对单位
px自动转换rem
使用px2rem-loader
页面渲染时计算根元素的font-size值
- 可以使用手淘的lib-flexible库
https://github.com/amfe/lib-flexible
module: {rules: [{test: /.less$/,use: ['css-loader','less-loader',{loader: 'px2rem-loader',options: {remUnit: 75, //1个rem相对于75像素,对应750的视觉设计稿remPrecision: 8 //小数点后8位}}]}]}
由于viewport单位得到众多浏览器的兼容,lib-flexible这个过渡方案已经可以放弃使用,不管是现在的版本还是以前的版本,都存有一定的问题。建议大家开始使用viewport来替代此方。
静态资源内联
含义
意义
代码层面
页面框架初始化层面
- 上报打点
- css内联避免页面闪动
网络请求层面
-
HTML和JS内联
HTML使用raw-loader
${require('raw-loader!./meta.html')}
JS内联
<script>${require('raw-loader!babel-loader!../../node_modules/lib-flexible/flexible.js')}</script>
CSS内联
方案- 借助style-loader
module: { rules: [ { test: /.less$/, use: [ { loader: 'style-loader', options: { insertAt: 'top', //样式插入到<head> singleton: tue //将所有style标签合并成一个 } }, 'css-loader', 'less-loader' ] } ] }方案二 html-inline-css-wbepack-plugin
多页面应用打包
多页面应用(MPA)概念
每一次页面跳转的时候,后台服务器都会给返回一个新的html文档。这种类型的网站就是多页网站,也是多页应用。
多页面打包基本思路
每个页面对应一个entry,一个html-webpack-plugin
缺点:每次新增删除页面都要修改webpack配置entry: { index: './src/index.js', search: './src/search.js' }, plugins: [ new HtmlWebpackPlugin({ template: path.join(__dirname, 'src/index.html'), filename: 'index.html', chunks: ['index'], inject: true, minify: { html5: true, collapseWhitespace: true, preserveLineBreaks: false, minifyCSS: true, minifyJS: true, removeComments: false } }), new HtmlWebpackPlugin({ template: path.join(__dirname, 'src/search.html'), filename: 'search.html', chunks: ['search'], inject: true, minify: { html5: true, collapseWhitespace: true, preserveLineBreaks: false, minifyCSS: true, minifyJS: true, removeComments: false } }) ]多页面打包通用方案
动态获取entry和设置html-webpack-plugin数量
利用glob.syncentry: global.sync(path.join(_dirname,'./src/*/index.js'))source map
使用source map
作用:通过source map定位到源代码
sourcemap科普文:http://www.ruanyifeng.com/blog/2013/01/javascript_source_map.html
开发环境开启,线上环境关闭
- 线上排查问题的时候可以将sourcemap上传到错误监控系统
source map关键字
| eval | 使用eval包裹模块代码 | | —- | —- | | source map | 产生.map文件 | | cheap | 不包含列信息 | | inline | 将.map作为DataURL嵌入,不单独产生.map文件 | | module | 包含loader的source map |
source map类型
提取页面公共资源
基础库分离
思路:将react、react-dom等基础包通过cdn引入,不打入bundle中
方法:
1.使用html-webpack-externals-plugin
2.使用SpiltChunkPlugin
webpack4内置的,代替CommonsChunkPlugin插件
chunks参数说明:
- async:异步引入的库进行分离(默认)
- initial:同步引入碓库进行分离
all:所有引入的库进行分离(推荐)
module.exports = { optimization: { splitChunks: { chunks: 'async', minSize: 30000, maxSize: 0, minChunks: 1, maxAsyncRequests: 5, maxInitialRequests: 3, automaticNameDelimiter: '~', name: true, cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 } } } } };test参数说明:匹配出需要分离的包
module.exports = { optimization: { splitChunks: { cacheGroups: { commons: { test: /(react|react-dom)/, name: 'vendors', chunks: 'all' } } } } };minChunks参数说明:设置最小饮用次数
minSize参数说明:设置分离包的体积大小module.exports = { optimization: { splitChunks: { minSize: 0, cacheGroups: { commons: { name: 'commons', chunks: 'all', minChunks: 2 } } } } };TreeShaking的使用和原理分析
概念:
1 个模块可能有多个方法,只要其中的某个方法使⽤到了,则整个文件都会被打到 bundle ⾥面去,tree shaking 就是只把用到的方法打入 bundle ,没⽤到的⽅法会在 uglify 阶段被擦除掉。
使用
webpack4 默认⽀支持,在 .babelrc ⾥设置 modules: false 即可
production mode的情况下默认开启
原理
DCE (Dead code elimination)
代码不会被执行,不可到达
代码执行的结果不会被⽤到
代码只会影响死变量(只写不读)
ES6 模块的特点
- 只能作为模块顶层的语句出现
- import 的模块名只能是字符串常量
-
代码擦除
Scope Hoisting的使用和原理分析
会导致什什么问题?
⼤量作⽤域包裹代码,导致体积增大(模块越多越明显)
运行代码时创建的函数作用域变多,内存开销变大模块转换分析

结论:
被 webpack 转换后的模块会带上一层包裹
import 会被转换成 __webpack_require
进⼀步分析 webpack 的模块机制

分析:
打包出来的是一个 IIFE (匿名闭包)
- modules 是一个数组,每一项是一个模块初始化函数
- __webpack_require ⽤来加载模块,返回 module.exports
- 通过 WEBPACK_REQUIRE_METHOD(0) 启动程序
scope hoisting 原理
原理:将所有模块的代码按照引用顺序放在⼀个函数作用域⾥,然后适当的重命名一 些变量以防止变量名冲突
对比: 通过 scope hoisting 可以减少函数声明代码和内存开销
scope hoisting 使⽤
webpack mode 为 production 默认开启
必须是 ES6 语法,CJS 不支持 ```javascript module.exports = { entry: {
}, output: {app: './src/app.js', search: './src/search.js'
path: __dirname + ‘/dist’ }, plugins: [filename: '[name][chunkhash:8].js',
- new webpack.optimize.ModuleConcatenationPlugin()
]
};
```
代码分割和动态import
代码分割的意义
对于⼤的 Web 应⽤用来讲,将所有的代码都放在一个文件中显然是不够有效的,特别是当你的某些代码块是在某些特殊的时候才会被使用到。webpack 有一个功能就是将你的代码库分割成 chunks(语块),当代码运⾏到需要它们的时候再进⾏加载。
适⽤用的场景:
- 抽离相同代码到一个共享块
-
懒加载 JS 脚本的方式
CommonJS:require.ensure
ES6:动态 import(目前还没有原⽣生⽀支持,需要 babel 转换)如何使用动态 import?
安装 babel 插件
npm install @babel/plugin-syntax-dynamic-import —save-dev
ES6:动态 import(⽬前还没有原⽣生⽀支持,需要 babel 转换)//.babelrc中增加plugins "plugins": [ "@babel/plugin-syntax-dynamic-import" ]在webpack中使用Eslint
制定团队的 ESLint 规范原则
不重复造轮子,基于 eslint:recommend 配置并改进
- 能够帮助发现代码错误的规则,全部开启
- 帮助保持团队的代码⻛风格统一,而不是限制开发体验
ESLint 如何执⾏行行落地

本地开发阶段增加 precommit 钩⼦
安装 husky
npm install husky —save-dev
增加 npm script,通过 lint-staged 增量量检查修改的⽂文件
"scripts": {
"precommit": "lint-staged"
},
"lint-staged": {
"linters": {
"*.{js,scss}": ["eslint --fix", "git add"]
}
},
webpack 与 ESLint 集成
使⽤用 eslint-loader,构建时检查 JS 规范
.webpack.js的配置
module.exports = {
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
use: [
"babel-loader",
"eslint-loader”
]
}]
}
};
.eslintrc的配置
module.exports = {
"parser": "babel-eslint",
"extends": "airbnb",
"env": {
"browser": true,
"node": true
},
"rules": {
"indent": ["error", 4]
}
};
webpack打包组件和基础库
webpack 除了了可以⽤用来打包应⽤用,也可以⽤用来打包 js 库
实现⼀个⼤整数加法库的打包
目录
|- /dist
|- large-number.js
|- large-number.min.js
|- webpack.config.js
|- package.json
|- index.js
|- /src
|- index.js
⽀持的使用方式
支持 ES module
import * as largeNumber from 'large-number'; // ... largeNumber.add('999', '1');支持 CJS
const largeNumbers = require('large-number'); // ... largeNumber.add('999', '1');支持 AMD
require(['large-number'], function (large-number) { // ... largeNumber.add('999', '1'); });可以直接通过 script 引⼊入
<!doctype html> <html> ... <script src="https://unpkg.com/large-number"></script> <script> // ... // Global variable largeNumber.add('999', '1'); // Property in the window object window. largeNumber.add('999', '1'); // ... </script> </html>如何将库暴暴露露出去?
设置output
- library: 指定库的全局变量量
- libraryTarget: ⽀支持库引⼊入的⽅方式
//.webpack.config.js配置 module.exports = { mode: "production", entry: { "large-number": "./src/index.js", "large-number.min": "./src/index.js" }, output: { filename: "[name].js", library: "largeNumber", libraryExport: "default", libraryTarget: "umd" } };如何指对 .min 压缩
1.通过 include 设置只压缩 min.js 结尾的文件
module.exports = { mode: "none", entry: { "large-number": "./src/index.js", "large-number.min": "./src/index.js" }, output: { filename: "[name].js", library: "largeNumber", libraryTarget: "umd" }, optimization: { minimize: true, minimizer: [ new TerserPlugin({ include: /\.min\.js$/, }), ], } };2.设置⼊口⽂件
package.json 的 main 字段为 index.js//index.js if (process.env.NODE_ENV === "production") { module.exports = require("./dist/large-number.min.js"); } else { module.exports = require("./dist/large-number.js"); }
代码位置:https://gitee.com/geektime-geekbang/geektime-webpack-course/tree/master/code/chapter03/large-number
webpack实现SSR打包
服务端渲染 (SSR) 是什么?
渲染: HTML+CSS+JS+Data->渲染后的HTML
服务端:
- 所有模板等资源都存储在服务端
- 内⽹机器器拉取数据更快
- 一个 HTML 返回所有数据
浏览器和服务器交互流程
客户端渲染 vs 服务端渲染
| | 客户端渲染 | 服务端渲染 | | —- | —- | —- | | 请求 | 多个请求(HTML、数据等) | 1个请求 | | 加载过程 | HTML&数据串行加载 | 1个请求返回HTML&数据 | | 渲染 | 前端渲染 | 服务端渲染 | | 可交互 | 图片等静态资源加载完成,JS逻辑执行完成可交互 | |
SSR 的优势
SSR 代码实现思路
服务端
- 使⽤ react-dom/server 的 renderToString 方法将 React 组件渲染成字符串
- 服务端路由返回对应的模板
客户端
-
webpack ssr 打包存在的问题
浏览器的全局变量 (Node.js 中没有 document, window)
组件适配:将不兼容的组件根据打包环境进⾏适配
请求适配:将 fetch 或者 ajax 发送请求的写法改成 isomorphic-fetch 或者 axios
样式问题 (Node.js ⽆无法解析 css)
⽅案一:服务端打包通过 ignore-loader 忽略略掉 CSS 的解析
方案二:将 style-loader 替换成 isomorphic-style-loader
如何解决样式不显示的问题?
使⽤打包出来的浏览器端 html 为模板
- 设置占位符,动态插⼊组件
⾸屏数据如何处理?
- 服务端获取数据
- 替换占位符
优化构建时命令行的显示日志
当前构建时的⽇志显示
统计信息 stats
开发环境-在devServer的stats中设置以下内容
生产环境-直接设置stats
如何优化命令⾏的构建日志
使⽤用 friendly-errors-webpack-plugin
- success: 构建成功的⽇志提示
- warning: 构建警告的⽇志提示
- error: 构建报错的日志提示
stats 设置成 errors-only
module.exports = {
entry: {
app: './src/app.js',
search: './src/search.js'
},
output: {
filename: '[name][chunkhash:8].js',
path: __dirname + '/dist'
},
plugins: [
+ new FriendlyErrorsWebpackPlugin()
],
+ stats: 'errors-only'
};
构建异常和中断处理
如何判断构建是否成功?
在 CI/CD 的 pipline 或者发布系统需要知道当前构建状态
每次构建完成后输⼊入 echo $? 获取错误码
构建异常和中断处理
webpack4 之前的版本构建失败不不会抛出错误码 (error code)
- 0表示成功,非0失败
Node.js 中的 process.exit 规范
· 0 表示成功完成,回调函数中,err 为 null
· ⾮ 0 表示执行失败,回调函数中,err 不为 null,err.code 就是传给 exit 的数字
如何主动捕获并处理构建错误?
compiler 在每次构建结束后会触发 done 这 个 hook
process.exit 主动处理构建报错
plugins: [
function() {
this.hooks.done.tap('done', (stats) => { if (stats.compilation.errors &&
stats.compilation.errors.length && process.argv.indexOf('- -watch') == -1){
console.log('build error');
process.exit(1);
}
})
}
]
