webpack4 中如何實現資源內聯?

本篇文章會系統的介紹下 Webpack4 裏面資源內聯(HTML/CSS/JS/Image/Font)的正確姿式javascript

首先,咱們一塊兒瞭解下什麼是資源內聯。css

什麼是資源內聯?

資源內聯(inline resource),就是將一個資源之內聯的方式嵌入進另外一個資源裏面,咱們經過幾個小例子來直觀感覺一下。html

HTML 內聯 CSS,這個其實就是咱們一般說的 內聯 CSS 或者 行內 CSS。咱們能夠寫幾行 reset CSS,而後經過 style 標籤的方式嵌入進了 HTML 裏面:前端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        body {
            font-size: 12px;
            font-family: Arial, Helvetica, sans-serif;
            background: #fff;
        }
        ul, ol, li {
            list-style-type: none;
        }
    </style>
</head>
<body>
    
</body>
</html>
複製代碼

CSS 內聯圖片,就是咱們一般將小圖片經過 base64 的方式內嵌進 CSS 裏面。咱們能夠將搜索小 icon 內聯進 CSS:java

// index.css
.search {
  background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABJ0lEQVQ4T6XSsUoEMRAG4H/ClZaLmbSW1pZ6+gAnFrK+gZXoK6jvIILgE6gIcnYWgmJno6AgYp1Z2EcIGQnsHbuaQ9abMkO+TGaGMGfQnPfxC3DOrajqPoB1AArgnohOvffPucc6ADMfAjgCUMYYH9MFY8wagEsAxyKScp2YAtbaERGNRST7LWZWVd2squq2LbSBMyK6E5GrXKnW2i1jzMh7v5sFmPkzhDCs69rngKIo3GAweBKRpVnAVwhh9Q/gRUQWs4Bz7jzGeFNV1ThXATOXAA5EJDV1Gr2aSETb3vvrLJAOmTmNKY2yVNUHVSVjzBDABYA3ADsi8j4TSIlmkfYAbABYUNUPACdE9NpAHaTXKjPz8k+kF9B8s4P0BibIpBf/AtpN/AYx54AR58WxmQAAAABJRU5ErkJggg==) no-repeat;
}
複製代碼

瞭解了資源內聯的基本概念後,可能你會問資源內聯有什麼意義?接下來咱們從幾個維度去看看爲何咱們須要資源內聯。node

資源內聯的意義

資源內聯的意義這裏我從三個方面去說明一下,分別是:工程維護、頁面加載性能、頁面加載體驗。webpack

工程維護

咱們看看資源內聯對於工程維護的意義,這個是一個基本的 HTML 結構。在現在流行的 Hybrid 混合開發架構裏,會有一個個的 H5 頁面,對應前端工程裏的多頁面應用(MPA)。git

HTML Structure

咱們去打包多頁面應用的時候會藉助 html-webpack-plugin,每一個頁面會有一個 HTML 模板與之對應。每一個 HTML 模板都會包含不少類似的內容,好比 meta 信息,或 SSR 時須要用到的一些佔位符等等。試想一下,若是將下面這段 meta 代碼分別複製一份放到每一個 HTML 模板裏面將會對代碼維護形成的影響。github

<meta charset="UTF-8">
<meta name="viewport" content="viewport-fit=cover,width=device-width,initial-scale=1,user-scalable=no">
<meta name="format-detection" content="telephone=no">
<meta name="keywords" content="now,now直播,直播,騰訊直播,QQ直播,美女直播,附近直播,才藝直播,小視頻,我的直播,美女視頻,在線直播,手機直播">
<meta name="name" itemprop="name" content="NOW直播—騰訊旗下全民視頻社交直播平臺"><meta name="description" itemprop="description" content="NOW直播,騰訊旗下全民高清視頻直播平臺,聚集中外大咖,最in網紅,草根偶像,明星藝人,校花,小鮮肉,逗逼段子手,各種美食、音樂、旅遊、時尚、健身達人與你24小時不間斷互動直播,各類奇葩刺激的直播玩法,讓你躍躍欲試,你會發現,原來人人均可以當主播賺錢!">
<meta name="image" itemprop="image" content="https://pub.idqqimg.com/pc/misc/files/20170831/60b60446e34b40b98fa26afcc62a5f74.jpg"><meta name="baidu-site-verification" content="G4ovcyX25V">
<meta name="apple-mobile-web-app-capable" content="no">
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
<link rel="dns-prefetch" href="//11.url.cn/">
<link rel="dns-prefetch" href="//open.mobile.qq.com/">
複製代碼

這個時候推薦的作法是維護一份 meta.html,將上面的這個代碼內容放置進去。每一個 HTML 模板將 meta.html 片斷內聯進去。web

