webpack多頁面實踐

JayJunG發表於2019-01-10

引言

對於一般的專案,特別是移動端專案,我們所熟知的都是webpack+vue,webpack+react等打包成一個單頁應用,包括我在github上所能找到的很多都是將我們所有的模組打包成一個大大的js包(甚至css也打包進去)然後在html中引入。

眾所周知,單頁應用依賴模組很大時會有很大的效能問題,當我們的web應用越來越大時,我們不可能會將所有的功能都放在同一個網頁上,這時一般會做的是按功能劃分模組,分成多個單頁應用,這樣做會有利於後期功能放入擴充套件調整。

要構建多頁應用,我們應該要考慮以下的問題:

  1. 單頁應用應該會有公共的程式碼部分,我們要將這些公用的部分抽離出來,以免重複引用載入。
  2. 隨著業務的發展,後面可能會不斷加入新的單頁應用,但是在加入新應用時都不
    能改動構建相關的程式碼。 
  3.  

先看看一般單頁應用的配置:

const path = require('path');
const UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const DefinePlugin = require('webpack/lib/DefinePlugin');
const { WebPlugin } = require('web-webpack-plugin');

module.exports = {
  entry: {
    app: './main.js'// Chunk app 的 JS 執行入口檔案
  },
  output: {
    filename: '[name]_[chunkhash:8].js',// 給輸出的檔名稱加上 hash 值
    path: path.resolve(__dirname, './dist'),
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['babel-loader'],
        // 排除 node_modules 目錄下的檔案,node_modules 目錄下的檔案都是採用的 ES5 語法,沒必要再通過 Babel 去轉換
        exclude: path.resolve(__dirname, 'node_modules'),
      },
      {
        test: /\.css/,// 增加對 CSS 檔案的支援
        // 提取出 Chunk 中的 CSS 程式碼到單獨的檔案中
        use: ExtractTextPlugin.extract({
          use: ['css-loader?minimize'] // 壓縮 CSS 程式碼
        }),
      },
    ]
  },
  plugins: [
    // 使用WebPlugin,一個 WebPlugin 對應一個 HTML 檔案
    new WebPlugin({
      template: './template.html', // HTML 模版檔案所在的檔案路徑
      filename: 'index.html' // 輸出的 HTML 的檔名稱
    }),
    new ExtractTextPlugin({
      filename: `[name]_[contenthash:8].css`,// 給輸出的 CSS 檔名稱加上 hash 值
    }),
    new DefinePlugin({
      // 定義 NODE_ENV 環境變數為 production 去除 react 程式碼中的開發時才需要的部分
      'process.env': {
        NODE_ENV: JSON.stringify('production')
      }
    }),
    // 壓縮輸出的 JS 程式碼
    new UglifyJsPlugin({
      // 最緊湊的輸出
      beautify: false,
      // 刪除所有的註釋
      comments: false,
     //忽略無關緊要的...
    }),
  ],
};
複製程式碼

這裡最主要的是WebPlugin這個外掛,能夠讓我們實現自動化輸出html檔案並引入打包後的模組,雖然這樣已經對我們打包應用提供了很大的便利,但對於多個頁面的管理依然是個麻煩的問題。如果我們要增加一個頁面我們需要new一個webPlugin例項並在entry入口增加配置項。

解決方案

這裡用AutoWebPlugin來構建我們的多頁應用,專案原始碼的目錄結構如下:

webpack多頁面實踐

 從目錄結構中可以看出以下幾點要求:

  •  所有單頁應用的程式碼都需要放到一個 目錄下,例如都放在 pages 目錄下;
  • 一個單頁應用對應一個單獨的資料夾,例如最後生成的 index.html 相關的程式碼都在 index 目錄下, login.html 同理:
  • 每個單頁應用的目錄下都有一個 index.js 檔案作為入口執行檔案。AutoWebP lugin 強制性地規定了專案部分的目錄結構,從實戰經驗來看,這是一種優雅的目錄規範,合理地拆分了程式碼,又能讓新人快速看懂專案的結構,方便日後維護。 

webpack配置檔案如下:

const path = require('path');
const UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const DefinePlugin = require('webpack/lib/DefinePlugin');
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 方法可以獲取到生成入口配置
  entry: autoWebPlugin.entry({
    // 這裡可以加入你額外需要的 Chunk 入口
  }),
  output: {
    filename: '[name]_[chunkhash:8].js',// 給輸出的檔名稱加上 hash 值
    path: path.resolve(__dirname, './dist'),
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['babel-loader'],
        // 排除 node_modules 目錄下的檔案,node_modules 目錄下的檔案都是採用的 ES5 語法,沒必要再通過 Babel 去轉換
        exclude: path.resolve(__dirname, 'node_modules'),
      },
      {
        test: /\.css/,// 增加對 CSS 檔案的支援
        // 提取出 Chunk 中的 CSS 程式碼到單獨的檔案中
        use: ExtractTextPlugin.extract({
          use: ['css-loader?minimize'] // 壓縮 CSS 程式碼
        }),
      },
    ]
  },
  plugins: [
    autoWebPlugin,
    new ExtractTextPlugin({
      filename: `[name]_[contenthash:8].css`,// 給輸出的 CSS 檔名稱加上 hash 值
    }),
    new DefinePlugin({
      // 定義 NODE_ENV 環境變數為 production 去除 react 程式碼中的開發時才需要的部分
      'process.env': {
        NODE_ENV: JSON.stringify('production')
      }
    }),
    // 壓縮輸出的 JS 程式碼
    new UglifyJsPlugin({
      // 最緊湊的輸出
      beautify: false,
      // 刪除所有的註釋
      comments: false,
     //忽略無關緊要的...

    }),
  ],
};

複製程式碼

AutoWebPlugin 會找出 pages 目錄下的兩個資料夾 index 和 login ,將這兩個資料夾看作兩個單頁應用,並且分別為每個單頁應用生成一個 Chunk 配置和 WebPlugin 配置 。每個單頁應用的 Chunk 名稱等同於資料夾的名稱,也就是說 autoWebPlugin.entry()方法返回的內容其實是:  

{  "index:["./pages/index/index.js", "./common.css"], 
     "login": ["./pages/login/index.js", "./common.css"] 
}複製程式碼
template.html 模板檔案如下:

<html>
<head>
  <meta charset="UTF-8">
  <!--該頁面所依賴的其它剩下的 CSS 注入的地方-->
  <!--STYLE-->
  <!--已忽略無關程式碼-->
</head>
<body>
<div id="app"></div>
<!--該頁面所依賴的其它剩下的 JavaScript 注入的地方-->
<!--SCRIPT-->
 <!--已忽略無關程式碼-->
</body>
</html>


複製程式碼

注意到在模板檔案中出現了兩個重要的新關鍵字<!--STYLE--><!--SCRIPT-->,

它們是什麼意思呢?  

由於該模板檔案被當作專案中所有單頁應用的模板,因為需要被注入當前頁面的 Chunk 名稱是不固定的,每個單頁應用都會有自己的名稱。<!--STYLE--><!--SCRIPT-->的作用在於保證該頁面所依賴的資源都會被注入生成的 HTML 模板裡。 

 web-webpack-plugin 能分析出每個頁面依賴哪些資源,例如對於 login.html 來說,

該外掛可以確定該頁面依賴以下資源:
  • 所有頁面都依賴的公共 css 程式碼 common. css;
  • 所有頁面都依賴的公共 JavaScript 程式碼 common. js;
  • 只有這個頁面依賴的 css 程式碼 login.css;
  • 只有這個頁面依賴的 JavaScript 程式碼 login .css 。
由於在模板檔案 template.html 裡沒有指出引入這些依賴資源的 HTML 語句,所以外掛會自動將沒有手動匯入但頁面依賴的資源按照不同的型別注入<!--STYLE--><!--SCRIPT-->所在的位置。
  • 將 css 型別的檔案注入<!--STYLE-->所在的位置,如果<!--STYLE-->不存在,就注入 HTML HEAD 標籤的最後 。
  • 將 JavaScript 型別的檔案注入<!--SCRIPT-->所在的位置,如果<<!--SCRIPT-->不存在,就注入 HTML BODY 標籤的最後 。
如果後續有新的頁面需要開發 ,就只需在 pages 目錄下新建一個目錄,該目錄名為所
輸出 HTML 檔案的名稱,在目錄下放這個頁面相關的程式碼即可,無須改動構建程式碼。  

npm run build後打包的程式碼如下:

webpack多頁面實踐

login.html:

<html>
<head>
  <meta charset="UTF-8">
  <!--該頁面所依賴的其它剩下的 CSS 注入的地方-->
  <link rel="stylesheet" href="common_7cc98ad0.css">
<link rel="stylesheet" href="login_e31e214b.css">
  <!--已忽略無關程式碼-->

</head>
<body>
<div id="app"></div>
<!--該頁面所依賴的其它剩下的 JavaScript 注入的地方-->
<script src="common_bdac69cb.js"></script>
<script src="login_0a3feca9.js"></script>
 <!--已忽略無關程式碼-->

</body>
</html>
複製程式碼

參考閱讀:


相關文章