基本使用
安装和配置
拆分 dev prod 配置,然后 merge
- 安装 nodejs
- 初始化
npm init -y
- 安装插件
npm i webpack webpack-cli webpack-merge --save-dev
- 新建
src
及其测试 js 代码(包含 ES6 模块化) - 创建配置文件
- 增加
scripts
,运行 - 安装
npm i clean-webpack-plugin --save-dev
- 配置 prod
本地服务和代理
- 新建
index.html
- 安装
npm i html-webpack-plugin --save-dev
,并配置 - 安装
npm i webpack-dev-server --save-dev
,并配置 - 修改
scripts
的dev
,运行
处理 ES6
- 安装
npm i @babel/core @babel/preset-env babel-loader --save-dev
- 配置 webpack module
- 配置
.babelrc
- 运行 dev
处理样式
- 安装
npm i style-loader css-loader less-loader less --save-dev
(注意要安装 less) - 配置 webpack module
- 新建 css less 文件,引入 index.js
- 运行 dev
postcss
- 安装
npm i postcss-loader autoprefixer -D
- 新建
postcss.config.js
- 配置 webpack module ,增加
postcss-loader
- 增加 css
transform: rotate(-45deg);
- 运行 dev
处理图片
- 安装
npm i file-loader url-loader --save-dev
- 分别配置 webpack.dev 和 webpack.prod
- 新建图片文件,并引入到 js 中,插入页面
- 运行 dev
- 运行 build
高级应用
多入口
- 新建
other.html
和other.js
- 修改 entry
- 修改 output
- 修改 HtmlWebpackPlugin
- 运行 dev
- 运行 build
抽离 & 压缩 css 文件
抽离
- 安装
npm i mini-css-extract-plugin -D
- 将之前 common 中的 css 处理,移动到 dev 配置中
- 配置 prod (配置 module ,配置 plugin)
- 运行 build
压缩
- 安装
npm i terser-webpack-plugin optimize-css-assets-webpack-plugin -D
- 配置 prod
抽离公共代码
- 配置
splitChunks
- 修改 HtmlWebpackPlugin 中的 chunks 。重要!!!
- 安装 lodash
npm i lodash --save
做第三方模块的测试,引用 lodash - 运行 build
懒加载
- 增加
dynamic-data.js
并动态引入 - 运行 dev 查看效果(看加载 js)
- 运行 build 看打包效果
至此,可以总结一下:
- module:就是js的模块化webpack支持commonJS、ES6等模块化规范,简单来说就是你通过import语句引入的代码。
- chunk: chunk是webpack根据功能拆分出来的,包含三种情况:
- 你的项目入口(entry)
- 通过import()动态引入的代码
- 通过splitChunks拆分出来的代码
- chunk包含着module,可能是一对多也可能是一对一
- bundle:bundle是webpack打包之后的各个文件,一般就是和chunk是一对一的关系,bundle就是对chunk进行编译压缩打包等处理之后的产出。
处理 react 和 vue
- vue-loader
- jsx 的编译,babel 已经支持,配置
@babel/preset-react
打包效率
优化 babel-loader
- babel-loader cache 未修改的不重新编译
- babel-loader include 明确范围
{
test: /\.js$/,
use: ['babel-loader?cacheDirectory'], // 开启缓存
include: path.resolve(__dirname, 'src'), // 明确范围
// // 排除范围,include 和 exclude 两者选一个即可
// exclude: path.resolve(__dirname, 'node_modules')
},
IgnorePlugin 避免引入哪些模块
以常用的 moment 为例。安装 npm i moment -d
并且 import moment from 'moment'
之后,monent 默认将所有语言的 js 都加载进来,使得打包文件过大。可以通过 ignorePlugin 插件忽略 locale 下的语言文件,不打包进来。
plugins: [
// 忽略 moment 下的 /locale 目录
new webpack.IgnorePlugin(/\.\/locale/, /moment/)
]
import moment from 'moment'
import 'moment/locale/zh-cn' // 手动引入中文语言包
moment.locale('zh-cn')
noParse 避免重复打包
module.noParse
配置项可以让 Webpack 忽略对部分没采用模块化的文件的递归解析处理,这样做的好处是能提高构建性能。 原因是一些库,例如 jQuery 、ChartJS, 它们庞大又没有采用模块化标准,让 Webpack 去解析这些文件耗时又没有意义。
module.exports = {
module: {
// 独完整的 `react.min.js` 文件就没有采用模块化
// 忽略对 `react.min.js` 文件的递归解析处理
noParse: [/react\.min\.js$/],
},
};
IgnorePlugin
直接不引入,代码中不存在noParse
引入,但不再打包编译
happyPack 多进程打包
大型项目,构建速度明显变慢时,作用才能明显。否则,反而会有副作用。
webpack 是基于 nodejs 运行,nodejs 是单线程的,happyPack 可以开启多个进程来进行构建,发挥多核 CPU 的优势。
const path = require('path')
const HappyPack = require('happypack')
module.exports = {
module: {
rules: [
{
test: /\.js$/,
// 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
use: ['happypack/loader?id=babel'],
exclude: path.resolve(__dirname, 'node_modules')
}
]
},
plugins: [
new HappyPack({
// 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
id: 'babel',
// 如何处理 .js 文件,用法和 Loader 配置中一样
loaders: ['babel-loader?cacheDirectory'],
// ... 其它配置项
})
]
}
ParallelUglifyPlugin 多进程压缩 js
webpack 默认用内置的 uglifyJS 压缩 js 代码。大型项目压缩 js 代码时,也可能会慢。可以开启多进程压缩,和 happyPack 同理。
const path = require('path')
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')
module.exports = {
plugins: [
// 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
new ParallelUglifyPlugin({
// 传递给 UglifyJS 的参数
// (还是使用 UglifyJS 压缩,只不过帮助开启了多进程)
uglifyJS: {
output: {
beautify: false, // 最紧凑的输出
comments: false, // 删除所有的注释
},
compress: {
// 在UglifyJs删除没有用到的代码时不输出警告
warnings: false,
// 删除所有的 `console` 语句,可以兼容ie浏览器
drop_console: true,
// 内嵌定义了但是只用到一次的变量
collapse_vars: true,
// 提取出出现多次但是没有定义成变量去引用的静态值
reduce_vars: true,
}
},
}),
],
};
自动刷新
module.export = {
watch: true, // 开启监听,默认为 false
// 注意,开启监听之后,webpack-dev-server 会自动开启刷新浏览器!!!
// 监听配置
watchOptions: {
ignored: /node_modules/, // 忽略哪些
// 监听到变化发生后会等300ms再去执行动作,防止文件更新太快导致重新编译频率太高
// 默认为 300ms
aggregateTimeout: 300,
// 判断文件是否发生变化是通过不停的去询问系统指定文件有没有变化实现的
// 默认每隔1000毫秒询问一次
poll: 1000
}
}
热更新
上文的自动刷新,会刷新整个网页。
- 速度更慢
- 网页当前的状态会丢失,如 input 输入的文字,图片要重新加载,vuex 和 redux 中的数据
操作步骤
- 把现有的 watch 注释掉
- 修改 css less 实验 —— 热替换生效
- 修改 js 实验 —— 热替换不生效
const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin');
module.exports = {
entry:{
// 为每个入口都注入代理客户端
index:[
'webpack-dev-server/client?http://localhost:8080/',
'webpack/hot/dev-server',
path.join(srcPath, 'index.js')
],
// other 先不改了
},
plugins: [
// 该插件的作用就是实现模块热替换,实际上当启动时带上 `--hot` 参数,会注入该插件,生成 .hot-update.json 文件。
new HotModuleReplacementPlugin(),
],
devServer:{
// 告诉 DevServer 要开启模块热替换模式
hot: true,
}
};
js 热替换不生效,是因为我们要自己增加代码逻辑。
// 增加,开启热更新之后的代码逻辑
if (module.hot) {
module.hot.accept(['./math'], () => {
const sumRes = sum(10, 20)
console.log('sumRes in hot', sumRes)
})
}
最后,热替换切勿用于 prod 环境!!!
DllPlugin
Dll 动态链接库,其中可以包含给其他模块调用的函数和数据。
要给 Web 项目构建接入动态链接库的思想,需要完成以下事情:
- 把网页依赖的基础模块抽离出来,打包到一个个单独的动态链接库中去。一个动态链接库中可以包含多个模块
- 当需要导入的模块存在于某个动态链接库中时,这个模块不能被再次被打包,而是去动态链接库中获取。
- 页面依赖的所有动态链接库需要被加载。
为什么给 Web 项目构建接入动态链接库的思想后,会大大提升构建速度呢?
- 前端依赖于第三方库
vue
react
等 - 其特点是:体积大,构建速度慢,版本升级慢
- 同一个版本,只需要编译一次,之后直接引用即可 —— 不用每次重复构建,提高构建速度
Webpack 已经内置了对动态链接库的支持,需要通过2个内置的插件接入,它们分别是:
- DllPlugin 插件:打包出 dll 文件
- DllReferencePlugin 插件:使用 dll 文件
打包出 dll 的过程
- 增加 webpack.dll.js
- 修改 package.json scripts
"dll": "webpack --config build/webpack.dll.js"
npm run dll
并查看输出结果
使用 dll
- 引入
DllReferencePlugin
- babel-loader 中排除
node_modules
- 配置
new DllReferencePlugin({...})
- index.html 中引入
react.dll.js
- 运行 dev
总结 - 提高构建效率的方法
- 优化 babel-loader(可用于线上)
- IgnorePlugin 避免引入哪些模块(可用于线上)
- noParse 避免重复打包(可用于线上)
- happyPack 多进程打包(可用于线上)
- ParallelUglifyPlugin 多进程压缩 js(可用于线上)
- 自动刷新(仅开发环境)
- 热更新(仅开发环境)
- DllPlugin(仅开发环境)
产出代码优化
使用 production
- 开启压缩代码
- 开启 tree shaking(必须是 ES6 Module 语法才行)
ES6 Module 和 commonjs 的区别
- ES6 Module 是静态引入,编译时引入
- commonjs 是动态引入,执行时引入
// commonjs
let apiList = require('../config/api.js')
if (isDev) {
// 可以动态引入,执行时引入
apiList = require('../config/api_dev.js')
}
import apiList from '../config/api.js'
if (isDev) {
// 编译时报错,只能静态引入
import apiList from '../config/api_dev.js'
}
小图片 base64 编码
webpack4.x为图片资源配置base64编码规范_webpack4 confogurewebpack配置图片为base64-CSDN博客
bundle 加 hash
动手写webpack配置--7.webpack加hash值命名。_webpack打包hash命名-CSDN博客
使用 CDN
配置 publicPath
https://juejin.cn/post/6977628582419890206
提取公共改代码
Webpack高级配置「四」-- 抽离公共代码和第三方模块 ***_webpack 抽取公共配置-CSDN博客
懒加载
https://juejin.cn/post/6990565311955075085?searchId=202407250628002FBB6F4BF86C4762E067
scope hosting 将 module 合并到一个函数中
https://juejin.cn/post/6850418110983241741
构建流程
几个核心概念
- Entry:入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入
- Module:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。
- Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。
- Loader:模块转换器,用于把模块原内容按照需求转换成新内容。
- Plugin:扩展插件,在 Webpack 构建流程中的特定时机会广播出对应的事件,插件可以监听这些事件的发生,在特定时机做对应的事情。
Webpack 的构建流程可以分为以下三大阶段:
- 初始化:启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler
- 编译:从 Entry 发出,针对每个 Module 串行调用对应的 Loader 去翻译文件内容,再找到该 Module 依赖的 Module,递归地进行编译处理。
- 输出:对编译后的 Module 组合成 Chunk,把 Chunk 转换成文件,输出到文件系统。
babel
初始化环境
-
安装 babel 插件
npm i @babel/cli @babel/core @babel/preset-env -D
-
新建
.babelrc
,配置 preset-env -
新建
src/index.js
,写一个箭头函数 -
运行
npx babel src/index.js
,看结果
使用 polyfill
- 什么是 polyfill ?—— 即一个补丁,引入以兼容新 API(注意不是新语法,如箭头函数),如搜索“Object.keys polyfill” 和 “Promise polyfill”
- core-js 集合了所有新 API 的 polyfill
- regenerator 是 generator 的 polyfill
- babel-polyfill 即 core-js 和 regenerator 的集合,它只做了一层封装而已。
基本使用
src/index.js
中写一个 Promise,打包看结果npm install --save @babel/polyfill
【注意】要--save
- 然后引入
import '@babel/polyfill'
- 再打包,看结果
- 解释:babel 仅仅是处理 ES6 语法,并不关心模模块化的事情。模块化归 webpack 管理
- 全部引入 polyfill ,体积很大
按需加载
- 新增
"useBuiltIns": "usage"
(注意要改写 preset 的 json 结构) - 删掉入口的
import '@babel/polyfill'
使用 runtime
babel-polyfill 的问题 —— 会污染全局变量
- 如果是一个网站或者系统,无碍
- 如果是做一个第三方工具,给其他系统使用,则会有问题
- 不能保证其他系统会用 Promise 和 includes 做什么,即便他们用错了,那你也不能偷偷的给更改了
// 源代码
Promise.resolve(100).then(data => data);
[10, 20, 30].includes(20);
// 结果 —— 可以看到,Promise 和 includes 都未改动,因为以注入全局变量了
"use strict";
require("core-js/modules/es.array.includes.js");
require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.promise.js");
Promise.resolve(100).then(function (data) {
return data;
});
[10, 20, 30].includes(20);
使用 babel-runtime
npm install --save-dev @babel/plugin-transform-runtime
`npm install --save @babel/runtime
,注意是--save
- 配置
"plugins": ["@babel/plugin-transform-runtime"]
- 删掉
"useBuiltIns": "usage"
- 运行代码
手把手教你在webpack中,配置两种常用的polyfills配置,以ie11为例,配置babel-loader-CSDN博客