Webpack實戰-管理多個單頁應用

引入問題

上一節3-9爲單頁應用生成HTML中只生成了一個 HTML 文件,但在實際應用中一個完整的系統不會把全部的功能都作到一個網頁中,由於這會致使這個網頁性能不佳。 實際的作法是按照功能模塊劃分紅多個單頁應用,每一個單頁應用生成一個 HTML 文件。而且隨着業務的發展更多的單頁應用可能會逐漸被加入到項目中去。javascript

雖然上一節已經解決了自動化生成 HTML 的痛點,可是手動去管理多個單頁應用的生成也是一件麻煩的事情。 來繼續改造上一節的例子,要求以下:css

  • 項目目前共有2個單頁應用組成,一個是主頁 index.html,一個是用戶登入頁 login.html
  • 多個單頁應用之間會有公共的代碼部分,須要把這些公共的部分抽離出來,放到單獨的文件中去以防止重複加載。例如多個頁面都使用一套 CSS 樣式,都採用了 React 框架,這些公共的部分須要抽離到單獨的文件中;
  • 隨着業務的發展後面可能會不斷的加入新的單頁應用,可是每次新加入單頁應用不能去改動構建相關的代碼。

在開始前先來看看該應用最終發佈到線上的代碼。html

login.html 文件內容:java

