Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Migrating from Webpack 1.x to 2.x

In Webpack 1.x I used to do the following on regular basis:

require.ensure([ './mod2.js' ], ( require ) => {
    setTimeout(() => {
        // some later point in time, most likely through any kind of event
        var data = require( './mod2.js' ); // actual evaluating the code
    },1100);
}, 'myModule2');

With this technique, we were able to transfer a webpack-bundle over the wire, but evaluate the actual contents (the JavaScript code) from that bundle at some later point in time. Also, using require.ensure we could name the bundle, in this case myModule2, so we could see the name / alias when bundling happened executing webpack.

In Webpack 2.x, the new way to go is using System.import. While I love receiving a Promise object now, I have two issues with that style. The equivalent of the above code would look like:

System.import( './mod2.js' ).then( MOD2 => {
    // bundle was transferred AND evaluated at this point   
});
  • How can we split the transfer and the evaluation now?
  • How can we still name the bundle?

The Webpack documentation on Github says the following:

Full dynamic requires now fail by default

A dependency with only an expression (i. e. require(expr)) will now create an empty context instead of an context of the complete directory.

Best refactor this code as it won't work with ES6 Modules. If this is not possible you can use the ContextReplacementPlugin to hint the compiler to the correct resolving.

I'm not sure if that plays a role in this case. They also talk about code splitting in there, but it's pretty briefly and they don't mention any of the "issues" or how to workaround.

like image 403
jAndy Avatar asked Oct 28 '16 23:10

jAndy


1 Answers

tl;dr: System.resolve and System.register do most of what you want. The rest of this answer is why require.ensure cannot and how System.import calls the others.

I think ES6 modules prevent this for working well, although following it through the relevants specs is tricky, so I may be totally wrong.

That said, let's start with a few references:

  1. the WhatWG module loader
  2. the ES6 specification on modules (§15.2)
  3. the CommonJS module specification
  4. the fantastic 2ality article on ES6 modules

The first reference explains more behavior, although I'm not entirely sure how normatize it is. The latter explains the implementation details on the JS side. Since no platforms implement this yet, I don't have references for how it actually work in real life, and we'll have to rely on the spec.

The require that has been available in webpack 1.x is a mashup of the CommonJS and AMD requires. The CommonJS side of that is described in ref#3, specifically the "Module Context" section. Nowhere does that mention require.ensure, nor does the AMD "specification" (such as it is), so this is purely an invention of webpack. That is, the feature was never real, in the sense of being specified somewhere official and fancy looking.

That said, I think require.ensure conflicts with ES6 modules. Calling System.import should invoke the import method from a Loader object. The relevant section in ref#2 does not lay that out explicitly, but §10.1 does mention attaching a loader to System.

The Loader.prototype.import method is not terribly involved, and step 4 is the only one that interests us:

  1. Return the result of transforming Resolve(loader, name, referrer) with a fulfillment handler that, when called with argument key, runs the following steps:
    1. Let entry be EnsureRegistered(loader, key).
    2. Return the result of transforming LoadModule(entry, "instantiate") with a fulfillment handler that, when called, runs the following steps:
      1. Return EnsureEvaluated(entry).

The flow is resolve-register-load-evaluate, and you want to break between load and evaluate. Note, however, that the load stage calls LoadModule with stage set to "instantiate". That implies and probably requires the module has already been translated via RequestTranslate, which does much of the heavy parsing as it tries to find the module's entry point and so on.

This has already done more work than you want, from the sounds of it. Since the basics of module loading require a known entry point, I don't think there's a way to avoid parsing and partially evaluating the module with the calls exposed from System. You already knew that.

The problem is that System.import can't possibly know -- until after parsing -- whether the module is an ES6 module that must be evaluated or a webpack bundle that could be deferred. The parsing must be done to figure out if we need to parse, leading to a chicken-and-egg problem.

Up to this point, we've been following the path from System.import through the Loader. The import call is dictating what stage of import we're at, assuming you want to go through the full end-to-end loading process. The underlying calls, like Loader.prototype.load, provide fine grained control over those stages.

I'm not sure how you would invoke the first two stages (fetch and translate), but if you were able to translate and register a module, later calls should simply evaluate and return it.

If the spec is accurate, this should be exposed (in supporting implementations) through the System.loader property and would have the methods you need to call. There are large parts of the flow you don't have access to, so I would suggest not doing this and instead setting up your code so nothing significant runs when a module is loaded. If that's not possible, you need to recreate the flow up through registering but stop shy of evaluation.

like image 95
ssube Avatar answered Oct 13 '22 14:10

ssube