diff --git a/examples/module-code-splitting/README.md b/examples/module-code-splitting/README.md
new file mode 100644
index 000000000..6e6120c6e
--- /dev/null
+++ b/examples/module-code-splitting/README.md
@@ -0,0 +1,274 @@
+# example.js
+
+```javascript
+import { resetCounter, print } from "./methods";
+
+setTimeout(async () => {
+ const counter = await import("./counter");
+ print(counter.value);
+ counter.increment();
+ counter.increment();
+ counter.increment();
+ print(counter.value);
+ await resetCounter();
+ print(counter.value);
+}, 100);
+```
+
+# methods.js
+
+```javascript
+export const resetCounter = async () => {
+ (await import("./counter")).reset();
+};
+
+export const print = value => console.log(value);
+```
+
+# counter.js
+
+```javascript
+export let value = 0;
+export function increment() {
+ value++;
+}
+export function decrement() {
+ value--;
+}
+export function reset() {
+ value = 0;
+}
+```
+
+# dist/output.js
+
+```javascript
+/******/ "use strict";
+/******/ var __webpack_modules__ = ({});
+```
+
+/* webpack runtime code */
+
+``` js
+/************************************************************************/
+/******/ // The module cache
+/******/ var __webpack_module_cache__ = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/ // Check if module is in cache
+/******/ var cachedModule = __webpack_module_cache__[moduleId];
+/******/ if (cachedModule !== undefined) {
+/******/ return cachedModule.exports;
+/******/ }
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = __webpack_module_cache__[moduleId] = {
+/******/ // no module.id needed
+/******/ // no module.loaded needed
+/******/ exports: {}
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = __webpack_modules__;
+/******/
+/************************************************************************/
+/******/ /* webpack/runtime/define property getters */
+/******/ (() => {
+/******/ // define getter functions for harmony exports
+/******/ __webpack_require__.d = (exports, definition) => {
+/******/ for(var key in definition) {
+/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
+/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
+/******/ }
+/******/ }
+/******/ };
+/******/ })();
+/******/
+/******/ /* webpack/runtime/ensure chunk */
+/******/ (() => {
+/******/ __webpack_require__.f = {};
+/******/ // This file contains only the entry chunk.
+/******/ // The chunk loading function for additional chunks
+/******/ __webpack_require__.e = (chunkId) => {
+/******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {
+/******/ __webpack_require__.f[key](chunkId, promises);
+/******/ return promises;
+/******/ }, []));
+/******/ };
+/******/ })();
+/******/
+/******/ /* webpack/runtime/get javascript chunk filename */
+/******/ (() => {
+/******/ // This function allow to reference async chunks
+/******/ __webpack_require__.u = (chunkId) => {
+/******/ // return url for filenames based on template
+/******/ return "" + chunkId + ".output.js";
+/******/ };
+/******/ })();
+/******/
+/******/ /* webpack/runtime/hasOwnProperty shorthand */
+/******/ (() => {
+/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
+/******/ })();
+/******/
+/******/ /* webpack/runtime/make namespace object */
+/******/ (() => {
+/******/ // define __esModule on exports
+/******/ __webpack_require__.r = (exports) => {
+/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
+/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
+/******/ }
+/******/ Object.defineProperty(exports, '__esModule', { value: true });
+/******/ };
+/******/ })();
+/******/
+/******/ /* webpack/runtime/import chunk loading */
+/******/ (() => {
+/******/ // no baseURI
+/******/
+/******/ // object to store loaded and loading chunks
+/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
+/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded
+/******/ var installedChunks = {
+/******/ 0: 0
+/******/ };
+/******/
+/******/ __webpack_require__.f.j = (chunkId, promises) => {
+/******/ // JSONP chunk loading for javascript
+/******/ var installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;
+/******/ if(installedChunkData !== 0) { // 0 means "already installed".
+/******/
+/******/ // a Promise means "currently loading".
+/******/ if(installedChunkData) {
+/******/ promises.push(installedChunkData[1]);
+/******/ } else {
+/******/ if(true) { // all chunks have JS
+/******/ // setup Promise in chunk cache
+/******/ var promise = import("./" + __webpack_require__.u(chunkId)).then((data) => {
+/******/ var {ids, modules, runtime} = data;
+/******/ // add "modules" to the modules object,
+/******/ // then flag all "ids" as loaded and fire callback
+/******/ var moduleId, chunkId, i = 0;
+/******/ for(moduleId in modules) {
+/******/ if(__webpack_require__.o(modules, moduleId)) {
+/******/ __webpack_require__.m[moduleId] = modules[moduleId];
+/******/ }
+/******/ }
+/******/ if(runtime) runtime(__webpack_require__);
+/******/ for(;i < ids.length; i++) {
+/******/ chunkId = ids[i];
+/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {
+/******/ installedChunks[chunkId][0]();
+/******/ }
+/******/ installedChunks[ids[i]] = 0;
+/******/ }
+/******/
+/******/ }, (e) => {
+/******/ if(installedChunks[chunkId] !== 0) installedChunks[chunkId] = undefined;
+/******/ throw e;
+/******/ });
+/******/ var promise = Promise.race([promise, new Promise((resolve) => (installedChunkData = installedChunks[chunkId] = [resolve]))])
+/******/ promises.push(installedChunkData[1] = promise);
+/******/ } else installedChunks[chunkId] = 0;
+/******/ }
+/******/ }
+/******/ };
+/******/
+/******/ // no on chunks loaded
+/******/ })();
+/******/
+/************************************************************************/
+```
+
+
+
+``` js
+var __webpack_exports__ = {};
+/*!********************************!*\
+ !*** ./example.js + 1 modules ***!
+ \********************************/
+/*! namespace exports */
+/*! runtime requirements: __webpack_require__.e, __webpack_require__, __webpack_require__.* */
+
+;// CONCATENATED MODULE: ./methods.js
+const resetCounter = async () => {
+ (await __webpack_require__.e(/*! import() */ 1).then(__webpack_require__.bind(__webpack_require__, /*! ./counter */ 1))).reset();
+};
+
+const print = value => console.log(value);
+
+;// CONCATENATED MODULE: ./example.js
+
+
+setTimeout(async () => {
+ const counter = await __webpack_require__.e(/*! import() */ 1).then(__webpack_require__.bind(__webpack_require__, /*! ./counter */ 1));
+ print(counter.value);
+ counter.increment();
+ counter.increment();
+ counter.increment();
+ print(counter.value);
+ await resetCounter();
+ print(counter.value);
+}, 100);
+```
+
+# dist/output.js (production)
+
+```javascript
+var e,o={},t={};function r(e){var n=t[e];if(void 0!==n)return n.exports;var i=t[e]={exports:{}};return o[e](i,i.exports,r),i.exports}r.m=o,r.d=(e,o)=>{for(var t in o)r.o(o,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:o[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce(((o,t)=>(r.f[t](e,o),o)),[])),r.u=e=>e+".output.js",r.o=(e,o)=>Object.prototype.hasOwnProperty.call(e,o),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},e={179:0},r.f.j=(o,t)=>{var n=r.o(e,o)?e[o]:void 0;if(0!==n)if(n)t.push(n[1]);else{var i=import("./"+r.u(o)).then((o=>{var t,n,{ids:i,modules:a,runtime:s}=o,u=0;for(t in a)r.o(a,t)&&(r.m[t]=a[t]);for(s&&s(r);u{throw 0!==e[o]&&(e[o]=void 0),t}));i=Promise.race([i,new Promise((t=>n=e[o]=[t]))]),t.push(n[1]=i)}};const n=e=>console.log(e);setTimeout((async()=>{const e=await r.e(946).then(r.bind(r,946));n(e.value),e.increment(),e.increment(),e.increment(),n(e.value),await(async()=>{(await r.e(946).then(r.bind(r,946))).reset()})(),n(e.value)}),100);
+```
+
+# Info
+
+## Unoptimized
+
+```
+asset output.js 6.35 KiB [emitted] [javascript module] (name: main)
+asset 1.output.js 1.36 KiB [emitted] [javascript module]
+chunk (runtime: main) output.js (main) 420 bytes (javascript) 2.89 KiB (runtime) [entry] [rendered]
+ > ./example.js main
+ runtime modules 2.89 KiB 6 modules
+ ./example.js + 1 modules 420 bytes [built] [code generated]
+ [no exports]
+ [no exports used]
+ entry ./example.js main
+ used as library export
+chunk (runtime: main) 1.output.js 146 bytes [rendered]
+ > ./counter ./methods.js 2:8-27
+ > ./counter ./example.js 4:23-42
+ ./counter.js 146 bytes [built] [code generated]
+ [exports: decrement, increment, reset, value]
+ import() ./counter ./example.js + 1 modules ./example.js 4:23-42
+ import() ./counter ./example.js + 1 modules ./methods.js 2:8-27
+webpack 5.40.0 compiled successfully
+```
+
+## Production mode
+
+```
+asset output.js 1.15 KiB [emitted] [javascript module] [minimized] (name: main)
+asset 946.output.js 213 bytes [emitted] [javascript module] [minimized]
+chunk (runtime: main) output.js (main) 420 bytes (javascript) 2.89 KiB (runtime) [entry] [rendered]
+ > ./example.js main
+ runtime modules 2.89 KiB 6 modules
+ ./example.js + 1 modules 420 bytes [built] [code generated]
+ [no exports]
+ [no exports used]
+ entry ./example.js main
+ used as library export
+chunk (runtime: main) 946.output.js 146 bytes [rendered]
+ > ./counter ./methods.js 2:8-27
+ > ./counter ./example.js 4:23-42
+ ./counter.js 146 bytes [built] [code generated]
+ [exports: decrement, increment, reset, value]
+ import() ./counter ./example.js + 1 modules ./example.js 4:23-42
+ import() ./counter ./example.js + 1 modules ./methods.js 2:8-27
+webpack 5.40.0 compiled successfully
+```
diff --git a/examples/module-code-splitting/build.js b/examples/module-code-splitting/build.js
new file mode 100644
index 000000000..41c29c9d1
--- /dev/null
+++ b/examples/module-code-splitting/build.js
@@ -0,0 +1 @@
+require("../build-common");
\ No newline at end of file
diff --git a/examples/module-code-splitting/counter.js b/examples/module-code-splitting/counter.js
new file mode 100644
index 000000000..7009896e2
--- /dev/null
+++ b/examples/module-code-splitting/counter.js
@@ -0,0 +1,10 @@
+export let value = 0;
+export function increment() {
+ value++;
+}
+export function decrement() {
+ value--;
+}
+export function reset() {
+ value = 0;
+}
diff --git a/examples/module-code-splitting/example.js b/examples/module-code-splitting/example.js
new file mode 100644
index 000000000..d9dc73232
--- /dev/null
+++ b/examples/module-code-splitting/example.js
@@ -0,0 +1,12 @@
+import { resetCounter, print } from "./methods";
+
+setTimeout(async () => {
+ const counter = await import("./counter");
+ print(counter.value);
+ counter.increment();
+ counter.increment();
+ counter.increment();
+ print(counter.value);
+ await resetCounter();
+ print(counter.value);
+}, 100);
diff --git a/examples/module-code-splitting/index.html b/examples/module-code-splitting/index.html
new file mode 100644
index 000000000..5ce1e0f88
--- /dev/null
+++ b/examples/module-code-splitting/index.html
@@ -0,0 +1,10 @@
+
+
+
+
+ Worker example
+
+
+
+
+
diff --git a/examples/module-code-splitting/methods.js b/examples/module-code-splitting/methods.js
new file mode 100644
index 000000000..81140850b
--- /dev/null
+++ b/examples/module-code-splitting/methods.js
@@ -0,0 +1,5 @@
+export const resetCounter = async () => {
+ (await import("./counter")).reset();
+};
+
+export const print = value => console.log(value);
diff --git a/examples/module-code-splitting/template.md b/examples/module-code-splitting/template.md
new file mode 100644
index 000000000..98d06e62e
--- /dev/null
+++ b/examples/module-code-splitting/template.md
@@ -0,0 +1,43 @@
+# example.js
+
+```javascript
+_{{example.js}}_
+```
+
+# methods.js
+
+```javascript
+_{{methods.js}}_
+```
+
+# counter.js
+
+```javascript
+_{{counter.js}}_
+```
+
+# dist/output.js
+
+```javascript
+_{{dist/output.js}}_
+```
+
+# dist/output.js (production)
+
+```javascript
+_{{production:dist/output.js}}_
+```
+
+# Info
+
+## Unoptimized
+
+```
+_{{stdout}}_
+```
+
+## Production mode
+
+```
+_{{production:stdout}}_
+```
diff --git a/examples/module-code-splitting/webpack.config.js b/examples/module-code-splitting/webpack.config.js
new file mode 100644
index 000000000..f5141e5ff
--- /dev/null
+++ b/examples/module-code-splitting/webpack.config.js
@@ -0,0 +1,16 @@
+module.exports = {
+ output: {
+ module: true,
+ library: {
+ type: "module"
+ }
+ },
+ optimization: {
+ usedExports: true,
+ concatenateModules: true
+ },
+ target: "browserslist: last 2 chrome versions",
+ experiments: {
+ outputModule: true
+ }
+};
diff --git a/examples/module-library/README.md b/examples/module-library/README.md
new file mode 100644
index 000000000..cf605d69d
--- /dev/null
+++ b/examples/module-library/README.md
@@ -0,0 +1,170 @@
+# example.js
+
+```javascript
+export * from "./counter";
+export * from "./methods";
+```
+
+# methods.js
+
+```javascript
+export { reset as resetCounter } from "./counter";
+
+export const print = value => console.log(value);
+```
+
+# counter.js
+
+```javascript
+export let value = 0;
+export function increment() {
+ value++;
+}
+export function decrement() {
+ value--;
+}
+export function reset() {
+ value = 0;
+}
+```
+
+# dist/output.js
+
+```javascript
+/******/ "use strict";
+/******/ // The require scope
+/******/ var __webpack_require__ = {};
+/******/
+```
+
+/* webpack runtime code */
+
+``` js
+/************************************************************************/
+/******/ /* webpack/runtime/define property getters */
+/******/ (() => {
+/******/ // define getter functions for harmony exports
+/******/ __webpack_require__.d = (exports, definition) => {
+/******/ for(var key in definition) {
+/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
+/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
+/******/ }
+/******/ }
+/******/ };
+/******/ })();
+/******/
+/******/ /* webpack/runtime/hasOwnProperty shorthand */
+/******/ (() => {
+/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
+/******/ })();
+/******/
+/******/ /* webpack/runtime/make namespace object */
+/******/ (() => {
+/******/ // define __esModule on exports
+/******/ __webpack_require__.r = (exports) => {
+/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
+/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
+/******/ }
+/******/ Object.defineProperty(exports, '__esModule', { value: true });
+/******/ };
+/******/ })();
+/******/
+/************************************************************************/
+```
+
+
+
+``` js
+var __webpack_exports__ = {};
+/*!********************************!*\
+ !*** ./example.js + 2 modules ***!
+ \********************************/
+/*! namespace exports */
+/*! export decrement [provided] [used in main] [missing usage info prevents renaming] -> ./counter.js .decrement */
+/*! export increment [provided] [used in main] [missing usage info prevents renaming] -> ./counter.js .increment */
+/*! export print [provided] [used in main] [missing usage info prevents renaming] -> ./methods.js .print */
+/*! export reset [provided] [used in main] [missing usage info prevents renaming] -> ./counter.js .reset */
+/*! export resetCounter [provided] [used in main] [missing usage info prevents renaming] -> ./counter.js .reset */
+/*! export value [provided] [used in main] [missing usage info prevents renaming] -> ./counter.js .value */
+/*! other exports [not provided] [no usage info] */
+/*! runtime requirements: __webpack_require__.r, __webpack_exports__, __webpack_require__.d, __webpack_require__.* */
+// ESM COMPAT FLAG
+__webpack_require__.r(__webpack_exports__);
+
+// EXPORTS
+__webpack_require__.d(__webpack_exports__, {
+ "decrement": () => (/* reexport */ decrement),
+ "increment": () => (/* reexport */ increment),
+ "print": () => (/* reexport */ print),
+ "reset": () => (/* reexport */ counter_reset),
+ "resetCounter": () => (/* reexport */ counter_reset),
+ "value": () => (/* reexport */ value)
+});
+
+;// CONCATENATED MODULE: ./counter.js
+let value = 0;
+function increment() {
+ value++;
+}
+function decrement() {
+ value--;
+}
+function counter_reset() {
+ value = 0;
+}
+
+;// CONCATENATED MODULE: ./methods.js
+
+
+const print = value => console.log(value);
+
+;// CONCATENATED MODULE: ./example.js
+
+
+
+var __webpack_exports__decrement = __webpack_exports__.decrement;
+var __webpack_exports__increment = __webpack_exports__.increment;
+var __webpack_exports__print = __webpack_exports__.print;
+var __webpack_exports__reset = __webpack_exports__.reset;
+var __webpack_exports__resetCounter = __webpack_exports__.resetCounter;
+var __webpack_exports__value = __webpack_exports__.value;
+export { __webpack_exports__decrement as decrement, __webpack_exports__increment as increment, __webpack_exports__print as print, __webpack_exports__reset as reset, __webpack_exports__resetCounter as resetCounter, __webpack_exports__value as value };
+```
+
+# dist/output.js (production)
+
+```javascript
+var e={d:(n,t)=>{for(var o in t)e.o(t,o)&&!e.o(n,o)&&Object.defineProperty(n,o,{enumerable:!0,get:t[o]})},o:(e,n)=>Object.prototype.hasOwnProperty.call(e,n)},n={};e.d(n,{Mj:()=>r,nP:()=>o,S0:()=>c,mc:()=>a,Uh:()=>a,S3:()=>t});let t=0;function o(){t++}function r(){t--}function a(){t=0}const c=e=>console.log(e);var s=n.Mj,i=n.nP,l=n.S0,p=n.mc,u=n.Uh,f=n.S3;export{s as decrement,i as increment,l as print,p as reset,u as resetCounter,f as value};
+```
+
+# Info
+
+## Unoptimized
+
+```
+asset output.js 3.63 KiB [emitted] [javascript module] (name: main)
+chunk (runtime: main) output.js (main) 302 bytes (javascript) 670 bytes (runtime) [entry] [rendered]
+ > ./example.js main
+ runtime modules 670 bytes 3 modules
+ ./example.js + 2 modules 302 bytes [built] [code generated]
+ [exports: decrement, increment, print, reset, resetCounter, value]
+ [used exports unknown]
+ entry ./example.js main
+ used as library export
+webpack 5.40.0 compiled successfully
+```
+
+## Production mode
+
+```
+asset output.js 446 bytes [emitted] [javascript module] [minimized] (name: main)
+chunk (runtime: main) output.js (main) 302 bytes (javascript) 396 bytes (runtime) [entry] [rendered]
+ > ./example.js main
+ runtime modules 396 bytes 2 modules
+ ./example.js + 2 modules 302 bytes [built] [code generated]
+ [exports: decrement, increment, print, reset, resetCounter, value]
+ [all exports used]
+ entry ./example.js main
+ used as library export
+webpack 5.40.0 compiled successfully
+```
diff --git a/examples/module-library/build.js b/examples/module-library/build.js
new file mode 100644
index 000000000..41c29c9d1
--- /dev/null
+++ b/examples/module-library/build.js
@@ -0,0 +1 @@
+require("../build-common");
\ No newline at end of file
diff --git a/examples/module-library/counter.js b/examples/module-library/counter.js
new file mode 100644
index 000000000..7009896e2
--- /dev/null
+++ b/examples/module-library/counter.js
@@ -0,0 +1,10 @@
+export let value = 0;
+export function increment() {
+ value++;
+}
+export function decrement() {
+ value--;
+}
+export function reset() {
+ value = 0;
+}
diff --git a/examples/module-library/example.js b/examples/module-library/example.js
new file mode 100644
index 000000000..ef58a21ff
--- /dev/null
+++ b/examples/module-library/example.js
@@ -0,0 +1,2 @@
+export * from "./counter";
+export * from "./methods";
diff --git a/examples/module-library/methods.js b/examples/module-library/methods.js
new file mode 100644
index 000000000..4be8f10f7
--- /dev/null
+++ b/examples/module-library/methods.js
@@ -0,0 +1,3 @@
+export { reset as resetCounter } from "./counter";
+
+export const print = value => console.log(value);
diff --git a/examples/module-library/template.md b/examples/module-library/template.md
new file mode 100644
index 000000000..98d06e62e
--- /dev/null
+++ b/examples/module-library/template.md
@@ -0,0 +1,43 @@
+# example.js
+
+```javascript
+_{{example.js}}_
+```
+
+# methods.js
+
+```javascript
+_{{methods.js}}_
+```
+
+# counter.js
+
+```javascript
+_{{counter.js}}_
+```
+
+# dist/output.js
+
+```javascript
+_{{dist/output.js}}_
+```
+
+# dist/output.js (production)
+
+```javascript
+_{{production:dist/output.js}}_
+```
+
+# Info
+
+## Unoptimized
+
+```
+_{{stdout}}_
+```
+
+## Production mode
+
+```
+_{{production:stdout}}_
+```
diff --git a/examples/module-library/webpack.config.js b/examples/module-library/webpack.config.js
new file mode 100644
index 000000000..d7f45aa69
--- /dev/null
+++ b/examples/module-library/webpack.config.js
@@ -0,0 +1,14 @@
+module.exports = {
+ output: {
+ module: true,
+ library: {
+ type: "module"
+ }
+ },
+ optimization: {
+ concatenateModules: true
+ },
+ experiments: {
+ outputModule: true
+ }
+};
diff --git a/lib/RuntimeTemplate.js b/lib/RuntimeTemplate.js
index 4199e9d05..d2da8af15 100644
--- a/lib/RuntimeTemplate.js
+++ b/lib/RuntimeTemplate.js
@@ -133,6 +133,14 @@ class RuntimeTemplate {
);
}
+ destructureObject(items, value) {
+ return this.supportsDestructuring()
+ ? `var {${items.join(", ")}} = ${value};`
+ : Template.asString(
+ items.map(item => `var ${item} = ${value}${propertyAccess([item])};`)
+ );
+ }
+
iife(args, body) {
return `(${this.basicFunction(args, body)})()`;
}
diff --git a/lib/WebpackOptionsApply.js b/lib/WebpackOptionsApply.js
index e3822f05b..044300140 100644
--- a/lib/WebpackOptionsApply.js
+++ b/lib/WebpackOptionsApply.js
@@ -139,10 +139,11 @@ class WebpackOptionsApply extends OptionsApply {
new CommonJsChunkFormatPlugin().apply(compiler);
break;
}
- case "module":
- throw new Error(
- "EcmaScript Module Chunk Format is not implemented yet"
- );
+ case "module": {
+ const ModuleChunkFormatPlugin = require("./esm/ModuleChunkFormatPlugin");
+ new ModuleChunkFormatPlugin().apply(compiler);
+ break;
+ }
default:
throw new Error(
"Unsupported chunk format '" + options.output.chunkFormat + "'."
diff --git a/lib/config/defaults.js b/lib/config/defaults.js
index 7a75b8395..8f348324d 100644
--- a/lib/config/defaults.js
+++ b/lib/config/defaults.js
@@ -182,6 +182,11 @@ const applyWebpackOptionsDefaults = options => {
applyOutputDefaults(options.output, {
context: options.context,
targetProperties,
+ isAffectedByBrowserslist:
+ target === undefined ||
+ (typeof target === "string" && target.startsWith("browserslist")) ||
+ (Array.isArray(target) &&
+ target.some(target => target.startsWith("browserslist"))),
outputModule: options.experiments.outputModule,
development,
entry: options.entry,
@@ -543,6 +548,7 @@ const applyModuleDefaults = (
* @param {Object} options options
* @param {string} options.context context
* @param {TargetProperties | false} options.targetProperties target properties
+ * @param {boolean} options.isAffectedByBrowserslist is affected by browserslist
* @param {boolean} options.outputModule is outputModule experiment enabled
* @param {boolean} options.development is development mode
* @param {Entry} options.entry entry option
@@ -551,7 +557,15 @@ const applyModuleDefaults = (
*/
const applyOutputDefaults = (
output,
- { context, targetProperties: tp, outputModule, development, entry, module }
+ {
+ context,
+ targetProperties: tp,
+ isAffectedByBrowserslist,
+ outputModule,
+ development,
+ entry,
+ module
+ }
) => {
/**
* @param {Library=} library the library option
@@ -591,8 +605,8 @@ const applyOutputDefaults = (
}
});
- D(output, "filename", "[name].js");
F(output, "module", () => !!outputModule);
+ D(output, "filename", output.module ? "[name].mjs" : "[name].js");
F(output, "iife", () => !output.module);
D(output, "importFunctionName", "import");
D(output, "importMetaName", "import.meta");
@@ -608,7 +622,7 @@ const applyOutputDefaults = (
// Otherwise prefix "[id]." in front of the basename to make it changing
return filename.replace(/(^|\/)([^/]*(?:\?|$))/, "$1[id].$2");
}
- return "[id].js";
+ return output.module ? "[id].mjs" : "[id].js";
});
D(output, "assetModuleFilename", "[hash][ext][query]");
D(output, "webassemblyModuleFilename", "[hash].module.wasm");
@@ -633,13 +647,34 @@ const applyOutputDefaults = (
});
F(output, "chunkFormat", () => {
if (tp) {
- if (tp.document) return "array-push";
- if (tp.require) return "commonjs";
- if (tp.nodeBuiltins) return "commonjs";
- if (tp.importScripts) return "array-push";
- if (tp.dynamicImport && output.module) return "module";
+ const helpMessage = isAffectedByBrowserslist
+ ? "Make sure that your 'browserslist' includes only platforms that support these features or select an appropriate 'target' to allow selecting a chunk format by default. Alternatively specify the 'output.chunkFormat' directly."
+ : "Select an appropriate 'target' to allow selecting one by default, or specify the 'output.chunkFormat' directly.";
+ if (output.module) {
+ if (tp.dynamicImport) return "module";
+ if (tp.document) return "array-push";
+ throw new Error(
+ "For the selected environment is no default ESM chunk format available:\n" +
+ "ESM exports can be chosen when 'import()' is available.\n" +
+ "JSONP Array push can be chosen when 'document' is available.\n" +
+ helpMessage
+ );
+ } else {
+ if (tp.document) return "array-push";
+ if (tp.require) return "commonjs";
+ if (tp.nodeBuiltins) return "commonjs";
+ if (tp.importScripts) return "array-push";
+ throw new Error(
+ "For the selected environment is no default script chunk format available:\n" +
+ "JSONP Array push can be chosen when 'document' or 'importScripts' is available.\n" +
+ "CommonJs exports can be chosen when 'require' or node builtins are available.\n" +
+ helpMessage
+ );
+ }
}
- return false;
+ throw new Error(
+ "Chunk format can't be selected by default when no target is specified"
+ );
});
F(output, "chunkLoading", () => {
if (tp) {
@@ -709,7 +744,11 @@ const applyOutputDefaults = (
F(output, "path", () => path.join(process.cwd(), "dist"));
F(output, "pathinfo", () => development);
D(output, "sourceMapFilename", "[file].map[query]");
- D(output, "hotUpdateChunkFilename", "[id].[fullhash].hot-update.js");
+ D(
+ output,
+ "hotUpdateChunkFilename",
+ `[id].[fullhash].hot-update.${output.module ? "mjs" : "js"}`
+ );
D(output, "hotUpdateMainFilename", "[runtime].[fullhash].hot-update.json");
D(output, "crossOriginLoading", false);
F(output, "scriptType", () => (output.module ? "module" : false));
diff --git a/lib/esm/ModuleChunkFormatPlugin.js b/lib/esm/ModuleChunkFormatPlugin.js
new file mode 100644
index 000000000..0602ec6be
--- /dev/null
+++ b/lib/esm/ModuleChunkFormatPlugin.js
@@ -0,0 +1,97 @@
+/*
+ MIT License http://www.opensource.org/licenses/mit-license.php
+ Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const { ConcatSource } = require("webpack-sources");
+const { RuntimeGlobals } = require("..");
+const HotUpdateChunk = require("../HotUpdateChunk");
+const Template = require("../Template");
+const {
+ getCompilationHooks
+} = require("../javascript/JavascriptModulesPlugin");
+
+/** @typedef {import("../Compiler")} Compiler */
+
+class ModuleChunkFormatPlugin {
+ /**
+ * Apply the plugin
+ * @param {Compiler} compiler the compiler instance
+ * @returns {void}
+ */
+ apply(compiler) {
+ compiler.hooks.thisCompilation.tap(
+ "ModuleChunkFormatPlugin",
+ compilation => {
+ compilation.hooks.additionalChunkRuntimeRequirements.tap(
+ "ModuleChunkFormatPlugin",
+ (chunk, set) => {
+ if (chunk.hasRuntime()) return;
+ if (compilation.chunkGraph.getNumberOfEntryModules(chunk) > 0) {
+ set.add(RuntimeGlobals.onChunksLoaded);
+ set.add(RuntimeGlobals.require);
+ }
+ }
+ );
+ const hooks = getCompilationHooks(compilation);
+ hooks.renderChunk.tap(
+ "ModuleChunkFormatPlugin",
+ (modules, renderContext) => {
+ const { chunk, chunkGraph } = renderContext;
+ const hotUpdateChunk =
+ chunk instanceof HotUpdateChunk ? chunk : null;
+ const source = new ConcatSource();
+ if (hotUpdateChunk) {
+ throw new Error(
+ "HMR is not implemented for module chunk format yet"
+ );
+ } else {
+ source.add(`export const id = ${JSON.stringify(chunk.id)};\n`);
+ source.add(`export const ids = ${JSON.stringify(chunk.ids)};\n`);
+ source.add(`export const modules = `);
+ source.add(modules);
+ source.add(`;\n`);
+ const runtimeModules =
+ chunkGraph.getChunkRuntimeModulesInOrder(chunk);
+ if (runtimeModules.length > 0) {
+ source.add("export const runtime =\n");
+ source.add(
+ Template.renderChunkRuntimeModules(
+ runtimeModules,
+ renderContext
+ )
+ );
+ }
+ const entries = Array.from(
+ chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk)
+ );
+ if (entries.length > 0) {
+ throw new Error(
+ "Entry modules in chunk is not implemented for module chunk format yet"
+ );
+ }
+ }
+ return source;
+ }
+ );
+ hooks.chunkHash.tap(
+ "ModuleChunkFormatPlugin",
+ (chunk, hash, { chunkGraph, runtimeTemplate }) => {
+ if (chunk.hasRuntime()) return;
+ hash.update("ModuleChunkFormatPlugin");
+ hash.update("1");
+ // TODO
+ // const entries = Array.from(
+ // chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk)
+ // );
+ // updateHashForEntryStartup(hash, chunkGraph, entries, chunk);
+ }
+ );
+ }
+ );
+ }
+}
+
+module.exports = ModuleChunkFormatPlugin;
diff --git a/lib/esm/ModuleChunkLoadingPlugin.js b/lib/esm/ModuleChunkLoadingPlugin.js
new file mode 100644
index 000000000..35fdd726d
--- /dev/null
+++ b/lib/esm/ModuleChunkLoadingPlugin.js
@@ -0,0 +1,63 @@
+/*
+ MIT License http://www.opensource.org/licenses/mit-license.php
+ Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const RuntimeGlobals = require("../RuntimeGlobals");
+const ModuleChunkLoadingRuntimeModule = require("./ModuleChunkLoadingRuntimeModule");
+
+/** @typedef {import("../Compiler")} Compiler */
+
+class ModuleChunkLoadingPlugin {
+ /**
+ * Apply the plugin
+ * @param {Compiler} compiler the compiler instance
+ * @returns {void}
+ */
+ apply(compiler) {
+ compiler.hooks.thisCompilation.tap(
+ "ModuleChunkLoadingPlugin",
+ compilation => {
+ const globalChunkLoading = compilation.outputOptions.chunkLoading;
+ const isEnabledForChunk = chunk => {
+ const options = chunk.getEntryOptions();
+ const chunkLoading =
+ (options && options.chunkLoading) || globalChunkLoading;
+ return chunkLoading === "import";
+ };
+ const onceForChunkSet = new WeakSet();
+ const handler = (chunk, set) => {
+ if (onceForChunkSet.has(chunk)) return;
+ onceForChunkSet.add(chunk);
+ if (!isEnabledForChunk(chunk)) return;
+ set.add(RuntimeGlobals.moduleFactoriesAddOnly);
+ set.add(RuntimeGlobals.hasOwnProperty);
+ compilation.addRuntimeModule(
+ chunk,
+ new ModuleChunkLoadingRuntimeModule(set)
+ );
+ };
+ compilation.hooks.runtimeRequirementInTree
+ .for(RuntimeGlobals.ensureChunkHandlers)
+ .tap("ModuleChunkLoadingPlugin", handler);
+ compilation.hooks.runtimeRequirementInTree
+ .for(RuntimeGlobals.baseURI)
+ .tap("ModuleChunkLoadingPlugin", handler);
+ compilation.hooks.runtimeRequirementInTree
+ .for(RuntimeGlobals.onChunksLoaded)
+ .tap("ModuleChunkLoadingPlugin", handler);
+
+ compilation.hooks.runtimeRequirementInTree
+ .for(RuntimeGlobals.ensureChunkHandlers)
+ .tap("ModuleChunkLoadingPlugin", (chunk, set) => {
+ if (!isEnabledForChunk(chunk)) return;
+ set.add(RuntimeGlobals.getChunkScriptFilename);
+ });
+ }
+ );
+ }
+}
+
+module.exports = ModuleChunkLoadingPlugin;
diff --git a/lib/esm/ModuleChunkLoadingRuntimeModule.js b/lib/esm/ModuleChunkLoadingRuntimeModule.js
new file mode 100644
index 000000000..ebeadc5d9
--- /dev/null
+++ b/lib/esm/ModuleChunkLoadingRuntimeModule.js
@@ -0,0 +1,208 @@
+/*
+ MIT License http://www.opensource.org/licenses/mit-license.php
+*/
+
+"use strict";
+
+const { SyncWaterfallHook } = require("tapable");
+const Compilation = require("../Compilation");
+const RuntimeGlobals = require("../RuntimeGlobals");
+const RuntimeModule = require("../RuntimeModule");
+const Template = require("../Template");
+const {
+ getChunkFilenameTemplate,
+ chunkHasJs
+} = require("../javascript/JavascriptModulesPlugin");
+const { getInitialChunkIds } = require("../javascript/StartupHelpers");
+const compileBooleanMatcher = require("../util/compileBooleanMatcher");
+const { getUndoPath } = require("../util/identifier");
+
+/** @typedef {import("../Chunk")} Chunk */
+
+/**
+ * @typedef {Object} JsonpCompilationPluginHooks
+ * @property {SyncWaterfallHook<[string, Chunk]>} linkPreload
+ * @property {SyncWaterfallHook<[string, Chunk]>} linkPrefetch
+ */
+
+/** @type {WeakMap} */
+const compilationHooksMap = new WeakMap();
+
+class ModuleChunkLoadingRuntimeModule extends RuntimeModule {
+ /**
+ * @param {Compilation} compilation the compilation
+ * @returns {JsonpCompilationPluginHooks} hooks
+ */
+ static getCompilationHooks(compilation) {
+ if (!(compilation instanceof Compilation)) {
+ throw new TypeError(
+ "The 'compilation' argument must be an instance of Compilation"
+ );
+ }
+ let hooks = compilationHooksMap.get(compilation);
+ if (hooks === undefined) {
+ hooks = {
+ linkPreload: new SyncWaterfallHook(["source", "chunk"]),
+ linkPrefetch: new SyncWaterfallHook(["source", "chunk"])
+ };
+ compilationHooksMap.set(compilation, hooks);
+ }
+ return hooks;
+ }
+
+ constructor(runtimeRequirements) {
+ super("import chunk loading", RuntimeModule.STAGE_ATTACH);
+ this._runtimeRequirements = runtimeRequirements;
+ }
+
+ /**
+ * @returns {string} runtime code
+ */
+ generate() {
+ const { compilation, chunk } = this;
+ const {
+ runtimeTemplate,
+ chunkGraph,
+ outputOptions: { importFunctionName, importMetaName }
+ } = compilation;
+ const fn = RuntimeGlobals.ensureChunkHandlers;
+ const withBaseURI = this._runtimeRequirements.has(RuntimeGlobals.baseURI);
+ const withLoading = this._runtimeRequirements.has(
+ RuntimeGlobals.ensureChunkHandlers
+ );
+ const withOnChunkLoad = this._runtimeRequirements.has(
+ RuntimeGlobals.onChunksLoaded
+ );
+ const conditionMap = chunkGraph.getChunkConditionMap(chunk, chunkHasJs);
+ const hasJsMatcher = compileBooleanMatcher(conditionMap);
+ const initialChunkIds = getInitialChunkIds(chunk, chunkGraph);
+
+ const outputName = this.compilation.getPath(
+ getChunkFilenameTemplate(chunk, this.compilation.outputOptions),
+ {
+ chunk,
+ contentHashType: "javascript"
+ }
+ );
+ const rootOutputDir = getUndoPath(
+ outputName,
+ this.compilation.outputOptions.path,
+ true
+ );
+
+ return Template.asString([
+ withBaseURI
+ ? Template.asString([
+ `${RuntimeGlobals.baseURI} = new URL(${JSON.stringify(
+ rootOutputDir
+ )}, ${importMetaName}.url);`
+ ])
+ : "// no baseURI",
+ "",
+ "// object to store loaded and loading chunks",
+ "// undefined = chunk not loaded, null = chunk preloaded/prefetched",
+ "// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded",
+ "var installedChunks = {",
+ Template.indent(
+ Array.from(initialChunkIds, id => `${JSON.stringify(id)}: 0`).join(
+ ",\n"
+ )
+ ),
+ "};",
+ "",
+ withLoading
+ ? Template.asString([
+ `${fn}.j = ${runtimeTemplate.basicFunction(
+ "chunkId, promises",
+ hasJsMatcher !== false
+ ? Template.indent([
+ "// JSONP chunk loading for javascript",
+ `var installedChunkData = ${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;`,
+ 'if(installedChunkData !== 0) { // 0 means "already installed".',
+ Template.indent([
+ "",
+ '// a Promise means "currently loading".',
+ "if(installedChunkData) {",
+ Template.indent([
+ "promises.push(installedChunkData[1]);"
+ ]),
+ "} else {",
+ Template.indent([
+ hasJsMatcher === true
+ ? "if(true) { // all chunks have JS"
+ : `if(${hasJsMatcher("chunkId")}) {`,
+ Template.indent([
+ "// setup Promise in chunk cache",
+ `var promise = ${importFunctionName}(${JSON.stringify(
+ rootOutputDir
+ )} + ${
+ RuntimeGlobals.getChunkScriptFilename
+ }(chunkId)).then(${runtimeTemplate.basicFunction(
+ "data",
+ [
+ runtimeTemplate.destructureObject(
+ ["ids", "modules", "runtime"],
+ "data"
+ ),
+ '// add "modules" to the modules object,',
+ '// then flag all "ids" as loaded and fire callback',
+ "var moduleId, chunkId, i = 0;",
+ "for(moduleId in modules) {",
+ Template.indent([
+ `if(${RuntimeGlobals.hasOwnProperty}(modules, moduleId)) {`,
+ Template.indent(
+ `${RuntimeGlobals.moduleFactories}[moduleId] = modules[moduleId];`
+ ),
+ "}"
+ ]),
+ "}",
+ "if(runtime) runtime(__webpack_require__);",
+ "for(;i < ids.length; i++) {",
+ Template.indent([
+ "chunkId = ids[i];",
+ `if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) && installedChunks[chunkId]) {`,
+ Template.indent(
+ "installedChunks[chunkId][0]();"
+ ),
+ "}",
+ "installedChunks[ids[i]] = 0;"
+ ]),
+ "}",
+ withOnChunkLoad
+ ? `${RuntimeGlobals.onChunksLoaded}();`
+ : ""
+ ]
+ )}, ${runtimeTemplate.basicFunction("e", [
+ "if(installedChunks[chunkId] !== 0) installedChunks[chunkId] = undefined;",
+ "throw e;"
+ ])});`,
+ `var promise = Promise.race([promise, new Promise(${runtimeTemplate.expressionFunction(
+ `installedChunkData = installedChunks[chunkId] = [resolve]`,
+ "resolve"
+ )})])`,
+ `promises.push(installedChunkData[1] = promise);`
+ ]),
+ "} else installedChunks[chunkId] = 0;"
+ ]),
+ "}"
+ ]),
+ "}"
+ ])
+ : Template.indent(["installedChunks[chunkId] = 0;"])
+ )};`
+ ])
+ : "// no chunk on demand loading",
+ "",
+ withOnChunkLoad
+ ? `${
+ RuntimeGlobals.onChunksLoaded
+ }.j = ${runtimeTemplate.returningFunction(
+ "installedChunks[chunkId] === 0",
+ "chunkId"
+ )};`
+ : "// no on chunks loaded"
+ ]);
+ }
+}
+
+module.exports = ModuleChunkLoadingRuntimeModule;
diff --git a/lib/javascript/EnableChunkLoadingPlugin.js b/lib/javascript/EnableChunkLoadingPlugin.js
index b1e53df06..2d938d2da 100644
--- a/lib/javascript/EnableChunkLoadingPlugin.js
+++ b/lib/javascript/EnableChunkLoadingPlugin.js
@@ -96,9 +96,11 @@ class EnableChunkLoadingPlugin {
}).apply(compiler);
break;
}
- case "import":
- // TODO implement import chunk loading
- throw new Error("Chunk Loading via import() is not implemented yet");
+ case "import": {
+ const ModuleChunkLoadingPlugin = require("../esm/ModuleChunkLoadingPlugin");
+ new ModuleChunkLoadingPlugin().apply(compiler);
+ break;
+ }
case "universal":
// TODO implement universal chunk loading
throw new Error("Universal Chunk Loading is not implemented yet");
diff --git a/package.json b/package.json
index 5de67d6d2..c26c94051 100644
--- a/package.json
+++ b/package.json
@@ -131,11 +131,11 @@
],
"scripts": {
"setup": "node ./setup/setup.js",
- "test": "node --max-old-space-size=4096 --trace-deprecation node_modules/jest-cli/bin/jest",
+ "test": "node --max-old-space-size=4096 --experimental-vm-modules --trace-deprecation node_modules/jest-cli/bin/jest",
"test:update-snapshots": "yarn jest -u",
- "test:integration": "node --max-old-space-size=4096 --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"/test/*.test.js\"",
- "test:basic": "node --max-old-space-size=4096 --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"/te{st/TestCasesNormal,st/StatsTestCases,st/ConfigTestCases}.test.js\"",
- "test:unit": "node --max-old-space-size=4096 --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"/test/*.unittest.js\"",
+ "test:integration": "node --max-old-space-size=4096 --experimental-vm-modules --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"/test/*.test.js\"",
+ "test:basic": "node --max-old-space-size=4096 --experimental-vm-modules --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"/te{st/TestCasesNormal,st/StatsTestCases,st/ConfigTestCases}.test.js\"",
+ "test:unit": "node --max-old-space-size=4096 --experimental-vm-modules --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"/test/*.unittest.js\"",
"travis:integration": "yarn cover:integration --ci $JEST",
"travis:basic": "yarn cover:basic --ci $JEST",
"travis:lintunit": "yarn lint && yarn cover:unit --ci $JEST",
@@ -162,13 +162,13 @@
"pretty-lint": "yarn pretty-lint-base --check",
"yarn-lint": "yarn-deduplicate --fail --list -s highest yarn.lock",
"yarn-lint-fix": "yarn-deduplicate -s highest yarn.lock",
- "benchmark": "node --max-old-space-size=4096 --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"/test/*.benchmark.js\" --runInBand",
+ "benchmark": "node --max-old-space-size=4096 --experimental-vm-modules --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"/test/*.benchmark.js\" --runInBand",
"cover": "yarn cover:all && yarn cover:report",
"cover:clean": "rimraf .nyc_output coverage",
- "cover:all": "node --max-old-space-size=4096 node_modules/jest-cli/bin/jest --coverage",
- "cover:basic": "node --max-old-space-size=4096 node_modules/jest-cli/bin/jest --testMatch \"/te{st/TestCasesNormal,st/StatsTestCases,st/ConfigTestCases}.test.js\" --coverage",
- "cover:integration": "node --max-old-space-size=4096 node_modules/jest-cli/bin/jest --testMatch \"/test/*.test.js\" --coverage",
- "cover:unit": "node --max-old-space-size=4096 node_modules/jest-cli/bin/jest --testMatch \"/test/*.unittest.js\" --coverage",
+ "cover:all": "node --max-old-space-size=4096 --experimental-vm-modules node_modules/jest-cli/bin/jest --coverage",
+ "cover:basic": "node --max-old-space-size=4096 --experimental-vm-modules node_modules/jest-cli/bin/jest --testMatch \"/te{st/TestCasesNormal,st/StatsTestCases,st/ConfigTestCases}.test.js\" --coverage",
+ "cover:integration": "node --max-old-space-size=4096 --experimental-vm-modules node_modules/jest-cli/bin/jest --testMatch \"/test/*.test.js\" --coverage",
+ "cover:unit": "node --max-old-space-size=4096 --experimental-vm-modules node_modules/jest-cli/bin/jest --testMatch \"/test/*.unittest.js\" --coverage",
"cover:types": "node node_modules/tooling/type-coverage",
"cover:merge": "nyc merge .nyc_output coverage/coverage-nyc.json && rimraf .nyc_output",
"cover:report": "nyc report -t coverage"
diff --git a/test/ConfigTestCases.template.js b/test/ConfigTestCases.template.js
index 79d7e7931..324f1b284 100644
--- a/test/ConfigTestCases.template.js
+++ b/test/ConfigTestCases.template.js
@@ -84,7 +84,12 @@ const describeCases = config => {
if (typeof options.output.pathinfo === "undefined")
options.output.pathinfo = true;
if (!options.output.filename)
- options.output.filename = "bundle" + idx + ".js";
+ options.output.filename =
+ "bundle" +
+ idx +
+ (options.experiments && options.experiments.outputModule
+ ? ".mjs"
+ : ".js");
if (config.cache) {
options.cache = {
cacheDirectory,
@@ -295,7 +300,12 @@ const describeCases = config => {
const requireCache = Object.create(null);
// eslint-disable-next-line no-loop-func
- const _require = (currentDirectory, options, module) => {
+ const _require = (
+ currentDirectory,
+ options,
+ module,
+ esmModule
+ ) => {
if (Array.isArray(module) || /^\.\.?\//.test(module)) {
let content;
let p;
@@ -339,35 +349,13 @@ const describeCases = config => {
};
requireCache[p] = m;
let runInNewContext = false;
- let oldCurrentScript = document.currentScript;
- document.currentScript = new CurrentScript(subPath);
const moduleScope = {
- require: _require.bind(
- null,
- path.dirname(p),
- options
- ),
- importScripts: url => {
- expect(url).toMatch(
- /^https:\/\/test\.cases\/path\//
- );
- _require(
- outputDirectory,
- options,
- `.${url.slice("https://test.cases/path".length)}`
- );
- },
- module: m,
- exports: m.exports,
- __dirname: path.dirname(p),
- __filename: p,
it: _it,
beforeEach: _beforeEach,
afterEach: _afterEach,
expect,
jest,
- _globalAssign: { expect },
__STATS__: jsonStats,
nsObj: m => {
Object.defineProperty(m, Symbol.toStringTag, {
@@ -376,6 +364,36 @@ const describeCases = config => {
return m;
}
};
+ const isModule =
+ p.endsWith(".mjs") &&
+ options.experiments &&
+ options.experiments.outputModule;
+ if (!isModule) {
+ Object.assign(moduleScope, {
+ require: _require.bind(
+ null,
+ path.dirname(p),
+ options
+ ),
+ importScripts: url => {
+ expect(url).toMatch(
+ /^https:\/\/test\.cases\/path\//
+ );
+ _require(
+ outputDirectory,
+ options,
+ `.${url.slice(
+ "https://test.cases/path".length
+ )}`
+ );
+ },
+ module: m,
+ exports: m.exports,
+ __dirname: path.dirname(p),
+ __filename: p,
+ _globalAssign: { expect }
+ });
+ }
if (
options.target === "web" ||
options.target === "webworker"
@@ -392,21 +410,88 @@ const describeCases = config => {
if (testConfig.moduleScope) {
testConfig.moduleScope(moduleScope);
}
- const args = Object.keys(moduleScope);
- const argValues = args.map(arg => moduleScope[arg]);
- if (!runInNewContext)
- content = `Object.assign(global, _globalAssign); ${content}`;
- const code = `(function(${args.join(
- ", "
- )}) {${content}\n})`;
- const fn = runInNewContext
- ? vm.runInNewContext(code, globalContext, p)
- : vm.runInThisContext(code, p);
- fn.call(m.exports, ...argValues);
-
- //restore state
- document.currentScript = oldCurrentScript;
+ if (isModule) {
+ if (!vm.SourceTextModule)
+ throw new Error(
+ "Running this test requires '--experimental-vm-modules'.\nRun with 'node --experimental-vm-modules node_modules/jest-cli/bin/jest'."
+ );
+ const esm = new vm.SourceTextModule(content, {
+ identifier: p,
+ context: vm.createContext(moduleScope, {
+ name: `context for ${p}`
+ }),
+ importModuleDynamically: async (
+ specifier,
+ module
+ ) => {
+ const result = await _require(
+ path.dirname(p),
+ options,
+ specifier,
+ "evaluated"
+ );
+ if (
+ result instanceof
+ (vm.Module ||
+ /* node.js 10 */ vm.SourceTextModule)
+ ) {
+ return result;
+ }
+ if (!vm.SyntheticModule) return result;
+ return new vm.SyntheticModule(
+ [
+ ...new Set([
+ "default",
+ ...Object.keys(result)
+ ])
+ ],
+ function () {
+ for (const key in result) {
+ this.setExport(key, result[key]);
+ }
+ this.setExport("default", result);
+ }
+ );
+ }
+ });
+ if (esmModule === "unlinked") return esm;
+ return (async () => {
+ await esm.link(
+ async (specifier, referencingModule) => {
+ return _require(
+ path.dirname(referencingModule.identfier),
+ options,
+ specifier,
+ "unlinked"
+ );
+ }
+ );
+ // node.js 10 needs instantiate
+ if (esm.instantiate) esm.instantiate();
+ await esm.evaluate();
+ if (esmModule === "evaluated") return esm;
+ const ns = esm.namespace;
+ return ns.default && ns.default instanceof Promise
+ ? ns.default
+ : ns;
+ })();
+ } else {
+ if (!runInNewContext)
+ content = `Object.assign(global, _globalAssign); ${content}`;
+ const args = Object.keys(moduleScope);
+ const argValues = args.map(arg => moduleScope[arg]);
+ const code = `(function(${args.join(
+ ", "
+ )}) {${content}\n})`;
+ let oldCurrentScript = document.currentScript;
+ document.currentScript = new CurrentScript(subPath);
+ const fn = runInNewContext
+ ? vm.runInNewContext(code, globalContext, p)
+ : vm.runInThisContext(code, p);
+ fn.call(m.exports, ...argValues);
+ document.currentScript = oldCurrentScript;
+ }
return m.exports;
} else if (
testConfig.modules &&
diff --git a/test/Defaults.unittest.js b/test/Defaults.unittest.js
index 1403be5aa..a0644aa66 100644
--- a/test/Defaults.unittest.js
+++ b/test/Defaults.unittest.js
@@ -861,6 +861,15 @@ describe("Defaults", () => {
- "externalsType": "var",
+ "externalsType": "module",
@@ ... @@
+ - "chunkFilename": "[name].js",
+ + "chunkFilename": "[name].mjs",
+ @@ ... @@
+ - "filename": "[name].js",
+ + "filename": "[name].mjs",
+ @@ ... @@
+ - "hotUpdateChunkFilename": "[id].[fullhash].hot-update.js",
+ + "hotUpdateChunkFilename": "[id].[fullhash].hot-update.mjs",
+ @@ ... @@
- "iife": true,
+ "iife": false,
@@ ... @@
diff --git a/test/TestCases.template.js b/test/TestCases.template.js
index c54a57d93..cbe9e1b41 100644
--- a/test/TestCases.template.js
+++ b/test/TestCases.template.js
@@ -3,6 +3,7 @@
const path = require("path");
const fs = require("graceful-fs");
const vm = require("vm");
+const { pathToFileURL } = require("url");
const rimraf = require("rimraf");
const webpack = require("..");
const TerserPlugin = require("terser-webpack-plugin");
@@ -122,7 +123,7 @@ const describeCases = config => {
output: {
pathinfo: true,
path: outputDirectory,
- filename: "bundle.js"
+ filename: config.module ? "bundle.mjs" : "bundle.js"
},
resolve: {
modules: ["web_modules", "node_modules"],
@@ -186,7 +187,8 @@ const describeCases = config => {
}),
experiments: {
asyncWebAssembly: true,
- topLevelAwait: true
+ topLevelAwait: true,
+ ...(config.module ? { outputModule: true } : {})
}
};
beforeAll(done => {
@@ -309,36 +311,73 @@ const describeCases = config => {
function _require(module) {
if (module.substr(0, 2) === "./") {
const p = path.join(outputDirectory, module);
- const fn = vm.runInThisContext(
- "(function(require, module, exports, __dirname, __filename, it, expect) {" +
- "global.expect = expect;" +
- 'function nsObj(m) { Object.defineProperty(m, Symbol.toStringTag, { value: "Module" }); return m; }' +
- fs.readFileSync(p, "utf-8") +
- "\n})",
- p
- );
- const m = {
- exports: {},
- webpackTestSuiteModule: true
- };
- fn.call(
- m.exports,
- _require,
- m,
- m.exports,
- outputDirectory,
- p,
- _it,
- expect
- );
- return m.exports;
+ if (p.endsWith(".mjs")) {
+ const module = new vm.SourceTextModule(
+ `import { it, expect } from "TEST_ENV";
+function nsObj(m) { Object.defineProperty(m, Symbol.toStringTag, { value: "Module" }); return m; }
+${fs.readFileSync(p, "utf-8")}`,
+ {
+ identifier: p,
+ lineOffset: 1,
+ initializeImportMeta: (meta, module) => {
+ meta.url = pathToFileURL(p);
+ },
+ importModuleDynamically: (specifier, module) => {
+ return _require(specifier);
+ }
+ }
+ );
+ return module
+ .link((specifier, module) => {
+ if (specifier === "TEST_ENV") {
+ const m = new vm.SyntheticModule(
+ ["it", "expect"],
+ function () {
+ this.setExport("it", _it);
+ this.setExport("expect", expect);
+ }
+ );
+ return m;
+ }
+ })
+ .then(() => module.evaluate())
+ .then(() => module.namespace);
+ } else {
+ const fn = vm.runInThisContext(
+ "(function(require, module, exports, __dirname, __filename, it, expect) {" +
+ "global.expect = expect;" +
+ 'function nsObj(m) { Object.defineProperty(m, Symbol.toStringTag, { value: "Module" }); return m; }' +
+ fs.readFileSync(p, "utf-8") +
+ "\n})",
+ p
+ );
+ const m = {
+ exports: {},
+ webpackTestSuiteModule: true
+ };
+ fn.call(
+ m.exports,
+ _require,
+ m,
+ m.exports,
+ outputDirectory,
+ p,
+ _it,
+ expect
+ );
+ return m.exports;
+ }
} else return require(module);
}
_require.webpackTestSuiteRequire = true;
- _require("./bundle.js");
- if (getNumberOfTests() === 0)
- return done(new Error("No tests exported by test case"));
- done();
+ const promise = _require("./" + options.output.filename);
+ if (promise && promise.then) promise.then(finish);
+ else finish();
+ function finish() {
+ if (getNumberOfTests() === 0)
+ return done(new Error("No tests exported by test case"));
+ done();
+ }
},
10000
);
diff --git a/test/TestCasesModule.test.js b/test/TestCasesModule.test.js
new file mode 100644
index 000000000..16d6b288e
--- /dev/null
+++ b/test/TestCasesModule.test.js
@@ -0,0 +1,13 @@
+const { describeCases } = require("./TestCases.template");
+const vm = require("vm");
+
+describe("TestCases", () => {
+ if (!vm.SourceTextModule) {
+ it("module can't run without --experimental-vm-modules");
+ return;
+ }
+ describeCases({
+ name: "module",
+ module: true
+ });
+});
diff --git a/test/__snapshots__/StatsTestCases.test.js.snap b/test/__snapshots__/StatsTestCases.test.js.snap
index 8335cbae2..09034942f 100644
--- a/test/__snapshots__/StatsTestCases.test.js.snap
+++ b/test/__snapshots__/StatsTestCases.test.js.snap
@@ -1673,8 +1673,8 @@ webpack x.x.x compiled successfully in X ms"
`;
exports[`StatsTestCases should print correct stats for output-module 1`] = `
-"asset main.js 9.95 KiB [emitted] [javascript module] (name: main)
-asset 52.js 417 bytes [emitted] [javascript module]
+"asset main.mjs 9.95 KiB [emitted] [javascript module] (name: main)
+asset 52.mjs 417 bytes [emitted] [javascript module]
runtime modules 6.01 KiB 8 modules
orphan modules 38 bytes [orphan] 1 module
cacheable modules 263 bytes
diff --git a/test/configCases/library/0-create-library/webpack.config.js b/test/configCases/library/0-create-library/webpack.config.js
index 61ada5eb1..f77134bc1 100644
--- a/test/configCases/library/0-create-library/webpack.config.js
+++ b/test/configCases/library/0-create-library/webpack.config.js
@@ -2,6 +2,21 @@ const path = require("path");
const webpack = require("../../../../");
/** @type {function(any, any): import("../../../../").Configuration[]} */
module.exports = (env, { testPath }) => [
+ {
+ output: {
+ filename: "esm.js",
+ libraryTarget: "module"
+ },
+ target: "node14",
+ resolve: {
+ alias: {
+ external: "./non-external"
+ }
+ },
+ experiments: {
+ outputModule: true
+ }
+ },
{
output: {
filename: "commonjs.js",
diff --git a/test/configCases/library/1-use-library/webpack.config.js b/test/configCases/library/1-use-library/webpack.config.js
index 0ca795138..4fc8cdb19 100644
--- a/test/configCases/library/1-use-library/webpack.config.js
+++ b/test/configCases/library/1-use-library/webpack.config.js
@@ -2,6 +2,18 @@ var webpack = require("../../../../");
var path = require("path");
/** @type {function(any, any): import("../../../../").Configuration[]} */
module.exports = (env, { testPath }) => [
+ {
+ resolve: {
+ alias: {
+ library: path.resolve(testPath, "../0-create-library/esm.js")
+ }
+ },
+ plugins: [
+ new webpack.DefinePlugin({
+ NAME: JSON.stringify("esm")
+ })
+ ]
+ },
{
resolve: {
alias: {
diff --git a/test/configCases/output-module/simple/chunk.js b/test/configCases/output-module/simple/chunk.js
new file mode 100644
index 000000000..7a4e8a723
--- /dev/null
+++ b/test/configCases/output-module/simple/chunk.js
@@ -0,0 +1 @@
+export default 42;
diff --git a/test/configCases/output-module/simple/index.js b/test/configCases/output-module/simple/index.js
new file mode 100644
index 000000000..bdf68397f
--- /dev/null
+++ b/test/configCases/output-module/simple/index.js
@@ -0,0 +1,12 @@
+it("should execute as module", () => {
+ expect(
+ (function () {
+ return !this;
+ })()
+ ).toBe(true);
+});
+
+it("should be able to load a chunk", async () => {
+ const module = await import("./chunk");
+ expect(module.default).toBe(42);
+});
diff --git a/test/configCases/output-module/simple/webpack.config.js b/test/configCases/output-module/simple/webpack.config.js
new file mode 100644
index 000000000..b8e5da8c1
--- /dev/null
+++ b/test/configCases/output-module/simple/webpack.config.js
@@ -0,0 +1,7 @@
+/** @type {import("../../../../").Configuration} */
+module.exports = {
+ experiments: {
+ outputModule: true
+ },
+ target: "node14"
+};
diff --git a/types.d.ts b/types.d.ts
index 3ce91a4ed..d7b4bc201 100644
--- a/types.d.ts
+++ b/types.d.ts
@@ -9671,6 +9671,7 @@ declare abstract class RuntimeTemplate {
expressionFunction(expression?: any, args?: string): string;
emptyFunction(): "x => {}" | "function() {}";
destructureArray(items?: any, value?: any): string;
+ destructureObject(items?: any, value?: any): string;
iife(args?: any, body?: any): string;
forEach(variable?: any, array?: any, body?: any): string;