<html>
<head>
<meta charset="UTF-8">
<!--從多個頁面中抽離出的公共 CSS 代碼-->
<link rel="stylesheet" href="common_7cc98ad0.css">
<!--只有這個頁面須要的 CSS 代碼-->
<link rel="stylesheet" href="login_e31e214b.css">
<!--注入 google_analytics 中的 JS 代碼-->
<script>(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-XXXXX-Y', 'auto'); ga('send', 'pageview');</script>
<!--異步加載 Disqus 評論-->
<script async="" src="https://dive-into-webpack.disqus.com/embed.js"></script>
</head>
<body>
<div id="app"></div>
<!--從多個頁面中抽離出的公共 JavaScript 代碼-->
<script src="common_a1d9142f.js"></script>
<!--只有這個頁面須要的 JavaScript 代碼-->
<script src="login_f926c4e6.js"></script>
<!--Disqus 評論容器-->
<div id="disqus_thread"></div>
</body>
</html>
複製代碼

構建出的目錄結構爲:webpack

dist
├── common_029086ff.js
├── common_7cc98ad0.css
├── index.html
├── index_04c08fbf.css
├── index_b3d3761c.js
├── login.html
├── login_0a3feca9.js
└── login_e31e214b.css
複製代碼

若是按照上節的思路,可能須要爲每一個單頁應用配置一段以下代碼:git

new WebPlugin({
  template: './template.html', // HTML 模版文件所在的文件路徑
  filename: 'login.html' // 輸出的 HTML 的文件名稱
})
複製代碼

而且把頁面對應的入口加入到 enrty 配置項中,就像這樣:github

entry: {
  index: './pages/index/index.js',// 頁面 index.html 的入口文件
  login: './pages/login/index.js',// 頁面 login.html 的入口文件
}
複製代碼

當有新頁面加入時就須要修改 Webpack 配置文件,新插入一段以上代碼,這會致使構建代碼難以維護並且易錯。web

解決方案

上一節中的 web-webpack-plugin 插件也內置瞭解決這個問題的方法,上一節中只使用了它的 WebPlugin, 這節將使用它的 AutoWebPlugin 來解決以上問題,使用方法很是簡單,下面來教你具體如何使用。json

項目源碼目錄結構以下:bash

├── pages
│   ├── index
│   │   ├── index.css // 該頁面單獨須要的 CSS 樣式
│   │   └── index.js // 該頁面的入口文件
│   └── login
│       ├── index.css
│       └── index.js
├── common.css // 全部頁面都須要的公共 CSS 樣式
├── google_analytics.js
├── template.html
└── webpack.config.js
複製代碼

從目錄結構中能夠當作出下幾點要求:

  • 全部單頁應用的代碼都須要放到一個目錄下,例如都放在 pages 目錄下;
  • 一個單頁應用一個單獨的文件夾,例如最後生成的 index.html 相關的代碼都在 index 目錄下,login.html 同理;
  • 每一個單頁應用的目錄下都有一個 index.js 文件做爲入口執行文件。

雖然 AutoWebPlugin 強制性的規定了項目部分的目錄結構,但從實戰經驗來看這是一種優雅的目錄規範,合理的拆分了代碼,又能讓新人快速的看懂項目結構,也方便往後的維護。

Webpack 配置文件修改以下:

const { AutoWebPlugin } = require('web-webpack-plugin');

// 使用本文的主角 AutoWebPlugin,自動尋找 pages 目錄下的全部目錄,把每個目錄當作一個單頁應用
const autoWebPlugin = new AutoWebPlugin('pages', {
  template: './template.html', // HTML 模版文件所在的文件路徑
  postEntrys: ['./common.css'],// 全部頁面都依賴這份通用的 CSS 樣式文件
  // 提取出全部頁面公共的代碼
  commonsChunk: {
    name: 'common',// 提取出公共代碼 Chunk 的名稱
  },
});

module.exports = {
  // AutoWebPlugin 會爲尋找到的全部單頁應用,生成對應的入口配置,
  // autoWebPlugin.entry 方法能夠獲取到全部由 autoWebPlugin 生成的入口配置
  entry: autoWebPlugin.entry({
    // 這裏能夠加入你額外須要的 Chunk 入口
  }),
  plugins: [
    autoWebPlugin,
  ],
};
複製代碼

以上配置文件爲了重點展現出本文側重修改的部分,省略了部分和上一節一致的代碼,完整代碼能夠參照上一節或者下載本項目完整代碼。

AutoWebPlugin 會找出 pages 目錄下的2個文件夾 indexlogin,把這兩個文件夾當作兩個單頁應用。 而且分別爲每一個單頁應用生成一個 Chunk 配置和 WebPlugin 配置。 每一個單頁應用的 Chunk 名稱就等於文件夾的名稱,也就是說 autoWebPlugin.entry() 方法返回的內容實際上是:

{
  "index":["./pages/index/index.js","./common.css"],
  "login":["./pages/login/index.js","./common.css"]
}
複製代碼

但這些事情 AutoWebPlugin 都會自動爲你完成,你不用操心,明白大體原理便可。

template.html 模版文件以下:

<html>
<head>
  <meta charset="UTF-8">
  <!--在這注入該頁面所依賴但沒有手動導入的 CSS-->
  <!--STYLE-->
  <!--注入 google_analytics 中的 JS 代碼-->
  <script src="./google_analytics.js?_inline"></script>
  <!--異步加載 Disqus 評論-->
  <script src="https://dive-into-webpack.disqus.com/embed.js" async></script>
</head>
<body>
<div id="app"></div>
<!--在這注入該頁面所依賴但沒有手動導入的 JavaScript-->
<!--SCRIPT-->
<!--Disqus 評論容器-->
<div id="disqus_thread"></div>
</body>
</html>
複製代碼

注意到模版文件中出現了2個重要的新關鍵字 <!--STYLE--><!--SCRIPT-->,它們是什麼意思呢?

因爲這個模版文件被看成項目中全部單頁應用的模版,就不能再像上一節中直接寫 Chunk 的名稱去引入資源,由於須要被注入到當前頁面的 Chunk 名稱是不定的,每一個單頁應用都會有本身的名稱。 <!--STYLE--><!--SCRIPT--> 的做用在於保證該頁面所依賴的資源都會被注入到生成的 HTML 模版裏去。

web-webpack-plugin 能分析出每一個頁面依賴哪些資源,例如對於 login.html 來講,插件能夠肯定該頁面依賴如下資源:

  • 全部頁面都依賴的公共 CSS 代碼 common.css
  • 全部頁面都依賴的公共 JavaScrip 代碼 common.js
  • 只有這個頁面依賴的 CSS 代碼 login.css
  • 只有這個頁面依賴的 JavaScrip 代碼 login.css

因爲模版文件 template.html 裏沒有指出引入這些依賴資源的 HTML 語句,插件會自動將沒有手動導入但頁面依賴的資源按照不一樣類型注入到 <!--STYLE--><!--SCRIPT--> 所在的位置。

  • CSS 類型的文件注入到 <!--STYLE--> 所在的位置,若是 <!--STYLE--> 不存在就注入到 HTML HEAD 標籤的最後;
  • JavaScrip 類型的文件注入到 <!--SCRIPT--> 所在的位置,若是 <!--SCRIPT--> 不存在就注入到 HTML BODY 標籤的最後。

若是後續有新的頁面須要開發,只須要在 pages 目錄下新建一個目錄,目錄名稱取爲輸出 HTML 文件的名稱,目錄下放這個頁面相關的代碼便可,無需改動構建代碼。

因爲 AutoWebPlugin 是間接的經過上一節提到的 WebPlugin 實現的,WebPlugin 支持的功能 AutoWebPlugin 都支持。

AutoWebPlugin 插件還支持一些其它更高級的用法,詳情能夠訪問該項目主頁閱讀文檔。

本實例提供項目完整代碼

《深刻淺出Webpack》全書在線閱讀連接

閱讀原文

相關文章
相關標籤/搜索