add `module` library support, test and example

add test cases for `experiments.outputModule`
This commit is contained in:
Tobias Koppers 2021-06-23 13:58:40 +02:00
parent c3af61fe2a
commit 4da99d8254
34 changed files with 1321 additions and 95 deletions

View File

@ -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__ = ({});
```
<details><summary><code>/* webpack runtime code */</code></summary>
``` 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
/******/ })();
/******/
/************************************************************************/
```
</details>
``` 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<i.length;u++)n=i[u],r.o(e,n)&&e[n]&&e[n][0](),e[i[u]]=0}),(t=>{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
```

View File

@ -0,0 +1 @@
require("../build-common");

View File

@ -0,0 +1,10 @@
export let value = 0;
export function increment() {
value++;
}
export function decrement() {
value--;
}
export function reset() {
value = 0;
}

View File

@ -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);

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Worker example</title>
</head>
<body>
<script src="./dist/main.mjs" type="module"></script>
</body>
</html>

View File

@ -0,0 +1,5 @@
export const resetCounter = async () => {
(await import("./counter")).reset();
};
export const print = value => console.log(value);

View File

@ -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}}_
```

View File

@ -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
}
};

View File

@ -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__ = {};
/******/
```
<details><summary><code>/* webpack runtime code */</code></summary>
``` 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 });
/******/ };
/******/ })();
/******/
/************************************************************************/
```
</details>
``` 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
```

View File

@ -0,0 +1 @@
require("../build-common");

View File

@ -0,0 +1,10 @@
export let value = 0;
export function increment() {
value++;
}
export function decrement() {
value--;
}
export function reset() {
value = 0;
}

View File

@ -0,0 +1,2 @@
export * from "./counter";
export * from "./methods";

View File

@ -0,0 +1,3 @@
export { reset as resetCounter } from "./counter";
export const print = value => console.log(value);

View File

@ -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}}_
```

View File

@ -0,0 +1,14 @@
module.exports = {
output: {
module: true,
library: {
type: "module"
}
},
optimization: {
concatenateModules: true
},
experiments: {
outputModule: true
}
};

View File

@ -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)})()`;
}

View File

@ -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 + "'."

View File

@ -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));

View File

@ -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;

View File

@ -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;

View File

@ -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<Compilation, JsonpCompilationPluginHooks>} */
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;

View File

@ -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");

View File

@ -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 \"<rootDir>/test/*.test.js\"",
"test:basic": "node --max-old-space-size=4096 --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"<rootDir>/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 \"<rootDir>/test/*.unittest.js\"",
"test:integration": "node --max-old-space-size=4096 --experimental-vm-modules --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"<rootDir>/test/*.test.js\"",
"test:basic": "node --max-old-space-size=4096 --experimental-vm-modules --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"<rootDir>/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 \"<rootDir>/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 \"<rootDir>/test/*.benchmark.js\" --runInBand",
"benchmark": "node --max-old-space-size=4096 --experimental-vm-modules --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"<rootDir>/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 \"<rootDir>/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 \"<rootDir>/test/*.test.js\" --coverage",
"cover:unit": "node --max-old-space-size=4096 node_modules/jest-cli/bin/jest --testMatch \"<rootDir>/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 \"<rootDir>/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 \"<rootDir>/test/*.test.js\" --coverage",
"cover:unit": "node --max-old-space-size=4096 --experimental-vm-modules node_modules/jest-cli/bin/jest --testMatch \"<rootDir>/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"

View File

@ -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 &&

View File

@ -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,
@@ ... @@

View File

@ -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
);

View File

@ -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
});
});

View File

@ -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

View File

@ -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",

View File

@ -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: {

View File

@ -0,0 +1 @@
export default 42;

View File

@ -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);
});

View File

@ -0,0 +1,7 @@
/** @type {import("../../../../").Configuration} */
module.exports = {
experiments: {
outputModule: true
},
target: "node14"
};

1
types.d.ts vendored
View File

@ -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;