1. 程式人生 > >webpack生成html檔案,用於後端渲染的研究

webpack生成html檔案,用於後端渲染的研究

不適用後端渲染的原因

webpack的打包方式是把所有的資源都打包成bundle.js,並用一個沒有內容的html引入生成的bundle.js,不太熟悉的同學可以參看慕課網的視訊教程。但是如果公司的建站方式是後端渲染的話(如jsp),那就不能使用webpack了,因為webpack會把html也打包在bundle.js中。本文就是介紹如何用webpack生成我們需要的html,以及其中的問題和優化。

主要思路

webpack的html-webpack-plugin外掛,可以設定一個template,我們可以在這個template上做文章,配合上相應的loader,就可以生成我們需要的html。

專案結構

下面是我的webpack目錄結構

這裡寫圖片描述

HTML部分

如何生成我們需要的html檔案呢?

html-webpack-plugin的使用

我們採用曲線救國的方式生成我們需要的html,用於後端渲染。這就要使用到html-webpack-plugin的template屬性。

module.exports = {
  entry: 'index.js',
  output: {
    path: __dirname + '/dist',
    filename: 'index_bundle.js'
  },
  plugins: [
    new HtmlWebpackPlugin(),    // 生成一個空的html,用於引入webpack打包好的js檔案
new HtmlWebpackPlugin({ // 再生成一個html filename: 'test.html', template: 'src/assets/test.html' //注意這裡可以使用一個template作為要生成的html的模板 }) ] }

上面程式碼中的template這裡是一個html,官方的介紹是,你可以使用jade或ejs等模板引擎,也就是說webpack不關心你使用什麼作為模板,只要輸出一串字串就行,於是我大膽的使用一個js檔案作為模板,輸出一段字串,結果完全可行。

這樣就產生了很多種方案,比如用js檔案作為模板,在這個js檔案中require其他的html檔案,進行字串拼接,最後輸出,但是這樣的話,每個需要輸出的頁面都需要配置也個這樣的js檔案,懶惰的我不允許這種事情的發生…..

再比如,直接使用一個html作為模板,配合使用html-loader,這個loader有一種引入其他檔案的語法支援:

<div> ${require('./components/gallery.html')} </div>

這樣我們就能引入其他的檔案模組…..

但是有的時候我們需要一些模板語法,例如我們需要迴圈生成li標籤,最後我採用的方案是使用 ejs 作為模板檔案,採用 underscore-template-loader 作為我的loader,而沒有采用 ejs-loader ,因為 ejs-loader 不會處理檔案html結構中的圖片路徑問題,而且 ejs-loader 也沒有require其他ejs檔案的語法支援,雖然 ejs-loader 官方推薦使用 ejs-compiled-loader 用它來引入其他的ejs模組,但是這樣顯得很麻煩,而且圖片路徑問題還是沒有解決。然後我就找到了神器 underscore-template-loader ,首先圖片路徑問題loader會幫你解決,其次該loader支援兩種require其他檔案的語法:

<div class="top-section">
    @require('header.ejs', {"title": "First Section"})
</div>

這裡引入一個ejs檔案,並向其中傳入對應的值。(如果你看不懂上面的程式碼,可以先熟悉一下ejs語法)這樣就能很輕鬆的引入一個component檔案,並傳入值,此外,如果你只想引入一段不帶語法的html結構(即純字串),也可以採用下面的寫法

<div class="wiki">
    <h3>Introduction</h3>
    @include('intro.htm')
    <h3>Authors</h3>
    @include('authors.htm')
</div>

include只會將檔案轉換成字串,並引入,所以確保你要引入的檔案沒有被loader處理過,不然很可能引入的是一個函式,而不是一串html結構。我在layout目錄下設定不同的資料夾,一個資料夾代表一個頁面,其中的ejs檔案作為html-webpack-plugin的template,用這個ejs檔案去require其他的component,如下圖:

這裡寫圖片描述

到這裡我,我們基本都已經解決了html結構的部分,那css應該寫在哪裡呢?另外我不想把css也打包在bundle.js中,我想要生成單獨的css檔案,怎麼辦?

生成CSS檔案

extract-text-webpack-plugin的使用

下面我們介紹另外一個webpack的外掛:extract-text-wepack-plugin,這個外掛用於提取出css檔案。
我把一個頁面的css放在layout下(這裡我用的是sass),用這個css去require其他的component的css,如下圖:

這裡寫圖片描述

這個總的css是有了,可是我們把它放在哪呢?任何資源只有被入口的js檔案require,才能被webpack處理,所以我們當然是用layout下的入口js檔案去require這個css,但是這樣css也就會被打包到bundle.js中,於是我們可以在這裡使用extract-text-wepack-plugin 怎麼用呢?主要是兩步:

首先在rule中,對所有的scss檔案使用extract-text-wepack-plugin

rules: [{
        test: /\.scss$/,
        use: ExtractTextPlugin.extract({ 
                fallback: "style-loader", 
                use: ["css-loader?importLoaders=2","postcss-loader","sass-loader"]
             })
}]

其次在plugin中使用extract-text-wepack-plugin

plugin: [new ExtractTextPlugin('[name]/[name].css')]

特別注意,我們還需要在webpack.config.js檔案的頭部引入extract-text-wepack-plugin 這個模組,不然ExtractTextPlugin 就會沒有定義,從而報錯:

const ExtractTextPlugin = require("extract-text-webpack-plugin");

打包一下,在dist檔案下,就能看到我們需要的css檔案了。並且生成好的html也自動引入了這個css檔案。

如何熱更新?

當我們開啟webpack的webpack-dev-server後,發現改動html和css都不能產生熱更新,只有改動js才能熱更新,我們可以在github官方頁面上找到答案,如下圖:

這裡寫圖片描述

也就是說 extract-text-webpack-plugin 不支援熱更新,於是我們可以這樣來改進它,使用兩種環境,一種是開發環境,一種是生產環境,在開發環境中,我們用入口的js去 require layout檔案下的ejs模板,而不再使用html-webpack-plugin生成。也就是說,在開發的時候,所有的資源都是打包在bundle.js檔案中的,只有在生產的時候,才像上面我們說的那樣生成html和css,大概思路就是這樣,那我們可以寫兩份不同的webpack的config檔案,一個是開發另一個用作生產。

但是我只用了一個webpack.config.js,我們可以使用node提供給我們的一個API,來設定一個全域性的值,使用方法如下:

//package.json

"scripts": {
    "build": "set NODE_ENV=production&& webpack -p --color"
}

使用set NODE_ENV=production 就可以設定這個全域性的值了,這裡設定的是production,注意: production&& 之間不能有空格,不然這個全域性的值就設定成'production ' ,production後面多了一個空格,怎麼獲取這個值呢?我們可以在程式的任意位置,通過process.env.NODE_ENV 來拿到這個值,做一個 if 判斷就可以知道是開發環境還是生產環境了。

以css為例,配置如下:

rules: [{
   test: /\.scss$/,
   use: process.env.NODE_ENV == 'production'
        ? ExtractTextPlugin.extract({ fallback: "style-loader", use: [
              "css-loader?importLoaders=2",
              "postcss-loader",
              "sass-loader"
            ] 
          })
        : ['style-loader','css-loader?importLoaders=2',"postcss-loader",'sass-loader']
}]

這樣就可以用一份配置實現不同的需求,當然,我們還可以使用webpack提供的一個外掛:DefinePlugin 把這個值暴露給整個webpack

plugin: [new webpack.DefinePlugin({
            'ENV': JSON.stringify(process.env.NODE_ENV)//獲取到NODE_ENV的值,並暴露為全域性變數
        })
]

這樣我們在任意的地方都可以直接使用ENV 這個值了。

到這裡就結束了,拋磚引玉說了一下大致的思路,沒有展開談具體細節,我把這個腳手架開源在github上,(https://github.com/cwj0130/webpack-cli),大家可以clone參看具體細節,我也在完善部分功能,例如單元測試等,如果此教程對您有幫助,麻煩在github上給個star,謝謝。

此文為原創,可以任意轉載,但請標明出處。