一文搞懂 Webpack 多入口配置
- 2019 年 10 月 4 日
- 筆記
最近在做項目的時候遇到了一個場景:一個項目有多個入口,不同的入口,路由、組件、資源等有重疊部分,也有各自不同的部分。由於不同入口下的路由頁面有一些是重複的,因此我考慮使用 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: 入口文件配置項,可以為字元串、對象、數組。以上面的對象形式為例,
app
是入口名稱,如果output.filename
中有[name]
的話,就會被替換成app
。 - context: 是 webpack 編譯時的基礎目錄,用於解析
entry
選項的基礎目錄(絕對路徑),entry
入口起點會相對於此目錄查找,相當於公共目錄,下面所有的目錄都在這個公共目錄下面。 - output: 出口文件的配置項。
- output/path: 打包文件輸出的目錄,比如上面的
dist
,那麼就會將輸出的文件放在當前目錄同級目錄的dist
文件夾下,沒有這個文件夾就新建一個。可以配置為path.resolve(__dirname,'./dist/${Date.now()}/')
(md 語法不方便改成模板字元串,請自行修改)方便做持續集成。 - output.filename: 輸出的文件名稱,
[name]
的意為根據入口文件的名稱,打包成相同的名稱,有幾個入口,就可以打包出幾個文件。比如入口的key
為app
,打包出來就是app.js
,入口是my-entry
,打包出來就是my-entry.js
。 - output.publicPath: 靜態資源的公共路徑,可以記住這個公式:
靜態資源最終訪問路徑=output.publicPath+資源loader或插件等配置路徑
。舉個例子,publicPath
配置為/dist/
,圖片的url-loader
配置項為name:'img/[name].[ext]'
,那麼最終打包出來文件中圖片的引用路徑為output.publicPath+'img/[name].[ext]'='/dist/img/[name].[ext]'
。
本文由於是入口和出口相關的配置,所以內容主要圍繞著 entry
、 output
和一個重要的 webpack 插件 html-webpack-plugin,這個插件是跟打包出來的 HTML 文件密切相關,主要有下面幾個作用:
- 根據模版生成 HTML 文件;
- 給生成的 HTML 文件引入外部資源比如
link
、script
等; - 改變每次引入的外部文件的 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,我們可以改變一下 entry
和 output
兩個配置,
// 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.html
,那麼如何打包出不同 HTML 文件,分別應用不同入口 JS 文件呢,此時我們需要藉助於 HtmlWebpackPlugin
這個插件。
HtmlWebpackPlugin
這個插件, new
一個,就打包一個 HTML 頁面,所以我們在 plugins
配置里 new
兩個,就能打包出兩個頁面來。
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 文件。配置了 chunks
之後,才能達到不同的 HTML 只引入對應 chunks
的 JS 文件的目的。
大家可以看到除了我們打包生成的 chunk
文件 entry.js
和 entry2.js
之外,還有 manifest
和 vendor
這兩個,這裡稍微解釋一下這兩個 chunk
:
vendor
是指提取涉及node_modules
中的公共模組;manifest
是對vendor
模組做的快取;
打包完的結果如下:

文件結構:

現在打包出來的樣式正是我們所需要的,此時我們在 dist
目錄下啟動 live-server
(如果你沒安裝的話可以先安裝 npm i-g live-server
),就可以看到效果出來了:

當前程式碼:Github – multi-entry-vue2
至此就實現了一個簡單的多入口項目的配置。
4. 配置改進
4.1 文件結構改動
我們在前文進行了多入口的配置,要想新建一個新的入口,就複製多個文件,再手動改一下對應配置。
但是如果不同的 HTML 文件下不同的 vue-router
、 vuex
都放到 src
目錄下,多個入口的內容平鋪在一起,項目目錄會變得凌亂不清晰,因此在下將多入口相關的文件放到一個單獨的文件夾中,以後如果有多入口的內容,就到這個文件夾中處理。
下面我們進行文件結構的改造:
- 首先我們在根目錄創建一個
entries
文件夾,把不同入口的router
、store
、main.js
都放這裡,每個入口相關單獨放在一個文件夾; - 在
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 的 entry
配置和 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高級配置與優化
文章轉載自公眾號 前端下午茶 , 作者 SHERlocked93