Prefetch and Preload with Handling in Webpack
Tags: html, webpack, performance
Published: 2020-06-07 11:34:38

`prefetch` and `preload` usage is good way to optimize website performance and user experience, this article introduce method to prefetch and preload, and fullfil with webpack
<!-- more -->

## Link Type

`rel` property of [`<link>`][mdn-link] tag is used to defined link type, and combined with `href` can prefetch and preload corresponding resource. `prefetch` is one of the link types
```
<link rel="prefetch" herf="URL">
```
`preload` is the other type, also use href to defined resource address, but besides handling pre fetch, it will resolve the resource, so the property `as` need to be added, to indicate type of resource 
```
<link rel="preload" href="URL" as="MIME_TYPE">
```

## Prefetch Resource

`prefetch` indicates that in the following navigation (ie: next webpage), user may need to use corresponding resource, and give a hint to browser that corresponding resource need to be gotten when idle

First create a new diretory `prefetch-preload-demo` (all the code in this article will create here), install the associated dependencies, and create new directory `static`
> mkdir prefetch-preload-demo  
cd prefetch-preload-demo  
npm init -y  
npm i -D http-server  
mkdir static

In static directory, create `prefetch.html`， `main.js` and `script.js`

`prefetch.html` defined a link with `prefetch` as `rel` 
```
<html>
<head>
<title>Prefetch</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="prefetch" href="script.js">
</head>
<body>
<script src="main.js"></script>
</body>
</html>
```

`main.js` create a button, and bind click event
```
let button = document.createElement('button');
button.innerHTML = 'Add Script';
button.addEventListener('click', e => {
  let script = document.createElement("script");
  script.src = "script.js";
  document.head.appendChild(script);
});
document.body.appendChild(button);
```

`script.js` just simply print
```
console.log('script run');
```

Run the sever (or add `server` script in `package.json`)
>npx http-server

Access http://localhost:8080 and navigate to `static`, click prefetch.html, or directly visit online [webpage][demo-prefetch], in the initial status, check the network tab of devtool, as the following image (don't check `Disable Cache`, click the gear on the right, check `Use large request rows`)

![](https://chanvinxiao.com/cn/blog/prefetch-and-preload-with-webpack/prefetch-init.png)

- `script.js` is fetched, the two number in the size column, 275 B shows the download size, 0 B shows the resolved size (which means that it's not been resolved yet)
- Console tab is empty, which means script has not run yet

Click `Add Script` on the page, `<script>` tag with address `script.js` will be added on the page, and the following content will been added on the network tab 

![](https://chanvinxiao.com/cn/blog/prefetch-and-preload-with-webpack/prefetch-add.png)

- Download size become `(prefetch cache)`, which means that the resource is directly gotten from prefetch cache, and the resolved size below is no longer 0
- The debug content is printed on the console, which means that the script is resolved and run just now


## Preload Resource

`preload` indicates that in the current navigation (mostly the current webpage), user very likely need to use corresponding resource, and give a hint to browser that corresponding resource need to be gotten preferentially

Change prefetch in link tag of prefetch.html to `preload`, and set resource type `as` to `script`, which form preload.html
```
<link rel="preload" href="script.js" as="script">
```

Access the local server corresponding `prefetch.html`, or directly access online [webpage][demo-preload], in the initial status, check the network tag of devtool

![](https://chanvinxiao.com/cn/blog/prefetch-and-preload-with-webpack/preload.png)

- `script.js` is downloaded preferentially, the resolved size of size column is no longer 0, namely `preload` no only download the script, but also resolve it
- The console tab is still empty, namely although the script is resolved, it has not run yet

Click `Add Script`, there is not additional record in the network tab, but the console output the print content of script
- Because script is resolved completely, there is even no need to fetch from cache, just directly run
- If `Add Script` is not clicked in 3 second, the console will output warning, because the resource which should be loaded preferentially is not used in time
>The resource https://chanvinxiao.com/demo/html/script.js was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally.


## webpack associated handling

Run the following command to install corresponding dependencies, and create new directory src
>npm i -D webpack webpack-cli html-webpack-plugin preload-webpack-plugin\@3.0.0-beta.4  
mkdir src

- Current version of PreloadWebpackPlugin is 2.x, which is not compatible with current version of webpack which is 4.x, so need to specify the version as the newest 3.x beta

copy `main.js` and `script.js` to src, and modify the click event handler in `main.js` to the following
```
button.addEventListener('click', e => {
  import(/* webpackChunkName: "script" */ './script.js');
});
```

- [import()][webpack-import] will load script dynamically, webpack will generate code which create `script` tag similar to above
- The comment in import is magical comment with special meaning, and if webpackChunkName is not set, the imported script will name with number sequence

Add `webpack.config.js` as following
```
const HtmlWebpackPlugin = require('html-webpack-plugin');
const PreloadWebpackPlugin = require('preload-webpack-plugin');

module.exports = {
  entry: './src/main.js',
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'preload.html'
    }),
    new PreloadWebpackPlugin()
  ]
}
```

- [HtmlWebpackPlugin][html-webpack-plugin] will generate corresponding html file automatically, index.html as default, here we change it via option `filename`
- [PreloadWebpackPlugin][preload-webpack-plugin] is plugin of HtmlWebpackPlugin, it will add `link` tag with link type as `preload` for the dynamically imported resource, and the `as` value can be determined by the suffix

PreloadWebpackPlugin also support prefetch, with `rel` option as `prefetch`
```
    new HtmlWebpackPlugin({
      filename: 'prefetch.html'
    }),
    new PreloadWebpackPlugin({
      rel: 'prefetch'
    })
```

But if we want to generate both preload.html and prefetch.html, option `excludeHtmlNames` need to be set in the corresponding PreloadWebpackPlugin configuration, or else both preload and prefetch link tags will be produced simultaneously
```
    new HtmlWebpackPlugin({
      filename: 'preload.html'
    }),
    new HtmlWebpackPlugin({
      filename: 'prefetch.html'
    }),
    new PreloadWebpackPlugin({
      excludeHtmlNames: ['prefetch.html']
    }),
    new PreloadWebpackPlugin({
      rel: 'prefetch',
      excludeHtmlNames: ['preload.html']
    })
```


Build files (or add `build` script in `package.json`)
>npx webpack

prefetch.html and preload.html will be generated in `dist` directory, access local server corresponding address, you will get the same effect as the above static page


## Summarize

This article show the fulfillment of prefetch and preload with two methods: static page and webpack building. The complete code can be found in [GitHub][github-demo], the main technical points are as follow:
- `ELEMENT.appendChild` create script dynamically
- `import()` load script dynamically and set the magical comment
- The configuration of `html-webpack-plugin` and its plugin



[mdn-link]: https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/link
[demo-prefetch]: https://chanvinxiao.com/demo/html/prefetch.html
[demo-preload]: https://chanvinxiao.com/demo/html/preload.html
[webpack-import]: https://webpack.js.org/api/module-methods/#import-1
[html-webpack-plugin]: https://webpack.js.org/plugins/html-webpack-plugin/
[preload-webpack-plugin]: https://github.com/GoogleChrome/preload-webpack-plugin
[github-demo]: https://github.com/vinzid/prefetch-preload-demo