first commit

This commit is contained in:
monjack
2025-06-20 18:01:48 +08:00
commit 6daa6d65c1
24611 changed files with 2512443 additions and 0 deletions

View File

@ -0,0 +1,16 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
*/
"use strict";
const WebpackError = require("../WebpackError");
module.exports = class UnsupportedWebAssemblyFeatureError extends WebpackError {
/** @param {string} message Error message */
constructor(message) {
super(message);
this.name = "UnsupportedWebAssemblyFeatureError";
this.hideStack = true;
}
};

View File

@ -0,0 +1,413 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
*/
"use strict";
const RuntimeGlobals = require("../RuntimeGlobals");
const RuntimeModule = require("../RuntimeModule");
const Template = require("../Template");
const { compareModulesByIdentifier } = require("../util/comparators");
const WebAssemblyUtils = require("./WebAssemblyUtils");
/** @typedef {import("@webassemblyjs/ast").Signature} Signature */
/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../ChunkGraph")} ChunkGraph */
/** @typedef {import("../ChunkGraph").ModuleId} ModuleId */
/** @typedef {import("../Compilation")} Compilation */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */
/** @typedef {import("../ModuleGraph")} ModuleGraph */
/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
// TODO webpack 6 remove the whole folder
// Get all wasm modules
/**
* @param {ModuleGraph} moduleGraph the module graph
* @param {ChunkGraph} chunkGraph the chunk graph
* @param {Chunk} chunk the chunk
* @returns {Module[]} all wasm modules
*/
const getAllWasmModules = (moduleGraph, chunkGraph, chunk) => {
const wasmModules = chunk.getAllAsyncChunks();
const array = [];
for (const chunk of wasmModules) {
for (const m of chunkGraph.getOrderedChunkModulesIterable(
chunk,
compareModulesByIdentifier
)) {
if (m.type.startsWith("webassembly")) {
array.push(m);
}
}
}
return array;
};
/**
* generates the import object function for a module
* @param {ChunkGraph} chunkGraph the chunk graph
* @param {Module} module the module
* @param {boolean | undefined} mangle mangle imports
* @param {string[]} declarations array where declarations are pushed to
* @param {RuntimeSpec} runtime the runtime
* @returns {string} source code
*/
const generateImportObject = (
chunkGraph,
module,
mangle,
declarations,
runtime
) => {
const moduleGraph = chunkGraph.moduleGraph;
/** @type {Map<string, string | number>} */
const waitForInstances = new Map();
const properties = [];
const usedWasmDependencies = WebAssemblyUtils.getUsedDependencies(
moduleGraph,
module,
mangle
);
for (const usedDep of usedWasmDependencies) {
const dep = usedDep.dependency;
const importedModule = moduleGraph.getModule(dep);
const exportName = dep.name;
const usedName =
importedModule &&
moduleGraph
.getExportsInfo(importedModule)
.getUsedName(exportName, runtime);
const description = dep.description;
const direct = dep.onlyDirectImport;
const module = usedDep.module;
const name = usedDep.name;
if (direct) {
const instanceVar = `m${waitForInstances.size}`;
waitForInstances.set(
instanceVar,
/** @type {ModuleId} */
(chunkGraph.getModuleId(/** @type {Module} */ (importedModule)))
);
properties.push({
module,
name,
value: `${instanceVar}[${JSON.stringify(usedName)}]`
});
} else {
const params =
/** @type {Signature} */
(description.signature).params.map(
(param, k) => `p${k}${param.valtype}`
);
const mod = `${RuntimeGlobals.moduleCache}[${JSON.stringify(
chunkGraph.getModuleId(/** @type {Module} */ (importedModule))
)}]`;
const modExports = `${mod}.exports`;
const cache = `wasmImportedFuncCache${declarations.length}`;
declarations.push(`var ${cache};`);
const modCode =
/** @type {Module} */
(importedModule).type.startsWith("webassembly")
? `${mod} ? ${modExports}[${JSON.stringify(usedName)}] : `
: "";
properties.push({
module,
name,
value: Template.asString([
`${modCode}function(${params}) {`,
Template.indent([
`if(${cache} === undefined) ${cache} = ${modExports};`,
`return ${cache}[${JSON.stringify(usedName)}](${params});`
]),
"}"
])
});
}
}
let importObject;
if (mangle) {
importObject = [
"return {",
Template.indent([
properties.map(p => `${JSON.stringify(p.name)}: ${p.value}`).join(",\n")
]),
"};"
];
} else {
/** @type {Map<string, Array<{ name: string, value: string }>>} */
const propertiesByModule = new Map();
for (const p of properties) {
let list = propertiesByModule.get(p.module);
if (list === undefined) {
propertiesByModule.set(p.module, (list = []));
}
list.push(p);
}
importObject = [
"return {",
Template.indent([
Array.from(propertiesByModule, ([module, list]) =>
Template.asString([
`${JSON.stringify(module)}: {`,
Template.indent([
list.map(p => `${JSON.stringify(p.name)}: ${p.value}`).join(",\n")
]),
"}"
])
).join(",\n")
]),
"};"
];
}
const moduleIdStringified = JSON.stringify(chunkGraph.getModuleId(module));
if (waitForInstances.size === 1) {
const moduleId = Array.from(waitForInstances.values())[0];
const promise = `installedWasmModules[${JSON.stringify(moduleId)}]`;
const variable = Array.from(waitForInstances.keys())[0];
return Template.asString([
`${moduleIdStringified}: function() {`,
Template.indent([
`return promiseResolve().then(function() { return ${promise}; }).then(function(${variable}) {`,
Template.indent(importObject),
"});"
]),
"},"
]);
} else if (waitForInstances.size > 0) {
const promises = Array.from(
waitForInstances.values(),
id => `installedWasmModules[${JSON.stringify(id)}]`
).join(", ");
const variables = Array.from(
waitForInstances.keys(),
(name, i) => `${name} = array[${i}]`
).join(", ");
return Template.asString([
`${moduleIdStringified}: function() {`,
Template.indent([
`return promiseResolve().then(function() { return Promise.all([${promises}]); }).then(function(array) {`,
Template.indent([`var ${variables};`, ...importObject]),
"});"
]),
"},"
]);
}
return Template.asString([
`${moduleIdStringified}: function() {`,
Template.indent(importObject),
"},"
]);
};
/**
* @typedef {object} WasmChunkLoadingRuntimeModuleOptions
* @property {(path: string) => string} generateLoadBinaryCode
* @property {boolean=} supportsStreaming
* @property {boolean=} mangleImports
* @property {ReadOnlyRuntimeRequirements} runtimeRequirements
*/
class WasmChunkLoadingRuntimeModule extends RuntimeModule {
/**
* @param {WasmChunkLoadingRuntimeModuleOptions} options options
*/
constructor({
generateLoadBinaryCode,
supportsStreaming,
mangleImports,
runtimeRequirements
}) {
super("wasm chunk loading", RuntimeModule.STAGE_ATTACH);
this.generateLoadBinaryCode = generateLoadBinaryCode;
this.supportsStreaming = supportsStreaming;
this.mangleImports = mangleImports;
this._runtimeRequirements = runtimeRequirements;
}
/**
* @returns {string | null} runtime code
*/
generate() {
const fn = RuntimeGlobals.ensureChunkHandlers;
const withHmr = this._runtimeRequirements.has(
RuntimeGlobals.hmrDownloadUpdateHandlers
);
const compilation = /** @type {Compilation} */ (this.compilation);
const { moduleGraph, outputOptions } = compilation;
const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph);
const chunk = /** @type {Chunk} */ (this.chunk);
const wasmModules = getAllWasmModules(moduleGraph, chunkGraph, chunk);
const { mangleImports } = this;
/** @type {string[]} */
const declarations = [];
const importObjects = wasmModules.map(module =>
generateImportObject(
chunkGraph,
module,
mangleImports,
declarations,
chunk.runtime
)
);
const chunkModuleIdMap = chunkGraph.getChunkModuleIdMap(chunk, m =>
m.type.startsWith("webassembly")
);
/**
* @param {string} content content
* @returns {string} created import object
*/
const createImportObject = content =>
mangleImports
? `{ ${JSON.stringify(WebAssemblyUtils.MANGLED_MODULE)}: ${content} }`
: content;
const wasmModuleSrcPath = compilation.getPath(
JSON.stringify(outputOptions.webassemblyModuleFilename),
{
hash: `" + ${RuntimeGlobals.getFullHash}() + "`,
hashWithLength: length =>
`" + ${RuntimeGlobals.getFullHash}}().slice(0, ${length}) + "`,
module: {
id: '" + wasmModuleId + "',
hash: `" + ${JSON.stringify(
chunkGraph.getChunkModuleRenderedHashMap(chunk, m =>
m.type.startsWith("webassembly")
)
)}[chunkId][wasmModuleId] + "`,
hashWithLength(length) {
return `" + ${JSON.stringify(
chunkGraph.getChunkModuleRenderedHashMap(
chunk,
m => m.type.startsWith("webassembly"),
length
)
)}[chunkId][wasmModuleId] + "`;
}
},
runtime: chunk.runtime
}
);
const stateExpression = withHmr
? `${RuntimeGlobals.hmrRuntimeStatePrefix}_wasm`
: undefined;
return Template.asString([
"// object to store loaded and loading wasm modules",
`var installedWasmModules = ${
stateExpression ? `${stateExpression} = ${stateExpression} || ` : ""
}{};`,
"",
// This function is used to delay reading the installed wasm module promises
// by a microtask. Sorting them doesn't help because there are edge cases where
// sorting is not possible (modules splitted into different chunks).
// So we not even trying and solve this by a microtask delay.
"function promiseResolve() { return Promise.resolve(); }",
"",
Template.asString(declarations),
"var wasmImportObjects = {",
Template.indent(importObjects),
"};",
"",
`var wasmModuleMap = ${JSON.stringify(
chunkModuleIdMap,
undefined,
"\t"
)};`,
"",
"// object with all WebAssembly.instance exports",
`${RuntimeGlobals.wasmInstances} = {};`,
"",
"// Fetch + compile chunk loading for webassembly",
`${fn}.wasm = function(chunkId, promises) {`,
Template.indent([
"",
"var wasmModules = wasmModuleMap[chunkId] || [];",
"",
"wasmModules.forEach(function(wasmModuleId, idx) {",
Template.indent([
"var installedWasmModuleData = installedWasmModules[wasmModuleId];",
"",
'// a Promise means "currently loading" or "already loaded".',
"if(installedWasmModuleData)",
Template.indent(["promises.push(installedWasmModuleData);"]),
"else {",
Template.indent([
"var importObject = wasmImportObjects[wasmModuleId]();",
`var req = ${this.generateLoadBinaryCode(wasmModuleSrcPath)};`,
"var promise;",
this.supportsStreaming
? Template.asString([
"if(importObject && typeof importObject.then === 'function' && typeof WebAssembly.compileStreaming === 'function') {",
Template.indent([
"promise = Promise.all([WebAssembly.compileStreaming(req), importObject]).then(function(items) {",
Template.indent([
`return WebAssembly.instantiate(items[0], ${createImportObject(
"items[1]"
)});`
]),
"});"
]),
"} else if(typeof WebAssembly.instantiateStreaming === 'function') {",
Template.indent([
`promise = WebAssembly.instantiateStreaming(req, ${createImportObject(
"importObject"
)});`
])
])
: Template.asString([
"if(importObject && typeof importObject.then === 'function') {",
Template.indent([
"var bytesPromise = req.then(function(x) { return x.arrayBuffer(); });",
"promise = Promise.all([",
Template.indent([
"bytesPromise.then(function(bytes) { return WebAssembly.compile(bytes); }),",
"importObject"
]),
"]).then(function(items) {",
Template.indent([
`return WebAssembly.instantiate(items[0], ${createImportObject(
"items[1]"
)});`
]),
"});"
])
]),
"} else {",
Template.indent([
"var bytesPromise = req.then(function(x) { return x.arrayBuffer(); });",
"promise = bytesPromise.then(function(bytes) {",
Template.indent([
`return WebAssembly.instantiate(bytes, ${createImportObject(
"importObject"
)});`
]),
"});"
]),
"}",
"promises.push(installedWasmModules[wasmModuleId] = promise.then(function(res) {",
Template.indent([
`return ${RuntimeGlobals.wasmInstances}[wasmModuleId] = (res.instance || res).exports;`
]),
"}));"
]),
"}"
]),
"});"
]),
"};"
]);
}
}
module.exports = WasmChunkLoadingRuntimeModule;

