How to implement resource inlining in webpack4?

Posted by nalkari on Sun, 06 Oct 2019 12:44:13 +0200

This article will systematically introduce the correct posture of resource inlining (HTML/CSS/JS/Image/Font) in Webpack4.

First, let's work together to understand what is resource inline.

What is resource inlining?

inline resource is to insert one resource inline into another resource. We can intuitively feel it through a few small examples.

HTML inline CSS, which is actually what we usually call inline CSS or in-line CSS. We can write a few lines of reset CSS and then embed it into HTML by style tags:

<!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 inline pictures, that is, we usually embedded small pictures into CSS through base64. We can inline search icon into CSS:

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

After understanding the basic concepts of resource inlining, you may ask what is the meaning of resource inlining. Next, let's look at several dimensions to see why we need resource inline.

Significance of resource inlining

The meaning of resource inlining is explained in three aspects: engineering maintenance, page loading performance and page loading experience.

Engineering maintenance

Let's look at the significance of resource inlining for engineering maintenance, which is a basic HTML structure. In today's popular Hybrid Development Architecture, there will be one H5 page after another, corresponding to the front-end project in the multi-page application (MPA).

When we package multi-page applications, we use html-webpack-plugin, and each page will have an HTML template corresponding to it. Each HTML template contains a lot of similar content, such as meta information, or some placeholders needed for SSR, and so on. Imagine the impact of copying the following meta-code into each HTML template on code maintenance.

<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 Live broadcast,Live broadcast,Live broadcast,QQ Live broadcast,Live beauty,Live nearby,Talent live broadcast,Small video,Live broadcast,Beauty Video,Live broadcast online,Live broadcast of mobile phone">
<meta name="name" itemprop="name" content="NOW Live broadcast - Tencent's universal video social broadcasting platform"><meta name="description" itemprop="description" content="NOW Live broadcast, Tencent's national HD video live platform, the largest collection of Chinese and foreign coffee, the most in Net red, grass roots idol, star artists, school flowers, small meat, forcing the Duan hand, all kinds of food, music, tourism, fashion, fitness people and you 24 hours continuous live broadcast, all kinds of wonderful exciting live play, let you eager to try, you will find that everyone can make money as anchor.">
<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/">

The recommended practice at this time is to maintain a meta.html and put the above code into it. Each HTML template inlines meta.html fragments.

Another common scenario of engineering maintenance is the inline of documents such as pictures and fonts. For example, many students usually go online to find an online base64 encoding tool (for example: https://www.base64code.com/ To code all kinds of pictures (png, jpg, gif) or font (ttf, otf), then place the encoded long string into the code. For example, the search icon icon in front of us, this long string string is placed in the source code without any semantics at all, and it is also a disaster for the maintainer.

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

We can avoid this problem by using more elegant resource inlining grammar, which will be introduced later in this article.

Page loading performance

The second point of resource inlining is to reduce the number of HTTP requests, which may not be so significant if your website uses HTTP2. Putting all kinds of small pictures and fonts (e.g. less than 5k) into the code in the production environment of base64 can greatly reduce the number of page requests, thereby increasing the page load time.

Page loading experience

Another important aspect of resource inlining is to enhance the page loading experience. We all know that browsers parse HTML source code from top to bottom, so we put CSS in the head and JS in the bottom. Take SSR scenario as an example. If you don't inline the packaged CSS into HTML, the structure of the page already exists when HTML comes out, but you still need to send a request to request css, then there will be page flicker, especially when the network is in bad condition.

Types of resource inlining

The types of resource inlining mainly include:

  • HTML inline
  • CSS inline
  • JS inline
  • Inline of pictures and fonts

If you have ever used FIS or seen FIS documents, you will find that FIS support for resource inlining is excellent. Detailed documentation: Embedded resources

FIS HTML Inline HTML Fragment:

 <link rel="import" href="demo.html?__inline">

FIS HTML inline JS script:

  <script type="text/javascript" src="demo.js?__inline"></script>

Next, let's look at the implementation of each inline in webpack4.

HTML inline

Basic Edition

The idea of HTML inline HTML fragments, CSS or JS(babel compiled, such as inlining an npm component) is simply to read the contents of a file directly and insert them into the corresponding location. With raw-loader@0.5.1, the latest raw-loader will have problems (because it uses export default when exporting modules), but you can implement such a raw-loader on your own.

0.5.1 version of raw-loader code:

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

The inline syntax implemented with raw-loader is as follows:

// Inline HTML fragments
${ require('raw-loader!./meta.html')}

// Inline JS
<script>${ require('raw-loader!babel-loader!../../node_modules/lib-flexible/flexible.js')}</script>

Enhanced Edition

We can implement a more developer-friendly grammar sugar, such as implementing a loader to parse the?_inline grammar in HTML. Here I implemented one html-inline-loader Its code is as follows:

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)}`;
}

Then, you can use this:

<!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>

View the effect:

CSS inline

Usually, in order to have a better loading experience, we will inline the packaged CSS into the HTML header, so that the HTML loaded CSS can be directly rendered to avoid page flicker. So how to implement CSS inlining?

The core idea of CSS inlining is to extract all the CSS generated during the page packaging process into a separate file, and then inline the CSS file into the HTML head. Here we need to use mini-css-extract-plugin and html-inline-css-webpack-plugin to realize the inline function of 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()
    ]
};

Note: html-inline-css-webpack-plugin needs to be placed behind html-webpack-plugin.

Inline of pictures and fonts

Basic Edition

Inline of pictures and fonts can be used url-loader For example, you can make the image or font file less than 10k automatically base64 during the construction phase by modifying the webpack configuration.

// 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
                        }
                    }
                ]
            }
        ]
    }
};

Enhanced Edition

However, the biggest drawback of url-loader in resource inlining is that it can not set up automatic encoding of a picture automatically. To solve this problem, we can learn from the syntax sugar of FIS to realize the grammatical sugar of __inline. When we use a picture, we can see the suffix automatically and make base64 encoding of this picture. This function is also very simple to implement, you can refer to my implementation. inline-file-loader Core code:

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')}`
    )}`;
  }

With the inlining function of images, we can modify the inlining method of the search icon to:

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

Last

The following is the code demo of this article. If you need it, you can get it by yourself.

My personal blog: https://github.com/cpselvis/b...

If you want to learn more dry goods, you can scan the code to pay attention to my public number: push frequency every Monday.

Topics: Javascript Webpack Mobile encoding