Webpack的基础应用
1.基本使用示例
1.1 安装
- 全局安装
npm i webpack webpack -cli --global
或者
npm i webpack webpack -cli --g
- 本地安装
npm install webpack webpack-cli webpack-dev-server --save-dev
或者
npm install webpack webpack-cli webpack-dev-server -D
1.2 运行
- 在终端中输入
webpack回车 会生成dist文件夹即打包后的文件 (使用全局webpack) npx webpack(使用当前目录webpack)
1.3 自定义Webpack配置
- 新建
webpack.config.js - 用
commonJs写法抛出配置模块
const path = require('path')
module.exports={
entry:'./src/index.js',
output:{
filename:'bundle.js',
path:path.resolve(__dirname,'./dist')
},
mode:'development',
//精准定位代码行数,便于查看
devtool: 'inline-source-map'
}
1.4 在Webpack中使用插件
下面以安装HtmlWebpackPlugin为例:
安装:
npm install html-webpack-plugin -D
在webpack.config.js中添加配置:
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:'development',
devtool: 'inline-source-map' ,
plugins:[
new HtmlWebpackPlugin()
],
}
新建public文件夹,里面新建index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>模版文件</title>
</head>
<body>
<div id="app">
</div>
</body>
</html>
新建src文件夹,里面新建index.js:
let a=12;
console.log(a);
在new HtmlWebpackPlugin()中添加配置项:
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:'development',
devtool: 'inline-source-map' ,
plugins:[
new HtmlWebpackPlugin({
template:'./public/index.html',//指向的html
filename:'index.html',//被打包后的html文件名,
inject:'body'//js打包后的生成位置
})
],
}
配置package.json:
{
"scripts": {
"build": "webpack",
"start": "webpack serve --open"
},
"devDependencies": {
"html-webpack-plugin": "^5.6.3",
"webpack": "^5.98.0",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.0"
}
}
npm run build进行打包,打包之后在dist生成相应文件:

dist/index.html中内容如下:

1.5 每次打包时先自动清除原有打包文件
在output里面加入属性clean:true:
output:{
filename:'bundle.js',
path:path.resolve(__dirname,'./dist'),
clean: true
},
2.资源模块
module.rule.type是string类型,可设置值有 'javascript/auto' | 'javascript/dynamic' | 'javascript/esm' | 'json' | 'webassembly/sync' | 'webassembly/async' | 'asset' | 'asset/source' | 'asset/resource' | 'asset/inline'。具体用法参见https://www.webpackjs.com/configuration/module/#ruletype,下面只是简单介绍几个常见的用法。
在讲解资源模块这个之前,需要引入vue,这样子好做测试。可以上网查询一下怎么从0到1搭建vue脚手架,后续我也会专门讲这个。在这先简单讲述一下:
2.1 配置Vue
安装vue:
npm i vue@2
安装vue-loader和vue-template-compiler(vue模板编译器):
npm i -D vue-loader vue-template-compiler
原本webpack只能解析JS、JSON文件,在js上加载css等其他文件需要通过loader来实现:
npm i -D css-loader style-loader
在webpack.config.js中追加module属性,并且引入vue-loader插件;并且追加关于css样式的loader配置:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
//引入vue-template-compiler插件
const {VueLoaderPlugin} = require('vue-loader');
module.exports={
entry:'./src/index.js',
output:{
filename:'bundle.js',
path:path.resolve(__dirname,'./dist'),
clean: true
},
mode:'development',
devtool: 'inline-source-map' ,
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader'
},
{
test:/\.css$/,
use:['style-loader','css-loader']
}
]
},
plugins:[
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template:'./public/index.html',//指向的html
filename:'index.html',//被打包后的html文件名,
inject:'body'//js打包后的生成位置
})
],
}
新建一个App.vue文件:
<template>
<div id="app">
</div>
</template>
<script>
export default {
name: "App"
}
</script>
<style scoped>
</style>
在index.js入口文件中初始化vue实例:
import Vue from 'vue';
import App from './App.vue';
Vue.config.productionTip=false;
new Vue({
el:"#app",
//渲染函数,这里h是形参,它表示的是createElement
//执行到这儿,创建一个app对应的vnode虚拟节点,把虚拟节点最终变成
//真实的dom节点插入到App里面替换掉id为app的元素
render:h=>h(App),
})
项目大致目录结构如下:

