# WebPack
官方简介:
webpack (opens new window) 是一个现代 JavaScript 应用程序的静态资源、模块化 打包器(module bundler)。
打包工具解析
- JavaScript
将 ES6+语法转换成 ES5(某些浏览器不支持)
将 CJS、ESM 模块化代码转换为浏览器可以执行代码
其他 vue、CSS 预处理器代码处理
安装:
npm i webpack webpack-cli -D
webpack 打包方式
# 使用局部的 webpack ✨
./node_modules/.bin/webpack
npx webpack
# 附带参数
npx webpack --entry ./src/main.js
npx webpack --output-path ./build
npx webpack --output-filenme bundle.js
npx webpack --mode production
# 使用全局的 webpack
webpack
或者:
显然 webpack 的使用是需要 node 环境的
{
"scripts": {
"build": "webpack"
}
}
webpack 项目默认结构
WebpackProject
├─ dist
| └─ main.js
├─ node_modules
├─ src
| └─ index.js
├─ index.html
├─ package-lock.json
├─ package.json
└─ webpack.config.js
下面介绍的是关于 webpack 手动配置的相关内容
webpack 环境搭建
- webpack.config.js
const path = require('path')
const HtmlPlugin = require('html-webpack-plugin')
module.exports = {
// 基本配置
mode: 'development',
entry: './src/index.js',
output: {
path: path.join(__dirname, './dist'),
filename: 'main.js'
},
// 辅助插件
// webpack-dev-server
devServer: {
open: true,
port: 80,
host: '127.0.0.1'
},
// html-webpack-plugin
plugins: [
new HtmlPlugin({
template: './index.html' // html模板
})
],
// ts-loader
module: {
rules: [
{
test: /\.ts$/,
loader: 'ts-loader'
}
]
}
}
# 一、基本配置
# 1、mode 模式
运行模式(mode):
默认为
production
,所以开发阶段必须配置。
module.exports = {
mode: 'development' // 这个设置会修改 process.env.NODE_ENV 的值 和 其他配置
}
- development:打包速度快
- production:会对生成的文件进行代码压缩和性能优化
环境区分
① 方式 1
WebpackProject
├─ config
| ├─ webpack.dev.config.js # 开发配置
| └─ webpack.prod.config.js # 生产配置
├─ package.json
└─ README.md
- package.json
{
"scripts": {
"build": "webpack --config ./config/webpack.prod.config.js",
"serve": "webpack serve --config ./config/webpack.dev.config.js"
}
}
② 方式 2
WebpackProject
├─ config
| ├─ webpack.comm.config.js # 公共配置
| ├─ webpack.dev.config.js # 开发配置
| └─ webpack.prod.config.js # 生产配置
├─ package.json
└─ README.md
npm install webpack-merge -D
- webpack.dev.config.js
const { merge } = require("webpack-merge")
const commonConfig = require("./webpack.comm.config")
module.exports = merge(commonConfig, {
mode: "development",
devServer: {
port: 8888
}
})
- webpack.prod.config.js
const { merge } = require("webpack-merge")
const commonConfig = require("./webpack.comm.config")
module.exports = merge(commonConfig, {
mode: "production",
output: {
clean: true
}
})
③ 方式 3
上面的两种方式实际上和 vue-cli 脚手架 2.0 时构建项目的方式基本类似,不过后来使用了 vue-service-cli 这个库来实现全部过程
# 2、entry/output
项目打包的入口(entry)、出口(output):
在 webpack 4.x 和 5.x 的版本中,默认的打包路径如下
const path = require('path')
module.exports = {
//下面是默认的配置
content: "" // 默认为根路径
entry: path.join(__dirname, './src/index.js'), // 相对于content
output: {
path: path.join(__dirname, './dist'),
filename: 'bundle.js'
}
}
# 3、本地服务
提示
webpack 虽然作为一个打包工具,但是它还可以利用插件实现实时更新,而不用反复使用 npm run build 来查看结果
先安装包 webpack-dev-server
此插件类似于 node.js 中的 nodemon 工具,可以自动帮我们完成编译。
npm i webpack-dev-server -D
然后配置 :
- webpack.config.js
module.exports = {
devServer: {
port: 80,
host: '127.0.0.1',
open: true, //首次打包成功时,自动打开浏览器
compress: true // 静态文件gzip压缩
}
}
关于 模块热更新 HMR
webpack 中是有相关配置的
module.exports = {
devServer: {
hot: true // 模块热更新HMR(默认开启)
}
}
但是一般情况下,是不需要我们手动配置 HMR 的,大多都是默认开启或者默认支持 HMR 的。
比如 vue 开发中,vue-loader 就支持 vue 组件的 HMR,并提供了开箱即用的解决方案
devServer 重要配置
- historyApiFallback
https://www.cnblogs.com/Lencamo/p/16860823.html
- proxy
http://localhost:8080/notes-library/frontend/node_Note/Node%E5%AD%A6%E4%B9%A0/%E8%B7%A8%E5%9F%9F.html#_3%E3%80%81vue-cli-%E7%9A%84%E4%BD%BF%E7%94%A8
- package.json
{
"scripts": {
"serve": "webpack serve" // 编译文件不会输出到output目录,而是保留在内存中
}
}
注意事项:实时预览 ✍
webpack-dev-server 插件除了可以自动打包外,还可以通过 http://localhost:8080 查看打包效果。
但是我们已经知道的是实际上 webpack-dev-server 使用的是一个库叫做 memfs,它是将编译代码放在内存中,然后提供一个本地服务供我们使用。
但是由于内存中和本地项目中的 bundle.js 文件并不在同一位置,所以 bundle.js 文件不是实时的
<!-- 原来:使用本地项目中的bundle.js -->
<script src="../dist/bundle.js"></script>
<!-- 现在: 直接使用内存中的bundle.js(保持bundle.js实时更新)-->
<script src="/bundle.js"></script>
# 二、打包辅助插件
plugins 和 loader 各自的作用
loader 可以先解析.css 文件,而 plugins 可以将所有的.css 文件融合在 style.css 中,而不是直接塞入 html 中
# 1、plugins 插件
上面手动解决通过 http://localhost:8080 直接实时预览项目效果显然不是长久之策。除了之外,我们还可以通过插件的方式:
① html-webpack-plugin 插件
先安装包 html-webpack-plugin
npm i html-webpack-plugin -D
然后配置:
- webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
plugins: [
new HtmlWebpackPlugin({
title: "xxxx" // 可以设置一个标题
template: './index.html' // 使用自定义模板(相对于项目根目录)
filename: './index.html' // 设置该模板打包后文件路径和文件名(相对于output目录)
})
]
}
html-webpack-plugin 插件会根据配置自动在 output 目录和项目根目录中生成 index.html 文件,并自动导入 bundle.js 文件
npm run build
<script src="bundle.js"></script>
② clean-webpack-plugin
每次打包前,原有的 dist 文件并不会自动删除,所有下次打包时可能会有残留文件。这个问题我们可以用下面这个插件解决:
提示
在最新的 webpack 官网 plugin (opens new window) 介绍中并没有 clean-webpack-plugin,转而可以直接使用 output.clean (opens new window) 进行配置
module.exports = {
//...
output: {
clean: true // 在生成文件之前清空 output 目录
}
}
安装
npm install clean-webpack-plugin -D
配置
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
plugins: [new CleanWebpackPlugin()]
}
webpack 内置插件
默认情况下,在 webpack 中我们可以使用下面这个全局变量
console.log(process.env.NODE_ENV)
除此之外,我们还可以自定义全局变量(以 vue-cli 中的某个效果为例:)
<!-- ejs模板 -->
<link rel="ico" href="<%= BASE_URL %>favicon.ico" />
const { DefinePlugin } = require('webpack')
module.exports = {
plugins: [
new DefinePlugin({
BASE_URL: "'./'"
// 其他自定义全局变量
})
]
}
# 2、loader 插件
实际开发中,webpack 默认只能打包处理.js 文件,而无法处理.css、.less 等文件。
先安装相关解析插件:
# css文件
npm i [email protected] [email protected] -D
# less文件
npm i [email protected] [email protected] -D
# 图片文件(webpack5之前)
npm i [email protected] [email protected] -D
# js高级语法🎈(ES6+、TS、JSX)
npm i babel-loader @babel/core @babel/preset-env -D
然后配置:
- webpack.config.js
limit 限制的是图片大小小于等于多少 B 字节,limit=1024 表示图片小于等于 1kB 才会转换为 base64 字符串。
module.exports = {
module: {
rules: [
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
{ test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] },
// { test: /\.(jpe?g|png|gif|svg)$/, use: 'url-loader?limit=8192' },
{
test: /\.(jpe?g|png|gif|svg)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192
}
}
]
},
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/, // 排除npm包文件(注意与 webpack-node-externals包的区别)
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
}
postcss 配置说明
use 中的多个 loader 的使用顺序是从后往前
module.exports = {
module: {
rules: [
{
test: /\.sass$/,
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: {
modules: true
}
},
{ loader: 'sass-loader' },
{
loader: 'postcss-loader'
options: {
postcssOptions: {
plugins: [
require('autoprefixer') // 可以自动为css添加浏览器前缀
]
}
}
}
]
}
]
}
}
除此此外,我们还可以将 postcss 配置单独抽离出来:
- postcss 插件
# 1、CSS新特性
npm install autoprefixer -D
# 或者
npm install postcss-preset-env -D
# 2、px -> rem/vm
- postcss.config.js
module.exports = {
plugins: [
// 'autoprefixer'
'postcss-preset-env' // 这个插件也可以,而且根据强大
]
}
资源模型类型
在 webpack5 之后,webpack 对于图片资源的处理,已经不需要手动下载 loader 了,转而使用资源模块类型(Asset Modules types)。
module.exports = {
module: {
rules: [
{
test: /\.(jpe?g|png|gif|svg)$/,
type: 'asset'
}
]
}
}
资源模块类型通过添加 4 种新模块类型来替换原先对应的 loader,下面简单介绍两个:
- asset/resource:
作用:和 file-loader 一样,webpack 打包图片,并放在 dist 文件夹根目录(图片名改变了)
缺点:大量图片会增加网络请求
- asset/inline:
作用:和 url-loader 一样,将图片转换为 base64 行内编码使用,并将其放在打包的 js 文件中
缺点:大量图片会造成 js 代码下载和解析/执行时间过长
① 打包优化 1:是否使用 url-loader
module.exports = {
module: {
rules: [
{
test: /\.(jpe?g|png|gif|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 4 * 1024 // 4kb(默认8kb) // 使用asset/inline时,文件大小限制
}
}
}
]
}
}
② 打包优化 2:自定义输出文件名
module.exports = {
module: {
rules: [
{
test: /\.(jpe?g|png|gif|svg)$/,
type: 'asset',
generator: {
filename: 'static/[name]_[hash:8][ext]' // 使用asset/resource时,文件名设置
}
}
]
}
}
babel 配置说明
现在我们已经知道了 babel 可以将复杂的、特殊的 js 文件转换为浏览器可识别的 js 文件。
module.exports = {
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'] // 预设
// plugins: [
// '@babel/plugin-transform-arrow-functions',
// '@babel/plugin-transform-block-scoping'
// ] // 手动添加插件
}
}
}
]
}
}
和 postcss 一样,我们也可以将 babel 配置单独抽离出来:
- babel.config.js
module.exports = {
presets: ['@babel/preset-env'] // 预设
}
常见的预设有 env、react、typescript,我们这里仅仅使用了 env 预设
@babel/preset-env
而已
拓展:webpack 处理 vue 代码
① 代码编写
npm install vue
- /src/components/Hello.vue
<template>
<p>{{ title }}</p>
</template>
<script>
export default {
data() {
return {
title: '你好呀!'
}
}
}
</script>
<style>
p {
color: red;
}
</style>
- /src/main.js
import { createApp } from 'vue'
import Hello from './components/Hello.vue'
createApp(Hello).mount('#app')
- index.html
<body>
<div id="app"></div>
</body>
② webpack 配置
npm install vue-loader -D
const { VueLoaderPlugin } = require('vue-loader/dist/index')
module.exports = {
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
},
plugins: [new VueLoaderPlugin()]
}
# 三、其他
# 1、dist 优化
# ① 模块化
普通方式打包生成的文件全部直接放在了 dist 文件夹下,没有进行分类,显得格外混乱。
- webpack.config.js 配置
module.exports = {
// 1、js文件处理
output: {
path: path.join(__dirname, './dist'),
filename: 'js/bundle.js'
},
// 2、图片文件处理
module: {
// &outputPath指定存放位置
rules: [{ test: /\.jpg|png|gif$/, use: 'url-loader?limit=1024&outputPath=images' }]
}
}
# ② 自动化
防止打包时,旧的 dist 内文件对新生成的 dist 内文件造成影响。所以自动删除旧的 dist 文件夹是非常有必要的:
安装包 clean-webpack-plugin
npm i [email protected] -D
然后配置
- webpack.config.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const cleanPlugin = new CleanWebpackPlugin()
module.exports = {
plugins: [cleanPlugin]
}
# 2、source-map 优化
默认情况下,运行项目报错时,控制台报错的提示行号(bundle.js)与源代码的对应行号是不一致的。
原因:
在浏览器中运行的代码是打包压缩后的代码(代码经过转换)
那如果我们在开发阶段想要浏览器正确显示报错的位置该怎么做?
开启 source-map
source-map 可以将已转换的代码,映射到原始的源文件。使浏览器可以重构原始源,并在调试器中显示重建的原始源
- webpack.config.js
module.exports = {
devtool: 'false' // 不使用source-map
// 1、production模式
devtool: 'none' // 默认值(不生成source-map文件)
devtool: 'source-map' // 会生成完整的 source_map 文件(浏览器会根据其中的 // # sourceMappingURL = bundle.js.map 找到并解析source-map文件)
devtool: 'nosources-source-map' // 只显示行号不暴露源码
// 2、development模式
devtool: 'eval' // 默认值(不生成source-map文件)
devtool: 'eval-source-map' // source-map是以DataUrl添加到bundle.js内eval函数的后面
}
TIP
开发阶段为了方便调试,可以手动关闭 Source Map。但项目发布时,出于安全角度(防止源码暴露),建议使用默认的none
或者使用nosources-source-map
(只显示行号不暴露源码)。
# 3、webpack 分包
import 函数可以让 webpack 对导入文件进行分包处理(首屏渲染速度 🤔)
问题描述
默认情况下,webpack 在打包时会将组件模块打包到一起(比如 app.js 文件中)。随着项目的不断庞大,app.js 文件的内容过大,会造成首屏的渲染速度变慢。
我们可以把一些不需要立即使用的组件从 app.js 文件中拆分出来,形成单独的 chunk.js 代码块,这些 chunk.js 会在需要时从服务器加载并运行。
常见的 vue-cli 打包时就默认包项目核心代码 app.js 和第三方库 chunk.js 进行了分离。
// 路由组件分包
const Home = () => import('@/components/Home.vue')
const Home = () => import(/* webpackChunkName: "home" */ '@/components/Home.vue')
# 4、路径优化
除了上面的细节,我们还可以对路径进行优化:
配置如下:
- webpack-config.js
@表示正处于项目根目录的 src 文件夹下
module.exports = {
// 内部实际是通过enhanced-resollve包实现的
resolve: {
extensions: ['.js', '.json', '.jsx', '.ts', '.vue'], // 让模块化导入可以省略后缀名(默认值为:['.js', '.json', '.wasm'])
alias: {
'@': path.join(__dirname, './src/')
}
}
}
使用示例:
// 原来:
import './css/index.css'
import './css/index.less'
// 现在:
import '@/css/index.css'
import '@/css/index.less'
babel →