Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Webpack with small initial script and async loading of all other scripts

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:

  • A small initial javascript file that loads dependencies asynchronous
  • Each page type can have their own javascript, which also may have dependencies.
  • Common modules, vendor scripts should be bundled in common scripts

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]  
like image 694
erzki Avatar asked Oct 07 '15 20:10

erzki


People also ask

What is chunking in webpack?

Chunk: This webpack-specific term is used internally to manage the bundling process. Bundles are composed out of chunks, of which there are several types (e.g. entry and child).

How does webpack load chunks?

Webpack injects some code into main. js which takes care of lazy loading async chunks and stops from loading same chunks again and again. When a route changes, React router calls a Webpack function to load a chunk file and Webpack after done loading runs it, which chunk then would internally ask React to do something.

How does webpack bundling work?

When webpack processes your application, it internally builds a dependency graph from one or more entry points and then combines every module your project needs into one or more bundles, which are static assets to serve your content from.

What is webpack list and explain its advantages?

Application of Webpack:It helps to use different javascript files without having tension that will load first. It makes code shorter. It helps in converting many files other than javascript into modules. It compiles different javascript module.


2 Answers

The solution to this problem is two-fold:

  1. First you need to understand how code-splitting works in webpack
  2. Secondly, you need to use something like the CommonsChunkPlugin to generate that shared bundle.

Code Splitting

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($) {
    /* ... */
});

Shared Bundles

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.

Conclusion

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!

like image 191
Jamund Ferguson Avatar answered Sep 17 '22 04:09

Jamund Ferguson


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);
}
like image 31
mpen Avatar answered Sep 17 '22 04:09

mpen