View File

@ -0,0 +1,91 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const formatLocation = require("../formatLocation");
const UnsupportedWebAssemblyFeatureError = require("./UnsupportedWebAssemblyFeatureError");
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../Module").BuildMeta} BuildMeta */
const PLUGIN_NAME = "WasmFinalizeExportsPlugin";
class WasmFinalizeExportsPlugin {
/**
* Apply the plugin
* @param {Compiler} compiler the compiler instance
* @returns {void}
*/
apply(compiler) {
compiler.hooks.compilation.tap(PLUGIN_NAME, compilation => {
compilation.hooks.finishModules.tap(PLUGIN_NAME, modules => {
for (const module of modules) {
// 1. if a WebAssembly module
if (module.type.startsWith("webassembly") === true) {
const jsIncompatibleExports =
/** @type {BuildMeta} */
(module.buildMeta).jsIncompatibleExports;
if (jsIncompatibleExports === undefined) {
continue;
}
for (const connection of compilation.moduleGraph.getIncomingConnections(
module
)) {
// 2. is active and referenced by a non-WebAssembly module
if (
connection.isTargetActive(undefined) &&
/** @type {Module} */
(connection.originModule).type.startsWith("webassembly") ===
false
) {
const referencedExports =
compilation.getDependencyReferencedExports(
/** @type {Dependency} */ (connection.dependency),
undefined
);
for (const info of referencedExports) {
const names = Array.isArray(info) ? info : info.name;
if (names.length === 0) continue;
const name = names[0];
if (typeof name === "object") continue;
// 3. and uses a func with an incompatible JS signature
if (
Object.prototype.hasOwnProperty.call(
jsIncompatibleExports,
name
)
) {
// 4. error
const error = new UnsupportedWebAssemblyFeatureError(
`Export "${name}" with ${jsIncompatibleExports[name]} can only be used for direct wasm to wasm dependencies\n` +
`It's used from ${
/** @type {Module} */
(connection.originModule).readableIdentifier(
compilation.requestShortener
)
} at ${formatLocation(
/** @type {Dependency} */ (connection.dependency).loc
)}.`
);
error.module = module;
compilation.errors.push(error);
}
}
}
}
}
}
});
});
}
}
module.exports = WasmFinalizeExportsPlugin;

