一文搞懂 Webpack 多入口配置

傷城~ 2023-10-11 10:27 83阅读 0赞

最近在做项目的时候遇到了一个场景:一个项目有多个入口,不同的入口,路由、组件、资源等有重叠部分,也有各自不同的部分。由于不同入口下的路由页面有一些是重复的,因此我考虑使用 Webpack 多入口配置来解决这个需求。

再一次,在网上找的不少文章都不合我的需求,很多文章都是只简单介绍了生产环境下配置,没有介绍开发环境下的配置,有的也没有将多入口结合 vue-routervuexElementUI 等进行配置,因此在下通过不断探坑,然后将思路和配置过程记录下来,留给自己作为笔记,同时也分享给大家,希望可以帮助到有同样需求的同学们~

1. 目标分析

  1. 一个项目中保存了多个 HTML 模版,不同的模版有不同的入口,并且有各自的 router、store 等;
  2. 不仅可以打包出不同 HTML,而且开发的时候也可以顺利进行调试;
  3. 不同入口的文件可以引用同一份组件、图片等资源,也可以引用不同的资源;

代码仓库:multi-entry-vue

示意图如下:watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxNDcwNjAzODIz_size_16_color_FFFFFF_t_70

2. 准备工作

首先我们 vue init webpack multi-entry-vue 使用 vue-cli 创建一个 webpack 模版的项。文件结构如下:

  1. .
  2. ├── build
  3. ├── config
  4. ├── src
  5. │ ├── assets
  6. │ │ └── logo.png
  7. │ ├── components
  8. │ │ └── HelloWorld.vue
  9. │ ├── router
  10. │ │ └── index.js
  11. │ ├── App.vue
  12. │ └── main.js
  13. ├── static
  14. ├── README.md
  15. ├── index.html
  16. ├── package-lock.json
  17. └── package.json

这里顺便介绍在不同系统下生成目录树的方法:

  1. mac 系统命令行生成目录树的方法 tree-I node_modules--dirsfirst ,这个命令的意思是,不显示 node_modules 路径的文件,并且以文件夹在前的排序方式生成目录树。如果报没有找到 tree 命令的错,安装 tree 命令行 brew install tree 即可。
  2. windows 系统在目标目录下使用 tree/f1.txt 即可把当前目录树生成到一个新文件 1.txt 中。

首先我们简单介绍一下 Webpack 的相关配置项,这些配置项根据使用的 Webpack 模版不同,一般存放在 webpack.config.jswebpack.base.conf.js中:

  1. const path = require('path')
  2. module.exports = {
  3. context: path.resolve(__dirname, '../'),
  4. entry: {
  5. app: './src/main.js'
  6. },
  7. output: {
  8. path: path.resolve(__dirname, '../dist'),
  9. filename: 'output-file.js',
  10. publicPath: '/'
  11. },
  12. module: {}, // 文件的解析 loader 配置
  13. plugins: [], // 插件,根据需要配置各种插件
  14. devServer: {} // 配置 dev 服务功能
  15. }

这个配置的意思是,进行 Webpack 后,会在命令的执行目录下新建 dist 目录(如果需要的话),并将打包 src 目录下的 main.js 和它的依赖,生成 output-file.js 放在 dist 目录中。

下面稍微解释一下相关配置项:

  1. entry: 入口文件配置项,可以为字符串、对象、数组。以上面的对象形式为例, app 是入口名称,如果 output.filename 中有 [name] 的话,就会被替换成 app
  2. context: 是 webpack 编译时的基础目录,用于解析 entry 选项的基础目录(绝对路径), entry 入口起点会相对于此目录查找,相当于公共目录,下面所有的目录都在这个公共目录下面。
  3. output: 出口文件的配置项。
  4. output/path: 打包文件输出的目录,比如上面的 dist,那么就会将输出的文件放在当前目录同级目录的 dist 文件夹下,没有这个文件夹就新建一个。可以配置为 path.resolve(__dirname,'./dist/${Date.now()}/') (md 语法不方便改成模板字符串,请自行修改)方便做持续集成。
  5. output.filename: 输出的文件名称, [name] 的意为根据入口文件的名称,打包成相同的名称,有几个入口,就可以打包出几个文件。比如入口的 keyapp,打包出来就是 app.js,入口是 my-entry,打包出来就是 my-entry.js
  6. output.publicPath: 静态资源的公共路径,可以记住这个公式: 静态资源最终访问路径=output.publicPath+资源loader或插件等配置路径。举个例子, publicPath 配置为 /dist/,图片的 url-loader 配置项为 name:'img/[name].[ext]' ,那么最终打包出来文件中图片的引用路径为 output.publicPath+'img/[name].[ext]'='/dist/img/[name].[ext]'

