Skip to content

Commit

Permalink
fix: WASM's imports not handled properly w/ optimizeDeps (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
Menci committed Oct 22, 2022
1 parent f333575 commit 5b2ed27
Show file tree
Hide file tree
Showing 9 changed files with 270 additions and 39 deletions.
4 changes: 3 additions & 1 deletion e2e/e2e.ts
Expand Up @@ -9,6 +9,7 @@ type RollupOutput = any;
import vitePluginWasm from "../src/index.js";

import express from "express";
import waitPort from "wait-port";
import mime from "mime";
import path from "path";
import url from "url";
Expand Down Expand Up @@ -102,7 +103,8 @@ async function startDevServer(vitePackages: VitePackages): Promise<string> {
if (typeof listeningAddress !== "object" || !listeningAddress)
throw new Error("Vite dev server doen't listen on a port");

return `http://127.0.0.1:${listeningAddress.port}`;
await waitPort({ port: listeningAddress.port, output: "silent" });
return `http://localhost:${listeningAddress.port}`;
}

async function createBrowser(modernBrowser: boolean) {
Expand Down
26 changes: 17 additions & 9 deletions e2e/src/test-wasm.ts
@@ -1,12 +1,20 @@
// @syntect/wasm requires WASM to ES module transform
import { highlight } from "@syntect/wasm";
import { parse } from "@jsona/openapi";

const TEST_CODE = "#include <cstdio>";
const TEST_LANG = "cpp";
const expectedResult = {
value: {
openapi: "3.0.0",
info: { version: "0.1.0", title: "openapi" },
paths: {},
components: {}
},
errors: null
};
const actualResult = parse("{}");

const div = document.createElement("div");
div.innerHTML = highlight(TEST_CODE, TEST_LANG, "hl-").html;
const result = div.innerText.trim();
if (result !== TEST_CODE) {
console.error(`Expected ${JSON.stringify(TEST_CODE)} but got ${JSON.stringify(result)}.`);
const expectedJson = JSON.stringify(expectedResult);
const actualJson = JSON.stringify(actualResult);
if (actualJson !== expectedJson) {
console.error(`
Expected ${expectedJson}
but got ${actualJson}`);
}
2 changes: 0 additions & 2 deletions e2e/yarn.lock
Expand Up @@ -2,5 +2,3 @@
# yarn lockfile v1


"vite@file:vite-placeholder":
version "0.0.0"
5 changes: 4 additions & 1 deletion package.json
Expand Up @@ -27,19 +27,22 @@
},
"devDependencies": {
"@jest/types": "^28.1.3",
"@jsona/openapi": "^0.2.5",
"@syntect/wasm": "^0.0.4",
"@types/express": "^4.17.13",
"@types/jest": "^28.1.5",
"cross-env": "^7.0.3",
"cz-conventional-changelog": "^3.3.0",
"esbuild": "^0.15.12",
"express": "^4.18.1",
"jest": "^28.1.3",
"jest-extended": "^3.0.1",
"playwright": "^1.23.3",
"prettier": "^2.7.1",
"terser": "^5.14.2",
"ts-jest": "^28.0.7",
"typescript": "^4.7.4"
"typescript": "^4.7.4",
"wait-port": "^1.0.4"
},
"dependencies": {
"vite": "^3"
Expand Down
34 changes: 34 additions & 0 deletions src/esbuild-plugin.ts
@@ -0,0 +1,34 @@
import fs from "fs";
import { posix as path } from "path";
import { Plugin } from "esbuild";

import * as wasmHelper from "./wasm-helper";
import { generateGlueCode } from "./wasm-parser";

export function esbuildPlugin(): Plugin {
return {
name: "vite-plugin-wasm",
setup(build) {
const NAMESPACE = "vite-plugin-wasm-namespace";

build.onResolve({ filter: /\.wasm$/ }, args => ({
path: path.join(path.dirname(args.importer), args.path),
namespace: NAMESPACE
}));

build.onLoad({ filter: /.*/, namespace: NAMESPACE }, async args => {
const base64 = await fs.promises.readFile(args.path, "base64");
const dataUri = "data:application/wasm;base64," + base64;
return {
contents: `
const wasmUrl = "${dataUri}";
const initWasm = ${wasmHelper.code};
${await generateGlueCode(args.path, { initWasm: "initWasm", wasmUrl: "wasmUrl" })}
`,
loader: "js",
resolveDir: path.dirname(args.path)
};
});
}
};
}
38 changes: 14 additions & 24 deletions src/index.ts
@@ -1,16 +1,24 @@
import type { Plugin, ResolvedConfig } from "vite";
import type { Plugin } from "vite";

import { parseWasm } from "./parse-wasm";
import { esbuildPlugin } from "./esbuild-plugin";
import { generateGlueCode } from "./wasm-parser";
import * as wasmHelper from "./wasm-helper";

export default function wasm(): any {
let resolvedConfig: ResolvedConfig;

return <Plugin>{
name: "vite-plugin-wasm",
enforce: "pre",
configResolved(config) {
resolvedConfig = config;
if (config.optimizeDeps?.esbuildOptions) {
// https://github.com/Menci/vite-plugin-wasm/pull/11
if (!config.optimizeDeps.esbuildOptions.plugins) {
config.optimizeDeps.esbuildOptions.plugins = [];
}
config.optimizeDeps.esbuildOptions.plugins.push(esbuildPlugin());

// Allow usage of top-level await during development build (not affacting the production build)
config.optimizeDeps.esbuildOptions.target = "esnext";
}
},
resolveId(id) {
if (id === wasmHelper.id) {
Expand All @@ -26,31 +34,13 @@ export default function wasm(): any {
return;
}

const { imports, exports } = await parseWasm(id);

// Get WASM's download URL by Vite's ?url import
const wasmUrlUrl = id + "?url";

return `
import __vite__wasmUrl from ${JSON.stringify(wasmUrlUrl)};
import __vite__initWasm from "${wasmHelper.id}"
${imports
.map(
({ from, names }, i) =>
`import { ${names.map((name, j) => `${name} as __vite__wasmImport_${i}_${j}`).join(", ")} } from ${JSON.stringify(
from
)};`
)
.join("\n")}
const __vite__wasmModule = await __vite__initWasm({ ${imports
.map(
({ from, names }, i) =>
`${JSON.stringify(from)}: { ${names.map((name, j) => `${name}: __vite__wasmImport_${i}_${j}`).join(", ")} }`
)
.join(", ")} }, __vite__wasmUrl);
${exports
.map(name => `export ${name === "default" ? "default" : `const ${name} =`} __vite__wasmModule.${name};`)
.join("\n")}
${await generateGlueCode(id, { initWasm: "__vite__initWasm", wasmUrl: "__vite__wasmUrl" })}
`;
}
};
Expand Down
2 changes: 1 addition & 1 deletion src/parse-wasm.spec.ts → src/wasm-parser.spec.ts
@@ -1,7 +1,7 @@
/// <reference types="jest-extended" />

import { createRequire } from "module";
import { parseWasm } from "./parse-wasm.js";
import { parseWasm } from "./wasm-parser.js";
import "jest-extended";

// @ts-ignore this file is ESM
Expand Down
25 changes: 25 additions & 0 deletions src/parse-wasm.ts → src/wasm-parser.ts
Expand Up @@ -29,3 +29,28 @@ export async function parseWasm(wasmFilePath: string): Promise<WasmInfo> {
throw new Error(`Failed to parse WASM file: ${e.message}`);
}
}

export async function generateGlueCode(
wasmFilePath: string,
names: { initWasm: string; wasmUrl: string }
): Promise<string> {
const { imports, exports } = await parseWasm(wasmFilePath);
return `
${imports
.map(
({ from, names }, i) =>
`import { ${names.map((name, j) => `${name} as __vite__wasmImport_${i}_${j}`).join(", ")} } from ${JSON.stringify(
from
)};`
)
.join("\n")}
const __vite__wasmModule = await ${names.initWasm}({ ${imports
.map(
({ from, names }, i) =>
`${JSON.stringify(from)}: { ${names.map((name, j) => `${name}: __vite__wasmImport_${i}_${j}`).join(", ")} }`
)
.join(", ")} }, ${names.wasmUrl});
${exports
.map(name => `export ${name === "default" ? "default" : `const ${name} =`} __vite__wasmModule.${name};`)
.join("\n")}`;
}

0 comments on commit 5b2ed27

Please sign in to comment.