entry 是 Webpack 配置里最先被接触、也最容易被轻视的字段。它告诉 Webpack 从哪个模块开始构建依赖图,顺着每一条 import 和 require 递归向下收集模块,直到把所有被引用到的代码都纳入打包范围。入口的写法会直接改变产物的数量、命名方式以及公共代码的分布,把 entry 的几种形态和背后的机制弄清楚,是写好多页面工程配置绕不开的一步。

entry 的本质:依赖图的遍历起点
Webpack 启动后做的第一件事就是解析 entry。无论配置里写成什么形式,内部都会被归一化成一份「chunk 名称到模块路径」的映射,比如字符串写法 ./src/index.js 会被转换成 main: './src/index.js',这个默认的 chunk 名 main 正是 output.filename 中 [name] 占位符的取值来源。理解这个归一化过程很有意义,因为后面所有写法的变化,本质上都是在控制这份映射表的内容和规模。
相对路径的解析基准由 context 字段决定,默认值是执行打包命令时所在的目录,也就是 process.cwd()。配置文件放在项目根目录时通常不用关心它,一旦把 Webpack 配置拆到子目录或者通过脚本调用,就要留意相对路径的参照物是否还符合预期,否则很容易碰到「模块找不到」的报错。稳妥的做法是显式声明 context: path.resolve(__dirname, 'src'),让解析行为完全确定,不依赖执行时的目录环境。
数组写法同样只产出一个 bundle,但允许把多个互不依赖的文件按顺序合并进去。经典用法是把 polyfill 垫片放在业务代码前面,保证浏览器环境先被补齐。需要注意的是,数组只是把文件拼接进同一个 chunk,并不会建立它们之间的依赖关系,打包顺序完全由书写顺序决定。现代项目大多改用 babel 的 preset-env 按需注入 polyfill,数组入口的使用频率下降了不少,但在给老项目拼接脚本、或者往 bundle 头部塞一段初始化逻辑时依然好用。
// 单入口:字符串写法,chunk 名默认为 main
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
// 数组入口:多个文件合并进同一个 bundle
module.exports = {
entry: ['./src/polyfills.js', './src/index.js'],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
};对象写法:多页面应用的标配
对象形式是三种基础写法里唯一能产出多个 bundle 的方式。对象的键就是 chunk 名称,值可以继续使用字符串或数组,配合 output.filename 里的 [name] 占位符,每个入口都会各自生成一个以 chunk 名命名的文件。比如 home: './src/pages/home/index.js' 配合 filename: 'js/[name].js',产物就是 dist/js/home.js。
chunk 命名有相当大的自由度,键里可以带斜杠形成目录层级,写成 'pages/home' 时产物路径会跟着变成 js/pages/home.js。要特别提醒的是,[name] 取的是 entry 的键而不是文件本身的名称,这是新手最容易混淆的一个点:文件叫什么无所谓,键叫什么产物就叫什么。给 chunk 加上 [contenthash] 之后,文件内容不变则哈希不变,可以放心设置长效缓存。
多页面场景下,entry 通常要和 html-webpack-plugin 联动。每个页面生成一份 HTML,并通过 chunks 选项声明只注入属于自己的 bundle,避免页面之间互相加载多余的脚本。下面是一份可以直接套用的配置骨架:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = {
entry: {
home: './src/pages/home/index.js',
about: './src/pages/about/index.js',
admin: './src/pages/admin/index.js'
},
output: {
filename: 'js/[name].[contenthash:8].js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/pages/home/index.html',
chunks: ['home'],
filename: 'home.html'
}),
new HtmlWebpackPlugin({
template: './src/pages/about/index.html',
chunks: ['about'],
filename: 'about.html'
})
]
};这份配置会产出 home.js、about.js、admin.js 三个入口文件,以及对应的 home.html 和 about.html,页面里通过插件自动注入的 <script> 标签加载各自的代码,互不干扰。admin 入口没有对应的 HTML 插件实例,产物依然会正常生成,适合作为被其他模块异步引用的独立包。
动态入口:用 glob 扫描目录自动生成
页面一旦多起来,手写 entry 既繁琐又容易漏。约定优于配置的思路是固定目录结构,比如约定每个页面的入口都放在 src/pages/页面名/index.js,然后用 glob 扫描整个目录,把扫描结果拼成 entry 对象。以后新增页面只需要建目录写代码,配置一行都不用改,删除页面也不会留下孤儿配置。
实现思路很直接:拿到所有匹配的文件路径,用正则从路径里截取出页面名作为 chunk 名,塞进一个空对象,最后把这个对象交给 entry。下面是常见写法:
const glob = require('glob');
const path = require('path');
// 扫描 src/pages 下所有页面的入口文件
const entries = {};
glob.sync('./src/pages/*/index.js').forEach(function (filePath) {
// filePath 形如 ./src/pages/home/index.js
const match = filePath.match(/pages\/(.*)\/index\.js/);
if (match) {
entries[match[1]] = filePath;
}
});
module.exports = {
entry: entries,
output: {
filename: 'js/[name].[contenthash:8].js',
path: path.resolve(__dirname, 'dist')
}
};除了用 glob 预先算好映射,entry 还支持函数形式。函数可以同步返回入口对象,也可以返回 Promise 做异步计算,比如从本地配置文件或远程接口里读取页面清单后再决定打包哪些入口,适合需要按环境、按租户动态裁剪页面的场景:
module.exports = {
// 函数形式:打包时动态计算入口
entry: function () {
return {
app: './src/app.js',
admin: './src/admin.js'
};
},
output: {
filename: 'js/[name].js'
}
};容易被忽略的细节与常见坑
第一个坑是公共依赖的重复打包。两个入口都引入了 lodash 时,默认情况下这份代码会被分别打进两个 bundle,体积直接翻倍。解决办法是开启 optimization.splitChunks,让被多个入口共享的模块自动抽成公共 chunk,浏览器只需下载一次就能被所有页面复用,第三方库单独成包后也更容易命中长效缓存。
第二个细节是 runtime 代码。每个入口默认会注入一份模块加载与解析的运行时逻辑,多入口意味着这份逻辑被复制多份。通过 optimization.runtimeChunk: 'single' 可以把它抽成一个单独文件供所有入口共享,配合内容哈希做缓存时收益明显,因为业务代码频繁变动也不会导致 runtime 文件的哈希跟着变化。
第三个容易踩的点是入口路径的写法。entry 指向一个目录时,Webpack 会自动解析其中的 index 文件,这虽然方便,却会让依赖关系变得不直观;两个不同的键指向同一个文件时,会产出两份几乎相同的 bundle,白白浪费体积。建议团队内部统一约定:入口一律写到具体文件,chunk 名与页面目录名保持一致,排查问题时会轻松很多。
module.exports = {
entry: {
home: './src/pages/home/index.js',
about: './src/pages/about/index.js'
},
optimization: {
// 把运行时代码抽成单独文件,避免每个入口各带一份
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
cacheGroups: {
// 被两个以上入口共享的模块抽成 commons
commons: {
name: 'commons',
minChunks: 2,
enforce: true
},
// 第三方库单独成包,利于长效缓存
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10
}
}
}
}
};把上面这些配置组合起来,entry 就不再只是一个路径字段,而是一套可控的多页面工程方案:目录扫描解决配置维护成本,对象键约定解决产物命名,splitChunks 和 runtimeChunk 解决体积与缓存。理解了 entry 从字符串到映射表的归一化过程,再面对任何复杂的入口需求,都能顺着这套思路拆解出清晰的配置。
Webpack entry入口配置多入口打包修改时间:2026-10-03 09:15:56