View File

@ -0,0 +1,536 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const t = require("@webassemblyjs/ast");
const { moduleContextFromModuleAST } = require("@webassemblyjs/ast");
const { editWithAST, addWithAST } = require("@webassemblyjs/wasm-edit");
const { decode } = require("@webassemblyjs/wasm-parser");
const { RawSource } = require("webpack-sources");
const Generator = require("../Generator");
const { WEBASSEMBLY_TYPES } = require("../ModuleSourceTypesConstants");
const WebAssemblyUtils = require("./WebAssemblyUtils");
const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency");
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../DependencyTemplates")} DependencyTemplates */
/** @typedef {import("../Generator").GenerateContext} GenerateContext */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../Module").SourceTypes} SourceTypes */
/** @typedef {import("../ModuleGraph")} ModuleGraph */
/** @typedef {import("../NormalModule")} NormalModule */
/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */
/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
/** @typedef {import("./WebAssemblyUtils").UsedWasmDependency} UsedWasmDependency */
/** @typedef {import("@webassemblyjs/ast").Instruction} Instruction */
/** @typedef {import("@webassemblyjs/ast").ModuleImport} ModuleImport */
/** @typedef {import("@webassemblyjs/ast").ModuleExport} ModuleExport */
/** @typedef {import("@webassemblyjs/ast").Global} Global */
/** @typedef {import("@webassemblyjs/ast").AST} AST */
/** @typedef {import("@webassemblyjs/ast").GlobalType} GlobalType */
/**
* @template T
* @typedef {import("@webassemblyjs/ast").NodePath<T>} NodePath
*/
/**
* @typedef {(buf: ArrayBuffer) => ArrayBuffer} ArrayBufferTransform
*/
/**
* @template T
* @param {((prev: ArrayBuffer) => ArrayBuffer)[]} fns transforms
* @returns {ArrayBufferTransform} composed transform
*/
const compose = (...fns) =>
fns.reduce(
(prevFn, nextFn) => value => nextFn(prevFn(value)),
value => value
);
/**
* Removes the start instruction
* @param {object} state state
* @param {AST} state.ast Module's ast
* @returns {ArrayBufferTransform} transform
*/
const removeStartFunc = state => bin =>
editWithAST(state.ast, bin, {
Start(path) {
path.remove();
}
});
/**
* Get imported globals
* @param {AST} ast Module's AST
* @returns {t.ModuleImport[]} - nodes
*/
const getImportedGlobals = ast => {
/** @type {t.ModuleImport[]} */
const importedGlobals = [];
t.traverse(ast, {
ModuleImport({ node }) {
if (t.isGlobalType(node.descr)) {
importedGlobals.push(node);
}
}
});
return importedGlobals;
};
/**
* Get the count for imported func
* @param {AST} ast Module's AST
* @returns {number} - count
*/
const getCountImportedFunc = ast => {
let count = 0;
t.traverse(ast, {
ModuleImport({ node }) {
if (t.isFuncImportDescr(node.descr)) {
count++;
}
}
});
return count;
};
/**
* Get next type index
* @param {AST} ast Module's AST
* @returns {t.Index} - index
*/
const getNextTypeIndex = ast => {
const typeSectionMetadata = t.getSectionMetadata(ast, "type");
if (typeSectionMetadata === undefined) {
return t.indexLiteral(0);
}
return t.indexLiteral(typeSectionMetadata.vectorOfSize.value);
};
/**
* Get next func index
* The Func section metadata provide information for implemented funcs
* in order to have the correct index we shift the index by number of external
* functions.
* @param {AST} ast Module's AST
* @param {number} countImportedFunc number of imported funcs
* @returns {t.Index} - index
*/
const getNextFuncIndex = (ast, countImportedFunc) => {
const funcSectionMetadata = t.getSectionMetadata(ast, "func");
if (funcSectionMetadata === undefined) {
return t.indexLiteral(0 + countImportedFunc);
}
const vectorOfSize = funcSectionMetadata.vectorOfSize.value;
return t.indexLiteral(vectorOfSize + countImportedFunc);
};
/**
* Creates an init instruction for a global type
* @param {t.GlobalType} globalType the global type
* @returns {t.Instruction} init expression
*/
const createDefaultInitForGlobal = globalType => {
if (globalType.valtype[0] === "i") {
// create NumberLiteral global initializer
return t.objectInstruction("const", globalType.valtype, [
t.numberLiteralFromRaw(66)
]);
} else if (globalType.valtype[0] === "f") {
// create FloatLiteral global initializer
return t.objectInstruction("const", globalType.valtype, [
t.floatLiteral(66, false, false, "66")
]);
}
throw new Error(`unknown type: ${globalType.valtype}`);
};
/**
* Rewrite the import globals:
* - removes the ModuleImport instruction
* - injects at the same offset a mutable global of the same type
*
* Since the imported globals are before the other global declarations, our
* indices will be preserved.
*
* Note that globals will become mutable.
* @param {object} state transformation state
* @param {AST} state.ast Module's ast
* @param {t.Instruction[]} state.additionalInitCode list of addition instructions for the init function
* @returns {ArrayBufferTransform} transform
*/
const rewriteImportedGlobals = state => bin => {
const additionalInitCode = state.additionalInitCode;
/** @type {Array<t.Global>} */
const newGlobals = [];
bin = editWithAST(state.ast, bin, {
ModuleImport(path) {
if (t.isGlobalType(path.node.descr)) {
const globalType =
/** @type {GlobalType} */
(path.node.descr);
globalType.mutability = "var";
const init = [
createDefaultInitForGlobal(globalType),
t.instruction("end")
];
newGlobals.push(t.global(globalType, init));
path.remove();
}
},
// in order to preserve non-imported global's order we need to re-inject
// those as well
/**
* @param {NodePath<Global>} path path
*/
Global(path) {
const { node } = path;
const [init] = node.init;
if (init.id === "get_global") {
node.globalType.mutability = "var";
const initialGlobalIdx = init.args[0];
node.init = [
createDefaultInitForGlobal(node.globalType),
t.instruction("end")
];
additionalInitCode.push(
/**
* get_global in global initializer only works for imported globals.
* They have the same indices as the init params, so use the
* same index.
*/
t.instruction("get_local", [initialGlobalIdx]),
t.instruction("set_global", [t.indexLiteral(newGlobals.length)])
);
}
newGlobals.push(node);
path.remove();
}
});
// Add global declaration instructions
return addWithAST(state.ast, bin, newGlobals);
};
/**
* Rewrite the export names
* @param {object} state state
* @param {AST} state.ast Module's ast
* @param {Module} state.module Module
* @param {ModuleGraph} state.moduleGraph module graph
* @param {Set<string>} state.externalExports Module
* @param {RuntimeSpec} state.runtime runtime
* @returns {ArrayBufferTransform} transform
*/
const rewriteExportNames =
({ ast, moduleGraph, module, externalExports, runtime }) =>
bin =>
editWithAST(ast, bin, {
/**
* @param {NodePath<ModuleExport>} path path
*/
ModuleExport(path) {
const isExternal = externalExports.has(path.node.name);
if (isExternal) {
path.remove();
return;
}
const usedName = moduleGraph
.getExportsInfo(module)
.getUsedName(path.node.name, runtime);
if (!usedName) {
path.remove();
return;
}
path.node.name = /** @type {string} */ (usedName);
}
});
/**
* Mangle import names and modules
* @param {object} state state
* @param {AST} state.ast Module's ast
* @param {Map<string, UsedWasmDependency>} state.usedDependencyMap mappings to mangle names
* @returns {ArrayBufferTransform} transform
*/
const rewriteImports =
({ ast, usedDependencyMap }) =>
bin =>
editWithAST(ast, bin, {
/**
* @param {NodePath<ModuleImport>} path path
*/
ModuleImport(path) {
const result = usedDependencyMap.get(
`${path.node.module}:${path.node.name}`
);
if (result !== undefined) {
path.node.module = result.module;
path.node.name = result.name;
}
}
});
/**
* Add an init function.
*
* The init function fills the globals given input arguments.
* @param {object} state transformation state
* @param {AST} state.ast Module's ast
* @param {t.Identifier} state.initFuncId identifier of the init function
* @param {t.Index} state.startAtFuncOffset index of the start function
* @param {t.ModuleImport[]} state.importedGlobals list of imported globals
* @param {t.Instruction[]} state.additionalInitCode list of addition instructions for the init function
* @param {t.Index} state.nextFuncIndex index of the next function
* @param {t.Index} state.nextTypeIndex index of the next type
* @returns {ArrayBufferTransform} transform
*/
const addInitFunction =
({
ast,
initFuncId,
startAtFuncOffset,
importedGlobals,
additionalInitCode,
nextFuncIndex,
nextTypeIndex
}) =>
bin => {
const funcParams = importedGlobals.map(importedGlobal => {
// used for debugging
const id = t.identifier(
`${importedGlobal.module}.${importedGlobal.name}`
);
return t.funcParam(
/** @type {string} */ (importedGlobal.descr.valtype),
id
);
});
/** @type {Instruction[]} */
const funcBody = [];
for (const [index, _importedGlobal] of importedGlobals.entries()) {
const args = [t.indexLiteral(index)];
const body = [
t.instruction("get_local", args),
t.instruction("set_global", args)
];
funcBody.push(...body);
}
if (typeof startAtFuncOffset === "number") {
funcBody.push(
t.callInstruction(t.numberLiteralFromRaw(startAtFuncOffset))
);
}
for (const instr of additionalInitCode) {
funcBody.push(instr);
}
funcBody.push(t.instruction("end"));
/** @type {string[]} */
const funcResults = [];
// Code section
const funcSignature = t.signature(funcParams, funcResults);
const func = t.func(initFuncId, funcSignature, funcBody);
// Type section
const functype = t.typeInstruction(undefined, funcSignature);
// Func section
const funcindex = t.indexInFuncSection(nextTypeIndex);
// Export section
const moduleExport = t.moduleExport(
initFuncId.value,
t.moduleExportDescr("Func", nextFuncIndex)
);
return addWithAST(ast, bin, [func, moduleExport, funcindex, functype]);
};
/**
* Extract mangle mappings from module
* @param {ModuleGraph} moduleGraph module graph
* @param {Module} module current module
* @param {boolean | undefined} mangle mangle imports
* @returns {Map<string, UsedWasmDependency>} mappings to mangled names
*/
const getUsedDependencyMap = (moduleGraph, module, mangle) => {
/** @type {Map<string, UsedWasmDependency>} */
const map = new Map();
for (const usedDep of WebAssemblyUtils.getUsedDependencies(
moduleGraph,
module,
mangle
)) {
const dep = usedDep.dependency;
const request = dep.request;
const exportName = dep.name;
map.set(`${request}:${exportName}`, usedDep);
}
return map;
};
/**
* @typedef {object} WebAssemblyGeneratorOptions
* @property {boolean=} mangleImports mangle imports
*/
class WebAssemblyGenerator extends Generator {
/**
* @param {WebAssemblyGeneratorOptions} options options
*/
constructor(options) {
super();
this.options = options;
}
/**
* @param {NormalModule} module fresh module
* @returns {SourceTypes} available types (do not mutate)
*/
getTypes(module) {
return WEBASSEMBLY_TYPES;
}
/**
* @param {NormalModule} module the module
* @param {string=} type source type
* @returns {number} estimate size of the module
*/
getSize(module, type) {
const originalSource = module.originalSource();
if (!originalSource) {
return 0;
}
return originalSource.size();
}
/**
* @param {NormalModule} module module for which the code should be generated
* @param {GenerateContext} generateContext context for generate
* @returns {Source | null} generated code
*/
generate(module, { moduleGraph, runtime }) {
const bin =
/** @type {Buffer} */
(/** @type {Source} */ (module.originalSource()).source());
const initFuncId = t.identifier("");
// parse it
const ast = decode(bin, {
ignoreDataSection: true,
ignoreCodeSection: true,
ignoreCustomNameSection: true
});
const moduleContext = moduleContextFromModuleAST(ast.body[0]);
const importedGlobals = getImportedGlobals(ast);
const countImportedFunc = getCountImportedFunc(ast);
const startAtFuncOffset = moduleContext.getStart();
const nextFuncIndex = getNextFuncIndex(ast, countImportedFunc);
const nextTypeIndex = getNextTypeIndex(ast);
const usedDependencyMap = getUsedDependencyMap(
moduleGraph,
module,
this.options.mangleImports
);
const externalExports = new Set(
module.dependencies
.filter(d => d instanceof WebAssemblyExportImportedDependency)
.map(d => {
const wasmDep = /** @type {WebAssemblyExportImportedDependency} */ (
d
);
return wasmDep.exportName;
})
);
/** @type {t.Instruction[]} */
const additionalInitCode = [];
const transform = compose(
rewriteExportNames({
ast,
moduleGraph,
module,
externalExports,
runtime
}),
removeStartFunc({ ast }),
rewriteImportedGlobals({ ast, additionalInitCode }),
rewriteImports({
ast,
usedDependencyMap
}),
addInitFunction({
ast,
initFuncId,
importedGlobals,
additionalInitCode,
startAtFuncOffset,
nextFuncIndex,
nextTypeIndex
})
);
const newBin = transform(bin);
const newBuf = Buffer.from(newBin);
return new RawSource(newBuf);
}
/**
* @param {Error} error the error
* @param {NormalModule} module module for which the code should be generated
* @param {GenerateContext} generateContext context for generate
* @returns {Source | null} generated code
*/
generateError(error, module, generateContext) {
return new RawSource(error.message);
}
}
module.exports = WebAssemblyGenerator;

