这里我们先简单熟悉下Webpack的基本概念,我们在搭建项目的时候都会要用到的!
这里我们分享的着重点是基本概念而不是具体配置项和使用方法
依赖图是指所有模块及其相互之间的依赖关系的一个整体表示。
简单来说,当Webpack处理应用程序时,它会从一个或多个入口文件(entry point)开始递归地构建一个图表。这个图表描述了项目中所有模块是如何相互依赖的。每个文件、模块都是图中的一个节点,节点之间的连接线(边)代表了文件的引入关系或依赖关系。
举个例子,如果你的应用程序有一个入口文件 index.js
,它依赖于另一个模块 moduleA.js
,而 moduleA.js
又依赖于 moduleB.js
,那么Webpack会生成一个包含这些文件的依赖图。通过这个依赖图,Webpack可以知道哪些模块需要被打包在一起,并以合适的顺序加载它们。
这种方式可以确保最终生成的打包文件是最优化的,只包含了实际使用的代码,并且依赖关系被正确解析,从而确保应用程序能够正常运行。
模式(mode) 是用来指定打包时的环境,主要分为三种:development
、production
和none
。不同的模式会影响 webpack的打包方式和优化策略,以适应不同的开发需求。
这里我们主要是说开发和生产两种模式:
development 模式 |
production 模式 |
|
---|---|---|
输出文件 | 非压缩,带有注释与调试信息 | 压缩,移除所有注释与调试信息 |
Source Map | 默认启用eval-source-map |
默认禁用或启用高效的source-map |
压缩 | 不压缩 | 自动压缩代码并移除未使用的代码 |
性能优化 | 关闭性能提示 | 启用性能提示与限制 |
树摇优化(Tree Shaking) | 不启用 | 启用,移除无用代码 |
环境变量 | process.env.NODE_ENV 被设为 'development' |
process.env.NODE_ENV 被设为 'production' |
通过上面的表格中的对比我们可以总结得出:
development
:适合本地开发,提供调试信息,构建速度快production
:适合生产环境,自动压缩和优化代码,提高性能而none
呢,它完全不应用任何优化,完全自定义,通常我们不使用这个模式…
我们上面在说依赖图的时候实际上也提到了我们的核心概念——入口
入口起点(entry point) 指示webpack应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后webpack会找出有哪些模块和库是入口起点(直接和间接)依赖的。
我们有多种方法可以配置入口:
字符串
用法:entry: string
在webpack.config.js
中:
module.exports = {
entry: './src/index.js',
// ...其他配置
}
数组
用法:entry: [string]
当你使用数组形式来定义入口时,Webpack会按照数组中文件的顺序依次加载它们,并且所有这些文件将会被编译进同一个bundle中。这种方式适合于需要按特定顺序加载多个文件的情况,即多页面应用程序。(注意:数组中所有文件会被合并到一个输出文件中)
在webpack.config.js
中:
module.exports = {
entry: ['./src/polyfills.js', './src/index.js'],
// ...其他配置
}
在这个例子中,Webpack会首先加载 polyfills.js
文件,然后加载 index.js
文件。这两个文件的内容会被合并到同一个输出文件中。
用法:entry: {
当你使用对象形式来定义入口时,每个键值对实际上是在定义一个独立的入口点。这意味着每个入口点都可能会有自己的输出文件(取决于 output
配置,我们这里简单说下,这里后面会详细说),并且它们之间是相互独立的。
在webpack.config.js
中:
const path = require('path');
module.exports = {
entry: {
main: './src/index.js',
vendor: './src/vendors.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js'
}
}
这里定义了两个入口点:main
和 vendor
。每个入口点都指向了一个单独的文件,Webpack会分别为它们创建对应的输出文件。例如,如果 output.filename
被设置为 [name].bundle.js
,那么最终的输出文件名将是 main.bundle.js
和 vendor.bundle.js
。(注意是两个文件)
用于描述入口对象的常用属性有以下几种:
dependOn
:指定当前入口所依赖的入口filename
:用于覆盖默认的文件名生成规则(优先级高)import
:启动时需要提前加载的模块看下面这段代码来简述这三个属性的使用:
module.exports = {
entry: {
one: './a',
two: {
import: './b',
dependOn: 'one',
filename: 'B.bundle.js'
}
},
output: {
filename: '[name].bundle.js'
}
}
它设定了两个入口点,其中two
入口点依赖于one
入口点,one
文件中的代码应该在two
之前执行;one
的输出文件名为one.bundle.js
,而two
由于设定了优先级更高的filename
属性,输出文件名为B.bundle.js
。
输出(output)告知webpack在向硬盘写入编译后(打包后)的文件的具体路径和名称,值得一提的是,output
配置并不像entry
一样可以有多个,它只可以被唯一指定。
在说输出之前,我们先来说一下输出中处处会用到的占位符:
我们来说几个常见的占位符:
[name]
:入口文件的名称[id]
:模块的id[ext]
:输出文件的扩展名(通常在最后)[hash]
:根据编译的版本计算的哈希值,文件内容变化时会变,适合做缓存。[chunkhash]
:根据chunk内容生成的哈希值,每个chunk不同。(chunk
是块,它在webpack中指打包过程中生成的一组模块,简单说,chunk
是在构建过程中生成的代码片段,它的目的就是将代码分割成多个块,这样可以显著的优化加载和提高性能)[contenthash]
:根据文件内容生成的哈希值(通常是静态文件)chunkhash |
contenthash |
|
---|---|---|
基于 | Chunk 的内容(多个模块的集合) | 单个文件的内容 |
常用场景 | JavaScript 文件(尤其是代码分割后的) | CSS 文件、静态资源文件(图片、字体) |
缓存控制 | 较为宽泛,依赖整个chunk的变化 | 更精确,依赖单个文件内容变化 |
变化条件 | 当chunk内的任一模块内容变化时改变 | 当文件内容变化时改变 |
占位符是固定的,不可以随便更改名字的哦~
输出(output) 很重要,但是却相对简单,下面我们来说一下output
的常见配置选项:
path
:指定打包文件的输出目录。
此配置通常与Node.js的path
模块结合使用(这应该不需要我过多解释吧…),来看一段代码:
const path = require('path')
module.exports = {
output: {
// 输出文件夹为项目根目录下的 dist 文件夹
path: path.resolve(__dirname, 'dist'),
},
}
filename
:指定输出文件的文件名,与入口相关。
此属性是output
属性的最低要求,这意味着你必须要在output
属性中添加此配置。属性允许我们动态命名文件,也就是使用我们上面说过的占位符。
来看一个小栗子:
const path = require('path')
module.exports = {
entry: {
main: './src/index.js',
admin: './src/admin.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[hash].bundle.js',
}
我们定义了两个入口文件,同时指定了输出目录(path)
,在这里我们使用了两个占位符[name]
和[hash]
,输出的文件名会是main.[hash].bundle.js
和admin.[hash].bundle.js
chunkFilename
:用于指定非入口chunk文件的名称,这些chunk通常由动态导入或代码分割生成。
此配置也允许我们使用占位符动态命名文件:
module.exports = {
// ...
output: {
// ...
chunkFilename: '[id].[chunkhash].js',
// ...
},
}
library
:可以让你控制你的库如何被其他代码导入或引用。当你构建一个库而不是应用程序时,这个选项变得尤为重要。
来看一段library
实际使用的代码段:
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
filename: 'my-library.js',
path: path.resolve(__dirname, 'dist'),
library: {
name: 'MyLib',
type: 'umd', // 支持CommonJS2, CommonJS, AMD, 和全局变量
// 如果需要,还可以指定exportName
export: 'default',
},
},
mode: 'production', // 后面再解释
}
在控制台输入npx webpack
来进行库的打包,上述代码打包后,你会在dist
文件夹中看到一个my-library.js
文件。
现在你可以将打包好的库部署到某个地方:
如果你发布到了一个可访问的URL(例如:https://example.com/dist/my-library.js),那么我们就可以在HTML页面中通过标签来进行引入:
<script src="https://example.com/dist/my-library.js">script>
如果你将库发布到了NPM,那么你可以通过npm install
来安装它,像我们熟知的模块一样导入使用:
import { add, sub } from 'myLibrary' // 假设我们定义了这两种方法...
publicPath
:用于配置项目中所有资源(如 JavaScript、CSS、图片等)的公共 URL 前缀。
它主要影响的是打包之后HTML中引用这些资源的路径,可以理解为输出目录的”虚拟“路径或URL前缀。我们来看一个例子就会瞬间理解publicPath
:
现在我们假设有一个项目要部署到https://cdn.example.com/
上,那么我们可以这样进行配置:
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'), // 打包输出的物理路径
filename: '[name].[hash].bundle.js', // 输出文件名
publicPath: 'https://cdn.example.com/assets/' // 静态资源的公共URL前缀
}
}
在我们打包后生成的HTML文件中,引用的脚本的形式可能为:
如果没有设置
publicPath
的话,webpack会默认使用相对路径(./)的形式来引用生成的资源文件。默认的相对路径可以满足简单的项目结构或者是静态的网站部署,但是如果针对特定复杂的项目(类似CDN)来说肯定是不合理的,所以是否需要配置
publicPath
还请君细细斟酌~
assetModuleFilename
:用于确定静态资源文件的输出路径和名称。
直接上例子:
const path = require('path')
module.exports = {
output: {
assetModuleFilename: 'assets/[hash].[contenthash][ext]',
},
}
假设你在代码中引入了一个logo.png
,那么输出路径类似于:assets/logo.[contenthash].png
clean
:用于清除输出目录(webpack5新增配置)。
它对于确保构建目录只包含当前构建生成的文件是非常有用的,特别是在构建过程中可能会出现旧文件残留的情况。为true
时为开启。
加载器(loader) 是webpack中处理和转换文件内容的工具。它允许webpack处理各种类型的非JavaScript文件(如CSS、图片、字体等)。使用loader,可以将这些文件转换为webpack认识的模块。
在webpack中通常使用配置文件中的 module.rules
中配置loader,每个规则包括:
test
:一个正则表达式,用于匹配需要处理的文件类型。
exclude
:排除某些文件或目录,以避免不必要的处理。
include
:指定包含的文件或目录。
use
:指定使用的loader配置
loader
: 指定使用的加载器(如果没有其他配置的话默认为loader)
options
: 传递给loader的配置选项
type
: 用于指定如何打包和处理静态文件资源(专门处理图片资源的属性,相当于use,webpack5新加属性)加载器最常见的使用场景就是针对js、css和静态资源文件,这里我们着重说一下打包静态资源时使用的type属性的几个值:
'asset/resource' |
'asset/inline' |
'asset' |
'asset/source' |
|
---|---|---|---|---|
作用 | 将静态资源打包到output 并返回资源的URL |
将静态资源作为Base64 编码的字符串嵌入到打包后的js文件中 |
webpack自动判断是将文件作为resource 还是inline 处理 |
将文件的内容作为字符串导入到js中 |
说明 | 文件单独生成,打包时会将文件复制到dist 目录中 |
适用于文件较小的情况,减少HTTP请求 | 是混合处理方式,文件小于8KB会使用inline ,反之会使用resource |
通常在倒入txt 文件时采用,使用场景较少 |
插件(plugin) 是用来扩展webpack功能的。
插件在整个构建过程中执行各种任务。它与loader
不同,loader
只是转换文件,而插件的功能更为强大,几乎能够完成你想要的任何任务。包括打包优化,资源管理,注入环境变量等一系列的任务。
可以说插件是基于webpack之外的工具库,它可以让你集成其他工具来强化、充盈你的webpack
在生产环境中,代码通常会被打包和压缩,这使得调试变得非常困难,因为生成的代码是混淆和压缩过的。而有了Source Maps,浏览器开发者工具可以帮助你查看原始的源代码,而并非是打包、压缩或编译后的,这会让调试更加直观和方便。
假设你有一个源文件app.js
,在构建过程中,Webpack会生成一个压缩后的文件(如app.bundle.js
),同时也会生成一个app.bundle.js.map
文件。这个.map
文件包含了原始代码的行号、列号和文件的对应关系。
Source Maps仅需要手动开启一下即可:
module.exports = {
devtool: 'source-map', // 生成完整的 Source Maps
}
开发服务器(devServer) 专为开发环境设置。它提供了一系列的功能,帮助开发者在本地快速开发、调试和预览项目。它的主要作用是提高开发效率,通过一些高效的配置项优化开发体验。
这里有一个常见的误区,就是被开发服务器映射到浏览器的代码其实并不是你编辑器中的代码(包括打包后和打包前的),实际被映射的代码是在内存中的。
webpack的开发服务器在开发模式下所提供的打包文件都是存在在内存中的,而不是从物理硬盘上直接读取的文件。
我们来说一些常见的配置项以及相应的作用:
static
:定义哪些文件夹中的内容将作为静态资源提供
directory
:通常由此属性指定静态文件目录。这些静态资源不会经过webpack的打包处理,而是直接提供给浏览器访问。这通常用于服务不需要通过webpack处理的文件,如图片、字体等,或某些独立的HTML文件。常见使用方式为:
module.exports = {
devServer: {
static: {
directory: path.resolve(__dirname, 'public')
}
}
}
这段代码的意思为,webpack会直接提供public
文件夹中的文件,它们不会经过任何打包处理,即使没有使用webpack的入口文件,也能直接通过URL访问这些资源。
同样也可以传递dist
值,但是正如上面误区所提示的,在开发模式下我们实际提供的打包文件都是在内存中的,所以即使你编辑器中的dist
文件夹下内容为空,也可以正常访问项目。那么我们来看一下public
和dist
的区别:
public |
dist |
|
---|---|---|
用途 | 通常用来存放不需要经过webpack打包处理的静态文件,这些文件可以是HTML文件、图片、Font文件、独立的js文件等,直接供浏览器访问 | 用于在开发服务器中模拟生产环境的静态文件 |
表现 | 开发服务器会直接提供public (存放静态资源文件的文件夹)文件夹下的所有文件,它们可以直接通过url访问 |
开发服务器不会从物理的dist 目录打包文件,而是从内存中读取它生成的文件 |
使用场景 | 图像、字体等不会修改的静态资源 | 查看已经打包完成的文件结构(但在开发模式下,文件其实是从内存提供的) |
port
:指定开发服务器运行的端口
open
:是否在启动开发服务器时自动打开服务器(默认值为false
)
hot
:启用HMR(热模块更新),使得在修改代码时页面不刷新而只替换修改的模块(默认值为false
)
historyApiFallback
:支持H5历史模式路由时,重定向404相应到index.html
(默认值为false
)
proxy
:将请求代理到另外一个服务器中,常用语开发环境下的API请求
target
: 代理的目标服务器changeOrigin
:修改请求的来源为目标服务器,解决跨域问题(默认值为false
)pathRewrite
:设置api路径配置compress
:启用Gzip压缩,提高资源的加载速度
client
:用于配制客户端设置
后续会继续持续更新补充文章中的概念相关的问题,同时也会继续分享一些使用场景来帮助大家巩固webpack的使用。
如果文章有哪里写错劳烦各位在评论区里指正,我会感激不尽!
如果你看到这里很啦希望你可以点个赞鼓励一下作者,非常感谢!