JavaScript has a lot of fun. One of the most popular programming languages in the world still does not have a stable syntax for breaking the code into parts. That is, the standard ESM syntax with “import” at the ready is already there, but in browsers and the node it is hidden behind flags, and in the webpack its support appeared only recently in version 2. Add to this the migration of the node and webpack from CommonJS “require” to ESM “import” and half a million NPM packages, the overwhelming part of which uses CommonJS. A little bit to deal with the zoo will help last week’s article from the author Webpack, an adapted translation of which is waiting for you under the cut.
We agree on terminology
Main file | So we will call the file in which the expression is used import () |
Include file | So we will call the file whose name is specified in the import () expression |
non-ESM | So we will call the CommonJS or AMD module, which does not export the __esModule flag |
trans-ESM | So we will call the CommonJS module, which exports the __esModule flag to true . It does this because it was compiled (transpired) from the ESM module. |
ESM | So we will call the EcmaScript (ES6) module as defined by the JavaScript language standard. |
strict-ESM | So we will call the EcmaScript module as the node wants to see it, with the file extension .mjs |
Json | So we will call JSON files. Webpack and node can and like to include JSON, turning its contents into JavaScript objects |
With all these options, we have the following zoo someone who includes:
- (A) Main file: non-ESM, transpiled-ESM or regular ESM
- (B) Main file: strict-ESM ( .mjs )
- (1) File to include: non-ESM
- (2) File to include: trans-ESM ( __esModule )
- (3) File to include: ESM or strict-ESM ( .mjs )
- (4) File Included: JSON
What does this look like in code?
Variants with options - this is not exactly what developers are used to. Let's look at the code:
// (A) source.js import("./target").then(result => console.log(result)); // (B) source.mjs import("./target").then(result => console.log(result)); // (1) target.js exports.name = "name"; exports.default = "default"; // (2) target.js exports.__esModule = true; exports.name = "name"; exports.default = "default"; // (3) target.js or target.mjs export const name = "name"; export default "default"; // (4) target.json { name: "name", default: "default" }
A3 and B3: import (ESM)
These two cases are the only ones described in the spec.
')
The result of the
import () call is the so-called “namespace object” corresponding to the loadable module. For compatibility, the webpack will automatically add the
__esModule flag to this object:
{ __esModule: true, name: "name", default: "default" }
Translator's note : We could not understand what is the condition of adding
__esModule . Both “import ... from ...”, and “import (...)”, both for the browser and for the node — we always obtained the namespace object without this flag. If someone shares in the comments what the author had in mind here - I will be grateful!
A1: import (CJS)
The situation when we use
import to load a CommonJS module. For example, if you installed this module using npm. Webpack version 3 in this case returned the value that the loadable module installed for
module.exports . Webpack version 4 will always create a namespace object and allow you to access exported identifiers either using the “import {property} ...” syntax or “import (...)” syntax.
Please note that Webpack 4 will replace the exported “default” property with its own “default” property for “all exported”. The corresponding code snippet from the example above:
// webpack 3 { name: "name", default: "default" } // webpack 4 { name: "name", default: { name: "name", default: "default" } }
B1: import (CJS)
If the strict-ESM file loads the old CommonJS module, then the Webpack will not allow access to the exported identifiers as the namespace object fields. The object will have only one “default” field with “all exported”:
{ default: { name: "name", default: "default" } }
A2: import (trans-js)
If the
__esModule flag is found for the loadable module, the Webpack immediately terminates the games with “default” and places everything exported into the namespace object:
{ __esModule: true, name: "name", default: "default" }
B2: import (trans-ESM)
Surprise: if the
__esModule flag is found on the loadable module, but the loading model is strict-ESM, then the flag will be ignored! For consistency with Node.js:
{ default: { __esModule: true, name: "name", default: "default" } }
A4 and B4: import (JSON)
Property picking will work anyway, even when loaded from strict-ESM, and we get all the contents of JSON in the namespace object. And the same thing again in the “default” field:
{ name: "name", default: { name: "name", default: "default" } }
Total
In fact, only one script exchanged between Webpack 3 and Webpack 4. Example:
module.exports = 42;
For correct work you need to use the “default” field:
// webpack 3 42 // webpack 4 { default: 42 }