Webpack實戰-爲單頁應用生成HTML

引入問題

在簡單的項目裏由於只輸出了一個 bundle.js 文件,因此手寫了一個 index.html 文件去引入這個 bundle.js,才能讓應用在瀏覽器中運行起來。javascript

在實際項目中遠比這複雜,一個頁面經常有不少資源要加載。接下來舉一個實戰中的例子,要求以下:css

  1. 項目採用 ES6 語言加 React 框架。
  2. 給頁面加入 Google Analytics,這部分代碼須要內嵌進 HEAD 標籤裏去。
  3. 給頁面加入 Disqus 用戶評論,這部分代碼須要異步加載以提高首屏加載速度。
  4. 壓縮和分離 JavaScript 和 CSS 代碼,提高加載速度。

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

<html>
<head>
  <meta charset="UTF-8">
  <!--注入 Chunk app 依賴的 CSS-->
  <style rel="stylesheet">h1{color:red}</style>
  <!--內嵌 google_analytics 中的 JavaScript 代碼-->
  <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>
<!--導入 app 依賴的 JS-->
<script src="app_746f32b2.js"></script>
<!--Disqus 評論容器-->
<div id="disqus_thread"></div>
</body>
</html>
複製代碼

HTML 應該是被壓縮過的,這裏爲了方便你們閱讀而格式化了 HTML,而且加入了註釋。java

構建出的目錄結構爲:node

dist
├── app_792b446e.js
└── index.html
複製代碼

能夠看到部分代碼被內嵌進了 HTML 的 HEAD 標籤中,部分文件的文件名稱被打上根據文件內容算出的 Hash 值,而且加載這些文件的 URL 地址也被正常的注入到了 HTML 中。 若是你還採用手寫 index.html 文件去完成以上要求,這就會使工做變得複雜、易錯,項目難以維護。 本節教你如何自動化的生成這個符合要求的 index.htmlwebpack

解決方案

推薦一個用於方便的解決以上問題的 Webpack 插件 web-webpack-plugin。 該插件已經被社區上許多人使用和驗證,解決了你們的痛點得到了不少好評,下面具體介紹如何用它來解決上面的問題。git

首先,修改 Webpack 配置爲以下:github

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'// app 的 JavaScript 執行入口文件
  },
  output: {
    filename: '[name]_[chunkhash:8].js',// 給輸出的文件名稱加上 Hash 值
    path: path.resolve(__dirname, './dist'),
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['babel-loader'],
        // 排除 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,以去除源碼中只有開發時才須要的部分
      'process.env': {
        NODE_ENV: JSON.stringify('production')
      }
    }),
    // 壓縮輸出的 JavaScript 代碼
    new UglifyJsPlugin({
      // 最緊湊的輸出
      beautify: false,
      // 刪除全部的註釋
      comments: false,
      compress: {
        // 在UglifyJs刪除沒有用到的代碼時不輸出警告
        warnings: false,
        // 刪除全部的 `console` 語句,能夠兼容ie瀏覽器
        drop_console: true,
        // 內嵌定義了可是隻用到一次的變量
        collapse_vars: true,
        // 提取出出現屢次可是沒有定義成變量去引用的靜態值
        reduce_vars: true,
      }
    }),
  ],
};
複製代碼

以上配置中,大多數都是按照前面已經講過的內容增長的配置,例如:web

  • 增長對 CSS 文件的支持,提取出 Chunk 中的 CSS 代碼到單獨的文件中,壓縮 CSS 文件;
  • 定義 NODE_ENV 環境變量爲 production,以去除源碼中只有開發時才須要的部分;
  • 給輸出的文件名稱加上 Hash 值;
  • 壓縮輸出的 JavaScript 代碼。

但最核心的部分在於 plugins 裏的:瀏覽器

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

其中 template: './template.html' 所指的模版文件 template.html 的內容是:

<html>
<head>
  <meta charset="UTF-8">
  <!--注入 Chunk app 中的 CSS-->
  <link rel="stylesheet" href="app?_inline">
  <!--注入 google_analytics 中的 JavaScript 代碼-->
  <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>
<!--導入 Chunk app 中的 JS-->
<script src="app"></script>
<!--Disqus 評論容器-->
<div id="disqus_thread"></div>
</body>
</html>
複製代碼

該文件描述了哪些資源須要被以何種方式加入到輸出的 HTML 文件中。

<link rel="stylesheet" href="app?_inline"> 爲例,按照正常引入 CSS 文件同樣的語法來引入 Webpack 生產的代碼。 href 屬性中的 app?_inline 能夠分爲兩部分,前面的 app 表示 CSS 代碼來自名叫 app 的 Chunk 中,後面的 _inline 表示這些代碼須要被內嵌到這個標籤所在的位置。

一樣的 <script src="./google_analytics.js?_inline"></script> 表示 JavaScript 代碼來自相對於當前模版文件 template.html 的本地文件 ./google_analytics.js, 並且文件中的 JavaScript 代碼也須要被內嵌到這個標籤所在的位置。

也就是說資源連接 URL 字符串裏問號前面的部分表示資源內容來自哪裏,後面的 querystring 表示這些資源注入的方式。

除了 _inline 表示內嵌外,還支持如下屬性:

  • _dist 只有在生產環境下才引入該資源
  • _dev 只有在開發環境下才引入該資源
  • _ie 只有IE瀏覽器才須要引入的資源,經過 [if IE]>resource<![endif] 註釋實現

這些屬性之間能夠搭配使用,互不衝突。例如 app?_inline&_dist 表示只在生產環境下才引入該資源,而且須要內嵌到 HTML 裏去。

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

本實例提供項目完整代碼

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

閱讀原文

相關文章
相關標籤/搜索