View File

@ -0,0 +1,106 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
*/
"use strict";
const WebpackError = require("../WebpackError");
/** @typedef {import("../ChunkGraph")} ChunkGraph */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../ModuleGraph")} ModuleGraph */
/** @typedef {import("../RequestShortener")} RequestShortener */
/**
* @param {Module} module module to get chains from
* @param {ModuleGraph} moduleGraph the module graph
* @param {ChunkGraph} chunkGraph the chunk graph
* @param {RequestShortener} requestShortener to make readable identifiers
* @returns {string[]} all chains to the module
*/
const getInitialModuleChains = (
module,
moduleGraph,
chunkGraph,
requestShortener
) => {
const queue = [
{ head: module, message: module.readableIdentifier(requestShortener) }
];
/** @type {Set<string>} */
const results = new Set();
/** @type {Set<string>} */
const incompleteResults = new Set();
/** @type {Set<Module>} */
const visitedModules = new Set();
for (const chain of queue) {
const { head, message } = chain;
let final = true;
/** @type {Set<Module>} */
const alreadyReferencedModules = new Set();
for (const connection of moduleGraph.getIncomingConnections(head)) {
const newHead = connection.originModule;
if (newHead) {
if (!chunkGraph.getModuleChunks(newHead).some(c => c.canBeInitial()))
continue;
final = false;
if (alreadyReferencedModules.has(newHead)) continue;
alreadyReferencedModules.add(newHead);
const moduleName = newHead.readableIdentifier(requestShortener);
const detail = connection.explanation
? ` (${connection.explanation})`
: "";
const newMessage = `${moduleName}${detail} --> ${message}`;
if (visitedModules.has(newHead)) {
incompleteResults.add(`... --> ${newMessage}`);
continue;
}
visitedModules.add(newHead);
queue.push({
head: newHead,
message: newMessage
});
} else {
final = false;
const newMessage = connection.explanation
? `(${connection.explanation}) --> ${message}`
: message;
results.add(newMessage);
}
}
if (final) {
results.add(message);
}
}
for (const result of incompleteResults) {
results.add(result);
}
return Array.from(results);
};
module.exports = class WebAssemblyInInitialChunkError extends WebpackError {
/**
* @param {Module} module WASM module
* @param {ModuleGraph} moduleGraph the module graph
* @param {ChunkGraph} chunkGraph the chunk graph
* @param {RequestShortener} requestShortener request shortener
*/
constructor(module, moduleGraph, chunkGraph, requestShortener) {
const moduleChains = getInitialModuleChains(
module,
moduleGraph,
chunkGraph,
requestShortener
);
const message = `WebAssembly module is included in initial chunk.
This is not allowed, because WebAssembly download and compilation must happen asynchronous.
Add an async split point (i. e. import()) somewhere between your entrypoint and the WebAssembly module:
${moduleChains.map(s => `* ${s}`).join("\n")}`;
super(message);
this.name = "WebAssemblyInInitialChunkError";
this.hideStack = true;
this.module = module;
}
};