工程維護的另外一個比較常見的場景就是圖片、字體等文件的內聯了,好比不少同窗一般會去網上找一個在線的 base64 編碼工具(如:www.base64code.com/ )去將各類圖片(png、jpg、gif) 或者 字體 (ttf、otf) 編碼,而後將編碼後的那一長串字符串放置到代碼裏面去。好比前面的這個搜索 icon 圖標,這段長串的字符串放置在源代碼裏面根本毫無語義,並且對維護者而言也是場災難。

// index.css
.search {
  background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABJ0lEQVQ4T6XSsUoEMRAG4H/ClZaLmbSW1pZ6+gAnFrK+gZXoK6jvIILgE6gIcnYWgmJno6AgYp1Z2EcIGQnsHbuaQ9abMkO+TGaGMGfQnPfxC3DOrajqPoB1AArgnohOvffPucc6ADMfAjgCUMYYH9MFY8wagEsAxyKScp2YAtbaERGNRST7LWZWVd2squq2LbSBMyK6E5GrXKnW2i1jzMh7v5sFmPkzhDCs69rngKIo3GAweBKRpVnAVwhh9Q/gRUQWs4Bz7jzGeFNV1ThXATOXAA5EJDV1Gr2aSETb3vvrLJAOmTmNKY2yVNUHVSVjzBDABYA3ADsi8j4TSIlmkfYAbABYUNUPACdE9NpAHaTXKjPz8k+kF9B8s4P0BibIpBf/AtpN/AYx54AR58WxmQAAAABJRU5ErkJggg==) no-repeat;
}
複製代碼

咱們能夠經過更優雅的資源內聯語法來避免這個問題,文章後面會介紹到。

頁面加載性能

資源內聯的第2點意義在於能夠減小 HTTP 的請求數,固然若是你的網站有使用 HTTP2 這點的意義可能不會那麼大。將各類小圖片、小字體(好比:小於5k) 在生產環境 base64 到代碼裏面能夠極大的減小頁面的請求數量,從而提高頁面的加載時間。

頁面加載體驗

資源內聯另一個重要的意義在於提高頁面加載體驗。咱們都知道瀏覽器解析 HTML源碼是從上到下解析,所以咱們會把 CSS 放到頭部,JS 放置到底部。以 SSR 場景爲例,若是不將打包出來的 CSS 內聯進 HTML 裏面,HTML 出來的時候頁面的結構已經有了,可是還須要發送一次請求去請求 css,這個時候就會出現頁面閃爍,網絡狀況差的時候更加明顯。

資源內聯的類型

資源內聯的類型主要包含:

  • HTML 內聯
  • CSS 內聯
  • JS 內聯
  • 圖片、字體內聯

若是你曾經使用過 FIS 或者看過 FIS 的文檔,你會發現 FIS 對於資源內聯的支持很是棒,詳細的文檔:嵌入資源

FIS HTML 內聯 HTML 片斷:

<link rel="import" href="demo.html?__inline">
複製代碼

FIS HTML 內聯 JS 腳本:

<script type="text/javascript" src="demo.js?__inline"></script>
複製代碼

接下來,咱們分別看看每種內聯在 webpack4 中的實現。

HTML 內聯

基礎版

HTML 內聯 HTML 片斷、CSS 或者 JS(babel 編譯後的,好比內聯某個 npm 組件) 的思路很簡單,就是直接讀取某個文件的內容,而後插入到對應的位置。咱們能夠藉助 raw-loader@0.5.1版本,最新的 raw-loader 會有問題(由於它導出模塊時是使用 export default),不過你徹底能夠本身實現這樣的一個 raw-loader。

0.5.1 版本的 raw-loader 的代碼:

module.exports = function(content) {
	this.cacheable && this.cacheable();
	this.value = content;
	return "module.exports = " + JSON.stringify(content);
}
複製代碼

藉助 raw-loader 實現的內聯語法以下:

// 內聯 HTML 片斷
${ require('raw-loader!./meta.html')}

// 內聯 JS
<script>${ require('raw-loader!babel-loader!../../node_modules/lib-flexible/flexible.js')}</script>
複製代碼

加強版

咱們能夠實現一個對開發者更友好的語法糖,好比實現一個 loader 去解析 HTML 裏面的?__inline 語法。這裏我實現了一個 html-inline-loader,它的代碼以下:

const fs = require('fs');
const path = require('path');

const getContent = (matched, reg, resourcePath) => {
    const result = matched.match(reg);
    const relativePath = result && result[1];
    const absolutePath = path.join(path.dirname(resourcePath), relativePath);
    return fs.readFileSync(absolutePath, 'utf-8');
};

