当前位置: 首页 > news >正文

一文搞懂 Webpack 多入口配置

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

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

1. 目标分析

  1. 一个项目中保存了多个 HTML 模版,不同的模版有不同的入口,并且有各自的 router、store 等;

  2. 不仅可以打包出不同 HTML,而且开发的时候也可以顺利进行调试;

  3. 不同入口的文件可以引用同一份组件、图片等资源,也可以引用不同的资源;

代码仓库:multi-entry-vue

示意图如下:

 

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] 的意为根据入口文件的名称,打包成相同的名称,有几个入口,就可以打包出几个文件。比如入口的 key 为 app,打包出来就是 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 文件引入外部资源比如 link、 script 等;

  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.  

  3. module.exports = {

  4. entry: {

  5. entry1: './src/main.js',

  6. entry2: './src/main2.js'

  7. },

  8. output: {

  9. filename: '[name].js',

  10. publicPath: '/'

  11. },

  12. plugins: [

  13. new HtmlWebpackPlugin({

  14. template: "index.html", // 要打包输出哪个文件,可以使用相对路径

  15. filename: "index.html" // 打包输出后该html文件的名称

  16. })

  17. ]

  18. }

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

打包的结果如下:

 

当前代码: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.  

  3. module.exports = {

  4. entry: {

  5. entry: './src/main.js', // 打包输出的chunk名为entry

  6. entry2: './src/main2.js' // 打包输出的chunk名为entry2

  7. },

  8. output: {

  9. filename: '[name].js',

  10. publicPath: '/'

  11. },

  12. plugins: [

  13. new HtmlWebpackPlugin({

  14. filename: 'entry.html', // 要打包输出的文件名

  15. template: 'index.html', // 打包输出后该html文件的名称

  16. chunks: ['manifest', 'vendor', 'entry'] // 输出的html文件引入的入口chunk

  17. // 还有一些其他配置比如minify、chunksSortMode和本文无关就省略,详见github

  18. }),

  19. new HtmlWebpackPlugin({

  20. filename: 'entry2.html',

  21. template: 'index.html',

  22. chunks: ['manifest', 'vendor', 'entry2']

  23. })

  24. ]

  25. }

上面一个配置要注意的是 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 模块做的缓存;

打包完的结果如下:

 

文件结构:

 

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

 

 

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

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

4. 配置改进

4.1 文件结构改动

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

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

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

  1. 首先我们在根目录创建一个 entries 文件夹,把不同入口的 router、 store、 main.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.  

  8. // 多入口配置,这个函数从 entries 文件夹中读取入口文件,装配成webpack.entry配置

  9. exports.entries = function() {

  10. const entryFiles = glob.sync(ENTRY_PATH + '/*/*.js')

  11. const map = {}

  12. entryFiles.forEach(filePath => {

  13. const filename = filePath.replace(/.*\/(\w+)\/\w+(\.html|\.js)$/, (rs, $1) => $1)

  14. map[filename] = filePath

  15. })

  16. return map

  17. }

  18.  

  19. // 多页面输出模版配置 HtmlWebpackPlugin,根据环境装配html模版配置

  20. exports.htmlPlugin = function() {

  21. let entryHtml = glob.sync(ENTRY_PATH + '/*/*.html')

  22. let arr = []

  23. entryHtml.forEach(filePath => {

  24. let filename = filePath.replace(/.*\/(\w+)\/\w+(\.html|\.js)$/, (rs, $1) => $1)

  25. let conf = {

  26. template: filePath,

  27. filename: filename + '.html',

  28. chunks: [filename],

  29. inject: true

  30. }

  31.  

  32. // production 生产模式下配置

  33. if (process.env.NODE_ENV === 'production') {

  34. conf = merge(conf, {

  35. chunks: ['manifest', 'vendor'],

  36. minify: {

  37. removeComments: true,

  38. collapseWhitespace: true,

  39. removeAttributeQuotes: true

  40. },

  41. chunksSortMode: 'dependency'

  42. })

  43. }

  44. arr.push(new HtmlWebpackPlugin(conf))

  45. })

  46. return arr

  47. }

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

 

  1. exports.entries 函数从 entries 文件夹中找到二级目录下的 JS 文件作为入口文件,并且将二级目录的文件夹名作为 key,生成这样一个对象: {"entry1":"/multi-entry-vue/entries/entry1/main.js"},多个入口情况下会有更多键值对;

     

  2. 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.  

  3. module.exports = {

  4. entry: utils.entries(), // 使用函数生成 entry 配置

  5. output: {

  6. path: config.build.assetsRoot,

  7. filename: '[name].js',

  8. publicPath: process.env.NODE_ENV === 'production'

  9. ? config.build.assetsPublicPath

  10. : config.dev.assetsPublicPath

  11. }

  12. }

 
  1. // build/webpack.dev.conf.js

  2.  

  3. // const HtmlWebpackPlugin = require('html-webpack-plugin') // 不需要了

  4.  

  5. const devWebpackConfig = merge(baseWebpackConfig, {

  6. devServer: {

  7. historyApiFallback: {

  8. rewrites: [ // 别忘了把 devserver 的默认路由改一下

  9. { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'entry1.html') },

  10. ],

  11. }

  12. },

  13. plugins: [

  14. // https://github.com/ampedandwired/html-webpack-plugin

  15. // new HtmlWebpackPlugin({

  16. // filename: 'index.html',

  17. // template: 'index.html',

  18. // inject: true

  19. // }), // 注释掉原来的 HtmlWebpackPlugin 配置,使用生成的配置

  20. ].concat(utils.htmlPlugin())

  21. })

 
  1. // build/webpack.prod.conf.js

  2.  

  3. // const HtmlWebpackPlugin = require('html-webpack-plugin')

  4.  

  5. const webpackConfig = merge(baseWebpackConfig, {

  6. plugins: [

  7. // new HtmlWebpackPlugin({

  8. // ... 注释掉,不需要了

  9. // }),

  10. ].concat(utils.htmlPlugin())

  11. })

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

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

 

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

