add experimental lazy compilation

This commit is contained in:
Tobias Koppers 2021-01-21 16:32:49 +01:00
parent 287707ce4d
commit a1515fa9f0
25 changed files with 857 additions and 9 deletions

View File

@ -1041,6 +1041,30 @@ export interface Experiments {
* Enable module and chunk layers.
*/
layers?: boolean;
/**
* Compile import()s only when they are accessed.
*/
lazyCompilation?:
| boolean
| {
/**
* A custom backend.
*/
backend?:
| ((
compiler: import("../lib/Compiler"),
client: string,
callback: (err?: Error, api?: any) => void
) => void)
| ((
compiler: import("../lib/Compiler"),
client: string
) => Promise<any>);
/**
* A custom client.
*/
client?: string;
};
/**
* Allow output javascript files as module source type.
*/

View File

@ -0,0 +1,67 @@
To run this example you need to install `webpack-dev-server` and run `webpack serve`.
# example.js
```javascript
const libraries = {
react: () => import("react"),
acorn: () => import("acorn"),
"core-js": () => import("core-js"),
lodash: () => import("lodash"),
xxhashjs: () => import("xxhashjs"),
"all of them": () => import("./all")
};
window.onload = () => {
document.body.style = "font-size: 16pt;";
const pre = document.createElement("pre");
pre.style = "height: 200px; overflow-y: auto";
pre.innerText =
"Click on a button to load the library with import(). The first click triggers a lazy compilation of the module.";
for (const key of Object.keys(libraries)) {
const button = document.createElement("button");
const loadFn = libraries[key];
button.innerText = key;
button.onclick = async () => {
pre.innerText = "Loading " + key + "...";
const result = await loadFn();
pre.innerText = `${key} = {\n ${Object.keys(result).join(",\n ")}\n}`;
};
document.body.appendChild(button);
}
const button = document.createElement("button");
button.innerText = "Load more...";
button.onclick = async () => {
pre.innerText = "Loading more...";
await import("./more");
pre.innerText = "More libraries available.";
};
document.body.appendChild(button);
document.body.appendChild(pre);
};
```
# webpack.config.js
```javascript
const { HotModuleReplacementPlugin } = require("../../");
module.exports = {
mode: "development",
entry: {
main: "./example.js"
},
cache: {
type: "filesystem",
idleTimeout: 5000
},
experiments: {
lazyCompilation: true
},
devServer: {
hot: true,
publicPath: "/dist/"
},
plugins: [new HotModuleReplacementPlugin()]
};
```

View File

@ -0,0 +1,8 @@
export * from "react";
export * from "react-dom";
export * from "acorn";
export * from "core-js";
export * from "date-fns";
export * from "lodash";
export * from "lodash-es";
export * from "xxhashjs";

View File

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

View File

@ -0,0 +1,36 @@
const libraries = {
react: () => import("react"),
acorn: () => import("acorn"),
"core-js": () => import("core-js"),
lodash: () => import("lodash"),
xxhashjs: () => import("xxhashjs"),
"all of them": () => import("./all")
};
window.onload = () => {
document.body.style = "font-size: 16pt;";
const pre = document.createElement("pre");
pre.style = "height: 200px; overflow-y: auto";
pre.innerText =
"Click on a button to load the library with import(). The first click triggers a lazy compilation of the module.";
for (const key of Object.keys(libraries)) {
const button = document.createElement("button");
const loadFn = libraries[key];
button.innerText = key;
button.onclick = async () => {
pre.innerText = "Loading " + key + "...";
const result = await loadFn();
pre.innerText = `${key} = {\n ${Object.keys(result).join(",\n ")}\n}`;
};
document.body.appendChild(button);
}
const button = document.createElement("button");
button.innerText = "Load more...";
button.onclick = async () => {
pre.innerText = "Loading more...";
await import("./more");
pre.innerText = "More libraries available.";
};
document.body.appendChild(button);
document.body.appendChild(pre);
};

View File

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<head>
<script src="dist/main.js"></script>
</head>
</html>

View File

@ -0,0 +1,21 @@
const libraries = {
"react-dom": () => import("react-dom"),
"date-fns": () => import("date-fns"),
xxhashjs: () => import("xxhashjs"),
"lodash-es": () => import("lodash-es")
};
const pre = document.querySelector("pre");
for (const key of Object.keys(libraries)) {
const button = document.createElement("button");
const loadFn = libraries[key];
button.innerText = key;
button.onclick = async () => {
pre.innerText = "Loading " + key + "...";
const result = await loadFn();
pre.innerText = `${key} = {\n ${Object.keys(result).join(",\n ")}\n}`;
};
document.body.appendChild(button);
}
export {};

View File

@ -0,0 +1,13 @@
To run this example you need to install `webpack-dev-server` and run `webpack serve`.
# example.js
```javascript
_{{example.js}}_
```
# webpack.config.js
```javascript
_{{webpack.config.js}}_
```

View File

@ -0,0 +1,20 @@
const { HotModuleReplacementPlugin } = require("../../");
module.exports = {
mode: "development",
entry: {
main: "./example.js"
},
cache: {
type: "filesystem",
idleTimeout: 5000
},
experiments: {
lazyCompilation: true
},
devServer: {
hot: true,
publicPath: "/dist/"
},
plugins: [new HotModuleReplacementPlugin()]
};

View File

@ -0,0 +1,29 @@
/* global __resourceQuery */
"use strict";
if (!module.hot) {
throw new Error(
"Environment doesn't support lazy compilation (requires Hot Module Replacement enabled)"
);
}
var urlBase = decodeURIComponent(__resourceQuery.slice(1));
exports.keepAlive = function (key) {
var response;
require("http")
.request(
urlBase + key,
{
agent: false,
headers: { accept: "text/event-stream" }
},
function (res) {
response = res;
}
)
.end();
return function () {
response.destroy();
};
};

View File

@ -0,0 +1,40 @@
/* global __resourceQuery */
"use strict";
if (typeof EventSource !== "function" || !module.hot) {
throw new Error(
"Environment doesn't support lazy compilation (requires EventSource and Hot Module Replacement enabled)"
);
}
var urlBase = decodeURIComponent(__resourceQuery.slice(1));
var activeEventSource;
var activeKeys = new Map();
var updateEventSource = function updateEventSource() {
if (activeEventSource) activeEventSource.close();
activeEventSource = new EventSource(
urlBase + Array.from(activeKeys.keys()).join("@")
);
};
exports.keepAlive = function (key) {
var value = activeKeys.get(key) || 0;
activeKeys.set(key, value + 1);
if (value === 0) {
updateEventSource();
}
return function () {
setTimeout(function () {
var value = activeKeys.get(key);
if (value === 1) {
activeKeys.delete(key);
updateEventSource();
} else {
activeKeys.set(key, value - 1);
}
}, 1000);
};
};

View File

@ -168,6 +168,8 @@ class Compiler {
invalid: new SyncHook(["filename", "changeTime"]),
/** @type {SyncHook<[]>} */
watchClose: new SyncHook([]),
/** @type {AsyncSeriesHook<[]>} */
shutdown: new AsyncSeriesHook([]),
/** @type {SyncBailHook<[string, string, any[]], true>} */
infrastructureLog: new SyncBailHook(["origin", "type", "args"]),
@ -1075,7 +1077,10 @@ ${other}`);
* @returns {void}
*/
close(callback) {
this.cache.shutdown(callback);
this.hooks.shutdown.callAsync(err => {
if (err) return callback(err);
this.cache.shutdown(callback);
});
}
}

View File

@ -244,6 +244,22 @@ class WebpackOptionsApply extends OptionsApply {
}).apply(compiler);
}
if (options.experiments.lazyCompilation) {
const LazyCompilationPlugin = require("./hmr/LazyCompilationPlugin");
new LazyCompilationPlugin({
backend:
(typeof options.experiments.lazyCompilation === "object" &&
options.experiments.lazyCompilation.backend) ||
require("./hmr/lazyCompilationBackend"),
client:
(typeof options.experiments.lazyCompilation === "object" &&
options.experiments.lazyCompilation.client) ||
`webpack/hot/lazy-compilation-${
options.externalsPresets.node ? "node" : "web"
}.js`
}).apply(compiler);
}
new EntryOptionPlugin().apply(compiler);
compiler.hooks.entryOption.call(options.context, options.entry);

View File

@ -11,7 +11,10 @@ const { formatSize } = require("../SizeFormatHelpers");
const LazySet = require("../util/LazySet");
const makeSerializable = require("../util/makeSerializable");
const memoize = require("../util/memoize");
const { createFileSerializer } = require("../util/serialization");
const {
createFileSerializer,
NOT_SERIALIZABLE
} = require("../util/serialization");
/** @typedef {import("../../declarations/WebpackOptions").SnapshotOptions} SnapshotOptions */
/** @typedef {import("../Cache").Etag} Etag */
@ -525,6 +528,7 @@ class PackContentItems {
write(value);
} catch (e) {
rollback(s);
if (e === NOT_SERIALIZABLE) continue;
logger.warn(
`Skipped not serializable cache item '${key}': ${e.message}`
);

View File

@ -0,0 +1,327 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const { RawSource } = require("webpack-sources");
const AsyncDependenciesBlock = require("../AsyncDependenciesBlock");
const Dependency = require("../Dependency");
const Module = require("../Module");
const ModuleFactory = require("../ModuleFactory");
const RuntimeGlobals = require("../RuntimeGlobals");
const Template = require("../Template");
const CommonJsRequireDependency = require("../dependencies/CommonJsRequireDependency");
const { registerNotSerializable } = require("../util/serialization");
/** @typedef {import("../../declarations/WebpackOptions")} WebpackOptions */
/** @typedef {import("../Compilation")} Compilation */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */
/** @typedef {import("../Module").BuildMeta} BuildMeta */
/** @typedef {import("../Module").CodeGenerationContext} CodeGenerationContext */
/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */
/** @typedef {import("../Module").LibIdentOptions} LibIdentOptions */
/** @typedef {import("../Module").NeedBuildContext} NeedBuildContext */
/** @typedef {import("../ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */
/** @typedef {import("../ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */
/** @typedef {import("../RequestShortener")} RequestShortener */
/** @typedef {import("../ResolverFactory").ResolverWithOptions} ResolverWithOptions */
/** @typedef {import("../WebpackError")} WebpackError */
/** @typedef {import("../util/Hash")} Hash */
/** @typedef {import("../util/fs").InputFileSystem} InputFileSystem */
const TYPES = new Set(["javascript"]);
class LazyCompilationDependency extends Dependency {
constructor(originalModule) {
super();
this.originalModule = originalModule;
}
get category() {
return "esm";
}
get type() {
return "lazy import()";
}
/**
* @returns {string | null} an identifier to merge equal requests
*/
getResourceIdentifier() {
return this.originalModule.identifier();
}
}
registerNotSerializable(LazyCompilationDependency);
class LazyCompilationProxyModule extends Module {
constructor(originalModule, request, client, data, active) {
super(
"lazy-compilation-proxy",
originalModule.context,
originalModule.layer
);
this.originalModule = originalModule;
this.request = request;
this.client = client;
this.data = data;
this.active = active;
}
/**
* @returns {string} a unique identifier of the module
*/
identifier() {
return `lazy-compilation-proxy|${this.originalModule.identifier()}`;
}
/**
* @param {RequestShortener} requestShortener the request shortener
* @returns {string} a user readable identifier of the module
*/
readableIdentifier(requestShortener) {
return `lazy-compilation-proxy ${this.originalModule.readableIdentifier(
requestShortener
)}`;
}
/**
* Assuming this module is in the cache. Update the (cached) module with
* the fresh module from the factory. Usually updates internal references
* and properties.
* @param {Module} module fresh module
* @returns {void}
*/
updateCacheModule(module) {
super.updateCacheModule(module);
const m = /** @type {LazyCompilationProxyModule} */ (module);
this.active = m.active;
}
/**
* @param {LibIdentOptions} options options
* @returns {string | null} an identifier for library inclusion
*/
libIdent(options) {
return `${this.originalModule.libIdent(options)}!lazy-compilation-proxy`;
}
/**
* @param {NeedBuildContext} context context info
* @param {function(WebpackError=, boolean=): void} callback callback function, returns true, if the module needs a rebuild
* @returns {void}
*/
needBuild(context, callback) {
callback(null, !this.buildInfo || this.buildInfo.active !== this.active);
}
/**
* @param {WebpackOptions} options webpack options
* @param {Compilation} compilation the compilation
* @param {ResolverWithOptions} resolver the resolver
* @param {InputFileSystem} fs the file system
* @param {function(WebpackError=): void} callback callback function
* @returns {void}
*/
build(options, compilation, resolver, fs, callback) {
this.buildInfo = {
active: this.active
};
/** @type {BuildMeta} */
this.buildMeta = {};
this.clearDependenciesAndBlocks();
const dep = new CommonJsRequireDependency(this.client);
this.addDependency(dep);
if (this.active) {
const dep = new LazyCompilationDependency(this.originalModule);
const block = new AsyncDependenciesBlock({});
block.addDependency(dep);
this.addBlock(block);
}
callback();
}
/**
* @returns {Set<string>} types available (do not mutate)
*/
getSourceTypes() {
return TYPES;
}
/**
* @param {string=} type the source type for which the size should be estimated
* @returns {number} the estimated size of the module (must be non-zero)
*/
size(type) {
return 200;
}
/**
* @param {CodeGenerationContext} context context for code generation
* @returns {CodeGenerationResult} result
*/
codeGeneration({ runtimeTemplate, chunkGraph, moduleGraph }) {
const sources = new Map();
const runtimeRequirements = new Set();
runtimeRequirements.add(RuntimeGlobals.module);
const clientDep = /** @type {CommonJsRequireDependency} */ (this
.dependencies[0]);
const clientModule = moduleGraph.getModule(clientDep);
const block = this.blocks[0];
const keepActive = Template.asString([
`var client = ${runtimeTemplate.moduleExports({
module: clientModule,
chunkGraph,
request: clientDep.userRequest,
runtimeRequirements
})}`,
`var data = ${JSON.stringify(this.data)};`,
`var dispose = client.keepAlive(data, ${JSON.stringify(
!!block
)}, module);`
]);
let source;
if (block) {
const dep = block.dependencies[0];
const module = moduleGraph.getModule(dep);
source = Template.asString([
"module.hot.accept();",
`module.hot.accept(${JSON.stringify(
chunkGraph.getModuleId(module)
)}, function() { module.hot.invalidate(); });`,
"module.hot.dispose(function(data) { delete data.resolveSelf; dispose(data); });",
`module.exports = ${runtimeTemplate.moduleNamespacePromise({
chunkGraph,
block,
module,
request: this.request,
strict: false, // TODO this should be inherited from the original module
message: "import()",
runtimeRequirements
})};`,
"if (module.hot.data && module.hot.data.resolveSelf) module.hot.data.resolveSelf(module.exports);"
]);
} else {
source = Template.asString([
"module.hot.accept();",
"var resolveSelf;",
`module.exports = new Promise(function(resolve) { resolveSelf = resolve; });`,
"if (module.hot.data && module.hot.data.resolveSelf) module.hot.data.resolveSelf(module.exports);",
"module.hot.dispose(function(data) { data.resolveSelf = resolveSelf; dispose(data); });"
]);
}
sources.set("javascript", new RawSource(keepActive + "\n\n" + source));
return {
sources,
runtimeRequirements
};
}
/**
* @param {Hash} hash the hash used to track dependencies
* @param {UpdateHashContext} context context
* @returns {void}
*/
updateHash(hash, context) {
super.updateHash(hash, context);
hash.update(this.active ? "active" : "");
hash.update(JSON.stringify(this.data));
}
}
registerNotSerializable(LazyCompilationProxyModule);
class LazyCompilationDependencyFactory extends ModuleFactory {
constructor(factory) {
super();
this._factory = factory;
}
/**
* @param {ModuleFactoryCreateData} data data object
* @param {function(Error=, ModuleFactoryResult=): void} callback callback
* @returns {void}
*/
create(data, callback) {
const dependency = /** @type {LazyCompilationDependency} */ (data
.dependencies[0]);
callback(null, {
module: dependency.originalModule
});
}
}
class LazyCompilationPlugin {
/**
* @param {Object} options options
* @param {(function(Compiler, string, function(Error?, any?): void): void) | function(Compiler, string): Promise<any>} options.backend the backend
* @param {string} options.client the client reference
*/
constructor({ backend, client }) {
this.backend = backend;
this.client = client;
}
/**
* Apply the plugin
* @param {Compiler} compiler the compiler instance
* @returns {void}
*/
apply(compiler) {
let backend;
compiler.hooks.beforeCompile.tapAsync(
"LazyCompilationPlugin",
(params, callback) => {
if (backend !== undefined) return callback();
const promise = this.backend(compiler, this.client, (err, result) => {
if (err) return callback(err);
backend = result;
callback();
});
if (promise && promise.then) {
promise.then(b => {
backend = b;
callback();
}, callback);
}
}
);
compiler.hooks.compilation.tap(
"LazyCompilationPlugin",
(compilation, { normalModuleFactory }) => {
normalModuleFactory.hooks.module.tap(
"LazyCompilationPlugin",
(originalModule, createData, resolveData) => {
if (
resolveData.dependencies.every(dep => dep.type === "import()")
) {
const moduleInfo = backend.module(originalModule);
if (!moduleInfo) return;
const { client, data, active } = moduleInfo;
return new LazyCompilationProxyModule(
originalModule,
resolveData.request,
client,
data,
active
);
}
}
);
compilation.dependencyFactories.set(
LazyCompilationDependency,
new LazyCompilationDependencyFactory()
);
}
);
compiler.hooks.shutdown.tapAsync("LazyCompilationPlugin", callback => {
backend.dispose(callback);
});
}
}
module.exports = LazyCompilationPlugin;

View File

@ -0,0 +1,86 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const http = require("http");
/** @typedef {import("../Compiler")} Compiler */
/**
* @param {Compiler} compiler compiler
* @param {string} client client reference
* @param {function(Error?, any?): void} callback callback
* @returns {void}
*/
module.exports = (compiler, client, callback) => {
const logger = compiler.getInfrastructureLogger("LazyCompilationBackend");
const activeModules = new Map();
const prefix = "/lazy-compilation-using-";
const server = http.createServer((req, res) => {
const keys = req.url.slice(prefix.length).split("@");
req.socket.on("close", () => {
setTimeout(() => {
for (const key of keys) {
const oldValue = activeModules.get(key) || 0;
activeModules.set(key, oldValue - 1);
if (oldValue === 1) {
logger.log(
`${key} is no longer in use. Next compilation will skip this module.`
);
}
}
}, 120000);
});
req.socket.setNoDelay(true);
res.writeHead(200, {
"content-type": "text/event-stream",
"Access-Control-Allow-Origin": "*"
});
res.write("\n");
let moduleActivated = false;
for (const key of keys) {
const oldValue = activeModules.get(key) || 0;
activeModules.set(key, oldValue + 1);
if (oldValue === 0) {
logger.log(`${key} is now in use and will be compiled.`);
moduleActivated = true;
}
}
if (moduleActivated && compiler.watching) compiler.watching.invalidate();
});
server.listen(err => {
if (err) return callback(err);
const addr = server.address();
if (typeof addr === "string") throw new Error("addr must not be a string");
const urlBase =
addr.address === "::" || addr.address === "0.0.0.0"
? `http://localhost:${addr.port}`
: addr.family === "IPv6"
? `http://[${addr.address}]:${addr.port}`
: `http://${addr.address}:${addr.port}`;
logger.log(
`Server-Sent-Events server for lazy compilation open at ${urlBase}.`
);
callback(null, {
dispose(callback) {
server.close(callback);
},
module(originalModule) {
const key = `${encodeURIComponent(
originalModule.identifier().replace(/\\/g, "/").replace(/@/g, "_")
).replace(/%(2F|3A|24|26|2B|2C|3B|3D|3A)/g, decodeURIComponent)}`;
const active = activeModules.get(key) > 0;
return {
client: `webpack/hot/lazy-compilation-${
compiler.options.externalsPresets.node ? "node" : "web"
}.js?${encodeURIComponent(urlBase + prefix)}`,
data: key,
active
};
}
});
});
};

