Reputation: 311
I have started using Webpack when developing usual web sites consisting of a number pages and of different pages types. I'm used to the RequireJs script loader that loads all dependencies on demand when needed. Just a small piece of javascript is downloaded when page loads.
What I want to achieve is this:
I have tried many configurations to achieve this but with no success.
entry: {
main: 'main.js', //Used on all pages, e.g. mobile menu
'standard-page': 'pages/standard-page.js',
'start-page': 'pages/start-page.js',
'vendor': ['jquery']
},
alias: {
jquery: 'jquery/dist/jquery.js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js"),
new webpack.optimize.CommonsChunkPlugin('common.js')
]
In the html I want to load the javascripts like this:
<script src="/Static/js/dist/common.js"></script>
<script src="/Static/js/dist/main.js" async></script>
And on a specific page type (start page)
<script src="/Static/js/dist/start-page.js" async></script>
common.js should be a tiny file for fast loading of the page. main.js loads async and require('jquery') inside.
The output from Webpack looks promising but I can't get the vendors bundle to load asynchronously. Other dependencies (my own modules and domReady) is loaded in ther autogenerated chunks, but not jquery.
I can find plenty of examples that does almost this but not the important part of loading vendors asynchronously.
Output from webpack build:
Asset Size Chunks Chunk Names
main.js.map 570 bytes 0, 7 [emitted] main
main.js 399 bytes 0, 7 [emitted] main
standard-page.js 355 bytes 2, 7 [emitted] standard-page
c6ff6378688eba5a294f.js 348 bytes 3, 7 [emitted]
start-page.js 361 bytes 4, 7 [emitted] start-page
8986b3741c0dddb9c762.js 387 bytes 5, 7 [emitted]
vendor.js 257 kB 6, 7 [emitted] vendor
common.js 3.86 kB 7 [emitted] common.js
2876de041eaa501e23a2.js 1.3 kB 1, 7 [emitted]
Upvotes: 16
Views: 18505
Reputation: 4113
Some time ago I made such a small "Proof of concept" to check how importlazy will work in IE11. I have to admit it works :) After clicking the button, the code responsible for changing the background color of the page is loaded - full example
Js:
// polyfils for IE11
import 'core-js/modules/es.array.iterator';
const button = document.getElementById('background');
button.addEventListener('click', async (event) => {
event.preventDefault();
try {
const background = await import(/* webpackChunkName: "background" */ `./${button.dataset.module}.js`);
background.default();
} catch (error) {
console.log(error);
}
})
Html:
<button id="background" class="button-primary" data-module="background">change the background</button>
Upvotes: 1
Reputation: 928
I've recently travelled this same road, I'm working on optimizing my Webpack output since I think bundles are too big, HTTP2 can load js files in parallel and caching will be better with separate files, I was getting some dependencies duplicated in bundles, etc. While I got a solution working with Webpack 4 SplitChunksPlugin configuration, I'm currently moving towards using mostly Webpack's dynamic import() syntax since just that syntax will cause Webpack to automatically bundle dynamically imported bundles in their own file which I can name via a "magic comment":
import(/* webpackChunkName: "mymodule" */ "mymodule"); // I added an resolve.alias.mymodule entry in Webpack.config
Upvotes: 1
Reputation: 282825
Here's the solution I came up with.
First, export these two functions to window.*
-- you'll want them in the browser.
export function requireAsync(module) {
return new Promise((resolve, reject) => require(`bundle!./pages/${module}`)(resolve));
}
export function runAsync(moduleName, data={}) {
return requireAsync(moduleName).then(module => {
if(module.__esModule) {
// if it's an es6 module, then the default function should be exported as module.default
if(_.isFunction(module.default)) {
return module.default(data);
}
} else if(_.isFunction(module)) {
// if it's not an es6 module, then the module itself should be the function
return module(data);
}
})
}
Then, when you want to include one of your scripts on a page, just add this to your HTML:
<script>requireAsync('script_name.js')</script>
Now everything in the pages/
directory will be pre-compiled into a separate chunk that can be asynchronously loaded at run time, only when needed.
Furthermore, using the functions above, you now have a convenient way of passing server-side data into your client-side scripts:
<script>runAsync('script_that_needs_data', {my:'data',wow:'much excite'})</script>
And now you can access it:
// script_that_needs_data.js
export default function({my,wow}) {
console.log(my,wow);
}
Upvotes: 1
Reputation: 17014
The solution to this problem is two-fold:
CommonsChunkPlugin
to generate that shared bundle.Before you start using webpack you need to unlearn to be dependent on configuration. Require.js was all about configuration files. This mindset made it difficult for me to transition into webpack which is modeled more closely after CommonJS in node.js, which relies on no configuration.
With that in mind consider the following. If you have an app and you want it to asynchronously load some other parts of javascript you need to use one of the following paradigms.
Require.ensure
require.ensure
is one way that you can create a "split point" in your application. Again, you may have thought you'd need to do this with configuration, but that is not the case. In the example when I hit require.ensure
in my file webpack will automatically create a second bundle and load it on-demand. Any code executed inside of that split-point will be bundled together in a separate file.
require.ensure(['jquery'], function() {
var $ = require('jquery');
/* ... */
});
Require([])
You can also achieve the same thing with the AMD-version of require()
, the one that takes an array of dependencies. This will also create the same split point:
require(['jquery'], function($) {
/* ... */
});
In your example above you use entry
to create a vendor
bundle which has jQuery. You don't need to manually specify these dependency bundles. Instead, using the split points above you webpack will generate this automatically.
Use entry
only for separate <script>
tags you want in your pages.
Now that you've done all of that you can use the CommonsChunkPlugin
to additional optimize your chunks, but again most of the magic is done for you and outside of specifying which dependencies should be shared you won't need to do anything else. webpack
will pull in the shared chunks automatically without the need for additional <script>
tags or entry
configuration.
The scenario you describe (multiple <script>
tags) may not actually be what you want. With webpack all of the dependencies and bundles can be managed automatically starting with only a single <script>
tag. Having gone through several iterations of re-factoring from require.js to webpack, I've found that's usually the simplest and best way to manage your dependencies.
All the best!
Upvotes: 14