View File

@ -0,0 +1,227 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const { RawSource } = require("webpack-sources");
const { UsageState } = require("../ExportsInfo");
const Generator = require("../Generator");
const InitFragment = require("../InitFragment");
const { WEBASSEMBLY_TYPES } = require("../ModuleSourceTypesConstants");
const RuntimeGlobals = require("../RuntimeGlobals");
const Template = require("../Template");
const ModuleDependency = require("../dependencies/ModuleDependency");
const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency");
const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency");
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../DependencyTemplates")} DependencyTemplates */
/** @typedef {import("../Generator").GenerateContext} GenerateContext */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../Module").SourceTypes} SourceTypes */
/** @typedef {import("../NormalModule")} NormalModule */
/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */
class WebAssemblyJavascriptGenerator extends Generator {
/**
* @param {NormalModule} module fresh module
* @returns {SourceTypes} available types (do not mutate)
*/
getTypes(module) {
return WEBASSEMBLY_TYPES;
}
/**
* @param {NormalModule} module the module
* @param {string=} type source type
* @returns {number} estimate size of the module
*/
getSize(module, type) {
return 95 + module.dependencies.length * 5;
}
/**
* @param {NormalModule} module module for which the code should be generated
* @param {GenerateContext} generateContext context for generate
* @returns {Source | null} generated code
*/
generate(module, generateContext) {
const {
runtimeTemplate,
moduleGraph,
chunkGraph,
runtimeRequirements,
runtime
} = generateContext;
/** @type {InitFragment<InitFragment<string>>[]} */
const initFragments = [];
const exportsInfo = moduleGraph.getExportsInfo(module);
let needExportsCopy = false;
const importedModules = new Map();
const initParams = [];
let index = 0;
for (const dep of module.dependencies) {
const moduleDep =
dep && dep instanceof ModuleDependency ? dep : undefined;
if (moduleGraph.getModule(dep)) {
let importData = importedModules.get(moduleGraph.getModule(dep));
if (importData === undefined) {
importedModules.set(
moduleGraph.getModule(dep),
(importData = {
importVar: `m${index}`,
index,
request: (moduleDep && moduleDep.userRequest) || undefined,
names: new Set(),
reexports: []
})
);
index++;
}
if (dep instanceof WebAssemblyImportDependency) {
importData.names.add(dep.name);
if (dep.description.type === "GlobalType") {
const exportName = dep.name;
const importedModule = moduleGraph.getModule(dep);
if (importedModule) {
const usedName = moduleGraph
.getExportsInfo(importedModule)
.getUsedName(exportName, runtime);
if (usedName) {
initParams.push(
runtimeTemplate.exportFromImport({
moduleGraph,
module: importedModule,
request: dep.request,
importVar: importData.importVar,
originModule: module,
exportName: dep.name,
asiSafe: true,
isCall: false,
callContext: null,
defaultInterop: true,
initFragments,
runtime,
runtimeRequirements
})
);
}
}
}
}
if (dep instanceof WebAssemblyExportImportedDependency) {
importData.names.add(dep.name);
const usedName = moduleGraph
.getExportsInfo(module)
.getUsedName(dep.exportName, runtime);
if (usedName) {
runtimeRequirements.add(RuntimeGlobals.exports);
const exportProp = `${module.exportsArgument}[${JSON.stringify(
usedName
)}]`;
const defineStatement = Template.asString([
`${exportProp} = ${runtimeTemplate.exportFromImport({
moduleGraph,
module: /** @type {Module} */ (moduleGraph.getModule(dep)),
request: dep.request,
importVar: importData.importVar,
originModule: module,
exportName: dep.name,
asiSafe: true,
isCall: false,
callContext: null,
defaultInterop: true,
initFragments,
runtime,
runtimeRequirements
})};`,
`if(WebAssembly.Global) ${exportProp} = ` +
`new WebAssembly.Global({ value: ${JSON.stringify(
dep.valueType
)} }, ${exportProp});`
]);
importData.reexports.push(defineStatement);
needExportsCopy = true;
}
}
}
}
const importsCode = Template.asString(
Array.from(
importedModules,
([module, { importVar, request, reexports }]) => {
const importStatement = runtimeTemplate.importStatement({
module,
chunkGraph,
request,
importVar,
originModule: module,
runtimeRequirements
});
return importStatement[0] + importStatement[1] + reexports.join("\n");
}
)
);
const copyAllExports =
exportsInfo.otherExportsInfo.getUsed(runtime) === UsageState.Unused &&
!needExportsCopy;
// need these globals
runtimeRequirements.add(RuntimeGlobals.module);
runtimeRequirements.add(RuntimeGlobals.moduleId);
runtimeRequirements.add(RuntimeGlobals.wasmInstances);
if (exportsInfo.otherExportsInfo.getUsed(runtime) !== UsageState.Unused) {
runtimeRequirements.add(RuntimeGlobals.makeNamespaceObject);
runtimeRequirements.add(RuntimeGlobals.exports);
}
if (!copyAllExports) {
runtimeRequirements.add(RuntimeGlobals.exports);
}
// create source
const source = new RawSource(
[
'"use strict";',
"// Instantiate WebAssembly module",
`var wasmExports = ${RuntimeGlobals.wasmInstances}[${module.moduleArgument}.id];`,
exportsInfo.otherExportsInfo.getUsed(runtime) !== UsageState.Unused
? `${RuntimeGlobals.makeNamespaceObject}(${module.exportsArgument});`
: "",
// this must be before import for circular dependencies
"// export exports from WebAssembly module",
copyAllExports
? `${module.moduleArgument}.exports = wasmExports;`
: "for(var name in wasmExports) " +
"if(name) " +
`${module.exportsArgument}[name] = wasmExports[name];`,
"// exec imports from WebAssembly module (for esm order)",
importsCode,
"",
"// exec wasm module",
`wasmExports[""](${initParams.join(", ")})`
].join("\n")
);
return InitFragment.addToSource(source, initFragments, generateContext);
}
/**
* @param {Error} error the error
* @param {NormalModule} module module for which the code should be generated
* @param {GenerateContext} generateContext context for generate
* @returns {Source | null} generated code
*/
generateError(error, module, generateContext) {
return new RawSource(`throw new Error(${JSON.stringify(error.message)});`);
}
}
module.exports = WebAssemblyJavascriptGenerator;