module.exports = function(content) {
  const htmlReg = /<link.*?href=".*?\__inline">/gmi;
  const jsReg = /<script.*?src=".*?\?__inline".*?>.*?<\/script>/gmi;

  content = content.replace(jsReg, (matched) => {
    const jsContent = getContent(matched, /src="(.*)\?__inline/, this.resourcePath); return `<script type="text/javascript">${jsContent}</script>`; }).replace(htmlReg, (matched) => { const htmlContent = getContent(matched, /href="(.*)\?__inline/, this.resourcePath);
    return htmlContent;
  });

  return `module.exports = ${JSON.stringify(content)}`;
}
複製代碼

而後,你能夠這樣使用:

<!DOCTYPE html>
<html lang="en">
<head>
    <link href="./meta.html?__inline">
    <title>Document</title>
    <script type="text/javascript" src="../../node_modules/lib-flexible/flexible.js?__inline"></script>
</head>
<body>
    <div id="root"><!--HTML_PLACEHOLDER--></div>
    <!--INITIAL_DATA_PLACEHOLDER-->
</body>
</html>
複製代碼

查看的效果:

CSS 內聯

一般狀況下,爲了更好的加載體驗,咱們會將打包好的 CSS 內聯到 HTML 頭部,這樣 HTML 加載完成 CSS 就能夠直接渲染出來,避免頁面閃動的狀況。那麼 CSS 內聯如何實現呢?

CSS 內聯的核心思路是:將頁面打包過程的產生的全部 CSS 提取成一個獨立的文件,而後將這個 CSS 文件內聯進 HTML head 裏面。這裏須要藉助 mini-css-extract-plugin 和 html-inline-css-webpack-plugin 來實現 CSS 的內聯功能。

// webpack.config.js

const path = require('path');

module.exports = {
    entry: {
        index: './src/index.js',
        search: './src/search.js'
    },
    output: {
        path: path.join(__dirname, 'dist'),
        filename: '[name]_[chunkhash:8].js'
    },
    mode: 'production',
    plugins: [
        new MiniCssExtractPlugin({
            filename: '[name]_[contenthash:8].css'
        }),
        new HtmlWebpackPlugin(),
        new HTMLInlineCSSWebpackPlugin()
    ]
};

複製代碼

注:html-inline-css-webpack-plugin 須要放在 html-webpack-plugin 後面。

圖片、字體內聯

基礎版

圖片和字體的內聯能夠藉助 url-loader,好比你能夠經過修改 webpack 配置讓小於 10k 的圖片或者字體文件在構建階段自動 base64。

// webpack.config.js

const path = require('path');

module.exports = {
    entry: {
        index: './src/index.js',
        search: './src/search.js'
    },
    output: {
        path: path.join(__dirname, 'dist'),
        filename: '[name]_[chunkhash:8].js'
    },
    mode: 'production',
    module: {
        rules: [
            {
                test: /.(png|jpg|gif|jpeg)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            name: '[name]_[hash:8].[ext]',
                            limit: 10240
                        }
                    }
                ]
            },
            {
                test: /.(woff|woff2|eot|ttf|otf)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            name: '[name]_[hash:8][ext]',
                            limit: 10240
                        }
                    }
                ]
            }
        ]
    }
};
複製代碼

加強版

不過 url-loader 作資源內聯最大的缺陷就是 不能個性化的去設置某張圖片自動編碼,針對這個問題,咱們能夠借鑑下 FIS 的語法糖,實現 ?__inline 的語法糖,引用某個圖片的時候看到這個後綴則自動的將這張圖片進行 base64 編碼。這個功能實現起來也很簡單,能夠參考我實現的 inline-file-loader,核心代碼:

export default function loader(content) {
  const options = loaderUtils.getOptions(this) || {};

  validateOptions(schema, options, {
    name: 'File Loader',
    baseDataPath: 'options',
  });

  const hasInlineFlag = /\?__inline$/.test(this.resource);

  if (hasInlineFlag) {
    const file = this.resourcePath;
    // Get MIME type
    const mimetype = options.mimetype || mime.getType(file);

    if (typeof content === 'string') {
      content = Buffer.from(content);
    }

    return `module.exports = ${JSON.stringify(
      `data:${mimetype || ''};base64,${content.toString('base64')}`
    )}`;
  }
複製代碼

有了圖片的內聯功能,咱們能夠將前面的搜索 icon 圖標內聯的寫法修改爲:

// index.css
.search {
  background: url(./search-icon.png?__inline) no-repeat;
}
複製代碼

最後

下面是本篇文章的代碼演示資料,若是有需求,能夠自行獲取。


個人我的博客:github.com/cpselvis/bl…

相關文章
相關標籤/搜索