webpack 前端项目构建打包工具
1,为什么要学webpack?
webpack
是一款可用于 前端项目构建和打包的工具,我们先来看看在webpack
之前,我们是如何来在web
中运行js
的
- 但是,代码自上向下执行,js文件可能相互之间存在 依赖关系 ,所以顺序很重要,一旦 顺序错乱 ,页
面可能就会出现问题- 导致:开发受限制,代码难以扩展
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>引入多个js文件时</title>
</head>
<body>
<div>HTML代码</div>
<div>代码自上向下执行,js文件可能相互之间存在依赖关系,所以顺序很重要,一旦顺序错乱,页面可能就会出现问题</div>
<div>导致:开发受限制,代码难以扩展</div>
</body>
<!-- 外部CDN资源 外部的js文件 -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.core.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/js/bootstrap.min.js"></script>
<!-- 本地js文件,可能对上面的外部CDN资源文件存在依赖关系 -->
<script src="./本地js文件/index1.js"></script>
<script src="./本地js文件/index2.js"></script>
<script src="./本地js文件/index3.js"></script>
<script src="./本地js文件/index4.js"></script>
<script src="./本地js文件/index5.js"></script>
</html>
如果我们将js代码放入一个js文件并引入:
- 作用域问题
- 例如多个
js
片段可能都会在window
对象上定义全局变量,可能造成全局变量污染,而且会使window
对象变的臃肿- 文件太大
- 如果
js
文件分为多个引入,页面会随着js
文件的逐个加载而逐渐显示内容- 但是,全部放入一个
js
文件,会造成网络瓶颈,要等待js
文件全部加载完成,才会显示页面,页面会造成短暂白屏,用户体验差- 可读性差
- 所有代码放在一起,可读性差,过于混乱,臃肿
- 可维护性差
- 所有代码放在一起,维护时可能会对其他代码造成影响
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>将js代码放入一个js文件并引入</title>
</head>
<body>
// 全部js代码放入一个文件
<script src="./本地js文件/all.js"></script>
</body>
</html>
早先前,我们使用 Grunt
和 Gulp
两个工具来管理我们项目的资源。
这两个工具称为任务执行器,它们将所有项目文件拼接在一起。利用了立即调用函数表达式(IIFE)-Immediatelyinvokedfunctionexpressions
, 解决了大型项目的作用域问题;
当脚本文件被封装在 IIFE 内部时,你可以安全地拼接或安全地组合所有文件,而不必担心作用域冲突。
IIFE
解决作用域的方式:
// gulp 和 grunt 利用IIFE解决作用域问题
// 利用IIFE立即执行函数,每个脚本都在自己的作用域中执行,不会造成作用域冲突问题
// 避免了造成全局变量污染,window对象臃肿
; (function () { var myName = '小蛤蟆' })()
console.log(myName) //报错,因为函数内部变量,外部无法访问
// 如果想把函数内部变量暴露在window中
let result = (function () {
var myName = '小蛤蟆'
return myName
})()
// 使用脚本内的变量,且不会造成全局变量污染
// 将 IIFE 分配给一个变量,存储的不是IIFE自身,而是存储 IIFE 执行后返回的结果
console.log(result)
利用
IIFE
这样虽然解决了代码的作用于问题,但是:
js
代码放在一个文件中,修改一处,就会重新构建整个文件- 如果我们只是需要使用这个文件中的一个方法,却需要引入构建整个文件
- 会导致我们需要大规模的加载没有用到的代码块,无法实现按需引用构建
面对上面情况,这个时候,node.js
的模块化就派上了用处。
node.js
所遵循的 COMMON JS
规范,它引入了 require
机制,它允许你在当前文件中 加载和使用某个模块。
导入需要的每个模块,这一开箱即用的功能,帮助我们解决了代码拆分的问题。
但 CommonJS
没有浏览器支持,循环引用存在问题。同步执行的模块解析加载器速度很慢。虽然 CommonJS 是 Node.js 项目的绝佳解决方案,但浏览器不支持模块,我们似乎又遇到了新问题。
- 在早期,我们可以使用
Browserify
和RequireJs
等打包工具编写能够在浏览器中运行的 CommonJS 模块。- 目前,我们还有一个选择,就是来自 Web 项目的好消息是,模块正在成为
ECMAScript
标准的官方功能。ES6的模块化导入导出
。然而,浏览器支持不完整,版本迭代速度也不够快,还是推荐上面两个早期模块实现。早期的任务构建工具基于 Google 的 Closure 编译器,要求我们手动在顶部声明所有的依赖,开发体验不好。
-
1.5,
Webpack
搞定一切webpack
是一个工具,可以 打包 你的 JavaScript 应用程序(支持 ESM 和 CommonJS),可以扩展为支持许多不同的静态资源,例如:images、fonts、stylesheets
。webPack
关心性能和加载时间;它始终在改进或添加新功能,例如:异步地加载和预先加载代码文件,以便为你的项目和用户提供最佳体验。webpack
为 处理资源管理和分割代码 而生,可以包含任何类型的文件。灵活,插件多。- **
webpack
运行在node.js
环境中
2,下载使用 webpack
npm i webpack webpack-cli --save--dev
- 同时下载
webpack
和webpack-cli
这两个构建工具--save--dev
,下载在 开发环境下 ,因为我们通常只是使用webpack
作为开发时的构建工具
npx webpack
- 使用
npx
来查找运行webpack
是因为,当前目录下可能没有webpack
,而npx
会自动帮我们向上层目录查找webpack
- 运行
webpack
后,会发现webpack
帮助我们进行了打包- 多出来的
dist
目录就是webpack
帮助我们打包后生成的文件- 运行
npx webpack --stats detailed
指令,可以看到 打包信息,例如入口文件从何而来等
3,自定义webpack
配置
webpack-cli
给我们提供了很多终端命令行指令,可以通过 webpack--help
来查看。
但是,命令很太多,不方便不直观,而且还不利于保存配置的内容。
因此,webpack
给我们提供了 通过配置文件,来自定义配置参数的能力。
在根目录下创建
webpack.config.js
文件
webpack.config.js
文件的文件名是固定的 ,webpack
打包时会自动查找该文件const path = require('path') module.exports = { // 配置入口文件 entry: './src/index.js', // 配置出口文件 output: { // 配置出口文件名 filename: 'bundle.js', // 配置出口文件的目录路径 目录名 (因为是决定路径,所以采用node的path模块,计算路径) path: path.resolve(__dirname, './dist') }, mode: 'none' }
4,自动引入资源
webpack
有非常强大的插件,可也帮助我们更方便的管理项目
根据指定
html
文件基础上,自动生成新的html
文件 在打包目录,并自动引入js
文件
npm i htmlwebpackplugin -D
,下载插件到开发环境- 在
webpack.config.js
配置文件中,引入
let HtmlWebpackPlugin = require('html-webpack-plugin')
- 配置插件:
const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, './dist'), }, mode: 'none', plugins: [ new HtmlWebpackPlugin({ // 模板文件,基于该模板生产新的html文件 template: './index.html', // 新生成文件名 filename: 'app.html', // 引入js文件的script标签位置 inject: 'body' }) ] }
- 自动引入,记得把之前的手动引入去除掉
每次重新打包,原来的打包文件仍然存在,我们可以通过
webpack
配置,每次打包,清空之前的打包文件const path = require('path') module.exports = { ... output: { filename: 'bundle.js', path: path.resolve(__dirname, './dist'), // 每次打包,清空之前的打包文件 clean: true }, ... }
5,搭建开发环境
目前为止,我们打开项目的方式比较麻烦,只能右击在浏览器打开或将文件路径复制到浏览器地址栏打开,比较麻烦,现在我们设置一个开发环境,帮助我们更好的维护和开发项目。
开发模式:
mode:"development"
- 将
mode
的值设置为development
,代表编译模式为开发模式module.exports = { mode:"development" }
在我们运行webpack
搭建的项目时,js
代码是合并压缩后的,在浏览器中报错,也是报的合并文件中的错误,有时候我们并不清楚具体的错误是在合并前的哪个文件那个位置。
devtool:"inlin-source-map"
,代码索引,实现精准定位
- 可以更方便的帮助我们 调试、查找错误
module.exports = { devtool:"inlin-source-map" }
之前我们编译代码都是 npx webpack
但是,每次代码更新完,都需要重新进行编译
npx webpack --watch
,代码监测,,代码变化后,自动进行编译
虽然上面实现了webpack
自动检测代码编译,但是自动编译后,我们还是要手动打开并刷新页面
实时加载刷新页面:
webpack-dev-server
- 下载插件:
npm i webpack-dev-server -D
- 配置根目录,告诉
devserver
从什么位置查找文件- 执行命令:
npx webpack serve --open
module.exports = { devServer:{ // 配置告知 webpack-dev-server ,将 dist 目录下的文件作为 web 服务的根目录 static:"./dist" } }
webpack-dev-server
在编译之后不会写入到任何输出文件。而是将bundle
文件保留在内存中,然后将它们serve
到server
中,就好像它们是挂载在server
根路径上的真实文件一样(但其实并没有一直访问监视根路径的文件)。
6,资源模块
目前为止,我们可以使用webpack
工具来处理我们的html、js
文件的打包,除此之外,我们还可以使用webpack
来处理我们的其他类型资源,例如images
资源。
在 webpack 出现之前,前端开发人员会使用 grunt 和 gulp 等工具来处理资源,并将它们从 /src
文件夹移动到 /dist 或 /build
目录中。webpack 最出色的功能之一就是,除了引入 JavaScript,还可以用 内置 的资源模块 Asset Modules
引入任何其他类型的文件。
资源模块(asset module)是一种模块类型,它允许我们应用Webpack来 打包其他资源文件(如字体,图标等) 资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些loader:
asset/resource
发送一个单独的文件并导出URL
。asset/inline
导出一个资源的data URI。
asset/source
导出资源的源代码
。asset
在导出一个data URI
和发送一个单独的文件
之间自动选择
。- 总之,就是帮助我们把引入的资源自动打包到打包后的文件目录中,并自动引入。
asset/resource
模块使用配置:module.exports = { output: { filename: 'bundle.js', path: path.resolve(__dirname, './dist'), clean: true, //生成的文件名和路径 assetModuleFilename: 'images/[contenthash][ext]' }, module:{ rules:[ { //匹配资源类型 test:/\.png$/, //采用的模块 asset/resource 模块,生成路径,会被打包到打包目录内 type:'asset/resource', //生成的文件名和路径,该方式生成的路径优先级更高,可以覆盖output里面的方式 generator:{ filename:'images/[contenthash][ext]' } }, { test:/\.jpg$/, // asset/inline 模块,会生成一个data url 路径,类似于网路路径,不会被打包到本地的打包目录 type:'asset/inline', }, { test:/\.txt$/, // asset/source 模块,会将资源源代码导出,不会被放入打包文件 type:'asset/source' }, { test:/\.jpg$/, // asset 模块,在resource 和 inline 之间自动选择,选择依据是文件大小是否达到条件 type:'asset' // 文件超过一定大小时才会resource,否则inline,这里设置条件大小 parser:{ dataUrlCondition:{ maxSize:4*1024*1024 // 超出4M才会resource,打包入打包目录 } } } ] } }
然后就可以引入文件,打包时会自动打包资源文件,并引入
// 这时候就可以把资源当成模块去引入,asset/resource模块 它会自动生成一个路径 import imgSrc from './assets/me.png' let img = document.createElement('img') img.src = imgSrc document.body.appendChild(img)
- 使用该模块后,可以把资源当成模块去引入
asset/resource:
模块: 它会自动生成一个url
路径 ,文件存在于打包目录asset/inline
模块:生成一个dataUrl
,文件不在打包目录asset/source
模块:引入资源源码,文件不在打包目录asset
模块:设置一个条件(文件大小),根据条件在resource
和inline
之间自动选择
7,管理资源
除了资源模块,我们还可以通过 loader
引入其他类型的文件。
一种加载器,需要下载但不需要引入,能让 webpack
去处理其他类型的文件,并将它们转换为 模块,以供程序使用,以及被添加到依赖图中。
上面我们使用了 资源模块 来处理资源,可以 让资源像模块一样被引入使用,这一点
loader
也可以做到
loader
的定义:module.exports = { module:{ // 在webpack的配置中,loader有两个属性 rules:[ { // test属性,识别出哪些 import 或 require 的文件需要编译 test:/\.txt$/, // use属性,在编译转换时,使用哪个 loader 来处理 use:'raw-loader' }, { // 同一类资源,多种格式 test:/\.(css|less|scss)$/, // loader 可以是多个,它是从后向前,链式调用的 // less转换为css,css处理后再传给style use:['style-loader','css-loader','less-loader'] }, ] } }
以上配置中,对一个单独的 module 对象定义了 rules 属性,里面包含两个必须属性: test 和 use 。这告诉 webpack 编译器(compiler) 如下信息:
“嘿,webpack 编译器,当你碰到「在 require() / import 语句中被解析为’.txt’ 的路径」时,在你对它打包之前,先 use(使用) raw-loader 转换一下。”
- 根据正则匹配相应文件的后缀,让指定类型文件使用指定的
loader
如果想在JavaScript
模块中import
使用一个css
文件,就需要先安装
npm i ~ -D
style-loader
css-loader
less-loader
- 等…都可以用来处理
css
资源use:['style-loader','css-loader','less-loader']
, 多个loader
可以链式调用,但是是从后向前 逆序调用 的- 被使用的
css
代码会 以style
样式的格式,被添加到html
文档中
let path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
module: {
rules: [
{
test: /\.(css|less)$/,
// 多种loader,逆序链式调用
use: ['style-loader', 'css-loader', 'less-loader']
}
]
}
}
import './style.css'
import './style.less'
// 使用了 loader 可以把css文件引入到js中,当做模块使用它
let div2 = document.createElement('div')
// add 后面括号里是样式文件里面的类名
div2.classList.add('fontColor')
document.body.appendChild(div2)
let body_ = document.body
body_.classList.add('bgc')
多数情况下,我们也可以进行压缩CSS,以便在生产环境中节省加载时间,同时还可以将CSS文件抽离成一个单独的文件。实现这个功能,需要 mini-css-extract-plugin
这个插件来帮忙。
- 下载插件:
npm i mini-css-extract-plugin -D
- 引入插件:在webpack配置文件中引入
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
- 配置抽离后的文件名和路径
new MiniCssExtractPlugin({filename:'styles/[contenthash].css'})
- 文件抽离出来后,就会放在打包目录中
- 调用插件:
use:[MiniCssExtractPlugin.loader,'css-loader','less-loader']
//引入插件 const MiniCssExtractPlugin = require('mini-css-extract-plugin') module.exports = { plugins: [ // 插件配置 文件抽离出来后,就会放在打包目录中的styles文件夹中 new MiniCssExtractPlugin({ filename:'styles/[contenthash].css' }) ], module: { rules:[ { test: /\.(css|less)$/, // 使用插件 use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'] } ] }
加载font
字体文件,使用的是 资源模块 的 resource
模块。
- 首先准备好字体文件
- 配置
loader
{ test: /\.(woff|woff2|eot|ttf|otf)/, type:'asset/resource' }
- 在
css
文件中配置字体样式,定义类名- 给
html
元素添加类名
除了资源,可加载的还有数据,如 JSON、CSV、TSV、XML
等,而 JSON
数据是内置支持的。
处理
CSV、TSV、XML
数据 :
npm i csv-loader xml-loader -D
xml-loader
处理xml
数据csv-loader
处理CSV、TSV
数据- 配置
loader
:module: { rules: [ { test: /\.(csv|tsv)$/i, use: ['csv-loader'], }, { test: /\.xml$/i, use: ['xml-loader'], }, ] }
- 配置完,就可以直接在
js
中import
数据,当做模块使用
通过使用 自定义 parser 替代特定的 webpack loader,可以将任何 toml 、 yaml 或json5 文件作为 JSON 模块导入。
- 下载插件 :
npm i toml yamljs json5 -D
- 在
webpack
配置文件中引入插件、配置插件- 然后就可以在
js
文件中import
引入数据,当作模块使用- 配置代码如下:
const toml = require('toml');
const yaml = require('yamljs');
const json5 = require('json5');
module.exports = {
module: {
rules: [
{
test: /\.toml$/i,
type: 'json',
parser: {
parse: toml.parse,
},
},
{
test: /\.yaml$/i,
type: 'json',
parser: {
parse: yaml.parse,
},
},
{
test: /\.json5$/i,
type: 'json',
parser: {
parse: json5.parse,
},
},
]
}
}
8,JS代码转换,babel-loader
前面的章节里,我们应用css-loader
编译了css
文件,xml-loader
编译了 xml
文件,那么js
文件需要编译吗?
正常情况下是不需要的,但是有些时候,浏览器版本过低,无法编译ES6
语法,这就需要我们提前将ES6
代码转换为ES5
代码。
webpack
自身具有自动加载js
文件的能力,但是要将ES6
代码转换为ES5
代码,就需要使用 babel-loader
来帮忙了。
babel-loader
可以将ES6+
的代码转换为ES5
代码
- 下载
loader
:
npm i babel-loader -D
,在webpack
中应用babel
解析ES6
的桥梁npm i @babel/core -D
,babel
核心模块npm i @babel/preset-env -D
,babel
预设,一组babel
插件的集合npm i @babel/runtime
,这个包中包含了regeneratorRuntime
函数,由babel
生成,用于兼容async/await
的语法。npm i @babel/plugin-transform-runtime
,这个插件会在需要regeneratorRuntime
的地方 自动require
导包,编译时需要
webpack
中配置babel
:
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
plugin:[
[
'@babel/plugin-transform-runtime'
]
]
}
}
}
]
}
9,代码分离
代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的bundle
中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。
常用的代码分离方法有三种:
- 入口起点: 使用
entry
配置多个入口文件,手动分离代码。- 防止重复: 使用
Entrydependencies
或者SplitChunksPlugin
去重和分离chunk
- 动态导入: 通过模块的内联函数调用来分离代码
这是迄今为止最简单直观的分离代码的方式。不过,这种方式手动配置较多,并有一些隐患,我们将会解决这些问题。
现在,我们有两个入口文件,且都使用了同一个模块。
// index.js
// 引入使用了a模块
......
// another.js
// 也引入使用了a模块
......
配置多入口:
module.exports = { entry: { index: './src/index.js', another: './src/another_module.js' }, output: { //这里的name是上面入口文件的key,即index、another filename: '[name].bundle.js', } }
- 配置好后,打包,打包目录下生成了两个
js
文件,且在app.html
文件中都被引入- 两个入口文件中都引入了
a
模块- 缺点: 如果多个入口文件之间包含一些重复的模块,那些重复模块都会被引入到各个
bundle
中。,造成了重复引入- 不能动态地将核心应用程序逻辑中的代码拆分出来
- 入口依赖:
- 配置
dependon
选项:,这样可以在多个打包模块之间共享模块:module.exports = { entry: { // 打包生成两个js文件 index: { import: './src/index.js', dependOn: 'shared', }, another: { import: './src/another-module.js', dependOn: 'shared', }, // 两个文件中需要共享的模块,多个就是数组,名字由引入时from后面的名字决定 shared: ['lodash','./src/helloWorld.js'] } }
除此之外,SplitChunksPlugin
插件可以将公共的依赖模块提取到已有的入口 chunk
中,或者提取到一个新生成的 chunk
。让我们使用这个插件,将之前的示例中重复的 lodash
模块去除:
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
optimization: {
splitChunks: {
chunks: 'all',
},
},
通过防止重复的方式分离代码:
- 共享某些模块的入口文件,不会在打包后生成的
js
文件中都引入这些模块- 被共享的模块,会被单独打包成一个
js
文件- 这些文件都会被自动引入到
html
文档中
使用符合ECMAScript
提案的 import()
语法来实现动态引入。
10,缓存
为什么要缓存?我们webpack打包后的/dist
目录,会被部署到服务器,而client
(通常是浏览器)技能访问服务器的网站及其资源。而访问服务器网站资源这一步通常是比较消耗时间的。这就是为什么浏览器会使用 缓存
技术的原因。
通过 命中缓存,可以降低网络流量,使网站加载速度更快,而我们部署新版本时,如果资源文件的名字没有发生变化,浏览器可能认为它没有被更新,就会使用它的缓存版本。 由于缓存的存在,当我们更新了代码,浏览器却没有获取最新代码时,就会显得比较棘手。
而下面的配置,就是确保webpack
编译生成的文件能被客户端缓存,代码变化后,能够请求到新的文件。
filename : '[name][contenthash].js'
[name]
,第一段名字根据入口文件命名[contenthash]
,第二段根据内容生成哈希字符串- 这样,无论是文件名变化还是文件内容变化,打包生成的文件名都会变化,就会被客户端重新获取
module.exports = {
output: {
filename: '[name].[contenthash].js',
},
};
因为第三方库的文件,并不会像我们的本地代码一样频繁发生改变,所以我们经常将它们放在一个单独的 vendor
chunk
文件中。来利用client
的长效缓存机制,命中缓存来消除对它的请求。减少资源请求。
module.exports = {
optimization: {
// 代码分离
splitChunks: {
// 缓存组
cacheGroups: {
vendor: {
// 将node_modules中的第三方代码单独打包到一个文件中
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
}
}
module.exports = {
//...
output: {
// 只需要在文件打包名字之前,加上一个文件夹路径即可
filename: 'scripts/[name].[contenthash].js',
//...
},
//...
}
现在,我们可以通过修改 mode
选项的值,来实现 开发环境 和 生产环境的切换,但很多配置在开发环境和生产环境存在不一致的情况。比如开发环境没有必要设置缓存,生产环境需要设置公共路径等等。
publicPath
配置选项在各种场景中都非常有用。
我们可以通过它来指定应用程序中所有资源的基础路径。
- 基于环境配置
在开发环境中,我们通常有一个assets/
文件夹,用来存放静态资源,它与索引页面位于同一级别。
但是,如果我们将所有的静态资源托管在CDN
,然后想在生产环境中使用该怎么办呢?
想要解决这个问题,可以直接用一个 环境变量(environment variable
),假设我们有一个变量:
import webpack from 'webpack';
// 尝试使用环境变量(CDN路径),否则使用根路径,有CDN就用CDN,没有CDN就用本地静态资源
const ASSET_PATH = process.env.ASSET_PATH || '/';
export default {
output: {
publicPath: ASSET_PATH,
},
plugins: [
// 设置环境变量
// 这可以帮助我们在代码中安全地使用环境变量
new webpack.DefinePlugin({
'process.env.ASSET_PATH': JSON.stringify(ASSET_PATH),
}),
],
};
webpack
会自动根据import.meta.url 、 document.currentScript 、 script.src
或者self.location
变量设置 publicPath
。你需要做的是将 output.publicPath
设为 'auto'
module.exports = {
output: {
// 自动设置环境变量
publicPath: 'auto',
},
};
想要消除webpack.config.js
在开发环境和生产环境之间的差异,可能需要使用到环境变量。
想要设置并传入环境变量,可以在命令行打包的时候,用 **--env
**来配置,在webpack.config.js
中可以访问到这些环境变量。
--env production
,定义一个环境变量production
,默认值为true
--env global=zxf
,定义一个环境变量global
,值为zxf
- 如果我们传入了
production
变量,就代表是生产环境,代码就会压缩,否则旧代码生产模式,代码不会被压缩webpack
默认会压缩代码,但是如果我们使用了例如:CssMinimizerPlugin
css 代码压缩,那默认的文件压缩就可能失效,可以使用terserWebpackPlugin
插件压缩js
文件
但是,要在webpack.config.js
中接收到这个值,要将 module.exports
转换为一个函数,他的参数就是env
对象
//...
module.exports = (env) => {
// env中包含了打包时定义传入的环境变量
return {
//...
// 根据命令行参数 env 来设置不同环境的 mode
mode: env.production ? 'production' : 'development',
//...
}
}
现在,我们的配置文件是同一个文件,现在根据不同的环境把配置文件拆分成不同的文件,根据不同的环境使用不同的配置文件,把这些配置文件放入一个文件夹中。
- 文件夹 :
config
- 开发环境配置:
webpack.config.dev.js
- 生产环境配置:
webpack.config.prod.js
- 运行开发环境:
npx webpack serve -c ./config/webpack.config.dev.js
- 运行生产环境:
npx webpack -c ./config/webpack.config.prod.js
环境配置文件拆分后,执行对应的环境配置,要执行很长的命令,我们可以修改 package.json
文件,来 利用npm
脚本来简化命令行的输入,这时还可以省略npx
{
"scripts": {
"start": "webpack serve -c ./config/webpack.config.dev.js",
"build": "webpack -c ./config/webpack.config.prod.js"
}
}
- 执行开发环境脚本
npm run start
- 执行生产环境脚本
npm run build
两个环境配置文件中,有大量的相同代码,
可以提取相同代码到一个文件中:webpack.config.common.js
。
然后,在不同的环境配置文件中,保留自己特有的配置。
配置文件拆分后,使用 webpack-merge
工具来合并文件。
- 创建一个
webpack.config.js
文件,用来导出合并后的配置文件npm i webpack-merge -D
,下载工具- 引入工具以及公共配置、其他环境配置
const { merge } = require('webpack-merge')
const conmonConfig = require('./webpack.config.common.js')
const envConfig = require('./webpack.config.envConfig.js')
- 环境判断,将当前环境配置和公共配置合并,返回出去
return merge(conmonConfig,envConfig)
const { merge } = require('webpack-merge')
const commonConfig = require('./webpack.config.common.js')
const productionConfig = require('./webpack.config.prod.js')
const developmentConfig = require('./webpack.config.dev')
module.exports = (env) => {
switch(true) {
case env.development:
return merge(commonConfig, developmentConfig)
case env.production:
return merge(commonConfig, productionConfig)
default:
throw new Error('No matching configuration was found!');
}
}