View File

@ -0,0 +1,152 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const Generator = require("../Generator");
const { WEBASSEMBLY_MODULE_TYPE_SYNC } = require("../ModuleTypeConstants");
const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency");
const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency");
const { compareModulesByIdOrIdentifier } = require("../util/comparators");
const memoize = require("../util/memoize");
const WebAssemblyInInitialChunkError = require("./WebAssemblyInInitialChunkError");
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../../declarations/WebpackOptions").OutputNormalized} OutputOptions */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../ModuleTemplate")} ModuleTemplate */
/** @typedef {import("../javascript/JavascriptModulesPlugin").RenderContext} RenderContext */
const getWebAssemblyGenerator = memoize(() =>
require("./WebAssemblyGenerator")
);
const getWebAssemblyJavascriptGenerator = memoize(() =>
require("./WebAssemblyJavascriptGenerator")
);
const getWebAssemblyParser = memoize(() => require("./WebAssemblyParser"));
const PLUGIN_NAME = "WebAssemblyModulesPlugin";
/**
* @typedef {object} WebAssemblyModulesPluginOptions
* @property {boolean=} mangleImports mangle imports
*/
class WebAssemblyModulesPlugin {
/**
* @param {WebAssemblyModulesPluginOptions} options options
*/
constructor(options) {
this.options = options;
}
/**
* Apply the plugin
* @param {Compiler} compiler the compiler instance
* @returns {void}
*/
apply(compiler) {
compiler.hooks.compilation.tap(
PLUGIN_NAME,
(compilation, { normalModuleFactory }) => {
compilation.dependencyFactories.set(
WebAssemblyImportDependency,
normalModuleFactory
);
compilation.dependencyFactories.set(
WebAssemblyExportImportedDependency,
normalModuleFactory
);
normalModuleFactory.hooks.createParser
.for(WEBASSEMBLY_MODULE_TYPE_SYNC)
.tap(PLUGIN_NAME, () => {
const WebAssemblyParser = getWebAssemblyParser();
return new WebAssemblyParser();
});
normalModuleFactory.hooks.createGenerator
.for(WEBASSEMBLY_MODULE_TYPE_SYNC)
.tap(PLUGIN_NAME, () => {
const WebAssemblyJavascriptGenerator =
getWebAssemblyJavascriptGenerator();
const WebAssemblyGenerator = getWebAssemblyGenerator();
return Generator.byType({
javascript: new WebAssemblyJavascriptGenerator(),
webassembly: new WebAssemblyGenerator(this.options)
});
});
compilation.hooks.renderManifest.tap(PLUGIN_NAME, (result, options) => {
const { chunkGraph } = compilation;
const { chunk, outputOptions, codeGenerationResults } = options;
for (const module of chunkGraph.getOrderedChunkModulesIterable(
chunk,
compareModulesByIdOrIdentifier(chunkGraph)
)) {
if (module.type === WEBASSEMBLY_MODULE_TYPE_SYNC) {
const filenameTemplate =
/** @type {NonNullable<OutputOptions["webassemblyModuleFilename"]>} */
(outputOptions.webassemblyModuleFilename);
result.push({
render: () =>
codeGenerationResults.getSource(
module,
chunk.runtime,
"webassembly"
),
filenameTemplate,
pathOptions: {
module,
runtime: chunk.runtime,
chunkGraph
},
auxiliary: true,
identifier: `webassemblyModule${chunkGraph.getModuleId(
module
)}`,
hash: chunkGraph.getModuleHash(module, chunk.runtime)
});
}
}
return result;
});
compilation.hooks.afterChunks.tap(PLUGIN_NAME, () => {
const chunkGraph = compilation.chunkGraph;
const initialWasmModules = new Set();
for (const chunk of compilation.chunks) {
if (chunk.canBeInitial()) {
for (const module of chunkGraph.getChunkModulesIterable(chunk)) {
if (module.type === WEBASSEMBLY_MODULE_TYPE_SYNC) {
initialWasmModules.add(module);
}
}
}
}
for (const module of initialWasmModules) {
compilation.errors.push(
new WebAssemblyInInitialChunkError(
module,
compilation.moduleGraph,
compilation.chunkGraph,
compilation.requestShortener
)
);
}
});
}
);
}
}
module.exports = WebAssemblyModulesPlugin;

