Webpack 进阶用法
当前构建时的问题
- 每次构建的时候不会清理目录,造成构建的输出目录
output
文件越来越多
通过npm script
清理构建目录(并不优雅的处理方式)
rm -rf ./dist && webpack
rimraf ./dist && webpack
自动清理构建目录
-
避免构建前每次都需要手动删除
dist
-
使用
clean-webpack-plugin
- 默认会删除
output
指定的输出目录
- 默认会删除
-
plugins:[ new CleanWebpackPlugin() ]
CSS
增强
CSS
兼容添加前缀
IE
==>Trident(-ms)
Geko(-moz)
火狐WebKit(-webkit)
谷歌Presto(-0)
欧鹏
使用PostCSS
插件autoprefixer
自动补齐CSS3
前缀
-
npm i postcss-loader autoprefixer -D
-
使用
autoprefixer
插件 -
根据
Can I Use规则
https://caniuse.com/
-
module :{ rules:[ { test:/.less$/, use:[ 'style-loader', 'css-loader', 'less-loader', { loader:'postcss-loader', //参数 options:{ plugins:()=>{ //引入 require('autoprefixer')({ //可以设置 autoprefixer 兼容浏览器版本 //>1% 使用人数占比 //ios7 兼容ios7版本以上 版本 browsers:['last 2 version','>1%,'ios7'] }) } } } ] }, ] }
将移动端CSS
px
自动转换成rem
CSS
媒体查询实现 响应式布局
- 缺陷:需要写多套适配样式代码
使用px2rem-loader
- 页面渲染时计算根元素的
font-size
值- 可以使用手淘的
lib-flexible
github.com/amfe/lib-flexible
- 可以使用手淘的
module :{
rules:[
{
test:/.less$/,
use:[
'style-loader',
'css-loader',
'less-loader',
{
loader:'postcss-loader',
//参数
options:{
remUnit:75,
remPrecision:8//转换成rem 后面的小数点位数
}
}
]
},
]
}
资源内联的意义
- 代码层面
- 页面框架的初始化脚本
- 上报相关打点
css
内联避免页面闪动
请求层面:减少http
网络请求数
- 小图片或者字体内联(
url-loader
)
HTML 和 JS
内联
raw-loader
内联html
与js
安装版本为:npm i raw-loader@0.5.1 -D
css
内联
- 方案一:借助
style-loader
- 方案二:
html-inline-css-webpack-pulugin
module :{
rules:[
{
test:/.less$/,
use:[
{
loader:'style-loader',
//参数
options:{
inserAt:'top',//样式插入到`<head>`
singleton:true,//将所有的`style`标签合并成一个
}
}
'css-loader',
'less-loader',
]
},
]
}
多页面应用(MPA
)概念
每一次页面跳转得时候,后台服务器都会返回一个新的html
文档,这种类型得网站也就是多页网站,也叫做多页应用
多页面打包基本思路
每个页面对应一个entry
,一个html-webpack-plugin
缺点:每次新增或删除页面需要修改webpack
配置
module.exports = {
entry :{
index:'./src/index.js',
search:'./src/search.js'
}
}
动态获取entry
和设置 html-webpack-plugin
数量
利用 glob.sync
entry:glob.sync(path.join(_dirname,'./src/*/index.js'))
module.exports = {
entry :{
index:'./src/index/index.js',
search:'./src/search/index.js'
}
}
//约定:把所有得页面都放在 src 目录下面,每个页面得人口文件都命名为index.js文件
- 通过
glob
多页面打包
npm i glob
const glob = require('glob')
const setMPA = () =>{
const entry = {};
const htmlWebpackPlugins = [];
const entryFiles = glob.sync(path.join(__dirname,'./src/*/'));//匹配src目录下所有页面内容
Object.keys(entryFiles).map(index=>{
const entryFire = entryFiles[index];
const match = entryFiles.mach(/src\(.*)\/index\.js/)
const pageName = match && match[1]
entryFiles[pageName] = entryFiles;
htmlWebpackPlugins.push(
new HtmlWebpackPlugin({
template:path.join(_dirname,`src/${pageName}/index.html`),
filename:`${pageName}.html`, //指定打包出来之后得文件名称
chunks:['search'],
inject:true,
minify:{
html:true,
collapseWhitespace:true,
preserveLineBreaks:false,
minifyCSS:true,
minifyJS:true,
removeComments:false
}
})
})
return {
entry,
htmlWebpackPlugins
}
}
使用source map
作用:通过source map
定位到源代码
开发环境开启,线上环境关闭
- 线上排查问题得时候可以将
sourcemap
上传到错误监控系统
source map
关键字
eval
:使用eval
包裹模块代码source map
:产生.map
文件cheap
:不包含列信息inline
:将.map
作为DataURL
嵌入,不单独生成.map
文件module
:包含loader
的sourcemap
提取页面公共资源
基础库基础
思路:将react 、 react-dom
基础包通过引入cdn
引入,不打入bundle
中
方法:使用html-webpack-externals-plugin
利用SplitChunksPlugin
进行公共脚本分离
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
}
}
}
}
}
利用**SplitChunksPlugin
**分离基础包
test
:匹配出需要分离的包
module.exports = {
//...
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name:'vendors',
chunks: 'all'
}
}
}
}
};
tree shaking
(摇树优化)
概念:1个模块可能有多个方法,只要把其中的某个方法使用到了,则整个文件都会被达到bundle
里面去,tree shaking
就是只把用到的方法打入bundle
,没用到的方法会在uglify
阶段被擦除掉
使用:webpack
默认支持,在.babelrc
里设置modules:false
即可.production mode
的情况下默认开启
要求:必须是ES6
的语法,CJS
的方式不支持
DCE (Elimination)
代码不会被执行,不可到达
代码执行的结果不会被用到
代码只会影响死变量(只写不读)
tree shaking
原理
利用ES6
模块的特点:
- 只能作为模块顶层的语句出现
import
模块名只能是字符串常量import binding
是immutable
的
代码擦除:ugkify
阶段删除无用代码
ScopeHoisting
使用和原理分析
现象:构建后代码存在大量闭包代码
模块转换分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eoaFSTUg-1586446111477)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200408234303736.png)]
结论:
- 被
webpack
转换后的模块会带上一层包裹 import
会被转换成__webpack_require
进一步分析webpack
的模块机制
分析:
- 打包出来的是一个
llFE
(匿名闭包) modules
是一个数组,每一项是一个模块初始化函数__webpack_require
用来加载模块,返回module.exports
- 通过
WEBPACK_REQUIRE_METHOD(0)
启动程序
scope hoisting
原理
原理:将所有模块的代码按照引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量名冲突
对比:通过scope hoisting
可以减少函数声明代码和内存开销
scope hoisting
webpack mode
为production
默认开启
必须是ES6
语法,CJS
不支持
代码分割得意义
对于大得web
应用来说,将所有的代码都放在一个文件中显然是不够有效的,特别是当你的某些代码块实在某些特殊的时候才会被用到,webpack
有有一个功能就是将你的代码库分割成chunks
(语块),当代码运行到需要它们的时候在在加载
适用的场景:
- 抽离相同代码到一个共享块
- 脚本懒加载,是的初始下载的代码更小
懒加载JS
脚本的方式
ComonJS
:require.ensure
ES6
:动态import
(目前还没有原生支持,需要babel
转换)
如何使用动态import
安装babel
插件
npm install @babel/plugin-syntax-dynamic-import --save-dev
{
"plugins":["@babel/plugin-syntax-dynamic-import"]
}
webpack
和ESLint
结合
安装husky
npm i husky --seve-dev
增加npm script
,通过lint-staged
增量检查修改的文件
"script":{
"precommit":"lint-staged"
},
"lint-staged":{
"linters":{
"*.{js.scss}":["eslint--fix","git add"]
}
}
新建项目 时候 webpack
与 ESLint
集成
- 使用
eslint-loader
,构建时检查JS
规范
module.exports = {
//...
module: {
rules: {
{
test:/\.js$/,
exclude:/node_modules/,
use:[
"babel-loader",
"eslint-loader"
]
}
}
}
};
webpack
打包库和组件
webpack
除了可以用来打包应用,也可以用来打包JS
库
实现一个大整数加法库的打包
- 需要打包压缩版和非压缩版本
- 支持
AMD / CJS / ESM
模块引入ESM=>import * as test from 'test'
CJS =>const test = require('test')
AMD =>require(['test'],function(test){ // ...})
如何将库暴露出去
library:
指定库的全局变量libraryTarget:
支持库引入的方式
module.exports = {
mode:'none',
entry :{
"large-number":'./src/index/index.js',
"large-number.min":'./src/search/index.js'
},
output:{
filename:"[name].js",
library:'largeNumber',
libraryExport:'default',
libraryTarget:'umd'
},
optimization:{
minimize:true,
minimizer:[
new TerserPlugin({
include:/\.min\.js$/ //通过include 设置只压缩 min.js 结尾的文件
})
]
}
}
设置人口文件
package.json 的 main字段为 index.js main:index.js
if(process.env.NODE_ENV === "production"){
module.exports = require("./dist/large-number.min.js")
}else{
module.exports = require("./dist/large-number.js")
}
webpack
实现ssr
打包
服务端渲染ssr
是什么
渲染:HTML + CSS + JS +Data==>
渲染后的HTML
服务端:
- 所有模板等资源都存储在服务端
- 内网机器拉取数据更快
- 一个
HTML
返回所有数据
浏览器和服务器交互流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lqQvoiaX-1586446111479)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200409222304222.png)]
客户端渲染 vs
服务端渲染
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oMGMtNN9-1586446111480)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200409222548020.png)]
总结:服务端渲染(SSR)的核心是减少请求
ssr
的优势
- 减少白屏的时间
- 对于
SEO
友好
SSR
代码实现思路
- 服务端
- 使用
react-dom / server
的renderToString
方法将React
组件渲染成字符串,因为在服务端是没有window
这些对象 - 服务端路由返回对应的模板
- 使用
- 客户端
- 打包出针对服务端的组件
webpack ssr
打包存在的问题
浏览器的全局变量(Node.js
中没有document
,window
)
- 组件适配:将不兼容的组件根据打包环境进行适配
- 请求适配:将
fetch
或者ajax
发送请求的写法改成isomorphic-fetch
或者axios
样式问题(Node.js
无法解析css
)
- 方案一:服务端打包通过
ignore-loader
忽略掉css
的解析 - 方案二:将
style-loader
替换成isomorphic-style-loader
如何解决SSR
打包之后样式不显示的问题
使用打包出来的浏览器端html
为模板
设置占位符,动态插入组件
比如 iview table
在查看网页源代码之后会出现很多<!---->
,就是占位从而显示样式
首屏数据如何处理
服务端获取数据
替换占位符
优化构建时命令行的显示日志
统计信息 stats
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g0kvygEF-1586446111481)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200409230846573.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q84SZkaE-1586446111482)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200409231203671.png)]
如何判断构建是否成功
在CI / CD
的 pipline
或者发布系统需要知道当前构建状态
每次构建完成后输入echo $
获取错误吗
构建异常和中断处理
webpack4
之前的版本构建失败不会抛出错误码(error code
)
Node.js
中的process.exit
规范
-
0 表示成功完成,回调函数中,
err
为null -
非0 表示执行失败,回调函数中,
err
不为null
,err.code
就是传给exit
的数字
如何主动捕获并处理构建错误
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OPuMUzFL-1586446111483)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200409232612853.png)]