相关文章:

  • 用生动有趣的emoij美化你的commit log
  • 一文吃透React SSR服务端同构渲染
  • Vue为啥可以成为2019年的一匹黑马?
  • 基于 Vue 和 TS 的 Web 移动端项目实战心得
  • 前端开发中79条不可忽视的知识点汇总
  • 爱奇艺 PC Web Node.js 中间层实践
  • 浅谈 Node.js 模块机制及常见面试问题解答
  • 面试之前,简历之上:给前端校招同学的简历建议
  • lndexedDB 实践
  • JS引擎V8如何与Lite模式两开花?
  • 君の古风操作系统Haiku现已支持Node.js
  • Web前端面试题目汇总
  • 30分钟教你学会前端模块化开发
  • React 初学者需要知道的一些知识
  • 7 个沙雕又带有陷阱的 JS 面试题
  • 「前端」从UglifyJSPlugin强制开启css压缩探究webpack插件运行机制
  • 2018一半小结一波
  • Java到底能干嘛?
  • Making An Indicator With Pure CSS
  • MQ框架的比较
  • ng6--错误信息小结(持续更新)
  • PHP 使用 Swoole - TaskWorker 实现异步操作 Mysql
  • Promise面试题2实现异步串行执行
  • React 快速上手 - 06 容器组件、展示组件、操作组件
  • 测试如何在敏捷团队中工作?
  • 动态魔术使用DBMS_SQL
  • 设计模式(12)迭代器模式(讲解+应用)
  • 我从编程教室毕业
  • 想写好前端,先练好内功
  • 携程小程序初体验
  • 要让cordova项目适配iphoneX + ios11.4,总共要几步?三步
  • Oracle Portal 11g Diagnostics using Remote Diagnostic Agent (RDA) [ID 1059805.
  • LevelDB 入门 —— 全面了解 LevelDB 的功能特性
  • ​LeetCode解法汇总2182. 构造限制重复的字符串
  • ​RecSys 2022 | 面向人岗匹配的双向选择偏好建模
  • ​一、什么是射频识别?二、射频识别系统组成及工作原理三、射频识别系统分类四、RFID与物联网​
  • #13 yum、编译安装与sed命令的使用
  • #中国IT界的第一本漂流日记 传递IT正能量# 【分享得“IT漂友”勋章】
  • (13)Hive调优——动态分区导致的小文件问题
  • (2)Java 简介
  • (4)logging(日志模块)
  • (HAL库版)freeRTOS移植STMF103
  • (js)循环条件满足时终止循环
  • (编译到47%失败)to be deleted
  • (顶刊)一个基于分类代理模型的超多目标优化算法
  • (分布式缓存)Redis哨兵
  • (一)基于IDEA的JAVA基础12
  • (轉貼) 2008 Altera 亞洲創新大賽 台灣學生成果傲視全球 [照片花絮] (SOC) (News)
  • ./indexer: error while loading shared libraries: libmysqlclient.so.18: cannot open shared object fil
  • .net 4.0 A potentially dangerous Request.Form value was detected from the client 的解决方案
  • .NET Framework .NET Core与 .NET 的区别
  • .NET/C# 判断某个类是否是泛型类型或泛型接口的子类型
  • .net访问oracle数据库性能问题
  • .net通用权限框架B/S (三)--MODEL层(2)
  • @TableLogic注解说明,以及对增删改查的影响