手摸手 Webpack 多入口配置實踐

最近在作項目的時候遇到了一個場景:一個項目有多個入口,不一樣的入口,路由、組件、資源等有重疊部分,也有各自不一樣的部分。因爲不一樣入口下的路由頁面有一些是重複的,所以我考慮使用 Webpack 多入口配置來解決這個需求。javascript

再一次,在網上找的很多文章都不合個人需求,不少文章都是隻簡單介紹了生產環境下配置,沒有介紹開發環境下的配置,有的也沒有將多入口結合 vue-routervuexElementUI 等進行配置,所以在下經過不斷探坑,而後將思路和配置過程記錄下來,留給本身做爲筆記,同時也分享給你們,但願能夠幫助到有一樣需求的同窗們~html

1. 目標分析

  1. 一個項目中保存了多個 HTML 模版,不一樣的模版有不一樣的入口,而且有各自的 router、store 等;
  2. 不只能夠打包出不一樣 HTML,並且開發的時候也能夠順利進行調試;
  3. 不一樣入口的文件能夠引用同一份組件、圖片等資源,也能夠引用不一樣的資源;

代碼倉庫:multi-entry-vue前端

示意圖以下:vue

2. 準備工做

首先咱們 vue init webpack multi-entry-vue 使用 vue-cli 建立一個 webpack 模版的項。文件結構以下:java

.
├── 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
複製代碼

這裏順便介紹在不一樣系統下生成目錄樹的方法:node

  1. mac 系統命令行生成目錄樹的方法 tree -I node_modules --dirsfirst ,這個命令的意思是,不顯示 node_modules 路徑的文件,而且以文件夾在前的排序方式生成目錄樹。若是報沒有找到 tree 命令的錯,安裝 tree 命令行 brew install tree 便可。
  2. windows 系統在目標目錄下使用 tree /f 1.txt 便可把當前目錄樹生成到一個新文件 1.txt 中。

首先咱們簡單介紹一下 Webpack 的相關配置項,這些配置項根據使用的 Webpack 模版不一樣,通常存放在 webpack.config.jswebpack.base.conf.js 中:webpack

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 目錄中。git

下面稍微解釋一下相關配置項:github

  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 文件密切相關,主要有下面幾個做用:web

  1. 根據模版生成 HTML 文件;
  2. 給生成的 HTML 文件引入外部資源好比 linkscript 等;
  3. 改變每次引入的外部文件的 Hash,防止 HTML 引用緩存中的過期資源;

下面咱們從頭一步步配置一個多入口項目。

3. 開始配置

3.1 文件結構改動

src 目錄下將 main.jsApp.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,咱們能夠改變一下 entryoutput 兩個配置,

// 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.jsentry2.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.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 文件夾,把不一樣入口的 routerstoremain.js 都放這裏,每一個入口相關單獨放在一個文件夾;
  2. 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 來讀取文件夾結構,所以須要引入 fsglob 等模塊:

// 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
}
複製代碼

稍微解釋一下這兩個函數:

  1. exports.entries 函數從 entries 文件夾中找到二級目錄下的 JS 文件做爲入口文件,而且將二級目錄的文件夾名做爲 key,生成這樣一個對象:{"entry1": "/multi-entry-vue/entries/entry1/main.js"},多個入口狀況下會有更多鍵值對;

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


網上的帖子大多深淺不一,甚至有些先後矛盾,在下的文章都是學習過程當中的總結,若是發現錯誤,歡迎留言指出~

參考:

  1. webpack解惑:多入口文件打包策略
  2. webpack配置文件:入口和出口,多入口、多出口配置
  3. 一看就懂之webpack高級配置與優化

PS:歡迎你們關注個人公衆號【前端下午茶】,一塊兒加油吧~

另外能夠加入「前端下午茶交流羣」微信羣,長按識別下面二維碼便可加我好友,備註加羣,我拉你入羣~

相關文章
相關標籤/搜索