2、Webpack核心配置:理解entry、output、chunk、bundle的概念,搭建基础分割环境

好,咱们正式开始动手了。

这一节,我会带你搞清楚 Webpack 里四个最基础、也最容易混淆的概念:entryoutputchunkbundle。说白了,它们是整个代码分割体系的基石。你不把这几个东西理清楚,后面做懒加载、做分包,很容易踩坑。

2.1 先聊聊 entry:一切打包的起点

entry 就是入口文件。Webpack 从哪个文件开始读,全靠它。

我个人习惯,单页应用通常只配一个入口:

// webpack.config.js
module.exports = {
  entry: './src/index.js'
};

但如果你做多页应用,或者想把第三方库单独拎出来,就得配多个入口。比如:

module.exports = {
  entry: {
    main: './src/index.js',
    vendor: './src/vendor.js'
  }
};

这里有个细节要注意:每个 entry 最终都会生成一个独立的 chunk。chunk 是什么?别急,我马上讲。

💡 我的经验: 刚开始做项目时,我习惯把所有东西塞进一个入口。后来发现首屏加载太慢,才意识到入口拆分的重要性。你想想看,一个入口文件里引了 50 个组件,用户打开页面就得全下载,这合理吗?

2.2 output:打包完放哪儿?

output 就是告诉 Webpack:打包好的文件,存到哪个目录、叫什么名字。

module.exports = {
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js'
  }
};

这里 [name] 对应 entry 的 key 名,[contenthash] 是根据文件内容生成的哈希值。为什么要加哈希?因为浏览器缓存。文件内容变了,哈希就变,用户就能拿到新版本。

嗯,这里要注意:output 只决定了最终文件的输出位置和命名,它不决定怎么分割代码。分割逻辑,得靠 optimization 和动态导入。

2.3 chunk vs bundle:别再傻傻分不清

这两个词,我见过太多人混着用了。其实它们有本质区别:

概念 含义 存在阶段
chunk Webpack 打包过程中的代码块 构建过程中
bundle 最终输出的文件 构建完成后

说白了:chunk 是中间产物,bundle 是最终结果

举个例子:你写了一个动态导入 import('./module.js'),Webpack 在构建时就会为它生成一个单独的 chunk。这个 chunk 经过处理、压缩后,最终输出成一个 bundle 文件。

核心理解:

  • 一个 entry 对应一个或多个 chunk
  • 一个 chunk 最终输出成一个 bundle
  • 代码分割的本质,就是控制 chunk 的生成方式

2.4 搭建基础分割环境

好,理论讲完了,咱们动手搭个环境。我会带你配一个最简单的多入口分割。

先装依赖:

npm init -y
npm install webpack webpack-cli --save-dev

然后创建项目结构:

my-app/
├── src/
│   ├── index.js
│   └── another.js
├── dist/
├── webpack.config.js
└── package.json

写两个入口文件:

// src/index.js
console.log('这是主入口');

// src/another.js
console.log('这是另一个入口');

配置 webpack.config.js:

const path = require('path');

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js',
    another: './src/another.js'
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].bundle.js'
  }
};

运行 npx webpack,你会看到 dist 目录下生成了两个文件:

  • main.bundle.js
  • another.bundle.js

这就是最基础的分割。每个入口独立打包,互不干扰。

⚠️ 我曾经踩过的坑: 有次我把两个入口文件里都引了同一个第三方库(比如 lodash),结果打包出来两个 bundle 都包含了 lodash 的代码。用户加载页面,相当于下载了两份 lodash。后来我才知道,这种情况需要用 SplitChunksPlugin 把公共依赖提取出来。这个咱们后面章节会详细讲。

2.5 动态导入:真正的懒加载起点

多入口分割虽然简单,但不够灵活。真正做懒加载,靠的是动态导入。

Webpack 原生支持 import() 语法。你写一个动态导入,Webpack 就会自动为它生成一个单独的 chunk。

举个例子:

// src/index.js
function loadModule() {
  import('./module.js').then(module => {
    module.default();
  });
}

document.getElementById('btn').addEventListener('click', loadModule);

这里 import('./module.js') 就是一个动态导入。Webpack 会把它拆成一个独立的 chunk,只有点击按钮时才会加载。

配置上,你只需要在 output 里加一个 chunkFilename

module.exports = {
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].bundle.js',
    chunkFilename: '[name].chunk.js'  // 动态导入生成的 chunk 命名
  }
};

运行打包,你会看到 dist 里多了一个类似 src_module_js.chunk.js 的文件。这就是动态导入生成的 bundle。

💡 小技巧: 动态导入的 chunk 名默认是数字(0.js、1.js),可读性很差。你可以用 /* webpackChunkName: "my-chunk" */ 魔法注释来命名:
import(/* webpackChunkName: "my-chunk" */ './module.js')
这样生成的 chunk 就会叫 my-chunk.chunk.js,调试时一眼就能认出来。

2.6 本章小结

这一节,我们搞清楚了:

  • entry 是打包的起点,一个入口对应一个或多个 chunk
  • output 控制最终文件的输出位置和命名
  • chunk 是构建过程中的代码块,bundle 是最终输出的文件
  • 多入口分割和动态导入,是两种最基础的分割方式

下一节,我们会深入 SplitChunksPlugin,看看怎么把公共依赖提取出来,避免重复打包。你想想看,如果每个页面都引了 React,那得浪费多少带宽?

好,先到这。动手试试上面的配置,有问题咱们下节聊。