378 lines
10 KiB
JavaScript
378 lines
10 KiB
JavaScript
'use strict';
|
|
|
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
|
const node_fs = require('node:fs');
|
|
const pathe = require('pathe');
|
|
const MagicString = require('magic-string');
|
|
const unplugin$1 = require('unplugin');
|
|
const node_crypto = require('node:crypto');
|
|
const knitwork = require('knitwork');
|
|
const tools = require('./tools.cjs');
|
|
|
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
|
|
const MagicString__default = /*#__PURE__*/_interopDefaultCompat(MagicString);
|
|
|
|
const UNWASM_EXTERNAL_PREFIX = "\0unwasm:external:";
|
|
const UNWASM_EXTERNAL_RE = /(\0|\\0)unwasm:external:([^"']+)/gu;
|
|
const UMWASM_HELPERS_ID = "\0unwasm:helpers";
|
|
function sha1(source) {
|
|
return node_crypto.createHash("sha1").update(source).digest("hex").slice(0, 16);
|
|
}
|
|
|
|
async function getWasmImports(asset, _opts) {
|
|
const importNames = Object.keys(asset.imports || {});
|
|
if (importNames.length === 0) {
|
|
return {
|
|
code: "const _imports = { /* no imports */ }",
|
|
resolved: true
|
|
};
|
|
}
|
|
const { readPackageJSON } = await import('pkg-types');
|
|
const pkgJSON = await readPackageJSON(asset.id);
|
|
let resolved = true;
|
|
const imports = [];
|
|
const importsObject = {};
|
|
for (const moduleName of importNames) {
|
|
const importNames2 = asset.imports[moduleName];
|
|
const pkgImport = pkgJSON.imports?.[moduleName] || pkgJSON.imports?.[`#${moduleName}`];
|
|
const importName = "_imports_" + knitwork.genSafeVariableName(moduleName);
|
|
if (pkgImport && typeof pkgImport === "string") {
|
|
imports.push(knitwork.genImport(pkgImport, { name: "*", as: importName }));
|
|
} else {
|
|
resolved = false;
|
|
}
|
|
importsObject[moduleName] = Object.fromEntries(
|
|
importNames2.map((name) => [
|
|
name,
|
|
pkgImport ? `${importName}[${knitwork.genString(name)}]` : `() => { throw new Error(${knitwork.genString(moduleName + "." + importName)} + " is not provided!")}`
|
|
])
|
|
);
|
|
}
|
|
const code = `${imports.join("\n")}
|
|
|
|
const _imports = ${knitwork.genObjectFromRaw(importsObject)}`;
|
|
return {
|
|
code,
|
|
resolved
|
|
};
|
|
}
|
|
|
|
async function getWasmESMBinding(asset, opts) {
|
|
const autoImports = await getWasmImports(asset);
|
|
const instantiateCode = opts.esmImport ? getESMImportInstantiate(asset, autoImports.code) : getBase64Instantiate(asset, autoImports.code);
|
|
return opts.lazy !== true && autoImports.resolved ? getExports(asset, instantiateCode) : getLazyExports(asset, instantiateCode);
|
|
}
|
|
function getWasmModuleBinding(asset, opts) {
|
|
return opts.esmImport ? (
|
|
/* js */
|
|
`
|
|
const _mod = ${opts.lazy === true ? "" : `await`} import("${UNWASM_EXTERNAL_PREFIX}${asset.name}").then(r => r.default || r);
|
|
export default _mod;
|
|
`
|
|
) : (
|
|
/* js */
|
|
`
|
|
import { base64ToUint8Array } from "${UMWASM_HELPERS_ID}";
|
|
const _data = base64ToUint8Array("${asset.source.toString("base64")}");
|
|
const _mod = new WebAssembly.Module(_data);
|
|
export default _mod;
|
|
`
|
|
);
|
|
}
|
|
function getESMImportInstantiate(asset, importsCode) {
|
|
return (
|
|
/* js */
|
|
`
|
|
${importsCode}
|
|
|
|
async function _instantiate(imports = _imports) {
|
|
const _mod = await import("${UNWASM_EXTERNAL_PREFIX}${asset.name}").then(r => r.default || r);
|
|
return WebAssembly.instantiate(_mod, imports)
|
|
}
|
|
`
|
|
);
|
|
}
|
|
function getBase64Instantiate(asset, importsCode) {
|
|
return (
|
|
/* js */
|
|
`
|
|
import { base64ToUint8Array } from "${UMWASM_HELPERS_ID}";
|
|
|
|
${importsCode}
|
|
|
|
function _instantiate(imports = _imports) {
|
|
const _data = base64ToUint8Array("${asset.source.toString("base64")}")
|
|
return WebAssembly.instantiate(_data, imports) }
|
|
`
|
|
);
|
|
}
|
|
function getExports(asset, instantiateCode) {
|
|
return (
|
|
/* js */
|
|
`
|
|
import { getExports } from "${UMWASM_HELPERS_ID}";
|
|
|
|
${instantiateCode}
|
|
|
|
const $exports = getExports(await _instantiate());
|
|
|
|
${asset.exports.map((name) => `export const ${name} = $exports.${name};`).join("\n")}
|
|
|
|
const defaultExport = () => $exports;
|
|
${asset.exports.map((name) => `defaultExport["${name}"] = $exports.${name};`).join("\n")}
|
|
export default defaultExport;
|
|
`
|
|
);
|
|
}
|
|
function getLazyExports(asset, instantiateCode) {
|
|
return (
|
|
/* js */
|
|
`
|
|
import { createLazyWasmModule } from "${UMWASM_HELPERS_ID}";
|
|
|
|
${instantiateCode}
|
|
|
|
const _mod = createLazyWasmModule(_instantiate);
|
|
|
|
${asset.exports.map((name) => `export const ${name} = _mod.${name};`).join("\n")}
|
|
|
|
export default _mod;
|
|
`
|
|
);
|
|
}
|
|
|
|
function getPluginUtils() {
|
|
return (
|
|
/* js */
|
|
`
|
|
export function debug(...args) {
|
|
console.log('[unwasm] [debug]', ...args);
|
|
}
|
|
|
|
export function getExports(input) {
|
|
return input?.instance?.exports || input?.exports || input;
|
|
}
|
|
|
|
export function base64ToUint8Array(str) {
|
|
const data = atob(str);
|
|
const size = data.length;
|
|
const bytes = new Uint8Array(size);
|
|
for (let i = 0; i < size; i++) {
|
|
bytes[i] = data.charCodeAt(i);
|
|
}
|
|
return bytes;
|
|
}
|
|
|
|
export function createLazyWasmModule(_instantiator) {
|
|
const _exports = Object.create(null);
|
|
let _loaded;
|
|
let _promise;
|
|
|
|
const init = (imports) => {
|
|
if (_loaded) {
|
|
return Promise.resolve(exportsProxy);
|
|
}
|
|
if (_promise) {
|
|
return _promise;
|
|
}
|
|
return _promise = _instantiator(imports)
|
|
.then(r => {
|
|
Object.assign(_exports, getExports(r));
|
|
_loaded = true;
|
|
_promise = undefined;
|
|
return exportsProxy;
|
|
})
|
|
.catch(error => {
|
|
_promise = undefined;
|
|
console.error('[wasm] [error]', error);
|
|
throw error;
|
|
});
|
|
}
|
|
|
|
const exportsProxy = new Proxy(_exports, {
|
|
get(_, prop) {
|
|
if (_loaded) {
|
|
return _exports[prop];
|
|
}
|
|
return (...args) => {
|
|
return _loaded
|
|
? _exports[prop]?.(...args)
|
|
: init().then(() => _exports[prop]?.(...args));
|
|
};
|
|
},
|
|
});
|
|
|
|
|
|
const lazyProxy = new Proxy(() => {}, {
|
|
get(_, prop) {
|
|
return exportsProxy[prop];
|
|
},
|
|
apply(_, __, args) {
|
|
return init(args[0])
|
|
},
|
|
});
|
|
|
|
return lazyProxy;
|
|
}
|
|
`
|
|
);
|
|
}
|
|
|
|
const WASM_ID_RE = /\.wasm\??.*$/i;
|
|
const unplugin = unplugin$1.createUnplugin((opts) => {
|
|
const assets = /* @__PURE__ */ Object.create(null);
|
|
const _parseCache = /* @__PURE__ */ Object.create(null);
|
|
function parse(name, source) {
|
|
if (_parseCache[name]) {
|
|
return _parseCache[name];
|
|
}
|
|
const imports = /* @__PURE__ */ Object.create(null);
|
|
const exports = [];
|
|
try {
|
|
const parsed = tools.parseWasm(source, { name });
|
|
for (const mod of parsed.modules) {
|
|
exports.push(...mod.exports.map((e) => e.name));
|
|
for (const imp of mod.imports) {
|
|
if (!imports[imp.module]) {
|
|
imports[imp.module] = [];
|
|
}
|
|
imports[imp.module].push(imp.name);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.warn(`[unwasm] Failed to parse WASM module ${name}:`, error);
|
|
}
|
|
_parseCache[name] = {
|
|
imports,
|
|
exports
|
|
};
|
|
return _parseCache[name];
|
|
}
|
|
return {
|
|
name: "unwasm",
|
|
rollup: {
|
|
async resolveId(id, importer) {
|
|
if (id === UMWASM_HELPERS_ID) {
|
|
return id;
|
|
}
|
|
if (id.startsWith(UNWASM_EXTERNAL_PREFIX)) {
|
|
return {
|
|
id,
|
|
external: true
|
|
};
|
|
}
|
|
if (WASM_ID_RE.test(id)) {
|
|
const r = await this.resolve(id, importer, { skipSelf: true });
|
|
if (r?.id && r.id !== id) {
|
|
return {
|
|
id: r.id.startsWith("file://") ? r.id.slice(7) : r.id,
|
|
external: false,
|
|
moduleSideEffects: false
|
|
};
|
|
}
|
|
}
|
|
},
|
|
generateBundle() {
|
|
if (opts.esmImport) {
|
|
for (const asset of Object.values(assets)) {
|
|
this.emitFile({
|
|
type: "asset",
|
|
source: asset.source,
|
|
fileName: asset.name
|
|
});
|
|
}
|
|
}
|
|
}
|
|
},
|
|
async load(id) {
|
|
if (id === UMWASM_HELPERS_ID) {
|
|
return getPluginUtils();
|
|
}
|
|
if (!WASM_ID_RE.test(id)) {
|
|
return;
|
|
}
|
|
const idPath = id.split("?")[0];
|
|
if (!node_fs.existsSync(idPath)) {
|
|
return;
|
|
}
|
|
this.addWatchFile(idPath);
|
|
const buff = await node_fs.promises.readFile(idPath);
|
|
return buff.toString("binary");
|
|
},
|
|
async transform(code, id) {
|
|
if (!WASM_ID_RE.test(id)) {
|
|
return;
|
|
}
|
|
const buff = Buffer.from(code, "binary");
|
|
const isModule = id.endsWith("?module");
|
|
const name = `wasm/${pathe.basename(id.split("?")[0], ".wasm")}-${sha1(buff)}.wasm`;
|
|
const parsed = isModule ? { imports: [], exports: ["default"] } : parse(name, buff);
|
|
const asset = assets[name] = {
|
|
name,
|
|
id,
|
|
source: buff,
|
|
imports: parsed.imports,
|
|
exports: parsed.exports
|
|
};
|
|
return {
|
|
code: isModule ? await getWasmModuleBinding(asset, opts) : await getWasmESMBinding(asset, opts),
|
|
map: { mappings: "" }
|
|
};
|
|
},
|
|
renderChunk(code, chunk) {
|
|
if (!opts.esmImport) {
|
|
return;
|
|
}
|
|
if (!(chunk.moduleIds.some((id) => WASM_ID_RE.test(id)) || chunk.imports.some((id) => WASM_ID_RE.test(id)))) {
|
|
return;
|
|
}
|
|
const s = new MagicString__default(code);
|
|
const resolveImport = (id) => {
|
|
if (typeof id !== "string") {
|
|
return;
|
|
}
|
|
const asset = assets[id];
|
|
if (!asset) {
|
|
return;
|
|
}
|
|
const nestedLevel = chunk.fileName.split("/").filter(
|
|
Boolean
|
|
/* handle // */
|
|
).length - 1;
|
|
const relativeId = (nestedLevel ? "../".repeat(nestedLevel) : "./") + asset.name;
|
|
return {
|
|
relativeId,
|
|
asset
|
|
};
|
|
};
|
|
for (const match of code.matchAll(UNWASM_EXTERNAL_RE)) {
|
|
const resolved = resolveImport(match[2]);
|
|
const index = match.index;
|
|
const len = match[0].length;
|
|
if (!resolved || !index) {
|
|
console.warn(
|
|
`Failed to resolve WASM import: ${JSON.stringify(match[1])}`
|
|
);
|
|
continue;
|
|
}
|
|
s.overwrite(index, index + len, resolved.relativeId);
|
|
}
|
|
if (s.hasChanged()) {
|
|
return {
|
|
code: s.toString(),
|
|
map: s.generateMap({ includeContent: true })
|
|
};
|
|
}
|
|
}
|
|
};
|
|
});
|
|
const rollup = unplugin.rollup;
|
|
const index = {
|
|
rollup
|
|
};
|
|
|
|
exports.default = index;
|
|
exports.rollup = rollup;
|