本文由于是入口和出口相关的配置,所以内容主要围绕着 entryoutput 和一个重要的 webpack 插件 html-webpack-plugin,这个插件是跟打包出来的 HTML 文件密切相关,主要有下面几个作用:

  1. 根据模版生成 HTML 文件;
  2. 给生成的 HTML 文件引入外部资源比如 linkscript 等;
  3. 改变每次引入的外部文件的 Hash,防止 HTML 引用缓存中的过时资源;

下面我们从头一步步配置一个多入口项目。

3. 开始配置

3.1 文件结构改动

src 目录下将 main.jsApp.vue 两个文件各复制一下,作为不同入口,文件结构变为:

  1. .
  2. ├── build
  3. │ ├── build.js
  4. │ ├── check-versions.js
  5. │ ├── logo.png
  6. │ ├── utils.js
  7. │ ├── vue-loader.conf.js
  8. │ ├── webpack.base.conf.js
  9. │ ├── webpack.dev.conf.js # 主要配置目标
  10. │ └── webpack.prod.conf.js # 主要配置目标
  11. ├── config
  12. │ ├── dev.env.js
  13. │ ├── index.js
  14. │ └── prod.env.js
  15. ├── src
  16. │ ├── assets
  17. │ │ └── logo.png
  18. │ ├── components
  19. │ │ └── HelloWorld.vue
  20. │ ├── router
  21. │ │ └── index.js
  22. │ ├── App.vue
  23. │ ├── App2.vue # 新增的入口
  24. │ ├── main.js
  25. │ └── main2.js # 新增的入口
  26. ├── static
  27. ├── README.md
  28. ├── index.html
  29. └── package.json

3.2 简单配置

要想从不同入口,打包出不同 HTML,我们可以改变一下 entryoutput 两个配置,

  1. // build/webpack.prod.conf.js
  2. module.exports = {
  3. entry: {
  4. entry1: './src/main.js',
  5. entry2: './src/main2.js'
  6. },
  7. output: {
  8. filename: '[name].js',
  9. publicPath: '/'
  10. },
  11. plugins: [
  12. new HtmlWebpackPlugin({
  13. template: "index.html", // 要打包输出哪个文件,可以使用相对路径
  14. filename: "index.html" // 打包输出后该html文件的名称
  15. })
  16. ]
  17. }

根据上面一小节我们知道,webpack 配置里的 output.filename 如果有 [name] 意为根据入口文件的名称,打包成对应名称的 JS 文件,那么现在我们是可以根据两个入口打包出 entry.jsentry2.js

打包的结果如下:watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxNDcwNjAzODIz_size_16_color_FFFFFF_t_70 1

当前代码:Github - multi-entry-vue1

如上图,此时我们 npm run build 打包出一个引用了这两个文件的 index.html,那么如何打包出不同 HTML 文件,分别应用不同入口 JS 文件呢,此时我们需要借助于 HtmlWebpackPlugin 这个插件。

HtmlWebpackPlugin 这个插件, new 一个,就打包一个 HTML 页面,所以我们在 plugins 配置里 new 两个,就能打包出两个页面来。

3.3 打包出不同的 HTML 页面

我们把配置文件改成下面这样:

  1. // build/webpack.prod.conf.js
  2. module.exports = {
  3. entry: {
  4. entry: './src/main.js', // 打包输出的chunk名为entry
  5. entry2: './src/main2.js' // 打包输出的chunk名为entry2
  6. },
  7. output: {
  8. filename: '[name].js',
  9. publicPath: '/'
  10. },
  11. plugins: [
  12. new HtmlWebpackPlugin({
  13. filename: 'entry.html', // 要打包输出的文件名
  14. template: 'index.html', // 打包输出后该html文件的名称
  15. chunks: ['manifest', 'vendor', 'entry'] // 输出的html文件引入的入口chunk
  16. // 还有一些其他配置比如minify、chunksSortMode和本文无关就省略,详见github
  17. }),
  18. new HtmlWebpackPlugin({
  19. filename: 'entry2.html',
  20. template: 'index.html',
  21. chunks: ['manifest', 'vendor', 'entry2']
  22. })
  23. ]
  24. }

上面一个配置要注意的是 chunks,如果没有配置,那么生成的 HTML 会引入所有入口 JS 文件,在上面的例子就是,生成的两个 HTML 文件都会引入 entry.jsentry2.js,所以要使用 chunks 配置来指定生成的 HTML 文件应该引入哪个 JS 文件。配置了 chunks 之后,才能达到不同的 HTML 只引入对应 chunks的 JS 文件的目的。

大家可以看到除了我们打包生成的 chunk 文件 entry.jsentry2.js 之外,还有 manifestvendor 这两个,这里稍微解释一下这两个 chunk

  1. vendor 是指提取涉及 node_modules 中的公共模块;
  2. manifest 是对 vendor 模块做的缓存;

