点击蓝字“前端技术优化”关注我们!
最近做项目的时候,遇到一个场景:一个项目有多个入口,不同的入口、路由、组件、资源等有重叠的部分,也有不同的部分。 由于不同条目下的一些路由页面是重复的,所以我考虑使用Webpack多条目配置来解决这个需求。
再次,我在网上找到的很多文章都不能满足我的需求。 很多文章只简单介绍了生产环境中的配置,而没有介绍开发环境中的配置,有的没有将多个条目与 vue-router 和 vuex 、 ElementUI 等结合起来,所以我会继续探索坑下面,再把思路和配置过程记录下来,给自己留个笔记,分享给大家,希望能帮助到有同样需求的朋友~
1. 目标分析
一个项目中保存了多个HTML模板,不同的模板有不同的入口,有自己的router、store等;
不仅可以封装不同的HTML,而且在开发过程中也可以顺利调试;
不同条目的文件可以引用相同的组件、镜像等资源,也可以引用不同的资源;
代码仓库:multi-entry-vue
示意图如下:
2. 准备工作
首先,我们使用 vue init webpack multi-entry-vue 使用 vue-cli 创建一个 webpack 模板项。 文件结构如下:
.
├── build
├── config
├── src
│ ├── assets
│ │ └── logo.png
│ ├── components
│ │ └── HelloWorld.vue
│ ├── router
│ │ └── index.js
│ ├── App.vue
│ └── main.js
├── static
├── README.md
├── index.html
├── package-lock.json
└── package.json
顺便说一下,下面是不同系统下生成目录树的方法:
mac系统命令行生成目录树tree -I node_modules --dirsfirst,该命令的意思是不显示node_modules路径下的文件,先对文件夹进行排序生成目录树。 如果报没有找到tree命令的错误,只需安装tree命令行brew install tree即可。
windows系统可以使用目标目录中的tree/f1.txt将当前目录树生成为新文件1.txt。
首先简单介绍一下Webpack的相关配置项。 这些配置项一般根据使用的Webpack模板存放在webpack.config.js或webpack.base.conf.js中:
const path = require('path')
module.exports = {
context: path.resolve(__dirname, '../'),
entry: {
app: './src/main.js'
},
output: {
path: path.resolve(__dirname, '../dist'),
filename: 'output-file.js',
publicPath: '/'
},
module: {}, // 文件的解析 loader 配置
plugins: [], // 插件,根据需要配置各种插件
devServer: {} // 配置 dev 服务功能
}
这个配置意味着Webpack执行后,会在命令的执行目录下创建一个新的dist目录(如果需要的话),并将src目录下的main.js及其依赖包打包生成output-file.js目录中的 dist。
下面简单解释一下相关配置项:
Entry:入口文件配置项,可以是字符串、对象或数组。 以里面的object方法为例,app是入口名称,如果output.filename中有[name],则会替换为app。
context:webpack编译时的基本目录。 用于解析入口选项的基本目录(绝对路径)。 入口条目的起始点将会相对于这个目录进行搜索,相当于一个公共目录。 以下所有目录都在这个公共目录下。
output:导出文件的配置项。
output/path:打包文件输出的目录,比如之前的dist,那么输出文件会放在与当前目录同级目录的dist文件夹下,如果有则新建一个没有这样的文件夹。 可以配置为path.resolve(__dirname,'./dist/${Date.now()}/')(md语法不方便改成模板字符串,请自行更改),以方便持续集成。
output.filename:输出文件的名称,[name]表示根据入口文件的名称打包成同名的,有几个入口,可以打包几个文件。 例如,条目的键是app,包是app.js,条目是my-entry,包是my-entry.js。
output.publicPath:静态资源的公共路径,可以记住这个公式:静态资源最终访问路径=output.publicPath+资源加载器或插件配置路径。 例如publicPath配置为/dist/,图片的url-loader配置项为name:'img/[name].[ext]',则输出最终打包文件中图片的引用路径.publicPath+'img/ [名称].[ext]'='/dist/img/[名称].[ext]'。
由于本文涉及入口和出口的配置,因此内容主要围绕入口、输出以及一个重要的webpack插件html-webpack-plugin进行。 该插件与打包的HTML文件密切相关,主要有以下功能:
根据模板生成HTML文件;
在生成的HTML文件中引入链接、脚本等外部资源;
更改每个导入的外部文件的Hash,以防止HTML引用缓存中过时的资源;
让我们从头开始逐步配置一个多条目项目。
3.开始配置 3.1 文件结构改变
复制src目录下的main.js和App.vue两个文件。 随着不同的条目,文件结构变为:
.
├── build
│ ├── build.js
│ ├── check-versions.js
│ ├── logo.png
│ ├── utils.js
│ ├── vue-loader.conf.js
│ ├── webpack.base.conf.js
│ ├── webpack.dev.conf.js # 主要配置目标
│ └── webpack.prod.conf.js # 主要配置目标
├── config
│ ├── dev.env.js
│ ├── index.js
│ └── prod.env.js
├── src
│ ├── assets
│ │ └── logo.png
│ ├── components
│ │ └── HelloWorld.vue
│ ├── router
│ │ └── index.js
│ ├── App.vue
│ ├── App2.vue # 新增的入口
│ ├── main.js
│ └── main2.js # 新增的入口
├── static
├── README.md
├── index.html
└── package.json
3.2 简单配置
为了从不同的入口打包不同的HTML,我们可以改变入口和输出两个配置。
// build/webpack.prod.conf.js
module.exports = {
entry: {
entry1: './src/main.js',
entry2: './src/main2.js'
},
output: {
filename: '[name].js',
publicPath: '/'
},
plugins: [
new HtmlWebpackPlugin({
template: "index.html", // 要打包输出哪个文件,可以使用相对路径
filename: "index.html" // 打包输出后该html文件的名称
})
]
}
根据上一节我们知道,如果webpack配置中的output.filename有[name],则表示会根据入口文件的名称打包成对应名称的JS文件,所以现在我们可以打包entry.js和entry2。 js.
打包结果如下:
当前代码:Github - multi-entry-vue1
如上图所示,此时我们通过npm run build打包了一个引用这两个文件的index.htmlwebpack本地配置,那么如何打包不同的HTML文件,分别应用不同的入口JS文件呢? 这时候我们就需要用到插件HtmlWebpackPlugin。
HtmlWebpackPlugin 插件是一个新插件,它打包一个 HTML 页面,因此我们可以通过在插件配置中添加两个新插件来打包两个页面。
3.3 打包不同的HTML页面
我们将配置文件更改为以下内容:
// build/webpack.prod.conf.js
module.exports = {
entry: {
entry: './src/main.js', // 打包输出的chunk名为entry
entry2: './src/main2.js' // 打包输出的chunk名为entry2
},
output: {
filename: '[name].js',
publicPath: '/'
},
plugins: [
new HtmlWebpackPlugin({
filename: 'entry.html', // 要打包输出的文件名
template: 'index.html', // 打包输出后该html文件的名称
chunks: ['manifest', 'vendor', 'entry'] // 输出的html文件引入的入口chunk
// 还有一些其他配置比如minify、chunksSortMode和本文无关就省略,详见github
}),
new HtmlWebpackPlugin({
filename: 'entry2.html',
template: 'index.html',
chunks: ['manifest', 'vendor', 'entry2']
})
]
}
上面的配置要注意chunks。 如果没有配置,生成的HTML会导入所有入口JS文件。 在前面的例子中,生成的两个HTML文件也会导入entry.js和entry2.js,因此使用chunks配置来指定生成的HTML文件应该导入哪些JS文件。 配置好 chunk 后,不同的 HTML 只能导入 chunk 对应的 JS 文件。
你不仅可以看到我们打包生成的块文件entry.js和entry2.js,还可以看到manifest和vendor。 下面简单解释一下这两个块:
Vendor是指提取node_modules中涉及的公共模块;
清单是供应商模块的缓存;
打包结果如下:
文件结构:
现在打包的样式正是我们所需要的。 这时我们启动dist目录下的live-server(如果还没有安装,可以先安装npm ig live-server),就可以看到疗效了:
当前代码:Github - multi-entry-vue2
至此webpack本地配置,一个简单的多入口项目的配置就已经实现了。
4. 配置改进 4.1 文件结构变更
我们在上一篇文章中配置了多个条目。 要创建新条目,请复制多个文件,然后自动更改相应的配置。
但如果不同的vue-router和vuex放在不同的html文件下的src目录下,并且多个条目的内容平铺在一起,项目目录会显得杂乱、不清晰,所以将多条目相关文件放在下面在一个单独的文件夹中,如果将来有更多条目,它们将在该文件夹中处理。
让我们更新一下文件结构:
首先我们在根目录下创建一个entries文件夹,将不同entry的router、store、main.js放在这里,并将每个entry放在单独的文件夹中;
在src目录下构建common文件夹,用于存放多个条目共享的组件等;
当前目录结构:
.
├── build # 没有改动
├── config # 没有改动
├── entries # 存放不同入口的文件
│ ├── entry1
│ │ ├── router # entry1 的 router
│ │ │ └── index.js
│ │ ├── store # entry1 的 store
│ │ │ └── index.js
│ │ ├── App.vue # entry1 的根组件
│ │ ├── index.html # entry1 的页面模版
│ │ └── main.js # entry1 的入口
│ └── entry2
│ ├── router
│ │ └── index.js
│ ├── store
│ │ └── index.js
│ ├── App.vue
│ ├── index.html
│ └── main.js
├── src
│ ├── assets
│ │ └── logo.png
│ ├── common # 多入口通用组件
│ │ └── CommonTemplate.vue
│ └── components
│ ├── HelloWorld.vue
│ ├── test1.vue
│ └── test2.vue
├── static
├── README.md
├── index.html
├── package-lock.json
└── package.json
4.2 webpack配置
然后我们在build/utils文件中添加两个函数,分别生成webpack入口配置和HtmlWebpackPlugin插件配置。 因为我们需要使用node.js读取文件夹结构,所以需要引入fs、glob等模块:
// build/utils
const fs = require('fs')
const glob = require('glob')
const merge = require('webpack-merge')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ENTRY_PATH = path.resolve(__dirname, '../entries')
// 多入口配置,这个函数从 entries 文件夹中读取入口文件,装配成webpack.entry配置
exports.entries = function() {
const entryFiles = glob.sync(ENTRY_PATH + '/*/*.js')
const map = {}
entryFiles.forEach(filePath => {
const filename = filePath.replace(/.*/(w+)/w+(.html|.js)$/, (rs, $1) => $1)
map[filename] = filePath
})
return map
}
// 多页面输出模版配置 HtmlWebpackPlugin,根据环境装配html模版配置
exports.htmlPlugin = function() {
let entryHtml = glob.sync(ENTRY_PATH + '/*/*.html')
let arr = []
entryHtml.forEach(filePath => {
let filename = filePath.replace(/.*/(w+)/w+(.html|.js)$/, (rs, $1) => $1)
let conf = {
template: filePath,
filename: filename + '.html',
chunks: [filename],
inject: true
}
// production 生产模式下配置
if (process.env.NODE_ENV === 'production') {
conf = merge(conf, {
chunks: ['manifest', 'vendor'],
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
chunksSortMode: 'dependency'
})
}
arr.push(new HtmlWebpackPlugin(conf))
})
return arr
}
对这两个函数的一点解释:
Exports.entries函数从entries文件夹中查找二级目录下的JS文件作为入口文件,并以二级目录的文件夹名作为key生成这样一个对象:{"entry1":"/multi-entry -vue /entries/entry1/main.js"},多个条目的情况下会有更多的通配符对;
Exports.htmlPlugin函数的原理和前面的函数类似,只不过是组装了HtmlWebpackPlugin插件的配置,并生成了这样一个链表。 可以看到和我们自动设置的配置基本一样,只不过现在是按照文件夹结构生成的:
// production 下
[
{
template: "/multi-entry-vue/entries/entry1/index.html",
chunks: ['manifest', 'vendor', 'entry1'],
filename: "entry1.html",
chunksSortMode: 'dependency'
},
{ ... } // 下一个入口的配置
]
有了这两个函数来根据entries文件夹的结构手动生成webpack配置,我们来更改几个与webpack相关的配置文件:
// build/webpack.base.conf.js
module.exports = {
entry: utils.entries(), // 使用函数生成 entry 配置
output: {
path: config.build.assetsRoot,
filename: '[name].js',
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
}
}
// build/webpack.dev.conf.js
// const HtmlWebpackPlugin = require('html-webpack-plugin') // 不需要了
const devWebpackConfig = merge(baseWebpackConfig, {
devServer: {
historyApiFallback: {
rewrites: [ // 别忘了把 devserver 的默认路由改一下
{ from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'entry1.html') },
],
}
},
plugins: [
// https://github.com/ampedandwired/html-webpack-plugin
// new HtmlWebpackPlugin({
// filename: 'index.html',
// template: 'index.html',
// inject: true
// }), // 注释掉原来的 HtmlWebpackPlugin 配置,使用生成的配置
].concat(utils.htmlPlugin())
})
// build/webpack.prod.conf.js
// const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpackConfig = merge(baseWebpackConfig, {
plugins: [
// new HtmlWebpackPlugin({
// ... 注释掉,不需要了
// }),
].concat(utils.htmlPlugin())
})
现在让我们再次运行 npm run build 来看看生成的目录是什么样的:
此时,我们在dist目录下启动live-server,看看有什么效果:
当前代码:Github - multi-entry-vue3
网上的帖子大多是深浅不一,甚至有些前后矛盾。 以下文章是学习过程的总结。 如果发现有错误的地方请留言强调哦~
参考:
webpack困惑:多入口文件打包策略
webpack配置文件:入口和出口、多入口、多出口配置
一目了然了解 webpack 中间配置和优化
在看点这里