On webpack how can I import a script without evaluate it?
Updates: I include all the things into a npm package, check it out! https://www.npmjs.com/package/webpack-prefetcher
After few days of research, I end up with writing a customize babel plugin...
In short, the plugin work like this:
- Gather all the import(args) statements in the code
- If the import(args) contains /* prefetch: true */ comment
- Find the chunkId from the import() statement
- Replace it with Prefetcher.fetch(chunkId)
Prefetcher is a helper class that contain the manifest of webpack output, and can help us on inserting the prefetch link:
export class Prefetcher {
static manifest = {
"pageA.js": "/pageA.hash.js",
"app.js": "/app.hash.js",
"index.html": "/index.html"
}
static function fetch(chunkId) {
const link = document.createElement('link')
link.rel = "prefetch"
link.as = "script"
link.href = Prefetcher.manifest[chunkId + '.js']
document.head.appendChild(link)
}
}
An usage example:
const pageAImporter = {
prefetch: () => import(/* prefetch: true */ './pageA.js')
load: () => import(/* webpackChunkName: 'pageA' */ './pageA.js')
}
a.onmousehover = () => pageAImporter.prefetch()
a.onclick = () => pageAImporter.load().then(...)
The detail of this plugin can found in here:
Prefetch - Take control from webpack
Again, this is a really hacky way and I don't like it, if u want webpack team to implement this, pls vote here:
Feature: prefetch dynamic import on demand
UPDATE
You can use preload-webpack-plugin with html-webpack-plugin it will let you define what to preload in configuration and it will automatically insert tags to preload your chunk
note if you are using webpack v4 as of now you will have to install this plugin using preload-webpack-plugin@next
example
plugins: [
new HtmlWebpackPlugin(),
new PreloadWebpackPlugin({
rel: 'preload',
include: 'asyncChunks'
})
]
For a project generating two async scripts with dynamically generated names, such as
chunk.31132ae6680e598f8879.js
andchunk.d15e7fdfc91b34bb78c4.js
, the following preloads will be injected into the documenthead
<link rel="preload" as="script" href="chunk.31132ae6680e598f8879.js">
<link rel="preload" as="script" href="chunk.d15e7fdfc91b34bb78c4.js">
UPDATE 2
if you don't want to preload all async chunk but only specific once you can do that too
either you can use migcoder's babel plugin or with preload-webpack-plugin
like following
first you will have to name that async chunk with help of
webpack magic comment
exampleimport(/* webpackChunkName: 'myAsyncPreloadChunk' */ './path/to/file')
and then in plugin configuration use that name like
plugins: [ new HtmlWebpackPlugin(), new PreloadWebpackPlugin({ rel: 'preload', include: ['myAsyncPreloadChunk'] }) ]
First of all let's see the behavior of browser when we specify script
tag or link
tag to load the script
- whenever a browser encounter a
script
tag it will load it parse it and execute it immediately - you can only delay the parsing and evaluating with help of
async
anddefer
tag only untilDOMContentLoaded
event - you can delay the execution (evaluation) if you don't insert the script tag ( only preload it with
link
)
now there are some other not recommended hackey way is you ship your entire script and string
or comment
( because evaluation time of comment or string is almost negligible) and when you need to execute that you can use Function() constructor
or eval
both are not recommended
Another Approach Service Workers: ( this will preserve you cache event after page reload or user goes offline after cache is loaded )
In modern browser you can use service worker
to fetch and cache a recourse ( JavaScript, image, css anything ) and when main thread request for that recourse you can intercept that request and return the recourse from cache this way you are not parsing and evaluating the script when you are loading it into the cache
read more about service workers here
example
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open('v1').then(function(cache) {
return cache.addAll([
'/sw-test/',
'/sw-test/index.html',
'/sw-test/style.css',
'/sw-test/app.js',
'/sw-test/image-list.js',
'/sw-test/star-wars-logo.jpg',
'/sw-test/gallery/bountyHunters.jpg',
'/sw-test/gallery/myLittleVader.jpg',
'/sw-test/gallery/snowTroopers.jpg'
]);
})
);
});
self.addEventListener('fetch', function(event) {
event.respondWith(caches.match(event.request).then(function(response) {
// caches.match() always resolves
// but in case of success response will have value
if (response !== undefined) {
return response;
} else {
return fetch(event.request).then(function (response) {
// response may be used only once
// we need to save clone to put one copy in cache
// and serve second one
let responseClone = response.clone();
caches.open('v1').then(function (cache) {
cache.put(event.request, responseClone);
});
return response;
}).catch(function () {
// any fallback code here
});
}
}));
});
as you can see this is not a webpack dependent thing this is out of scope of webpack however with help of webpack you can split your bundle which will help utilizing service worker better