View File

@ -0,0 +1,211 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const t = require("@webassemblyjs/ast");
const { moduleContextFromModuleAST } = require("@webassemblyjs/ast");
const { decode } = require("@webassemblyjs/wasm-parser");
const Parser = require("../Parser");
const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency");
const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency");
/** @typedef {import("@webassemblyjs/ast").ModuleImport} ModuleImport */
/** @typedef {import("@webassemblyjs/ast").NumberLiteral} NumberLiteral */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../Module").BuildInfo} BuildInfo */
/** @typedef {import("../Module").BuildMeta} BuildMeta */
/** @typedef {import("../Parser").ParserState} ParserState */
/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */
const JS_COMPAT_TYPES = new Set(["i32", "i64", "f32", "f64", "externref"]);
/**
* @param {t.Signature} signature the func signature
* @returns {null | string} the type incompatible with js types
*/
const getJsIncompatibleType = signature => {
for (const param of signature.params) {
if (!JS_COMPAT_TYPES.has(param.valtype)) {
return `${param.valtype} as parameter`;
}
}
for (const type of signature.results) {
if (!JS_COMPAT_TYPES.has(type)) return `${type} as result`;
}
return null;
};
/**
* TODO why are there two different Signature types?
* @param {t.FuncSignature} signature the func signature
* @returns {null | string} the type incompatible with js types
*/
const getJsIncompatibleTypeOfFuncSignature = signature => {
for (const param of signature.args) {
if (!JS_COMPAT_TYPES.has(param)) {
return `${param} as parameter`;
}
}
for (const type of signature.result) {
if (!JS_COMPAT_TYPES.has(type)) return `${type} as result`;
}
return null;
};
const decoderOpts = {
ignoreCodeSection: true,
ignoreDataSection: true,
// this will avoid having to lookup with identifiers in the ModuleContext
ignoreCustomNameSection: true
};
class WebAssemblyParser extends Parser {
/**
* @param {{}=} options parser options
*/
constructor(options) {
super();
this.hooks = Object.freeze({});
this.options = options;
}
/**
* @param {string | Buffer | PreparsedAst} source the source to parse
* @param {ParserState} state the parser state
* @returns {ParserState} the parser state
*/
parse(source, state) {
if (!Buffer.isBuffer(source)) {
throw new Error("WebAssemblyParser input must be a Buffer");
}
// flag it as ESM
/** @type {BuildInfo} */
(state.module.buildInfo).strict = true;
/** @type {BuildMeta} */
(state.module.buildMeta).exportsType = "namespace";
// parse it
const program = decode(source, decoderOpts);
const module = program.body[0];
const moduleContext = moduleContextFromModuleAST(module);
// extract imports and exports
/** @type {string[]} */
const exports = [];
const buildMeta = /** @type {BuildMeta} */ (state.module.buildMeta);
/** @type {Record<string, string> | undefined} */
let jsIncompatibleExports = (buildMeta.jsIncompatibleExports = undefined);
/** @type {(ModuleImport | null)[]} */
const importedGlobals = [];
t.traverse(module, {
ModuleExport({ node }) {
const descriptor = node.descr;
if (descriptor.exportType === "Func") {
const funcIdx = descriptor.id.value;
/** @type {t.FuncSignature} */
const funcSignature = moduleContext.getFunction(funcIdx);
const incompatibleType =
getJsIncompatibleTypeOfFuncSignature(funcSignature);
if (incompatibleType) {
if (jsIncompatibleExports === undefined) {
jsIncompatibleExports =
/** @type {BuildMeta} */
(state.module.buildMeta).jsIncompatibleExports = {};
}
jsIncompatibleExports[node.name] = incompatibleType;
}
}
exports.push(node.name);
if (node.descr && node.descr.exportType === "Global") {
const refNode =
importedGlobals[/** @type {NumberLiteral} */ (node.descr.id).value];
if (refNode) {
const dep = new WebAssemblyExportImportedDependency(
node.name,
refNode.module,
refNode.name,
/** @type {string} */
(refNode.descr.valtype)
);
state.module.addDependency(dep);
}
}
},
Global({ node }) {
const init = node.init[0];
let importNode = null;
if (init.id === "get_global") {
const globalIdx = init.args[0].value;
if (globalIdx < importedGlobals.length) {
importNode = importedGlobals[globalIdx];
}
}
importedGlobals.push(importNode);
},
ModuleImport({ node }) {
/** @type {false | string} */
let onlyDirectImport = false;
if (t.isMemory(node.descr) === true) {
onlyDirectImport = "Memory";
} else if (t.isTable(node.descr) === true) {
onlyDirectImport = "Table";
} else if (t.isFuncImportDescr(node.descr) === true) {
const incompatibleType = getJsIncompatibleType(
/** @type {t.Signature} */
(node.descr.signature)
);
if (incompatibleType) {
onlyDirectImport = `Non-JS-compatible Func Signature (${incompatibleType})`;
}
} else if (t.isGlobalType(node.descr) === true) {
const type = /** @type {string} */ (node.descr.valtype);
if (!JS_COMPAT_TYPES.has(type)) {
onlyDirectImport = `Non-JS-compatible Global Type (${type})`;
}
}
const dep = new WebAssemblyImportDependency(
node.module,
node.name,
node.descr,
onlyDirectImport
);
state.module.addDependency(dep);
if (t.isGlobalType(node.descr)) {
importedGlobals.push(node);
}
}
});
state.module.addDependency(new StaticExportsDependency(exports, false));
return state;
}
}
module.exports = WebAssemblyParser;