运行npm run start,发现报错:

这是因为vue-loader版本不兼容,把vue-loader修改为如下版本,好与vue-template-compiler版本匹配:
"vue-loader": "^15.10.1",
再次运行就不报错了。
2.2 resource资源
发送单独文件并导出Url。
我们先在App.vue中追加一些会用到图片的元素,并且在src下新建一个images,在里面放置一张图片resource-original-img.jpg:
<template>
<div id="app">
<div class="resource-img-area">
</div>
</div>
</template>
<script>
export default {
name: "App"
}
</script>
<style scoped>
.resource-img-area{
width: 100px;
height: 100px;
background-image: url("./images/resource-original-img.jpg");
background-repeat: no-repeat;
background-size: 100% 100%;
}
</style>
在webpack.config.js中module.rules中追加配置:
{
test:/resource-original-img.jpg/,
type:'asset/resource'
}
并且在output中追加配置:
output:{
...
assetModuleFilename:'images/[name].[contenthash][ext]',
},
然后运行项目,打开控制台看网络面板:

打包npm run build之后:

2.3 inline资源
导出资源的Data Url,实际展示出的文件的url为base64格式。
在App.vue中继续追加一些会用到图片的元素,在images里面放置一张图片inline-original-img.jpg:
<template>
<div id="app">
<div class="resource-img-area"></div>
<div class="inline-img-area"></div>
</div>
</template>
<script>
export default {
name: "App"
}
</script>
<style scoped>
.resource-img-area{
width: 100px;
height: 100px;
background-image: url("./images/resource-original-img.jpg");
background-repeat: no-repeat;
background-size: 100% 100%;
}
.inline-img-area{
width: 100px;
height: 100px;
background-image: url("./images/inline-original-img.jpg");
background-repeat: no-repeat;
background-size: 100% 100%;
}
</style>
在webpack.config.js中module.rules中追加配置:
{
test:/inline-original-img.jpg/,
type:'asset/inline'
}
然后运行项目,打开控制台看网络面板,是base64格式:

打包npm run build之后,不会单独创建新jpg文件:

2.4 source资源
导出资源的源代码。
source的形式是将资源的内容原样应用到项目中,不生成文件,只利用内容,例如,在src下新建txt/a.txt文件:

在webpack.config.js中module.rules中追加配置:
{
test: /\.txt$/,
type: 'asset/source'
}
在App.vue中追加内容:
<template>
<div id="app">
<div class="resource-img-area"></div>
<div class="inline-img-area"></div>
<div class="source-txt-area"></div>
</div>
</template>
<script>
import helloWebpack from './txt/a.txt'
export default {
name: "App",
mounted() {
const SourceTxtArea=document.querySelector(".source-txt-area");
SourceTxtArea.textContent=helloWebpack;
},
}
</script>
<style scoped>
.resource-img-area{
width: 100px;
height: 100px;
background-image: url("./images/resource-original-img.jpg");
background-repeat: no-repeat;
background-size: 100% 100%;
}
.inline-img-area{
width: 100px;
height: 100px;
background-image: url("./images/inline-original-img.jpg");
background-repeat: no-repeat;
background-size: 100% 100%;
}
</style>
运行项目之后,txt文件中的内容显示在界面:

执行npm run build,在dist中没有新增资源。
2.5 asset资源
表示通用资源。可以通过配置临界值实现自动切换资源格式(在resource和inline之间切换),更加灵活可控(默认大小为8kb,可以自己设置)。
在App.vue中继续追加一些会用到图片的元素,在images里面放置一张图片asset-original-img.jpg:
<template>
<div id="app">
<div class="resource-img-area"></div>
<div class="inline-img-area"></div>
<div class="source-txt-area"></div>
<div class="asset-img-area"></div>
</div>
</template>
<script>
import helloWebpack from './txt/a.txt'
export default {
name: "App",
mounted() {
const SourceTxtArea=document.querySelector(".source-txt-area");
SourceTxtArea.textContent=helloWebpack;
},
}
</script>
<style scoped>
.resource-img-area{
width: 100px;
height: 100px;
background-image: url("./images/resource-original-img.jpg");
background-repeat: no-repeat;
background-size: 100% 100%;
}
.inline-img-area{
width: 100px;
height: 100px;
background-image: url("./images/inline-original-img.jpg");
background-repeat: no-repeat;
background-size: 100% 100%;
}
.asset-img-area{
width: 100px;
height: 100px;
background-image: url("./images/asset-original-img.jpg");
background-repeat: no-repeat;
background-size: 100% 100%;
}
</style>
在webpack.config.js中module.rules中追加配置:
{
// 这张图片是29.8KB左右
test:/asset-original-img.jpg/,
type:'asset',
parser:{
dataUrlCondition:{
maxSize:32 * 1024 //当图片大小大于32KB时生成资源文件,否则为base64url
}
}
}
由于该图片大小小于限制值,所以会以base64url的格式显示:

如果把限制改为24KB:
{
// 这张图片是29.8KB左右
test:/asset-original-img.jpg/,
type:'asset',
parser:{
dataUrlCondition:{
maxSize:24 * 1024 //当图片大小大于24KB时生成资源文件,否则为base64url
}
}
}
由于该图片大于限制值,所以会以resource的格式显示:

3.loader
原本webpack只能解析JS、JSON文件,在js上加载css等其他文件需要通过loader来实现。
3.1 加载css
安装:
npm i -D css-loader style-loader
在规则中添加css规则:
module:{
rules:[
...
{
test:/\.css$/,
//注意顺序。链式调用会从后往前执行
use:['style-loader','css-loader']
}
]
}
3.2 加载less或者sass(scss)
以less为例子。安装:
npm i -D less-loader less
在规则中添加less:
{
test:/\.(css|less)$/,//注意兼容less和css
use:['style-loader','css-loader','less-loader']//多个处理器,注意是从后往前执行
},
3.3 抽离和压缩css
3.3.1 抽离css文件
本地安装mini-css-extract-plugin(该插件之后可以取代style-loader)
npm install mini-css-extract-plugin -D
在plugin中添加:
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports={
...
plugins:[
...
new MiniCssExtractPlugin({
filename: "style/[name].css"
})
],
}
在原有css规则中替换掉style-loader
{
test:/\.(css|less)$/,//注意兼容sass和scss
use:[MiniCssExtractPlugin.loader,'css-loader','less-loader']
//多个处理器,注意是从后往前执行
},
npm run build打包之后会在dist中style文件夹下生成css文件:

3.3.2 压缩css文件
本地安装css-minimizer-webpack-plugin
npm install css-minimizer-webpack-plugin -D
和其它插件不同,它需在optimization中添加配置,并且mode要切换为production:
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports={
...
mode:'production',
...
optimization:{
minimizer: [
new CssMinimizerPlugin()
]
}
}
npm run build打包之后,css文件被压缩:

3.4 加载图片
依赖于资源模块,在上面资源模块那一节我详细讲述过。
3.5 加载字体
依旧依赖于资源模块。
去https://www.dafont.com/找到一个字体包进行安装。放在src/fonts文件夹下:

在App.vue中引入字体:
<template>
<div id="app">
...
<div class="fonts-area">
<span>来测试一下字体:hello webpack!!!</span>
</div>
</div>
</template>
<script>
...
</script>
<style lang="less" scoped>
...
@font-face {
font-family: "custom-font";
src: url('./fonts/KOMIKAX_.ttf') format('truetype');
}
.fonts-area{
width: 100%;
height: 30px;
font-family:"custom-font";
font-size:24px;
}
</style>
运行之后查看界面:

给字体添加规则:
rules:[
...
{
test:/\.(woff|woff2|eot|ttf|otf)$/,
type:'asset/resource'//会在dist生成新资源
}
]
修改一下output.assetModuleFilename配置:

npm run build会在dist中的resources文件夹生成字体新文件:

3.6 加载数据(json、csv、tsv、xml)
对于json,webpack本身就支持;其它类型需要根据数据格式安装不同的loader模块
本地安装csv-loader、xml-loader:
npm install csv-loader xml-loader -D
添加规则(不添加规则,直接在代码中引入xml、csv文件会报错):
{
test:/\.(csv|tsv)$/,
use:'csv-loader'
},
{
test:/\.xml$/,
use:'xml-loader'
}
下面来测试一下:新建一个xml文件:

在App.vue中添加:
<script>
import helloWebpack from './txt/a.txt';
import bookstore from './xml/bookstore.xml'
export default {
created() {
},
mounted() {
const box3=document.querySelector(".box3");
box3.textContent=helloWebpack;
console.log(bookstore)
},
methods:{
},
}
</script>
控制台如下:

3.7 自定义JSON模块parser
通过自定义parser替代特定的webpack loader,可将任何toml、yaml或json5的文件作为模块导入。
本地安装toml、yaml、json5:
npm install toml yaml json5 -D
添加规则:
const toml = require('toml')
const yaml = require('yaml')
const json5 = require('json5')
module.exports={
...
module:{
rules:[
...
{
test:/\.toml$/,
type:'json',
parser:{
parse:toml.parse
}
},
{
test:/\.yaml$/,
type:'json',
parser:{
parse:yaml.parse
}
},
{
test:/\.json5$/,
type:'json',
parser:{
parse:json5.parse
}
}
]
},
...
}
新建一个json文件:

在App.vue中添加:
<script>
...
import fruits from "./json/fruits.json"
export default {
created() {
},
mounted() {
...
console.log(fruits)
},
methods:{
},
}
</script>
控制台如下:

4.babel-loader(javascript兼容性处理)
js虽然可被webpack接受,但如果是es6及以上的新增语法,转为es5及以下的版本,则可能会出现兼容性报错问题。所以需要babel-loader。
在src/index.js引入es6语法:

打包后去dist/bundle.js文件中找testA变量:

发现打包后的语法还是用的es6的,这可能在某些低版本浏览器上不兼容。
安装babel-loader,@babel/core(babel核心库),@babel/preset-env(预设配置集合):
npm i -D babel-loader @babel/core @babel/preset-env
如需兼容async/await语法则还需要添加regeneratorRuntime模块:
npm install --save @babel/runtime
npm install --save-dev @babel/plugin-transform-runtime
添加规则:
{
test:/\.m?js$/,//注意问号是可选的意思,表示前面一个字符选和不选
exclude:/node_modules/,//注意:需在exclude中排除node_modules包
use:{
loader:'babel-loader',
//配置对象
options:{
// 预设
presets:['@babel/preset-env'],
plugins:[
[
'@babel/plugin-transform-runtime'
]
]
}
}
}
打包后dist/bundle.js文件中找testA变量:

ES6的语法已经转为了ES5了。
5.代码分离(Code Splitting)
项目中若存在多个入口文件时,则需要代码分离;若存在多个模块共用的代码时,也需要分离代码来防止重复打包。(上述有两种情况都需要代码分离)
5.1 使用entry配置手动分离代码
配置如下:
entry:{
index:'./src/index.js',
another:'./src/another-module.js'
},
output:{
filename:'[name].bundle.js',
...
},
打包后在dist文件夹中生成两个bundle文件:

这种方法有一些不好的地方:当几个入口文件存在相同文件时,会重复打包至各自模块。
如下,在src/common下有一个common.js文件:

在index.js和another-module.js中都引用这个模块:

还
执行打包命令之后,会把common.js内容重复打包到两次,分别在index.bundle.js和another-module.bundle.js中:


5.2 用entry dependencies去重和分离
在src/common中新建module2.js文件:
const module2=100;
export default module2;
在src/index.js和src/another-module.js中都引入module2.js这个模块:


添加配置:
entry:{
index:{
import:'./src/index.js',
dependOn: "sharedModule",
},
another:{
import:'./src/another-module.js',
dependOn:"sharedModule",
},
sharedModule: [path.resolve(__dirname,'./src/common/common.js'),path.resolve(__dirname,'./src/common/common2.js')], //默认从node_modules中寻找
},
打包后多出的sharedModule.bundle.js即为定义中的模块,此时common.js和common2.js是共用的,做到了模块的去重和分离。总共生成了index.bundle.js、another-module.bundle.js和sharedModule.bundle.js。

5.3 用SplitChunksPlugin去重和分离
配置时依旧可以采用独立命名。
entry:{
index:'./src/index.js',
another:'./src/another-module.js'
},
在optimization优化配置项中添加splitChunks:
optimization:{
...
splitChunks:{
chunks:'all'
}
},
npm run build之后就可以看到生成了一个公共文件。

SplitChunksPlugin的主要作用是将代码分割成多个块(chunks),这些块可以在需要时按需加载,从而减少初始加载时间。具体功能包括:
- 提取公共模块:将多个入口共享的模块提取到一个单独的chunk中,减少重复代码。
- 按需加载:将不常用的代码或模块延迟加载,直到真正需要时才加载,减少初始加载时间。
- 优化缓存:通过合理的缓存策略,提高缓存命中率,进一步减少加载时间。
下面是它的一个示例:
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
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
};
这些配置选项包括:
- chunks:
定义如何分割chunks,可以是’async’(动态导入的文件其静态依赖会根据规则分离)、‘all’(所有文件都会根据规则分离)、‘initial’(入口文件的静态依赖会根据规则分离)。 - minSize:定义分割的最小体积(以字节为单位)。
- maxSize:定义分割的最大体积。
- minChunks:定义模块被多少个chunks共享时才会被分割。
- maxAsyncRequests:定义异步请求的最大数量。
- maxInitialRequests:定义初始请求的最大数量。
- automaticNameDelimiter:用于生成chunk名称的分隔符。
- cacheGroups:定义缓存组,可以进一步优化缓存策略。
5.4 动态导入拆分代码
5.4.1 基本场景
其实就是编译器一看到import(文件路径),就会把这个文件视为要拆分的文件,自动会把它单独打一个包。实现代码如下:
// 假设我们有一个moduleA.js文件
// src/modules/moduleA.js
export default '我是module A!';
// 在主文件中,我们可以这样动态引入moduleA.js
// src/index.js
function loadModuleA() {
return import(/* webpackChunkName:'moduleA' */'./mudules/moduleA.js')
.then(moduleA => {
console.log(moduleA.default); // 输出: '我是module A!'
})
.catch(error => {
console.error('Module loading failed:', error);
});
}
// 当你需要的时候调用这个函数
loadModuleA();
这样子就可以把moduleA.js单独打包!!!

5.4.2 动态导入的作用1----懒加载(按需加载)
只有在一些代码块完成某操作时候,才会引用或即将引用另外一些新的代码块。比如第一次加载首屏时候,某些页面是不会加载的。
//src/modules/add.js
export function add(...args){
let sum=0;
for (let i=0;i<args.length;i++){
sum+=args[i]
}
return sum;
}
//src/App.vue
<template>
<div id="app">
...
<div class="add-area">
<button @click="addFun">
加法
</button>
</div>
</div>
</template>
<script>
export default {
...
methods:{
addFun(){
//这里有句注释,我们把它称为 webpack 魔法注释: webpackChunkName: 'add' ,
//告诉webpack打包生成的文件名为 add。
import(/* webpackChunkName:'add' */'./modules/add.js').then(({add})=>{
console.log(add(3,4,5))
})
}
}
}
</script>
<style lang="less" scoped>
...
</style>
点击页面上加法按钮,控制台网络上会单独加载add.bundle.js的文件:

5.4.3 动态导入的作用2----预获取prefetch/预加载preload
如果我们需要加载的资源很庞大,这时候用上面的方法就不太合适,因为这需要一定的加载时间。这个时候我们可以选择使用预获取prefetch或者预加载preload,在网络空闲时候提前加载好某些资源。
Webpack v4.6.0+增加了对预获取和预加载的支持。
在声明import时,使用下面这些内置指令,可以让webpack输出 "resource hint(资源提示)",来告知浏览器:
- webpackPrefetch(预获取):将来某些导航下可能需要的资源
- webpackPreload(预加载):当前导航下可能需要资源
下面来看一个预获取的例子
//src/index.js
//测试预获取
const button = document.createElement('button')
button.textContent = '点击执行加法运算'
button.addEventListener('click', () => {
import(/* webpackChunkName: 'minus', webpackPrefetch: true */
'./modules/minus.js').then(({ minus }) => {
console.log(minus(10, 5))
})
})
document.body.appendChild(button)
如上添加魔法注释:webpackPrefetch: true。告诉webpack执行预获取。这会生成<link rel="prefetch" href="./modules/minus.js">并追加到页面头部,指示着浏览器在闲置时间预取minus.js文件。启动服务,在浏览器上查看:

我们发现,在还没有点击按钮时,minus.bundle.js就已经下载下来了。同时,在index.html里webpack自动添加了一句:

点击按钮,会立即调用已经下载好的 math.bundle.js 文件中的 add 方法。
与prefetch指令相比,preload指令有许多不同之处:
preload chunk会在父chunk加载时,以并行方式开始加载。prefetch chunk会在父chunk加载结束后开始加载。preload chunk具有中等优先级,并立即下载。prefetch chunk在浏览器闲置时下载。preload chunk会在父chunk中立即请求,用于当下时刻。prefetch chunk会用于未来的某个时刻。- 浏览器支持程度不同。
创建一个print.js文件
//src/modules/print.js
export const print = () => {
console.log('preload chunk.')
}
追加index.js文件:
//src/index.js
//测试预加载
const button2 = document.createElement('button')
button2.textContent = '点击执行字符串打印'
button2.addEventListener('click', () => {
import(/* webpackChunkName: 'print', webpackPreload: true */
'./modules/print.js').then(({ print }) => {
print()
})
})
document.body.appendChild(button2)
6.缓存
打包后的dist文件部署到服务器以后便能被浏览器客户端所访问,由于浏览器的解析特性会优先选择缓存资源,我们要确保文件发生更新时浏览器能识别到,所以需要对输出文件的文件名做处理。
ps:我们既需要webpack编译的文件被客户端缓存,又需要在文件内容变化时候,能请求到新的文件。
6.1 输出文件的文件名
我们可以通过替换output.filename中的substitutions设置,来定义输出文件的名称。webpack提供了一种使用 substitution(可替换模板字符串)的方式,通过带括号字符串来模板化文件名,其中,[contenthash] substitution 将根据资源内容创建出唯一的hash。当资源内容发生变化时, [contenthash] 也会发生变化。
output:{
filename:'[name].[contenthash:7].bundle.js',
...
},

6.2 缓存第三方库(如lodash)
将第三方库 (library) (例如lodash) 提取到单独的vendor chunk文件中,是比较推荐的做法。因为第三方库文件很少像本地源码那样会频繁修改,所以我们可以利用client的长效缓存机制,命中缓存来消除请求,并减少向server获取资源,同时保证client和server的代码版本一致。
optimization:{
minimizer: [
new CssMinimizerPlugin()
],
splitChunks:{
chunks:'all',
cacheGroups:{
vendor:{
test:/[\\/]node_modules[\\/]/,
name:'vendors',
chunks:'all'
}
},
}
}
安装lodash第三方库:
"lodash": "^4.17.21",
在src/index.js中追加内容:

打包后会将第三方库提取到单独的vendor chunk文件中:

6.3 将js 文件放到一个文件夹中
目前,全部 js 文件都在 dist 文件夹根目录下,我们尝试把它们放到一个文件夹中,这个其实也简单,修改配置文件:
filename:'scripts/[name].bundle.js',
打包后在scripts中生成js文件:

截止目前,我们已经把JS文件、样式文件及图片等资源文件分别放到了scripts 、style 、resources 三个文件夹中。
7. 拆分开发环境和生产环境配置
现在,我们只能手工的来调整mode选项,实现生产环境和开发环境的切换,且很多配置在生产环境和开发环境中存在不一致的情况,比如开发环境没有必要设置缓存,生产环境还需要设置公共路径等等。
7.1 公共路径
publicPath用于配置 Webpack 打包后的资源路径,指示浏览器加载 JavaScript、CSS、图片等静态文件时的根路径。它可以设置为相对路径、绝对路径或完整的 URL(例如CDN 地址)。
7.1.1 publicPath 的配置形式
绝对路径:
当你设置 publicPath 为绝对路径时,Webpack 会将所有资源的加载路径都从该路径开始。
module.exports = {
output: {
//dev这意味着所有的资源会从网站的根目录下的 /dev/ 路径加载
publicPath: '/dev/'
}
};
这时候,需要输入网址http://localhost:8080/dev/,查看控制台网络,发现 http://localhost:8080/scripts/index.bundle.js变成了 http://localhost:8080/dev/scripts/index.bundle.js。

但一般情况下我们都会去掉dev,所以开发阶段我们应该像下面这样子写:
publicPath: '/'
如上,在开发阶段publicPath为/,我们用vscode打开项目然后找到dist/index.html并且右键,点击open with live Server用服务器模拟运行打包后的index.html:

发现页面一片空白,并且所有资源的路径都不对。比如http://127.0.0.1:5500/style/index.css这明显是错的,我们想要的应该是http://127.0.0.1:5500/dist/style/index.css(多一个dist)。
module.exports = {
output: {
publicPath: '/dist/'
}
};
再次找到dist/index.html并且右键,点击open with live Server用服务器模拟运行打包后的index.html:

相对路径:
如果你设置 publicPath 为相对路径,资源的加载路径会相对于当前页面的位置。
module.exports = {
output: {
publicPath: './'
}
};
假设页面路径是 https://example.com/page/,那么资源将从 https://example.com/page/ 开始加载。
动态配置(开发环境与生产环境不同的路径):
可以根据环境(开发或生产)动态调整publicPath,这在使用 CDN 或根据部署环境的不同需要调整路径时特别有用。
output: {
publicPath: process.env.NODE_ENV === 'production' ? '/dist/' : '/'
}
开发环境:publicPath 为 '/',表示资源相对于当前页面加载。
生产环境:publicPath 为 '/static/',所有资源将从 https://example.com/static/ 加载。
7.1.2 如何选择合适的publicPath 配置
- 开发模式:在开发环境下,通常使用相对路径(如
'/'或'./')),这样可以在本地开发时,资源加载会相对当前页面路径。 - 生产模式:在生产环境中,通常会使用绝对路径或者 CDN 路径(如
'/dist/'或'https://cdn.xxx.com/dist/')来提高性能和可靠性。 - 混合模式:有时候你希望根据环境变量(如
process.env.NODE_ENV)来动态选择路径,这样开发和生产环境可以使用不同的路径配置。
7.2 环境变量
想要消除webpack.config.js在 开发环境 和 生产环境 之间的差异,你可能需要环境变量。
webpack命令行 环境配置的--env参数,可以允许你传入任意数量的环境变量。而在webpack.config.js 中可以访问到这些环境变量。例如,--env production或--env goal=local。这时候我们需要修改一下 webpack配置,要使用env变量,你必须将module.exports转换成一个函数:
//...
module.exports = (env) => {
console.log(env)
return {
//...
// 根据命令行参数 env 来设置不同环境的 mode
mode: env.production ? 'production' : 'development',
//...
}
}
然后执行npx webpack --env production。打印的结果如下:

7.3 拆分配置文件
上面是通过函数来区分生产和开发环境。也可以将生产和开发环境单独放到不同的配置文件中。如 webpack.config.dev.js (开发环境配置)和webpack.config.prod.js (生产环境配置)。在项目根目录下创建一个配置文件夹 config 来存放他们。
两个文件内容分别如下:
//webpack.config.dev.js
module.exports={
entry: {
index: "./src/index.js",
another: "./src/another-module.js",
},
output: {
filename: "scripts/[name].bundle.js",
path: path.resolve(__dirname, "./dist"),
clean: true,
assetModuleFilename: "resources/[name].[contenthash][ext]",
},
mode: 'development',
...
};
//webpack.config.prod.js
module.exports={
entry: {
index: "./src/index.js",
another: "./src/another-module.js",
},
output: {
filename: "scripts/[name].bundle.js",
path: path.resolve(__dirname, "./dist"),
clean: true,
assetModuleFilename: "resources/[name].[contenthash][ext]",
publicPath: "/dist/",
},
mode: 'production',
...
};
分别运行这两个文件:
//开发环境:
npx webpack serve -c ./config/webpack.config.dev.js
//生产环境(没有serve):
npx webpack -c ./config/webpack.config.prod.js
7.4 npm脚本
配置npm脚本来简化命令行的输入,这时可以省略npx:
"start": "webpack serve -c ./config/webpack.config.dev.js",
"build": "webpack -c ./config/webpack.config.prod.js"
这时候就可以用npm run start和npm run build了。执行npm run start没问题,执行npm run build发现打包的文件放在了config/dist中:

排查原因,是因为webpack.config.prod.js中的output中的path设置错误:

改为以下配置即可(多出一个点):
path: path.resolve(__dirname, "../dist"),
7.5 提取公共配置
这时,我们发现这两个配置文件里存在大量的重复代码,可以手动的将这些重复的代码单独提取到一个文件里,创建webpack.config.common.js,配置公共的内容:
...
module.exports={
//用SplitChunksPlugin去重和分离
entry: {
index: "./src/index.js",
another: "./src/another-module.js",
},
output: {
// 注意这个dist的路径设置成上一级
path: path.resolve(__dirname, "../dist"),
clean: true,
assetModuleFilename: "resources/[name].[contenthash][ext]",
},
module: {
...
},
plugins: [
...
],
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "vendors",
chunks: "all",
},
},
},
},
};
改写webpack.config.dev.js:
module.exports = {
output: {
// 开发环境不需要配置缓存,所以不需要hash
filename: "scripts/[name].js",
},
mode: "development",
// 配置 source-map
devtool: "inline-source-map",
};
改写webpack.config.prod.js:
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
output: {
filename: "scripts/[name].[contenthash].js",
publicPath: "/dist/",
},
mode: "production",
optimization: {
// 生产环境 css 压缩
minimizer: [new CssMinimizerPlugin()],
},
};
7.6 合并配置文件
配置文件拆分好后,新的问题来了,如何保证配置合并没用问题呢?webpack-merge 这个工具可以完美解决这个问题。
安装 webpack-merge :
npm install webpack-merge -D
创建 webpack.config.js ,合并代码:
//config/webpack.config.js
const {merge} = require('webpack-merge')
const commonConfig = require('./webpack.config.common')
const productionConfig = require('./webpack.config.prod')
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:
return new Error('No matching configuration was found')
}
})
修改package.json中的script脚本:
"scripts": {
"start": "webpack serve --env development -c ./config/webpack.config.js",
"build": "webpack --env production -c ./config/webpack.config.js"
},
这样子就大功告成了。npm run build和npm run serve都会成功。


1437

被折叠的 条评论
为什么被折叠?



