自动清理构建目录
避免构建前每次都需要手动删除dist,可以通过clean-webpack-plugin
自动清理。 webpack4.0配置
npm install clean-webpack-plugin -D
复制代码
webpack.prod.js
// webpack.prod.js
// 自动清理构建目录
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
...
...
plugins: [
new CleanWebpackPlugin(),
]
}
复制代码
webpack5.0配置, 无需下载插件官方文档
output: {
path: path.resolve(__dirname, 'dist'),
// path: path.join(__dirname, 'dist'),
// 使用 [name]占位符 设置文件指纹
filename: '[name]_[chunkhash:8].js',
// webpack5.0 清理构建目录配置
clean: true,
},
复制代码
自动添加CSS3前缀
不同浏览器,内核不一样,css3需要补全不同的前缀,实现兼容问题。
使用PostCSS插件:autoprefixer自动补齐CSS3前缀
autoprefixer
是css的后置处理器,而less或者sass是css预处理器。预处理器是打包前去处理,而autoprefixer是代码打包生成之后,才开始处理。
可以在caniuse查看样式兼容问题。
通常autoprefixer会跟postcss配合使用。
npm install --save-dev postcss-loader postcss配合使用。 autoprefixer
复制代码
postcss-loader
执行顺序必须保证在 css-loader
之前,建议还是放在 less或者 sass 等预处理器之后更好。即 loader 顺序:
less-loader -> postcss-loader -> css-loader -> style-loader 或者 MiniCssExtractPlugin.loader
webpack.prod.js
{
test: /\.less$/,
use: [
// 'style-loader',
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader',
{ loader: "postcss-loader" }
]
},
复制代码
根目录新建postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')()
]
}
复制代码
package.json
"browserslist": [
"defaults",
"not ie < 11",
"last 2 version",
"> 1%",
"ios 7",
"last 3 ios version"
]
复制代码
移动端CSS px 自动转换成rem
为适配不同的机型,实现响应式布局,可使用rem作为尺寸单位。
使用 px2rem-loader。
页面渲染是计算根元素的font-size。lib-flexible库
现在大部分使用viewport去兼容不同的浏览器。
viewport 待更新...
资源内联
资源内联的意义
代码层面:
- 页面框架的初始化脚本
- 上报相关打点
- css内联避免页面闪动
请求层面:减少HTTP网络请求数
- 小图片或者字体内联(url-loader)
HTML和JS内联
raw-loader内联html,js
CSS内联
- 方案一:借助style-loader 把 CSS 插入到 DOM 中。
- 方案二:html-inline-css-webpack-plugin
多页面应用
每⼀次⻚⾯跳转的时候,后台服务器都会给返回⼀个新的 html ⽂档,这种类型的⽹站也就是多⻚⽹站,也叫做多⻚应⽤。
动态获取entry和设置html-webpack-plugin数量
利用glob.sync,以同步的方式查询文件。
glob.sync(path.join(__dirname, './src/*/index.js'));
查询根目录下的文件夹下的所有index.js文件
npm i glob -D
复制代码
webpack.prod.js
const glob = require('glob');
const setMPA = () => {
const entry = {};
const htmlWebpackPlugins = [];
// 查找出这个项目下的所有index.js文件
const entryFiles = glob.sync(path.join(__dirname, './src/*/index.js'));
// console.log(entryFiles, 'entryFiles')
// [ '/Users/**/program/webpack-demo/src/index/index.js','/Users/**/program/webpack-demo/src/search/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;
htmlWebpackPlugins.push(
new HtmlWebpackPlugin({
// 模板所在位置
template: path.join(__dirname, `src/${pageName}/index.html`),
// 指定打包后的文件名称
filename: `${pageName}.html`,
// 指定生成的html需要哪些chunk
chunks: [pageName],
inject: true,
minify: {
html5: true,
collapseWhitespace: true,
preserveLineBreaks: false,
minifyCSS: true,
minifyJS: true,
removeComments: false
}
})
);
})
return {
entry,
htmlWebpackPlugins
}
}
const { entry, htmlWebpackPlugins } = setMPA();
module.exports = {
entry,
...
plugins: [
...htmlWebpackPlugins,
// 压缩css
new MiniCssExtractPlugin({
filename: '[name]_[contenthash:8].css'
}),
new OptimizeCSSAssetsPlugin(),
]
}
复制代码
Source map
作⽤:通过 source map 定位到源代码。
简单说,Source map就是一个信息文件,里面储存着位置信息。也就是说,转换后的代码的每一个位置,所对应的转换前的位置。
基本上开发环境直接用source-map。
production环境就把source-map添加到Error Reporting Tool(e.g. Sentry)上。这样既不直接暴露源代码,也能方便解决production环境遇到的bug。
source map科普⽂:www.ruanyifeng.com/blog/2013/0… \
source-map关键字
- eval:使用eval包裹模块代码
- source-map:产生.map文件
- cheap 不包含列信息
- inline 将.map作为DataURI嵌入,不单独生成.map文件
- module: 包含loader的sourcemap
souce-map类型
devtool配置对应打包后的文件差异
// webpack.prod.js
module.exports = {
...
mode: 'none',
devtool: 'eval'
}
复制代码
1、设置为eval,打包后,会有eval包裹
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _helloWorld__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);\n\nsetTimeout(function () {\n document.write((0,_helloWorld__WEBPACK_IMPORTED_MODULE_0__.helloWorld)());\n});\n\n//# sourceURL=webpack://webpack-demo/./src/index/index.js?");
复制代码
2、设置为source-map
js会和map内容进行分离 3、设置为inline-source-map
js会和map内容不会进行分离
基础库分离
如果需要引用一个库,但是又不想让webpack打包(减少打包的时间),并且又不影响我们在程序中以CMD、AMD或者window/global全局等方式进行使用(一般都以import方式引用使用),那就可以通过配置externals。
这样做的目的就是将不怎么需要更新的第三方库脱离webpack打包,不被打入bundle中,从而减少打包时间,但又不影响运用第三方库的方式,例如import方式等。
- 思路:将react、react-dom基础包通过cdn引入,不打入bundle中
- 方法:使用html-webpack-externals-plugin
npm install --save-dev html-webpack-externals-plugin
// npm@6+
npm install html-webpack-externals-plugin -D --legacy-peer-deps
复制代码
1、配置webpack.prod.js
// 分离基础库
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');
module.export = {
...
plugins: [
...htmlWebpackPlugins,
// 压缩css
new MiniCssExtractPlugin({
filename: '[name]_[contenthash:8].css'
}),
new OptimizeCSSAssetsPlugin(),
new HtmlWebpackExternalsPlugin({
externals: [
{
module: 'react',
entry: 'https://11.url.cn/now/lib/16.2.0/react.min.js',
global: 'React',
},
{
module: 'react-dom',
entry: 'https://11.url.cn/now/lib/16.2.0/react-dom.min.js',
global: 'ReactDOM',
},
]
}),
]
}
复制代码
2、在src/search/index.html页面引入react react-dom包
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="root"></div>
// 引入react react-dom包
<script type="text/javascript" src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script type="text/javascript" src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
</body>
</html>
复制代码
分离react react-dom包之后,打包之后的体积大大缩小
关于安装可能出现的问题:
如果[email protected]+,下载会报错,错误如下:
错误解决方案:
gitee.com/vincentyun/…
www.it1352.com/2315867.htm…
找了好久才解决,原来因为npm7.x对某些事情比npm6.x更严格。
通常,最简单的解决方法是将--legacy-peer-deps标志传递给 npm ( npm install --legacy-peer-deps ),或者使用npm@6。
如果这不能立即起作用,也许可以先删除node_modules和package-lock.json。它们将被重新创建。
提示:使用npm@6不需要卸载npm@7。使用npx指定npm的版本。例如:npx -p npm@6 npm i --legacy-peer-deps。
方式二:利用splitChunks进行公共脚本分离(去掉html-webpack-externals-plugin相关配置)
从 webpack v4 开始内置了SplitChunksPlugin
,直接通过optimization.splitChunks
配置 webpack.prod.js
const setMPA = () => {
...
htmlWebpackPlugins.push(
new HtmlWebpackPlugin({
chunks: ['vendors', pageName],//此处多配置一个vendors,与cacheGroups中的name对应
})
)
}
module.exports = {
...
optimization: {
// splitChunks分离基础包
splitChunks: {
cacheGroups: {
commons: {
test: /(react|react-dom)/,
// vendors需要添加到htmlWebpackPlugins的chunk里
name: 'vendors',
chunks: 'all'
}
}
},
},
// source-map,产生.map文件
devtool: 'inline-source-map',
}
复制代码
执行npm run build
编译打包之后,在html文件里,会引入这个vendors
tree shaking(摇树优化)
1、概念
1个模块可能有多个⽅法,只要其中的某个⽅法使⽤到了,则整个⽂件都会被打到bundle ⾥⾯去,tree shaking 就是只把⽤到的⽅法打⼊ bundle ,没⽤到的⽅法会在uglify 阶段被擦除掉。
2、使用
webpack 默认⽀持,在 .babelrc ⾥设置 modules: false 即可
production mode的情况下默认开启
3、要求
必须是ES6的语法,CJS的方式不支持
4、DCE (Dead code elimination)
- 代码不会被执行,不可到达
- 代码执行的结果不会被用到
- 代码只会影响死变量(只写不读)
if(false){
console.log('这段代码永远不会被执行')
}
复制代码
原理
利⽤ ES6 模块的特点:
- 只能作为模块顶层的语句出现
- import 的模块名只能是字符串常量
- import binding 是 immutable的
代码擦除:uglify 阶段删除⽆⽤代码
(不懂......)
代码分割和动态import
一、代码分割
1、意义
对于⼤的 Web 应⽤来讲,将所有的代码都放在⼀个⽂件中显然是不够有效的,特别是当你的某些代码块是在某些特殊的时候才会被使⽤到。webpack 有⼀个功能就是将你的代码库分割成chunks(语块),当代码运⾏到需要它们的时候再进⾏加载。
2、适用场景
- 抽离相同代码到一个共享块
- 脚本懒加载,使得初始下载的代码更小
二、动态import
懒加载JS脚本的方式:
- commonJS:require.ensure
- es6:动态import(目前还没有原生支持,需要babel转换)
1、安装插件
npm install @babel/plugin-syntax-dynamic-import --save-dev
2、在.babelrc文件里加入这个插件
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-syntax-dynamic-import"
]
}
复制代码
3、新建text.js
import React from 'react';
export default () => <div>动态 import</div>
复制代码
4、在src/search/index.js文件里引入
'use strict';
import React from 'react';
import ReactDom from 'react-dom';
import './search.less'
import imgSrc from './images/2.png'
class Search extends React.Component{
constructor() {
super()
this.state = {
Text: null
}
}
render() {
// debugger
const { Text } = this.state;
return <div className="search-text">
<div>Search Text111</div>
<img className="img" src={imgSrc} onClick={this.loadComponent.bind(this)}></img>
{/* 渲染页面 */}
{
Text ? <Text/> : null
}
</div>
}
loadComponent() {
// 动态引入文件
import('./text.js').then((Text) => {
this.setState({
Text: Text.default,
})
})
}
}
ReactDom.render(
<Search/>,
document.getElementById('root')
)
复制代码
5、执行npm run build,打包完成之后会多一个数字开头的js文件,就是动态引入的文件。开头的数字就是懒加载的id。
点击图片,就会加载js
在webpack中使用ESLint
⾏业⾥⾯优秀的 ESLint 规范实践
腾讯:
- alloyteam团队 eslint-config-alloy
- ivweb 团队:eslint-config-ivweb
一、webpack与CI/CD集成
(loading......)
二、webpack与ESLint集成
1、在react中集成 1.1 安装
npm install --save-dev eslint @babel/eslint-parser @babel/preset-react@latest eslint-plugin-react eslint-config-alloy
复制代码
npm install eslint-loader babel-eslint -D
复制代码
npm install eslint-config-airbnb -D
复制代码
1.2webpack.prod.js中加入eslint-loader
module.exports = {
module: {
rules: [
{
test: /\.js$/,
// 添加eslint-loader
use: ['babel-loader', 'eslint-loader'],
},
]
}
}
复制代码
1.3 配置.eslintrc.js
eslint官网
module.exports = {
parser: 'babel-eslint',
// 继承
extends: ['airbnb'],
// 环境变量
env: {
browser: true,
node: true,
},
};
复制代码
在webpack中打包组件和基础库
webpack 除了可以⽤来打包应⽤,也可以⽤来打包 js 库,实现⼀个⼤整数加法库的打包。
- 需要打包压缩版和⾮压缩版本
- 打包好的组件和基础库,⽀持 AMD/CommonJS/ESModule 模块引⼊,也支持直接通过script引入
如何将库暴露出去?
- library:指定库的全局变量
- libraryTarget:支持库引入的方式
创建一个简单的组件库
1、新建一个项目larger-num,初始化该项目
npm init -y
复制代码
npm install webpack webpack-cli
复制代码
在根目录下新建webpack.config.js
,src/index.js
2、src/index.js
导出一个方法
export default function add(a,b) {
let i = a.length - 1;
let j = b.length - 1;
// 进位
let carry = 0;
let ret = "";
while(i >= 0 || j >= 0) {
let x = 0,y = 0, sum;
if (i >= 0) {
x = a[i] - '0';
i--;
}
if (j >= 0) {
y = b[j] - '0';
j--;
}
sum = x + y + carry;
if (sum >= 10) {
carry = 1;
sum -= 10;
} else {
carry = 0;
}
// 0+''
ret = sum + ret;
}
if (carry) {
ret = carry + ret;
}
return ret;
}
// add('999','1')
// add('1','999')
// add('123','2123')
复制代码
3、配置打包信息webpack.config.js
webpack.docschina.org/configurati…
module.exports = {
entry: {
'large-number': './src/index.js',
'large-number.min': './src/index.js',
},
// 导出一个库
output: {
filename: '[name].js',
library: {
// 配置库的名字
name: "largeNumber",
// 配置将库暴露的方式
type: "umd",
export: "default"
},
// library: 'largeNumber',
// libraryTarget: 'umd',
},
mode: "production",
}
复制代码
4、打包
添加一个script
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
复制代码
执行打包命令
npm run build
复制代码
打包结果
只打.min文件压缩
webpack.docschina.org/plugins/ter…
1、下载terser-webpack-plugin
npm install terser-webpack-plugin --save-dev
复制代码
2、配置webpack.config.js
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
...
...
mode: "none",
optimization: {
minimize: true,
minimizer: [
// 通过 include 设置只压缩 min.js 结尾的⽂件
new TerserPlugin({
include: /\.min\.js$/,
})
]
},
}
复制代码
3、执行npm run build,可以看到文件大小有明显变化,dist目录下的生成了一个压缩的一个未压缩的文件
4、发布到npm
webpack实现SSR打包
页面打开过程(客户端渲染)
客户端渲染,页面整体的打包过程如下:
用户点击一个按钮,会加载一个新的webview,加载完新的webview之后,开始加载新的页面
- 页面开始加载,此时会出现一段时间的白屏时间,页面没有内容(因为还没加载html)
- HTML加载成功,开始加载数据
- 此时会有loading图标等等,告诉用户页面正在加载
- 浏览器开始请求CSS、JS
- 解析和执行CSS,页面上就会出现一些样式
- 解析和执行JS,会执行JS的一些逻辑,比如请求图片资源,请求数据等等
- 数据加载成功,渲染成功开始,加载图片资源
- 图片加载成功,页面可交互
缺点:整个过程是串行过程,白屏时间长
服务端渲染(SSR)是什么
渲染: HTML + CSS + JS + Data -> 渲染后的 HTML
服务端渲染:
- 所有模板等资源都存储在服务端,可将多个请求数量,优化成一个,是一个并行的加载
- 客户端渲染是依赖用户的网络,而服务端渲染是利用内⽹机器拉取数据,加载速度更快
- ⼀个 HTML 返回所有数据
优势:
- 减少白屏时间
- 对于SEO友好
浏览器和服务器交互过程
具体过程:
- 页面请求开始,请求会到达服务端(server)
- 服务端(server)会拿到HTML模板(HTML templete)和页面数据(data),渲染引擎会将HTML templete和data会进行server render
- server render之后,浏览器会解析并渲染拼装好的HTML文件,此时,用户可以看到页面
- 再加载并执行JS文件和其他资源文件,页面到达完全可交互的状态
客户端渲染 VS 服务端渲染
总结:
- 客服端渲染是一个前端的JS渲染,而服务端渲染是在node端等底层语言里进行的渲染
- 服务端渲染 (SSR) 的核⼼是减少请求