View File

@ -0,0 +1,66 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const Template = require("../Template");
const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency");
/** @typedef {import("../Module")} Module */
/** @typedef {import("../ModuleGraph")} ModuleGraph */
/**
* @typedef {object} UsedWasmDependency
* @property {WebAssemblyImportDependency} dependency the dependency
* @property {string} name the export name
* @property {string} module the module name
*/
const MANGLED_MODULE = "a";
/**
* @param {ModuleGraph} moduleGraph the module graph
* @param {Module} module the module
* @param {boolean | undefined} mangle mangle module and export names
* @returns {UsedWasmDependency[]} used dependencies and (mangled) name
*/
const getUsedDependencies = (moduleGraph, module, mangle) => {
/** @type {UsedWasmDependency[]} */
const array = [];
let importIndex = 0;
for (const dep of module.dependencies) {
if (dep instanceof WebAssemblyImportDependency) {
if (
dep.description.type === "GlobalType" ||
moduleGraph.getModule(dep) === null
) {
continue;
}
const exportName = dep.name;
// TODO add the following 3 lines when removing of ModuleExport is possible
// const importedModule = moduleGraph.getModule(dep);
// const usedName = importedModule && moduleGraph.getExportsInfo(importedModule).getUsedName(exportName, runtime);
// if (usedName !== false) {
if (mangle) {
array.push({
dependency: dep,
name: Template.numberToIdentifier(importIndex++),
module: MANGLED_MODULE
});
} else {
array.push({
dependency: dep,
name: exportName,
module: dep.request
});
}
}
}
return array;
};
module.exports.getUsedDependencies = getUsedDependencies;
module.exports.MANGLED_MODULE = MANGLED_MODULE;