打包完的结果如下:watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxNDcwNjAzODIz_size_16_color_FFFFFF_t_70 2

文件结构:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxNDcwNjAzODIz_size_16_color_FFFFFF_t_70 3

现在打包出来的样式正是我们所需要的,此时我们在 dist 目录下启动 live-server(如果你没安装的话可以先安装 npm i-g live-server),就可以看到效果出来了:watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxNDcwNjAzODIz_size_16_color_FFFFFF_t_70 4

当前代码:Github - multi-entry-vue2

至此就实现了一个简单的多入口项目的配置。

4. 配置改进

4.1 文件结构改动

我们在前文进行了多入口的配置,要想新建一个新的入口,就复制多个文件,再手动改一下对应配置。

但是如果不同的 HTML 文件下不同的 vue-routervuex 都放到 src 目录下,多个入口的内容平铺在一起,项目目录会变得凌乱不清晰,因此在下将多入口相关的文件放到一个单独的文件夹中,以后如果有多入口的内容,就到这个文件夹中处理。

下面我们进行文件结构的改造:

  1. 首先我们在根目录创建一个 entries 文件夹,把不同入口的 routerstoremain.js 都放这里,每个入口相关单独放在一个文件夹;
  2. src 目录下建立一个 common 文件夹,用来存放多入口共用的组件等;

现在的目录结构:

  1. .
  2. ├── build # 没有改动
  3. ├── config # 没有改动
  4. ├── entries # 存放不同入口的文件
  5. │ ├── entry1
  6. │ │ ├── router # entry1 的 router
  7. │ │ │ └── index.js
  8. │ │ ├── store # entry1 的 store
  9. │ │ │ └── index.js
  10. │ │ ├── App.vue # entry1 的根组件
  11. │ │ ├── index.html # entry1 的页面模版
  12. │ │ └── main.js # entry1 的入口
  13. │ └── entry2
  14. │ ├── router
  15. │ │ └── index.js
  16. │ ├── store
  17. │ │ └── index.js
  18. │ ├── App.vue
  19. │ ├── index.html
  20. │ └── main.js
  21. ├── src
  22. │ ├── assets
  23. │ │ └── logo.png
  24. │ ├── common # 多入口通用组件
  25. │ │ └── CommonTemplate.vue
  26. │ └── components
  27. │ ├── HelloWorld.vue
  28. │ ├── test1.vue
  29. │ └── test2.vue
  30. ├── static
  31. ├── README.md
  32. ├── index.html
  33. ├── package-lock.json
  34. └── package.json

4.2 webpack 配置

然后我们在 build/utils 文件中加两个函数,分别用来生成 webpack 的 entry 配置和 HtmlWebpackPlugin 插件配置,由于要使用 node.js 来读取文件夹结构,因此需要引入 fsglob 等模块:

  1. // build/utils
  2. const fs = require('fs')
  3. const glob = require('glob')
  4. const merge = require('webpack-merge')
  5. const HtmlWebpackPlugin = require('html-webpack-plugin')
  6. const ENTRY_PATH = path.resolve(__dirname, '../entries')
  7. // 多入口配置,这个函数从 entries 文件夹中读取入口文件,装配成webpack.entry配置
  8. exports.entries = function() {
  9. const entryFiles = glob.sync(ENTRY_PATH + '/*/*.js')
  10. const map = {}
  11. entryFiles.forEach(filePath => {
  12. const filename = filePath.replace(/.*\/(\w+)\/\w+(\.html|\.js)$/, (rs, $1) => $1)
  13. map[filename] = filePath
  14. })
  15. return map
  16. }
  17. // 多页面输出模版配置 HtmlWebpackPlugin,根据环境装配html模版配置
  18. exports.htmlPlugin = function() {
  19. let entryHtml = glob.sync(ENTRY_PATH + '/*/*.html')
  20. let arr = []
  21. entryHtml.forEach(filePath => {
  22. let filename = filePath.replace(/.*\/(\w+)\/\w+(\.html|\.js)$/, (rs, $1) => $1)
  23. let conf = {
  24. template: filePath,
  25. filename: filename + '.html',
  26. chunks: [filename],
  27. inject: true
  28. }
  29. // production 生产模式下配置
  30. if (process.env.NODE_ENV === 'production') {
  31. conf = merge(conf, {
  32. chunks: ['manifest', 'vendor'],
  33. minify: {
  34. removeComments: true,
  35. collapseWhitespace: true,
  36. removeAttributeQuotes: true
  37. },
  38. chunksSortMode: 'dependency'
  39. })
  40. }
  41. arr.push(new HtmlWebpackPlugin(conf))
  42. })
  43. return arr
  44. }