View File

@ -367,11 +367,13 @@ class ObjectMiddleware extends SerializerMiddleware {
try {
process(value);
} catch (e) {
if (hasDebugInfoAttached === undefined)
hasDebugInfoAttached = new WeakSet();
if (!hasDebugInfoAttached.has(e)) {
e.message += `\nwhile serializing ${stackToString(value)}`;
hasDebugInfoAttached.add(e);
if (e !== NOT_SERIALIZABLE) {
if (hasDebugInfoAttached === undefined)
hasDebugInfoAttached = new WeakSet();
if (!hasDebugInfoAttached.has(e)) {
e.message += `\nwhile serializing ${stackToString(value)}`;
hasDebugInfoAttached.add(e);
}
}
throw e;
}

View File

@ -581,6 +581,29 @@
"description": "Enable module and chunk layers.",
"type": "boolean"
},
"lazyCompilation": {
"description": "Compile import()s only when they are accessed.",
"anyOf": [
{
"type": "boolean"
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"backend": {
"description": "A custom backend.",
"instanceof": "Function",
"tsType": "(((compiler: import('../lib/Compiler'), client: string, callback: (err?: Error, api?: any) => void) => void) | ((compiler: import('../lib/Compiler'), client: string) => Promise<any>))"
},
"client": {
"description": "A custom client.",
"type": "string"
}
}
}
]
},
"outputModule": {
"description": "Allow output javascript files as module source type.",
"type": "boolean"

View File

@ -36,6 +36,11 @@ const describeCases = config => {
return;
}
describe(testName, () => {
let compiler;
afterAll(callback => {
compiler.close(callback);
});
it(
testName + " should compile",
done => {
@ -93,7 +98,7 @@ const describeCases = config => {
new webpack.LoaderOptionsPlugin(fakeUpdateLoaderOptions)
);
if (!options.recordsPath) options.recordsPath = recordsPath;
const compiler = webpack(options);
compiler = webpack(options);
compiler.run((err, stats) => {
if (err) return done(err);
const jsonStats = stats.toJson({
@ -192,6 +197,7 @@ const describeCases = config => {
Worker: require("./helpers/createFakeWorker")({
outputDirectory
}),
EventSource: require("./helpers/EventSourceForNode"),
location: {
href: "https://test.cases/path/index.html",
origin: "https://test.cases",
@ -243,7 +249,7 @@ const describeCases = config => {
return JSON.parse(fs.readFileSync(p, "utf-8"));
} else {
const fn = vm.runInThisContext(
"(function(require, module, exports, __dirname, __filename, it, beforeEach, afterEach, expect, self, window, fetch, document, importScripts, Worker, NEXT, STATS) {" +
"(function(require, module, exports, __dirname, __filename, it, beforeEach, afterEach, expect, self, window, fetch, document, importScripts, Worker, EventSource, NEXT, STATS) {" +
"global.expect = expect;" +
'function nsObj(m) { Object.defineProperty(m, Symbol.toStringTag, { value: "Module" }); return m; }' +
fs.readFileSync(p, "utf-8") +
@ -270,6 +276,7 @@ const describeCases = config => {
window.document,
window.importScripts,
window.Worker,
window.EventSource,
_next,
jsonStats
);

View File

@ -367,6 +367,32 @@ Object {
"multiple": false,
"simpleType": "boolean",
},
"experiments-lazy-compilation": Object {
"configs": Array [
Object {
"description": "Compile import()s only when they are accessed.",
"multiple": false,
"path": "experiments.lazyCompilation",
"type": "boolean",
},
],
"description": "Compile import()s only when they are accessed.",
"multiple": false,
"simpleType": "boolean",
},
"experiments-lazy-compilation-client": Object {
"configs": Array [
Object {
"description": "A custom client.",
"multiple": false,
"path": "experiments.lazyCompilation.client",
"type": "string",
},
],
"description": "A custom client.",
"multiple": false,
"simpleType": "string",
},
"experiments-output-module": Object {
"configs": Array [
Object {

View File

@ -0,0 +1,39 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
module.exports = class EventSource {
constructor(url) {
this.response = undefined;
require("http")
.request(
url,
{
agent: false,
headers: { accept: "text/event-stream" }
},
res => {
this.response = res;
res.on("error", err => {
if (this.onerror) this.onerror(err);
});
}
)
.end();
}
close() {
this.response.destroy();
}
set onopen(value) {
throw new Error("not implemented");
}
set onmessage(value) {
throw new Error("not implemented");
}
};

View File

@ -0,0 +1,16 @@
it("should compile to lazy imported module", done => {
let resolved;
const promise = import("./module").then(r => (resolved = r));
expect(resolved).toBe(undefined);
setTimeout(() => {
expect(resolved).toBe(undefined);
NEXT(
require("../../update")(done, true, () => {
promise.then(result => {
expect(result).toHaveProperty("default", 42);
done();
}, done);
})
);
}, 1000);
});

View File

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

View File

@ -0,0 +1,8 @@
"use strict";
/** @type {import("../../../../").Configuration} */
module.exports = {
experiments: {
lazyCompilation: true
}
};

23
types.d.ts vendored
View File

@ -1621,6 +1621,7 @@ declare class Compiler {
failed: SyncHook<[Error]>;
invalid: SyncHook<[null | string, number]>;
watchClose: SyncHook<[]>;
shutdown: AsyncSeriesHook<[]>;
infrastructureLog: SyncBailHook<[string, string, any[]], true>;
environment: SyncHook<[]>;
afterEnvironment: SyncHook<[]>;
@ -2954,6 +2955,28 @@ declare interface Experiments {
*/
layers?: boolean;
/**
* Compile import()s only when they are accessed.
*/
lazyCompilation?:
| boolean
| {
/**
* A custom backend.
*/
backend?:
| ((
compiler: Compiler,
client: string,
callback: (err?: Error, api?: any) => void
) => void)
| ((compiler: Compiler, client: string) => Promise<any>);
/**
* A custom client.
*/
client?: string;
};
/**
* Allow output javascript files as module source type.
*/