npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

webpack-prefetcher

v1.0.7

Published

A babel plugin to let u take over prefetch control from webpack

Downloads

55

Readme

webpack-prefetcher

A babel plugin to let u take over prefetch control from webpack

Purpose of this plugin

Webpack is awesome for splitting chunks, but it's chunk prefetch/preload strategy is not ideal, if your chunk load other chunk in some conditions like this:

function renderSearch() {
    if (isMobile) {
      // Tiny, 4KB, fast rendering
      import('./mobile.search.js').then(render)
    } else {
      // Huge, 40KB, slow rendering
      import('./desktop.search.js').then(render)
    }
}
showSearchBtn.onclick = () => renderSearch()

Now u want to prefetch search chunks when user hover the button, u may do this:

showSearchBtn.onmouseover = () => {
    if (isMobile) {
      // Tiny, 4KB, fast rendering
      import(/* webpackPrefetch: true */ './mobile.search.js')
    } else {
      // Huge, 40KB, slow rendering
      import(/* webpackPrefetch: true */ './desktop.search.js')
    }
}

But this won't works, it will prefetch both desktop & mobile search chunks at same time, u actually don't have the control of this...

See the issue here: https://github.com/webpack/webpack/issues/8470

Requirements:

Currently this plugin only support webpack 4, maybe works in webpack 2 or 3, but I didn't got time to test it out.

Usage

First:

npm install webpack-prefetcher

Then add this plugin into your .babelrc file:

"plugins": [
  "webpack-prefetcher/lib/babel-plugin"
]

Then in the begin of your app code, add this to load manifest data:

import { Prefetcher } from "webpack-prefetcher";

// window.manifestPath is the url of your manifest file
Prefetcher.loadManifest(
  fetch(window.manifestPath)
    .then(res => res.json())
);

// Or pass the data directly
const manifest = {'lazy-chunk': 'lazy-chunk.hash.js'}
Prefetcher.loadManifest(manifest);

Finally, declare your lazy load component like this:

import { prefetchable } from "webpack-prefetcher";
import Loadable from 'react-loadable';

function createLoadable(prefetchable) {
  return {
    load: prefetchable.load,
    preload: prefetchable.preload,
    prefetch: prefetchable.prefetch,
    component: Loadable({
      loader: prefetchable.load,
      loading: () => null
    })
  }
}


export const lazyJS = createLoadable(prefetchable(() => import('./lazy')));

Now u can control the prefetch/preload by yourself!

<button
  onMouseOver={() => lazyJS.prefetch()}
  onClick={() => lazyJS.load()}>
    Load lazyJS
</button>

How to generate manifest?

Method A - Generate it your-self

For most of project, I would recommend the webpack-manifest-plugin plugin.

But webpack-manifest-plugin can't generate a manifest file with hash file name, since we want to control the prefetch in client side, we need to send down the manifest to browser every time, so this limitation is a drawback because we can't cache the manifest file even it's content never changed.

In short, we want to generate something like manifest.[hash].json, not manifest.json

So I copy & modify this plugin, create my own version of WebpackManifestPlugin, u can use it like this:

const WebpackManifestPlugin = require('webpack-prefetcher/lib/webpack-manifest-plugin');

It will generate manifest.[hash].json in your build output folder.

Then put the manifest path into html, u have to write your own webpack plugin to do so

The WebpackManifestPlugin I created will emit an event to a webpack hook call webpackManifestPluginAfterEmit, which will work like this:

compiler.hooks.webpackManifestPluginAfterEmit.tap('ManifestInlinePlugin', (manifest, outputPath) => {
  // outputPath is where the manifest file go.
  // U can check "sample/src/manifest.inline.plugin.js" for how to implement the below function
  replaceManifestPathInHTML(outputPath)
});

Then in your index.html

<html>
  <body>
    <div id="root"></div>
    <script>
      window.manifestPath = '$MANIFESTPATH$'
    </script>
  </body>
</html>

Method B - Expose webpack manifest APIs

Note: this is an really hacky method and may broken in many cases

The easiest way to get manifest data is get it from webpack, webpack knows everything about chunks so it can help u them.

Webpack 4 contains such data in a function call jsonpScriptSrc:

// script path function
function jsonpScriptSrc(chunkId) {
    /******/
    return __webpack_require__.p + "" + ({
      "lazy-chunk": "lazy-chunk",
      "lazy-css-file-chunk": "lazy-css-file-chunk",
      "lazy-scss-file-chunk": "lazy-scss-file-chunk",
      "lazy.2-chunk": "lazy.2-chunk"
    }[chunkId] || chunkId) + "-" + {
      "lazy-chunk": "86ef26e3221877d60d43",
      "lazy-css-file-chunk": "5f0e6cf30483ee380632",
      "lazy-scss-file-chunk": "814341c216b82b0a5b6e",
      "lazy.2-chunk": "e0d63e2f3d8d35544af5"
    }[chunkId] + ".js"
    /******/
  }

But webpack 4 doesn't expose this as an api, so we can't get the manifest data from it.

Luckily it's easy to hack in, I created another hacky webpack plugin to modify this function's declaration from:

function jsonpScriptSrc(chunkId) {
 ...
}

to:

window.jsonpScriptSrc = function(chunkId) {
 ...
}

U can get this plugin from webpack-prefetcher/lib/webpack-manifest-api-expose-plugin, put it into webpack's plugin list, then pass these apis to Prefetcher:

Prefetcher.loadManifestFromWebPack({
   jsSrc: window.jsonpScriptSrc,
   cssSrc: window.cssSrc
});

↑↑ you have to enable namedChunks in webpack to make this method works ↑↑

One more thing about manifest

If u open the manifest.[hash].json file, it look like this:

{
  "lazy-chunk.js": "/lazy-chunk.7ef8e208d1617ed06120.js",
  "lazy.style-chunk.css": "/lazy.style-chunk.7ef8e208.css",
  "lazy.style-chunk.js": "/lazy.style-chunk.7ef8e208d1617ed06120.js",
  "main.js": "/main.7ef8e208d1617ed06120.js",
  "index.html": "/index.html"
}

Here, the key lazy-chunk.js & lazy.style-chunk.js is usually set by webpack magic comment /* webpackChunkName: name */, my babel-plugin.js set this magic comment automatically for u.

But the plugin never check for chunk name confliction, so if u got two js file with same name, things will mess-up.

In such case u have to set the chunk name yourself by /* webpackChunkName: name */, if u feel this is a verbose thing to do, consider use this babel-plugin to generate the chunk name with hash:

babel-plugin-smart-webpack-import

Remember to put it before webpack-prefetcher/lib/babel-plugin in .babelrc

"plugins": [
  "babel-plugin-smart-webpack-import",
  "webpack-prefetcher/lib/babel-plugin"
]

Known issues

If the chunk name of a lazy load module is not set by /* webpackChunkName: name */ comment, for example it's set by

webpack > optimization > splitChunks > chunkGroup > name function

then the plugin can't get the correct chunk name from it, thus can't prefetch the correct resources.

Exactly, how this plugin works?

Prefetch - Take control from webpack