稍微解释一下这两个函数:

  1. exports.entries 函数从 entries 文件夹中找到二级目录下的 JS 文件作为入口文件,并且将二级目录的文件夹名作为 key,生成这样一个对象: {"entry1":"/multi-entry-vue/entries/entry1/main.js"},多个入口情况下会有更多键值对;
  1. exports.htmlPlugin 函数和之前函数的原理类似,不过组装的是 HtmlWebpackPlugin 插件的配置,生成这样一个数组,可以看到和我们手动设置的配置基本一样,只不过现在是根据文件夹结构来生成的:
  1. // production 下
  2. [
  3. {
  4. template: "/multi-entry-vue/entries/entry1/index.html",
  5. chunks: ['manifest', 'vendor', 'entry1'],
  6. filename: "entry1.html",
  7. chunksSortMode: 'dependency'
  8. },
  9. { ... } // 下一个入口的配置
  10. ]

有了这两个根据 entries 文件夹的结构来自动生成 webpack 配置的函数,下面来改一下 webpack 相关的几个配置文件:

  1. // build/webpack.base.conf.js
  2. module.exports = {
  3. entry: utils.entries(), // 使用函数生成 entry 配置
  4. output: {
  5. path: config.build.assetsRoot,
  6. filename: '[name].js',
  7. publicPath: process.env.NODE_ENV === 'production'
  8. ? config.build.assetsPublicPath
  9. : config.dev.assetsPublicPath
  10. }
  11. }
  1. // build/webpack.dev.conf.js
  2. // const HtmlWebpackPlugin = require('html-webpack-plugin') // 不需要了
  3. const devWebpackConfig = merge(baseWebpackConfig, {
  4. devServer: {
  5. historyApiFallback: {
  6. rewrites: [ // 别忘了把 devserver 的默认路由改一下
  7. { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'entry1.html') },
  8. ],
  9. }
  10. },
  11. plugins: [
  12. // https://github.com/ampedandwired/html-webpack-plugin
  13. // new HtmlWebpackPlugin({
  14. // filename: 'index.html',
  15. // template: 'index.html',
  16. // inject: true
  17. // }), // 注释掉原来的 HtmlWebpackPlugin 配置,使用生成的配置
  18. ].concat(utils.htmlPlugin())
  19. })
  1. // build/webpack.prod.conf.js
  2. // const HtmlWebpackPlugin = require('html-webpack-plugin')
  3. const webpackConfig = merge(baseWebpackConfig, {
  4. plugins: [
  5. // new HtmlWebpackPlugin({
  6. // ... 注释掉,不需要了
  7. // }),
  8. ].concat(utils.htmlPlugin())
  9. })

现在我们再 npm run build,看看生成的目录是什么样的:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxNDcwNjAzODIz_size_16_color_FFFFFF_t_70 5

此时我们在 dist 目录下启动 live-server 看看是什么效果:watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxNDcwNjAzODIz_size_16_color_FFFFFF_t_70 6

当前代码:Github - multi-entry-vue3

发表评论

表情:
评论列表 (有 0 条评论,83人围观)

还没有评论,来说两句吧...

相关阅读

    相关 static(

    一、聊一聊static与JVM Java 把内存分为栈内存和堆内存,其中栈内存用来存放一些基本类型的变量、数组和对象的引用,堆内存主要存放一些对象。在 JVM 加载一个类的

    相关 ThreadPoolExecutor

    前言 线程池是Java中使用较多的并发框架,合理使用线程池,可以:降低资源消耗,提高响应速度,提高线程的可管理性。 本篇文章将从线程池简单原理,线程池的创建,线程池执行

    相关 Webpack 入口配置

    最近在做项目的时候遇到了一个场景:一个项目有多个入口,不同的入口,路由、组件、资源等有重叠部分,也有各自不同的部分。由于不同入口下的路由页面有一些是重复的,因此我考虑使用 We

    相关 Webpack Loader

      50 亿观众的 “云上奥运”,顶级媒体背后的数智化力量   东京 2020 奥运会即将闭幕,本届奥运会由于疫情限制,东京地区赛事以无观众的空场形式举行,在无法亲临现场的情

    相关 Raft 算法

    一文搞懂Raft算法 正文 raft是工程上使用较为广泛的强一致性、去中心化、高可用的分布式协议。在这里强调了是在工程上,因为在学术理论界,最耀眼的还是大名鼎鼎的Pax

    相关 DDD

    最近看了很多和DDD相关的内容,这篇文章对DDD做一个总结,希望可以通过这篇文章不但知道什么是DDD,而且还可以知道如何实施DDD 一、什么是DDD DDD(领域驱动设

    相关 Webpack 入口配置 (收藏)

    最近在做项目的时候遇到了一个场景:一个项目有多个入口,不同的入口,路由、组件、资源等有重叠部分,也有各自不同的部分。由于不同入口下的路由页面有一些是重复的,因此我考虑使用 We