From 185dbdf87f976d3b6da41bec91d8bca7d4731e3e Mon Sep 17 00:00:00 2001 From: "Daniel J. Rollins" Date: Wed, 4 Sep 2024 11:00:05 +0100 Subject: [PATCH 01/10] prevent resultProcessor from recursively prepending output paths from output processor --- src/plugin.ts | 6 +----- src/results.ts | 9 +++++++-- test/__snapshots__/test.ts.snap | 24 ++++++++++++------------ 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/plugin.ts b/src/plugin.ts index 2df443d..9225850 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -67,7 +67,7 @@ export function mml(args: MMLPluginOptions = {}): esbuild.Plugin { documents, options: initialOptions, onEnd: async (result, importStubs) => { - await onResult("document", result, importStubs); + await onResult("document", result, importStubs, false); }, verbose, }); @@ -91,10 +91,6 @@ export function mml(args: MMLPluginOptions = {}): esbuild.Plugin { await worldCtx.rebuild(); }); - build.onEnd(async (result) => { - await onResult("root", result, {}); - }); - build.onDispose(() => { log("onDispose"); void (async () => { diff --git a/src/results.ts b/src/results.ts index 4295fa1..d511227 100644 --- a/src/results.ts +++ b/src/results.ts @@ -40,6 +40,7 @@ export const makeResultProcessor = ( key: string, result: esbuild.BuildResult, importStubs: Record, + process = true, ) => { log("new result", util.inspect({ key, importStubs, result }, { depth: 5 })); @@ -50,12 +51,16 @@ export const makeResultProcessor = ( metaResults.set(key, { importStubs, result }); + if (!process) { + return; + } + const combinedResult = {} as esbuild.BuildResult; const combinedStubs = {} as Record; for (const [, { importStubs, result }] of metaResults) { - merge(combinedResult, result); - merge(combinedStubs, importStubs); + merge(combinedResult, structuredClone(result)); + merge(combinedStubs, structuredClone(importStubs)); } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion diff --git a/test/__snapshots__/test.ts.snap b/test/__snapshots__/test.ts.snap index 1823f36..814f455 100644 --- a/test/__snapshots__/test.ts.snap +++ b/test/__snapshots__/test.ts.snap @@ -320,7 +320,7 @@ exports[`mml plugin with absolute outdir path with import prefix: generated/abso " `; -exports[`mml plugin with absolute outdir path with new import and path from output processor: generated/absolute-outpath/new-import-and-path/flump/flump/flump/a.html 1`] = ` +exports[`mml plugin with absolute outdir path with new import and path from output processor: generated/absolute-outpath/new-import-and-path/flump/a.html 1`] = ` "" `; -exports[`mml plugin with absolute outdir path with new import and path from output processor: generated/absolute-outpath/new-import-and-path/flump/flump/flump/b.html 1`] = ` +exports[`mml plugin with absolute outdir path with new import and path from output processor: generated/absolute-outpath/new-import-and-path/flump/b.html 1`] = ` "" `; -exports[`mml plugin with absolute outdir path with new import and path from output processor: generated/absolute-outpath/new-import-and-path/flump/flump/flump/c/d.html 1`] = ` +exports[`mml plugin with absolute outdir path with new import and path from output processor: generated/absolute-outpath/new-import-and-path/flump/c/d.html 1`] = ` "

I'm html!

@@ -378,7 +378,7 @@ exports[`mml plugin with absolute outdir path with new import from output proces " `; -exports[`mml plugin with absolute outdir path with new path from output processor: generated/absolute-outpath/new-path/bar/bar/bar/a.html 1`] = ` +exports[`mml plugin with absolute outdir path with new path from output processor: generated/absolute-outpath/new-path/bar/a.html 1`] = ` "" `; -exports[`mml plugin with absolute outdir path with new path from output processor: generated/absolute-outpath/new-path/bar/bar/bar/b.html 1`] = ` +exports[`mml plugin with absolute outdir path with new path from output processor: generated/absolute-outpath/new-path/bar/b.html 1`] = ` "" `; -exports[`mml plugin with absolute outdir path with new path from output processor: generated/absolute-outpath/new-path/bar/bar/bar/c/d.html 1`] = ` +exports[`mml plugin with absolute outdir path with new path from output processor: generated/absolute-outpath/new-path/bar/c/d.html 1`] = ` "

I'm html!

@@ -509,7 +509,7 @@ exports[`mml plugin with relative outdir path with import prefix: generated/rela " `; -exports[`mml plugin with relative outdir path with new import and path from output processor: generated/relative-outpath/new-import-and-path/flump/flump/flump/a.html 1`] = ` +exports[`mml plugin with relative outdir path with new import and path from output processor: generated/relative-outpath/new-import-and-path/flump/a.html 1`] = ` "" `; -exports[`mml plugin with relative outdir path with new import and path from output processor: generated/relative-outpath/new-import-and-path/flump/flump/flump/b.html 1`] = ` +exports[`mml plugin with relative outdir path with new import and path from output processor: generated/relative-outpath/new-import-and-path/flump/b.html 1`] = ` "" `; -exports[`mml plugin with relative outdir path with new import and path from output processor: generated/relative-outpath/new-import-and-path/flump/flump/flump/c/d.html 1`] = ` +exports[`mml plugin with relative outdir path with new import and path from output processor: generated/relative-outpath/new-import-and-path/flump/c/d.html 1`] = ` "

I'm html!

@@ -567,7 +567,7 @@ exports[`mml plugin with relative outdir path with new import from output proces " `; -exports[`mml plugin with relative outdir path with new path from output processor: generated/relative-outpath/new-path/bar/bar/bar/a.html 1`] = ` +exports[`mml plugin with relative outdir path with new path from output processor: generated/relative-outpath/new-path/bar/a.html 1`] = ` "" `; -exports[`mml plugin with relative outdir path with new path from output processor: generated/relative-outpath/new-path/bar/bar/bar/b.html 1`] = ` +exports[`mml plugin with relative outdir path with new path from output processor: generated/relative-outpath/new-path/bar/b.html 1`] = ` "" `; -exports[`mml plugin with relative outdir path with new path from output processor: generated/relative-outpath/new-path/bar/bar/bar/c/d.html 1`] = ` +exports[`mml plugin with relative outdir path with new path from output processor: generated/relative-outpath/new-path/bar/c/d.html 1`] = ` "

I'm html!

From ed882fb3f7c59f41b7591192bed335a43ea19bf0 Mon Sep 17 00:00:00 2001 From: "Daniel J. Rollins" Date: Thu, 5 Sep 2024 13:06:34 +0100 Subject: [PATCH 02/10] split resultProcessor into two functions --- package.json | 3 +- src/plugin.ts | 16 +++-- src/results.ts | 185 +++++++++++++++++++++++++------------------------ test/test.ts | 15 ++-- 4 files changed, 117 insertions(+), 102 deletions(-) diff --git a/package.json b/package.json index f23d7e8..56a7b22 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "types": "./dist/types/index.d.ts", "import": "./dist/index.cjs", "require": "./dist/index.js" - } + }, + "./package.json": "./package.json" }, "keywords": [ "esbuild", diff --git a/src/plugin.ts b/src/plugin.ts index 9225850..24c3c42 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -55,7 +55,7 @@ export function mml(args: MMLPluginOptions = {}): esbuild.Plugin { initialOptions.entryPoints = []; - const onResult = makeResultProcessor( + const processor = makeResultProcessor( outdir, importPrefix, log, @@ -67,7 +67,8 @@ export function mml(args: MMLPluginOptions = {}): esbuild.Plugin { documents, options: initialOptions, onEnd: async (result, importStubs) => { - await onResult("document", result, importStubs, false); + processor.pushResult("document", result, importStubs); + await processor.process(); }, verbose, }); @@ -76,11 +77,14 @@ export function mml(args: MMLPluginOptions = {}): esbuild.Plugin { build: build.esbuild, worlds, onEnd: async (result, discoveredDocuments, importStubs) => { + processor.pushResult("world", result, importStubs); + if (discoveredDocuments.size === 0) { + return; + } await documentCtx.rebuildWithDocuments( discoveredDocuments, importStubs, ); - await onResult("world", result, importStubs); }, options: initialOptions, verbose, @@ -88,7 +92,11 @@ export function mml(args: MMLPluginOptions = {}): esbuild.Plugin { build.onStart(async () => { log("onStart"); - await worldCtx.rebuild(); + if (worlds.length > 0) { + await worldCtx.rebuild(); + } else { + await documentCtx.rebuild(); + } }); build.onDispose(() => { diff --git a/src/results.ts b/src/results.ts index d511227..7180f8c 100644 --- a/src/results.ts +++ b/src/results.ts @@ -36,109 +36,110 @@ export const makeResultProcessor = ( ) => { const metaResults = new Map(); - return async ( - key: string, - result: esbuild.BuildResult, - importStubs: Record, - process = true, - ) => { - log("new result", util.inspect({ key, importStubs, result }, { depth: 5 })); - - if (result.errors.length > 0) { - log("build failed with errors", result.errors); - return; - } - - metaResults.set(key, { importStubs, result }); - - if (!process) { - return; - } - - const combinedResult = {} as esbuild.BuildResult; - const combinedStubs = {} as Record; + return { + pushResult( + key: string, + result: esbuild.BuildResult, + importStubs: Record, + ) { + log( + "new result", + util.inspect({ key, importStubs, result }, { depth: 5 }), + ); + + if (result.errors.length > 0) { + log("build failed with errors", result.errors); + return; + } - for (const [, { importStubs, result }] of metaResults) { - merge(combinedResult, structuredClone(result)); - merge(combinedStubs, structuredClone(importStubs)); - } + metaResults.set(key, { importStubs, result }); + }, + process: async () => { + const combinedResult = {} as esbuild.BuildResult; + const combinedStubs = {} as Record; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const outputs = combinedResult.metafile!.outputs; + for (const [, { importStubs, result }] of metaResults) { + merge(combinedResult, structuredClone(result)); + merge(combinedStubs, structuredClone(importStubs)); + } - if (outputProcessor) { - for (const [output, meta] of Object.entries(outputs)) { - const relPath = path.relative(outdir, output); - const result = await outputProcessor.onOutput(relPath); - if (!result) { - continue; - } - const { path: newPath = relPath, importStr: newImport = newPath } = - result; - if (newPath !== relPath) { - const newOutput = path.join(outdir, newPath); - log("Renaming:", relPath, "->", newPath); - await fsp.mkdir(path.dirname(newOutput), { recursive: true }); - await fsp.rename(output, newOutput); - outputs[newOutput] = meta; - remove(outputs, output); - } - const entryPoint = meta.entryPoint ?? Object.keys(meta.inputs)[0]; - if (combinedStubs[entryPoint] && newImport !== entryPoint) { - combinedStubs[newImport] = combinedStubs[entryPoint]; - remove(combinedStubs, entryPoint); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const outputs = combinedResult.metafile!.outputs; + + if (outputProcessor) { + for (const [output, meta] of Object.entries(outputs)) { + const relPath = path.relative(outdir, output); + const result = await outputProcessor.onOutput(relPath); + if (!result) { + continue; + } + const { path: newPath = relPath, importStr: newImport = newPath } = + result; + if (newPath !== relPath) { + const newOutput = path.join(outdir, newPath); + log("Renaming:", relPath, "->", newPath); + await fsp.mkdir(path.dirname(newOutput), { recursive: true }); + await fsp.rename(output, newOutput); + outputs[newOutput] = meta; + remove(outputs, output); + } + const entryPoint = meta.entryPoint ?? Object.keys(meta.inputs)[0]; + if (combinedStubs[entryPoint] && newImport !== entryPoint) { + combinedStubs[newImport] = combinedStubs[entryPoint]; + remove(combinedStubs, entryPoint); + } + log("Output processor result", { + entryPoint, + result, + newPath, + newImport, + combinedStubs, + }); } - log("Output processor result", { - entryPoint, - result, - newPath, - newImport, - combinedStubs, - }); - } - log("New stubs", combinedStubs); - } else { - for (const [output, meta] of Object.entries(outputs)) { - const entryPoint = meta.entryPoint ?? Object.keys(meta.inputs)[0]; + log("New stubs", combinedStubs); + } else { + for (const [output, meta] of Object.entries(outputs)) { + const entryPoint = meta.entryPoint ?? Object.keys(meta.inputs)[0]; - if (!combinedStubs[entryPoint]) { - continue; - } + if (!combinedStubs[entryPoint]) { + continue; + } - const newImport = path.relative(outdir, output); + const newImport = path.relative(outdir, output); - if (newImport !== entryPoint) { - combinedStubs[newImport] = combinedStubs[entryPoint]; - remove(combinedStubs, entryPoint); + if (newImport !== entryPoint) { + combinedStubs[newImport] = combinedStubs[entryPoint]; + remove(combinedStubs, entryPoint); + } } } - } - cleanupJS(outdir, log); - - // Now we go through all of the output files and rewrite the import stubs to - // correct output path. - await Promise.all( - Object.keys(outputs).map(async (output) => { - let contents = await fsp.readFile(output, { encoding: "utf8" }); - for (const [file, stub] of Object.entries(combinedStubs)) { - const replacement = importPrefix + file; - log("Replacing import stub:", { - stub, - replacement, - output, - contents, - }); - contents = contents.replaceAll(stub, replacement); - } - await fsp.writeFile(output, contents); - }), - ); - - if (outputProcessor?.onEnd) { - return outputProcessor.onEnd(outdir, combinedResult); - } + cleanupJS(outdir, log); + + // Now we go through all of the output files and rewrite the import stubs to + // correct output path. + await Promise.all( + Object.keys(outputs).map(async (output) => { + let contents = await fsp.readFile(output, { encoding: "utf8" }); + for (const [file, stub] of Object.entries(combinedStubs)) { + const replacement = importPrefix + file; + log("Replacing import stub:", { + stub, + replacement, + output, + contents, + }); + contents = contents.replaceAll(stub, replacement); + } + await fsp.writeFile(output, contents); + }), + ); + + if (outputProcessor?.onEnd) { + return outputProcessor.onEnd(outdir, combinedResult); + } + }, }; }; diff --git a/test/test.ts b/test/test.ts index b7e6962..554dd9b 100644 --- a/test/test.ts +++ b/test/test.ts @@ -84,13 +84,14 @@ describe("resultProcessor", () => { const outdir = path.join(relativeOutdir, "result-processor", "world"); await fsp.rm(outdir, { recursive: true, force: true }); - const onEnd = makeResultProcessor(outdir, "wss://", noop); + const processor = makeResultProcessor(outdir, "wss://", noop); ctx = await worldContext({ worlds: ["test/src/world.ts"], options: { outdir }, onEnd: async (result, _, importStubs) => { - await onEnd("world", result, importStubs); + processor.pushResult("world", result, importStubs); + await processor.process(); for await (const { path, content } of walk(outdir)) { expect(content).toMatchSnapshot(path); } @@ -106,13 +107,14 @@ describe("resultProcessor", () => { const outdir = path.join(relativeOutdir, "result-processor", "world"); await fsp.rm(outdir, { recursive: true, force: true }); - const onEnd = makeResultProcessor(outdir, "wss://", noop); + const processor = makeResultProcessor(outdir, "wss://", noop); ctx = await documentContext({ documents: ["test/src/a.ts"], options: { outdir }, onEnd: async (result, importStubs) => { - await onEnd("document", result, importStubs); + processor.pushResult("document", result, importStubs); + await processor.process(); for await (const { path, content } of walk(outdir)) { expect(content).toMatchSnapshot(path); } @@ -229,6 +231,9 @@ describe("mml plugin", () => { }; await esbuild.build(config); + // This is a hack to wait for dispose to finish, because the onDispose plugin + // callback is not async so we cannot wait the promises running inside it. + await new Promise((resolve) => setTimeout(resolve, 100)); for await (const { path, content } of walk(outdir)) { expect(content).toMatchSnapshot(path); @@ -384,6 +389,6 @@ describe("context", () => { // This is a hack to wait for dispose to finish, because the onDispose plugin // callback is not async so we cannot wait the promises running inside it. - await new Promise((resolve) => setTimeout(resolve, 10)); + await new Promise((resolve) => setTimeout(resolve, 20)); }); }); From 73a10ba2fc0f16f8e45fa11d3a6bc3330cc6a8a5 Mon Sep 17 00:00:00 2001 From: "Daniel J. Rollins" Date: Thu, 5 Sep 2024 16:07:53 +0100 Subject: [PATCH 03/10] use ESM instead of CJS for world config format --- package.json | 4 ++-- src/results.ts | 9 +-------- src/world.ts | 5 ++--- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 56a7b22..47e2fc1 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,8 @@ "exports": { ".": { "types": "./dist/types/index.d.ts", - "import": "./dist/index.cjs", - "require": "./dist/index.js" + "import": "./dist/index.js", + "require": "./dist/index.cjs" }, "./package.json": "./package.json" }, diff --git a/src/results.ts b/src/results.ts index 7180f8c..6a6cfcf 100644 --- a/src/results.ts +++ b/src/results.ts @@ -85,16 +85,10 @@ export const makeResultProcessor = ( } const entryPoint = meta.entryPoint ?? Object.keys(meta.inputs)[0]; if (combinedStubs[entryPoint] && newImport !== entryPoint) { + log("Replacing import stub:", { entryPoint, newImport }); combinedStubs[newImport] = combinedStubs[entryPoint]; remove(combinedStubs, entryPoint); } - log("Output processor result", { - entryPoint, - result, - newPath, - newImport, - combinedStubs, - }); } log("New stubs", combinedStubs); @@ -128,7 +122,6 @@ export const makeResultProcessor = ( stub, replacement, output, - contents, }); contents = contents.replaceAll(stub, replacement); } diff --git a/src/world.ts b/src/world.ts index 0658ca1..f332341 100644 --- a/src/world.ts +++ b/src/world.ts @@ -36,7 +36,7 @@ export async function worldContext({ const ctx = await build.context({ ...options, entryPoints: worlds, - format: "cjs", + format: "esm", bundle: true, metafile: true, plugins: [ @@ -105,8 +105,7 @@ export async function worldContext({ for (const [jsPath, meta] of Object.entries(outputs)) { if (!meta.entryPoint) continue; const jsonPath = jsPath.replace(jsExt, ".json"); - // eslint-disable-next-line @typescript-eslint/no-require-imports - const { default: js } = require(path.resolve(jsPath)) as { + const { default: js } = (await import(path.resolve(jsPath))) as { default: MMLWorldConfig; }; const json = JSON.stringify(js, null, 2); From 40924cf80793c03f16594e2b5d007b07b067c2de Mon Sep 17 00:00:00 2001 From: "Daniel J. Rollins" Date: Thu, 5 Sep 2024 16:56:16 +0100 Subject: [PATCH 04/10] use CJS again, but keep await import --- src/results.ts | 5 +++++ src/world.ts | 2 +- test/src/package.json | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 test/src/package.json diff --git a/src/results.ts b/src/results.ts index 6a6cfcf..4b105ae 100644 --- a/src/results.ts +++ b/src/results.ts @@ -63,6 +63,11 @@ export const makeResultProcessor = ( merge(combinedStubs, structuredClone(importStubs)); } + if (combinedResult.errors.length > 0) { + log("build failed with errors", combinedResult.errors); + return; + } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const outputs = combinedResult.metafile!.outputs; diff --git a/src/world.ts b/src/world.ts index f332341..2b410ad 100644 --- a/src/world.ts +++ b/src/world.ts @@ -36,7 +36,7 @@ export async function worldContext({ const ctx = await build.context({ ...options, entryPoints: worlds, - format: "esm", + format: "cjs", bundle: true, metafile: true, plugins: [ diff --git a/test/src/package.json b/test/src/package.json new file mode 100644 index 0000000..3dbc1ca --- /dev/null +++ b/test/src/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} From e4068babe0c9e5f3fe19e8d6dde7b483589e9be7 Mon Sep 17 00:00:00 2001 From: "Daniel J. Rollins" Date: Thu, 5 Sep 2024 16:56:16 +0100 Subject: [PATCH 05/10] use CJS again, but keep await import --- jest.config.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/jest.config.js b/jest.config.js index c44ef36..afc91c4 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,7 +1,10 @@ /** @type {import('ts-jest').JestConfigWithTsJest} **/ +import { createDefaultEsmPreset } from "ts-jest"; + export default { testEnvironment: "node", - transform: { - "^.+.tsx?$": ["ts-jest", {}], + ...createDefaultEsmPreset(), + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", }, }; From 45bdc2cdfe997709d8012696c5174285b4be687d Mon Sep 17 00:00:00 2001 From: "Daniel J. Rollins" Date: Tue, 3 Sep 2024 14:52:21 +0100 Subject: [PATCH 06/10] treat any imports with extensions (excluding html and [tj]sx?) as asset imports --- src/documents.ts | 73 ++++++++++++++- src/plugin.ts | 23 +++-- src/results.ts | 29 ++++-- test/__snapshots__/test.ts.snap | 154 ++++++++++++++++++++++++-------- test/src/.gitattributes | 1 + test/src/a.ts | 3 +- test/src/duck.glb | 3 + test/src/types.d.ts | 4 + test/test.ts | 43 ++++----- 9 files changed, 258 insertions(+), 75 deletions(-) create mode 100644 test/src/.gitattributes create mode 100644 test/src/duck.glb create mode 100644 test/src/types.d.ts diff --git a/src/documents.ts b/src/documents.ts index f31e0bf..a5ec500 100644 --- a/src/documents.ts +++ b/src/documents.ts @@ -1,5 +1,5 @@ import esbuild from "esbuild"; -import path from "node:path"; +import path, { basename } from "node:path"; import util from "node:util"; import fsp from "node:fs/promises"; @@ -99,6 +99,15 @@ export async function documentContext({ }; } +const nonAssetExtensions = new Set([ + ".html", + ".css", + ".js", + ".ts", + ".jsx", + ".tsx", +]); + export function documentPlugin(args: DocumentPluginOptions): esbuild.Plugin { const { verbose, importStubs, onEnd } = args; const log = verbose @@ -113,8 +122,16 @@ export function documentPlugin(args: DocumentPluginOptions): esbuild.Plugin { build.initialOptions.metafile ??= true; build.initialOptions.bundle ??= true; (build.initialOptions.loader ??= {})[".html"] = "copy"; + const outdir = (build.initialOptions.outdir ??= "build"); const discoveredDocuments = new Set(); + const assets: { output: string; entrypoint: string }[] = []; + + build.onStart(() => { + log("onStart"); + discoveredDocuments.clear(); + assets.length = 0; + }); build.onResolve({ filter: /mml:/ }, async (args) => { log("onResolve(/mml:/)", args); @@ -140,6 +157,49 @@ export function documentPlugin(args: DocumentPluginOptions): esbuild.Plugin { }; }); + build.onResolve({ filter: /\.[^./]+$/ }, (args) => { + if (nonAssetExtensions.has(path.extname(args.path))) return; + + log("onResolve: asset", args); + + const resolved = path.resolve(args.resolveDir, args.path); + const relpath = path.relative(process.cwd(), resolved); + importStubs[relpath] = `asset:${relpath}`; + + return { + path: resolved, + namespace: "asset", + watchFiles: [args.path], + }; + }); + + build.onLoad( + { filter: /.*/, namespace: "asset" }, + async (args: esbuild.OnLoadArgs) => { + log("onLoad: asset", { + ...args, + importStubs, + cwd: process.cwd(), + }); + + // TODO: add this to the metafile + const output = path.resolve(outdir, basename(args.path)); + const entrypoint = path.relative(process.cwd(), args.path); + + assets.push({ output, entrypoint }); + + await fsp.mkdir(path.dirname(output), { recursive: true }); + await fsp.copyFile(args.path, output); + + const contents = importStubs[entrypoint]; + + return { + contents, + loader: "text", + }; + }, + ); + build.onLoad( { filter: /.*/, namespace: "mml" }, (args: esbuild.OnLoadArgs) => { @@ -180,6 +240,17 @@ export function documentPlugin(args: DocumentPluginOptions): esbuild.Plugin { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const outputs = result.metafile!.outputs; + for (const asset of assets) { + const stats = await fsp.stat(asset.output); + outputs[asset.output] = { + entryPoint: asset.entrypoint, + bytes: stats.size, + inputs: {}, + imports: [], + exports: [], + }; + } + for (const [jsPath, meta] of Object.entries(outputs)) { if (!meta.entryPoint || !jsPath.endsWith(".js")) continue; diff --git a/src/plugin.ts b/src/plugin.ts index 24c3c42..e146a46 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -13,6 +13,8 @@ export type MaybePromise = Promise | T; export interface MMLPluginOptions { verbose?: boolean; outputProcessor?: OutputProcessorProvider; + documentPrefix?: string; + assetPrefix?: string; importPrefix?: string; } @@ -20,14 +22,24 @@ export function mml(args: MMLPluginOptions = {}): esbuild.Plugin { const { verbose, outputProcessor: outputProcessorProvider, - importPrefix = "ws:///", + importPrefix, + assetPrefix = "", } = args; + let { documentPrefix = "ws:///" } = args; + const log = verbose ? (...args: unknown[]) => { console.log("[mml]:", ...args); } : noop; + if (importPrefix) { + log("importPrefix is deprecated, use documentPrefix instead"); + if (!documentPrefix) { + documentPrefix = importPrefix; + } + } + return { name: "mml", async setup(build) { @@ -55,12 +67,13 @@ export function mml(args: MMLPluginOptions = {}): esbuild.Plugin { initialOptions.entryPoints = []; - const processor = makeResultProcessor( + const processor = makeResultProcessor({ outdir, - importPrefix, + documentPrefix, + assetPrefix, log, - outputProcessorProvider?.(log), - ); + outputProcessor: outputProcessorProvider?.(log), + }); const documentCtx = await documentContext({ build: build.esbuild, diff --git a/src/results.ts b/src/results.ts index 4b105ae..73e10fe 100644 --- a/src/results.ts +++ b/src/results.ts @@ -28,12 +28,22 @@ interface MetaResult { result: esbuild.BuildResult<{ metafile: true }>; } -export const makeResultProcessor = ( - outdir: string, - importPrefix: string, - log: typeof console.log, - outputProcessor?: OutputProcessor, -) => { +interface MakeResultProcessorOptions { + outdir: string; + documentPrefix: string; + assetPrefix: string; + log: typeof console.log; + outputProcessor?: OutputProcessor; +} + +// TODO: make this return two functions, ont for onResult and one for onEnd +export const makeResultProcessor = ({ + outdir, + documentPrefix, + assetPrefix, + log, + outputProcessor, +}: MakeResultProcessorOptions) => { const metaResults = new Map(); return { @@ -122,7 +132,12 @@ export const makeResultProcessor = ( Object.keys(outputs).map(async (output) => { let contents = await fsp.readFile(output, { encoding: "utf8" }); for (const [file, stub] of Object.entries(combinedStubs)) { - const replacement = importPrefix + file; + if (!stub.startsWith("mml:") && !stub.startsWith("asset:")) { + continue; + } + const replacement = stub.startsWith("mml") + ? documentPrefix + file + : assetPrefix + file; log("Replacing import stub:", { stub, replacement, diff --git a/test/__snapshots__/test.ts.snap b/test/__snapshots__/test.ts.snap index 814f455..1790dfb 100644 --- a/test/__snapshots__/test.ts.snap +++ b/test/__snapshots__/test.ts.snap @@ -6,7 +6,8 @@ exports[`context rebuild: generated/relative-outpath/context/rebuild/a.html/1 1` var b_default = "ws:///b.html"; var d_default = "ws:///c/d.html"; var c = "wat-" + d_default; - console.log(b_default, d_default); + var duck_default = "duck.glb"; + console.log(b_default, d_default, duck_default); })(); " `; @@ -17,8 +18,9 @@ exports[`context rebuild: generated/relative-outpath/context/rebuild/a.html/2 1` var b_default = "ws:///b.html"; var d_default = "ws:///c/d.html"; var c = "wat-" + d_default; + var duck_default = "duck.glb"; var e_default = "ws:///e.html"; - console.log(b_default, d_default); + console.log(b_default, d_default, duck_default); console.log(e_default); })(); " @@ -30,7 +32,8 @@ exports[`context rebuild: generated/relative-outpath/context/rebuild/a.html/3 1` var b_default = "ws:///b.html"; var d_default = "ws:///c/d.html"; var c = "wat-" + d_default; - console.log(b_default, d_default); + var duck_default = "duck.glb"; + console.log(b_default, d_default, duck_default); })(); " `; @@ -89,6 +92,12 @@ exports[`context rebuild: generated/relative-outpath/context/rebuild/c/d.html/3 " `; +exports[`context rebuild: generated/relative-outpath/context/rebuild/duck.glb/1 1`] = `"duck.glb"`; + +exports[`context rebuild: generated/relative-outpath/context/rebuild/duck.glb/2 1`] = `"duck.glb"`; + +exports[`context rebuild: generated/relative-outpath/context/rebuild/duck.glb/3 1`] = `"duck.glb"`; + exports[`context rebuild: generated/relative-outpath/context/rebuild/e.html/2 1`] = ` "" `; @@ -152,7 +162,8 @@ exports[`context watch: generated/relative-outpath/context/watch/a.html/2 1`] = var b_default = "ws:///b.html"; var d_default = "ws:///c/d.html"; var c = "wat-" + d_default; - console.log(b_default, d_default); + var duck_default = "duck.glb"; + console.log(b_default, d_default, duck_default); })(); " `; @@ -193,6 +204,10 @@ exports[`context watch: generated/relative-outpath/context/watch/c/d.html/2 1`] " `; +exports[`context watch: generated/relative-outpath/context/watch/duck.glb/1 1`] = `"duck.glb"`; + +exports[`context watch: generated/relative-outpath/context/watch/duck.glb/2 1`] = `"duck.glb"`; + exports[`context watch: generated/relative-outpath/context/watch/world.json/1 1`] = ` "{ "mmlDocuments": { @@ -219,7 +234,8 @@ exports[`documentContext creates MML HTML documents: generated/relative-outpath/ var b_default = "mml:test/src/b.ts"; var d_default = "mml:test/src/c/d.html"; var c = "wat-" + d_default; - console.log(b_default, d_default); + var duck_default = "asset:test/src/duck.glb"; + console.log(b_default, d_default, duck_default); })(); " `; @@ -230,7 +246,8 @@ exports[`documentContext creates MML HTML documents: generated/relative-outpath/ var b_default = "mml:test/src/b.ts"; var d_default = "mml:test/src/c/d.html"; var c = "wat-" + d_default; - console.log(b_default, d_default); + var duck_default = "asset:test/src/duck.glb"; + console.log(b_default, d_default, duck_default); })(); " `; @@ -262,13 +279,16 @@ exports[`documentContext creates MML HTML documents: generated/relative-outpath/ " `; +exports[`documentContext creates MML HTML documents: generated/relative-outpath/document-context/duck.glb 1`] = `"duck.glb"`; + exports[`mml plugin with absolute outdir path with default options: generated/absolute-outpath/default-options/a.html 1`] = ` "" `; @@ -291,13 +311,16 @@ exports[`mml plugin with absolute outdir path with default options: generated/ab " `; +exports[`mml plugin with absolute outdir path with default options: generated/absolute-outpath/default-options/duck.glb 1`] = `"duck.glb"`; + exports[`mml plugin with absolute outdir path with import prefix: generated/absolute-outpath/import-prefix/a.html 1`] = ` "" `; @@ -305,7 +328,7 @@ exports[`mml plugin with absolute outdir path with import prefix: generated/abso exports[`mml plugin with absolute outdir path with import prefix: generated/absolute-outpath/import-prefix/b.html 1`] = ` "" @@ -320,13 +343,16 @@ exports[`mml plugin with absolute outdir path with import prefix: generated/abso " `; +exports[`mml plugin with absolute outdir path with import prefix: generated/absolute-outpath/import-prefix/duck.glb 1`] = `"duck.glb"`; + exports[`mml plugin with absolute outdir path with new import and path from output processor: generated/absolute-outpath/new-import-and-path/flump/a.html 1`] = ` "" `; @@ -349,13 +375,16 @@ exports[`mml plugin with absolute outdir path with new import and path from outp " `; +exports[`mml plugin with absolute outdir path with new import and path from output processor: generated/absolute-outpath/new-import-and-path/flump/duck.glb 1`] = `"duck.glb"`; + exports[`mml plugin with absolute outdir path with new import from output processor: generated/absolute-outpath/new-import/a.html 1`] = ` "" `; @@ -378,13 +407,16 @@ exports[`mml plugin with absolute outdir path with new import from output proces " `; +exports[`mml plugin with absolute outdir path with new import from output processor: generated/absolute-outpath/new-import/duck.glb 1`] = `"duck.glb"`; + exports[`mml plugin with absolute outdir path with new path from output processor: generated/absolute-outpath/new-path/bar/a.html 1`] = ` "" `; @@ -407,13 +439,16 @@ exports[`mml plugin with absolute outdir path with new path from output processo " `; +exports[`mml plugin with absolute outdir path with new path from output processor: generated/absolute-outpath/new-path/bar/duck.glb 1`] = `"duck.glb"`; + exports[`mml plugin with absolute outdir path world: generated/absolute-outpath/world/a.html 1`] = ` "" `; @@ -436,6 +471,8 @@ exports[`mml plugin with absolute outdir path world: generated/absolute-outpath/ " `; +exports[`mml plugin with absolute outdir path world: generated/absolute-outpath/world/duck.glb 1`] = `"duck.glb"`; + exports[`mml plugin with absolute outdir path world: generated/absolute-outpath/world/world.html 1`] = ` "" `; @@ -480,13 +518,16 @@ exports[`mml plugin with relative outdir path with default options: generated/re " `; +exports[`mml plugin with relative outdir path with default options: generated/relative-outpath/default-options/duck.glb 1`] = `"duck.glb"`; + exports[`mml plugin with relative outdir path with import prefix: generated/relative-outpath/import-prefix/a.html 1`] = ` "" `; @@ -494,7 +535,7 @@ exports[`mml plugin with relative outdir path with import prefix: generated/rela exports[`mml plugin with relative outdir path with import prefix: generated/relative-outpath/import-prefix/b.html 1`] = ` "" @@ -509,13 +550,16 @@ exports[`mml plugin with relative outdir path with import prefix: generated/rela " `; +exports[`mml plugin with relative outdir path with import prefix: generated/relative-outpath/import-prefix/duck.glb 1`] = `"duck.glb"`; + exports[`mml plugin with relative outdir path with new import and path from output processor: generated/relative-outpath/new-import-and-path/flump/a.html 1`] = ` "" `; @@ -538,13 +582,16 @@ exports[`mml plugin with relative outdir path with new import and path from outp " `; +exports[`mml plugin with relative outdir path with new import and path from output processor: generated/relative-outpath/new-import-and-path/flump/duck.glb 1`] = `"duck.glb"`; + exports[`mml plugin with relative outdir path with new import from output processor: generated/relative-outpath/new-import/a.html 1`] = ` "" `; @@ -567,13 +614,16 @@ exports[`mml plugin with relative outdir path with new import from output proces " `; +exports[`mml plugin with relative outdir path with new import from output processor: generated/relative-outpath/new-import/duck.glb 1`] = `"duck.glb"`; + exports[`mml plugin with relative outdir path with new path from output processor: generated/relative-outpath/new-path/bar/a.html 1`] = ` "" `; @@ -596,13 +646,16 @@ exports[`mml plugin with relative outdir path with new path from output processo " `; +exports[`mml plugin with relative outdir path with new path from output processor: generated/relative-outpath/new-path/bar/duck.glb 1`] = `"duck.glb"`; + exports[`mml plugin with relative outdir path world: generated/relative-outpath/world/a.html 1`] = ` "" `; @@ -625,6 +678,8 @@ exports[`mml plugin with relative outdir path world: generated/relative-outpath/ " `; +exports[`mml plugin with relative outdir path world: generated/relative-outpath/world/duck.glb 1`] = `"duck.glb"`; + exports[`mml plugin with relative outdir path world: generated/relative-outpath/world/world.html 1`] = ` "" +`; + +exports[`resultProcessor re-writes import paths for documents: generated/relative-outpath/result-processor/world/a.html 2`] = ` +"" `; @@ -654,7 +722,16 @@ exports[`resultProcessor re-writes import paths for documents: generated/relativ exports[`resultProcessor re-writes import paths for documents: generated/relative-outpath/result-processor/world/b.html 1`] = ` "" +`; + +exports[`resultProcessor re-writes import paths for documents: generated/relative-outpath/result-processor/world/b.html 2`] = ` +"" @@ -669,16 +746,19 @@ exports[`resultProcessor re-writes import paths for documents: generated/relativ " `; -exports[`resultProcessor re-writes import paths for worlds: generated/relative-outpath/result-processor/world/world.json 1`] = ` -"{ - "mmlDocuments": { - "duck": { - "url": "wss://test/src/a.ts" - } - } -}" +exports[`resultProcessor re-writes import paths for documents: generated/relative-outpath/result-processor/world/c/d.html 2`] = ` +" + +

I'm html!

+ + +" `; +exports[`resultProcessor re-writes import paths for documents: generated/relative-outpath/result-processor/world/duck.glb 1`] = `"duck.glb"`; + +exports[`resultProcessor re-writes import paths for documents: generated/relative-outpath/result-processor/world/duck.glb 2`] = `"duck.glb"`; + exports[`worldContext creates world JSON: generated/relative-outpath/world-context/world.js 1`] = ` ""use strict"; var __defProp = Object.defineProperty; diff --git a/test/src/.gitattributes b/test/src/.gitattributes new file mode 100644 index 0000000..71f5ac0 --- /dev/null +++ b/test/src/.gitattributes @@ -0,0 +1 @@ +duck.glb filter=lfs diff=lfs merge=lfs -text diff --git a/test/src/a.ts b/test/src/a.ts index 4006b86..3c94607 100644 --- a/test/src/a.ts +++ b/test/src/a.ts @@ -1,4 +1,5 @@ import b from "mml:./b"; import { d } from "./b"; +import duck from "./duck.glb"; -console.log(b, d); +console.log(b, d, duck); diff --git a/test/src/duck.glb b/test/src/duck.glb new file mode 100644 index 0000000..94bef4c --- /dev/null +++ b/test/src/duck.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65bf938f54d6073e619e76e007820bbf980cdc3dc0daec0d94830ffc4ae54ab5 +size 120484 diff --git a/test/src/types.d.ts b/test/src/types.d.ts new file mode 100644 index 0000000..cd0315f --- /dev/null +++ b/test/src/types.d.ts @@ -0,0 +1,4 @@ +declare module "*.glb" { + const value: string; + export default value; +} diff --git a/test/test.ts b/test/test.ts index 554dd9b..4619c1c 100644 --- a/test/test.ts +++ b/test/test.ts @@ -34,6 +34,17 @@ async function* walk(dir: string): AsyncGenerator<{ if (stat.isDirectory()) { yield* walk(fullPath); } else { + if ( + !file.endsWith(".html") && + !file.endsWith(".json") && + !file.endsWith(".js") + ) { + yield { + path: path.relative(__dirname, fullPath), + content: file, + }; + continue; + } const content = await fsp.readFile(fullPath, "utf-8"); yield { path: path.relative(__dirname, fullPath), @@ -80,34 +91,16 @@ describe("resultProcessor", () => { afterEach(() => ctx?.dispose()); - it("re-writes import paths for worlds", async () => { - const outdir = path.join(relativeOutdir, "result-processor", "world"); - await fsp.rm(outdir, { recursive: true, force: true }); - - const processor = makeResultProcessor(outdir, "wss://", noop); - - ctx = await worldContext({ - worlds: ["test/src/world.ts"], - options: { outdir }, - onEnd: async (result, _, importStubs) => { - processor.pushResult("world", result, importStubs); - await processor.process(); - for await (const { path, content } of walk(outdir)) { - expect(content).toMatchSnapshot(path); - } - }, - }); - - await ctx.rebuild(); - - expect.hasAssertions(); - }); - it("re-writes import paths for documents", async () => { const outdir = path.join(relativeOutdir, "result-processor", "world"); await fsp.rm(outdir, { recursive: true, force: true }); - const processor = makeResultProcessor(outdir, "wss://", noop); + const processor = makeResultProcessor({ + outdir, + documentPrefix: "ws:///", + assetPrefix: "http://", + log: noop, + }); ctx = await documentContext({ documents: ["test/src/a.ts"], @@ -146,6 +139,7 @@ describe("documentContext", () => { expect(importStubs).toEqual({ "test/src/b.ts": "mml:test/src/b.ts", "test/src/c/d.html": "mml:test/src/c/d.html", + "test/src/duck.glb": "asset:test/src/duck.glb", }); }, }); @@ -221,6 +215,7 @@ describe("mml plugin", () => { entryPoints: ["mml:test/src/a.ts"], plugins: [ mml({ + verbose: true, outputProcessor: () => ({ onOutput(output) { return { path: path.join("bar/", output) }; From 31b751fbbc9cddc196a0f9fea0e8941244beae68 Mon Sep 17 00:00:00 2001 From: "Daniel J. Rollins" Date: Thu, 5 Sep 2024 17:05:25 +0100 Subject: [PATCH 07/10] copy assets to sub-directory (default assets) --- jest.config.js | 7 +- src/documents.ts | 14 +++- src/plugin.ts | 3 + test/__snapshots__/test.ts.snap | 142 +++++++++++++------------------- test/test.ts | 3 +- 5 files changed, 72 insertions(+), 97 deletions(-) diff --git a/jest.config.js b/jest.config.js index afc91c4..c44ef36 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,10 +1,7 @@ /** @type {import('ts-jest').JestConfigWithTsJest} **/ -import { createDefaultEsmPreset } from "ts-jest"; - export default { testEnvironment: "node", - ...createDefaultEsmPreset(), - moduleNameMapper: { - "^(\\.{1,2}/.*)\\.js$": "$1", + transform: { + "^.+.tsx?$": ["ts-jest", {}], }, }; diff --git a/src/documents.ts b/src/documents.ts index a5ec500..b91b6c6 100644 --- a/src/documents.ts +++ b/src/documents.ts @@ -6,6 +6,7 @@ import fsp from "node:fs/promises"; export interface DocumentPluginOptions { importStubs: Record; verbose?: boolean; + assetDir: string; onEnd?: ( result: esbuild.BuildResult, importStubs: Record, @@ -23,6 +24,7 @@ export interface DocumentContextOptions { documents: string[]; build?: esbuild.PluginBuild["esbuild"]; worldDocuments?: Set; + assetDir: string; verbose?: boolean; onEnd?: ( result: esbuild.BuildResult, @@ -36,6 +38,7 @@ export async function documentContext({ options, worldDocuments = new Set(), verbose, + assetDir, onEnd, build = esbuild, }: DocumentContextOptions): Promise { @@ -46,6 +49,7 @@ export async function documentContext({ format: "iife", plugins: [ documentPlugin({ + assetDir, importStubs, verbose, onEnd, @@ -70,6 +74,7 @@ export async function documentContext({ format: "iife", plugins: [ documentPlugin({ + assetDir, importStubs, verbose, onEnd, @@ -109,7 +114,7 @@ const nonAssetExtensions = new Set([ ]); export function documentPlugin(args: DocumentPluginOptions): esbuild.Plugin { - const { verbose, importStubs, onEnd } = args; + const { verbose, importStubs, assetDir, onEnd } = args; const log = verbose ? (...args: unknown[]) => { console.log("[mml-world]:", ...args); @@ -182,8 +187,7 @@ export function documentPlugin(args: DocumentPluginOptions): esbuild.Plugin { cwd: process.cwd(), }); - // TODO: add this to the metafile - const output = path.resolve(outdir, basename(args.path)); + const output = path.resolve(outdir, assetDir, basename(args.path)); const entrypoint = path.relative(process.cwd(), args.path); assets.push({ output, entrypoint }); @@ -231,7 +235,9 @@ export function documentPlugin(args: DocumentPluginOptions): esbuild.Plugin { ...((build.initialOptions.entryPoints ?? []) as string[]), ...discoveredDocuments, ], - plugins: [documentPlugin({ importStubs, verbose, onEnd })], + plugins: [ + documentPlugin({ importStubs, verbose, assetDir, onEnd }), + ], }); return; diff --git a/src/plugin.ts b/src/plugin.ts index e146a46..109b36e 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -15,6 +15,7 @@ export interface MMLPluginOptions { outputProcessor?: OutputProcessorProvider; documentPrefix?: string; assetPrefix?: string; + assetDir?: string; importPrefix?: string; } @@ -24,6 +25,7 @@ export function mml(args: MMLPluginOptions = {}): esbuild.Plugin { outputProcessor: outputProcessorProvider, importPrefix, assetPrefix = "", + assetDir = "assets", } = args; let { documentPrefix = "ws:///" } = args; @@ -78,6 +80,7 @@ export function mml(args: MMLPluginOptions = {}): esbuild.Plugin { const documentCtx = await documentContext({ build: build.esbuild, documents, + assetDir, options: initialOptions, onEnd: async (result, importStubs) => { processor.pushResult("document", result, importStubs); diff --git a/test/__snapshots__/test.ts.snap b/test/__snapshots__/test.ts.snap index 1790dfb..bbeb31c 100644 --- a/test/__snapshots__/test.ts.snap +++ b/test/__snapshots__/test.ts.snap @@ -6,7 +6,7 @@ exports[`context rebuild: generated/relative-outpath/context/rebuild/a.html/1 1` var b_default = "ws:///b.html"; var d_default = "ws:///c/d.html"; var c = "wat-" + d_default; - var duck_default = "duck.glb"; + var duck_default = "assets/duck.glb"; console.log(b_default, d_default, duck_default); })(); " @@ -18,7 +18,7 @@ exports[`context rebuild: generated/relative-outpath/context/rebuild/a.html/2 1` var b_default = "ws:///b.html"; var d_default = "ws:///c/d.html"; var c = "wat-" + d_default; - var duck_default = "duck.glb"; + var duck_default = "assets/duck.glb"; var e_default = "ws:///e.html"; console.log(b_default, d_default, duck_default); console.log(e_default); @@ -32,12 +32,18 @@ exports[`context rebuild: generated/relative-outpath/context/rebuild/a.html/3 1` var b_default = "ws:///b.html"; var d_default = "ws:///c/d.html"; var c = "wat-" + d_default; - var duck_default = "duck.glb"; + var duck_default = "assets/duck.glb"; console.log(b_default, d_default, duck_default); })(); " `; +exports[`context rebuild: generated/relative-outpath/context/rebuild/assets/duck.glb/1 1`] = `"duck.glb"`; + +exports[`context rebuild: generated/relative-outpath/context/rebuild/assets/duck.glb/2 1`] = `"duck.glb"`; + +exports[`context rebuild: generated/relative-outpath/context/rebuild/assets/duck.glb/3 1`] = `"duck.glb"`; + exports[`context rebuild: generated/relative-outpath/context/rebuild/b.html/1 1`] = ` "" @@ -162,12 +162,16 @@ exports[`context watch: generated/relative-outpath/context/watch/a.html/2 1`] = var b_default = "ws:///b.html"; var d_default = "ws:///c/d.html"; var c = "wat-" + d_default; - var duck_default = "duck.glb"; + var duck_default = "assets/duck.glb"; console.log(b_default, d_default, duck_default); })(); " `; +exports[`context watch: generated/relative-outpath/context/watch/assets/duck.glb/1 1`] = `"duck.glb"`; + +exports[`context watch: generated/relative-outpath/context/watch/assets/duck.glb/2 1`] = `"duck.glb"`; + exports[`context watch: generated/relative-outpath/context/watch/b.html/1 1`] = ` "" `; +exports[`mml plugin with absolute outdir path with default options: generated/absolute-outpath/default-options/assets/duck.glb 1`] = `"duck.glb"`; + exports[`mml plugin with absolute outdir path with default options: generated/absolute-outpath/default-options/b.html 1`] = ` "" `; +exports[`mml plugin with absolute outdir path with import prefix: generated/absolute-outpath/import-prefix/assets/duck.glb 1`] = `"duck.glb"`; + exports[`mml plugin with absolute outdir path with import prefix: generated/absolute-outpath/import-prefix/b.html 1`] = ` "" `; +exports[`mml plugin with absolute outdir path with new import and path from output processor: generated/absolute-outpath/new-import-and-path/flump/assets/duck.glb 1`] = `"duck.glb"`; + exports[`mml plugin with absolute outdir path with new import and path from output processor: generated/absolute-outpath/new-import-and-path/flump/b.html 1`] = ` "" `; +exports[`mml plugin with absolute outdir path with new import from output processor: generated/absolute-outpath/new-import/assets/duck.glb 1`] = `"duck.glb"`; + exports[`mml plugin with absolute outdir path with new import from output processor: generated/absolute-outpath/new-import/b.html 1`] = ` "" `; +exports[`mml plugin with absolute outdir path with new path from output processor: generated/absolute-outpath/new-path/bar/assets/duck.glb 1`] = `"duck.glb"`; + exports[`mml plugin with absolute outdir path with new path from output processor: generated/absolute-outpath/new-path/bar/b.html 1`] = ` "" `; +exports[`mml plugin with absolute outdir path world: generated/absolute-outpath/world/assets/duck.glb 1`] = `"duck.glb"`; + exports[`mml plugin with absolute outdir path world: generated/absolute-outpath/world/b.html 1`] = ` "" `; +exports[`mml plugin with relative outdir path with default options: generated/relative-outpath/default-options/assets/duck.glb 1`] = `"duck.glb"`; + exports[`mml plugin with relative outdir path with default options: generated/relative-outpath/default-options/b.html 1`] = ` "" `; +exports[`mml plugin with relative outdir path with import prefix: generated/relative-outpath/import-prefix/assets/duck.glb 1`] = `"duck.glb"`; + exports[`mml plugin with relative outdir path with import prefix: generated/relative-outpath/import-prefix/b.html 1`] = ` "" `; +exports[`mml plugin with relative outdir path with new import and path from output processor: generated/relative-outpath/new-import-and-path/flump/assets/duck.glb 1`] = `"duck.glb"`; + exports[`mml plugin with relative outdir path with new import and path from output processor: generated/relative-outpath/new-import-and-path/flump/b.html 1`] = ` "" `; +exports[`mml plugin with relative outdir path with new import from output processor: generated/relative-outpath/new-import/assets/duck.glb 1`] = `"duck.glb"`; + exports[`mml plugin with relative outdir path with new import from output processor: generated/relative-outpath/new-import/b.html 1`] = ` "" `; +exports[`mml plugin with relative outdir path with new path from output processor: generated/relative-outpath/new-path/bar/assets/duck.glb 1`] = `"duck.glb"`; + exports[`mml plugin with relative outdir path with new path from output processor: generated/relative-outpath/new-path/bar/b.html 1`] = ` "" `; +exports[`mml plugin with relative outdir path world: generated/relative-outpath/world/assets/duck.glb 1`] = `"duck.glb"`; + exports[`mml plugin with relative outdir path world: generated/relative-outpath/world/b.html 1`] = ` "" `; -exports[`resultProcessor re-writes import paths for documents: generated/relative-outpath/result-processor/world/a.html 2`] = ` -"" -`; +exports[`resultProcessor re-writes import paths for documents: generated/relative-outpath/result-processor/world/assets/duck.glb 1`] = `"duck.glb"`; exports[`resultProcessor re-writes import paths for documents: generated/relative-outpath/result-processor/world/b.html 1`] = ` "" `; -exports[`resultProcessor re-writes import paths for documents: generated/relative-outpath/result-processor/world/b.html 2`] = ` -"" -`; - exports[`resultProcessor re-writes import paths for documents: generated/relative-outpath/result-processor/world/c/d.html 1`] = ` " @@ -746,19 +727,6 @@ exports[`resultProcessor re-writes import paths for documents: generated/relativ " `; -exports[`resultProcessor re-writes import paths for documents: generated/relative-outpath/result-processor/world/c/d.html 2`] = ` -" - -

I'm html!

- - -" -`; - -exports[`resultProcessor re-writes import paths for documents: generated/relative-outpath/result-processor/world/duck.glb 1`] = `"duck.glb"`; - -exports[`resultProcessor re-writes import paths for documents: generated/relative-outpath/result-processor/world/duck.glb 2`] = `"duck.glb"`; - exports[`worldContext creates world JSON: generated/relative-outpath/world-context/world.js 1`] = ` ""use strict"; var __defProp = Object.defineProperty; diff --git a/test/test.ts b/test/test.ts index 4619c1c..dd123b8 100644 --- a/test/test.ts +++ b/test/test.ts @@ -105,6 +105,7 @@ describe("resultProcessor", () => { ctx = await documentContext({ documents: ["test/src/a.ts"], options: { outdir }, + assetDir: "assets", onEnd: async (result, importStubs) => { processor.pushResult("document", result, importStubs); await processor.process(); @@ -132,6 +133,7 @@ describe("documentContext", () => { ctx = await documentContext({ documents: ["test/src/a.ts"], options: { outdir }, + assetDir: "assets", onEnd: async (_result, importStubs) => { for await (const { path, content } of walk(outdir)) { expect(content).toMatchSnapshot(path); @@ -215,7 +217,6 @@ describe("mml plugin", () => { entryPoints: ["mml:test/src/a.ts"], plugins: [ mml({ - verbose: true, outputProcessor: () => ({ onOutput(output) { return { path: path.join("bar/", output) }; From da843203264070ea0f4488df2112dbbbca2ee22e Mon Sep 17 00:00:00 2001 From: "Daniel J. Rollins" Date: Fri, 6 Sep 2024 10:50:27 +0100 Subject: [PATCH 08/10] separate asset output processing --- src/documents.ts | 5 ++++- src/results.ts | 10 +++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/documents.ts b/src/documents.ts index b91b6c6..d9e1c63 100644 --- a/src/documents.ts +++ b/src/documents.ts @@ -187,7 +187,10 @@ export function documentPlugin(args: DocumentPluginOptions): esbuild.Plugin { cwd: process.cwd(), }); - const output = path.resolve(outdir, assetDir, basename(args.path)); + const output = path.relative( + process.cwd(), + path.resolve(outdir, assetDir, basename(args.path)), + ); const entrypoint = path.relative(process.cwd(), args.path); assets.push({ output, entrypoint }); diff --git a/src/results.ts b/src/results.ts index 73e10fe..2a7d70c 100644 --- a/src/results.ts +++ b/src/results.ts @@ -13,6 +13,7 @@ export type MaybePromise = Promise | T; export interface OutputProcessor { onOutput(path: string): MaybePromise; + onAsset?(path: string): MaybePromise; onEnd?( outdir: string, result: esbuild.BuildResult, @@ -83,8 +84,15 @@ export const makeResultProcessor = ({ if (outputProcessor) { for (const [output, meta] of Object.entries(outputs)) { + console.log({ output, combinedStubs, meta }); + const isAsset = + meta.entryPoint && + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + combinedStubs[meta.entryPoint]?.startsWith("asset:"); const relPath = path.relative(outdir, output); - const result = await outputProcessor.onOutput(relPath); + const result = isAsset + ? await outputProcessor.onAsset?.(relPath) + : await outputProcessor.onOutput(relPath); if (!result) { continue; } From 8a69321201de8dfec2006a3aca46d447b5694cc1 Mon Sep 17 00:00:00 2001 From: "Daniel J. Rollins" Date: Fri, 6 Sep 2024 15:09:10 +0100 Subject: [PATCH 09/10] handle path and import re-writes of assets correctly --- src/plugin.ts | 2 +- src/results.ts | 23 +++++++++++---------- test/__snapshots__/test.ts.snap | 36 ++++++++++++++++----------------- test/test.ts | 12 +++++++++++ 4 files changed, 43 insertions(+), 30 deletions(-) diff --git a/src/plugin.ts b/src/plugin.ts index 109b36e..7e91a1f 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -24,7 +24,7 @@ export function mml(args: MMLPluginOptions = {}): esbuild.Plugin { verbose, outputProcessor: outputProcessorProvider, importPrefix, - assetPrefix = "", + assetPrefix = "/", assetDir = "assets", } = args; let { documentPrefix = "ws:///" } = args; diff --git a/src/results.ts b/src/results.ts index 2a7d70c..c20733a 100644 --- a/src/results.ts +++ b/src/results.ts @@ -82,20 +82,18 @@ export const makeResultProcessor = ({ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const outputs = combinedResult.metafile!.outputs; + const isAsset = (meta: (typeof outputs)[string]) => + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + meta.entryPoint && combinedStubs[meta.entryPoint]?.startsWith("asset:"); + if (outputProcessor) { for (const [output, meta] of Object.entries(outputs)) { - console.log({ output, combinedStubs, meta }); - const isAsset = - meta.entryPoint && - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - combinedStubs[meta.entryPoint]?.startsWith("asset:"); const relPath = path.relative(outdir, output); - const result = isAsset - ? await outputProcessor.onAsset?.(relPath) - : await outputProcessor.onOutput(relPath); - if (!result) { - continue; - } + const result = + (isAsset(meta) + ? await outputProcessor.onAsset?.(relPath) + : await outputProcessor.onOutput(relPath)) ?? {}; + const { path: newPath = relPath, importStr: newImport = newPath } = result; if (newPath !== relPath) { @@ -138,7 +136,10 @@ export const makeResultProcessor = ({ // correct output path. await Promise.all( Object.keys(outputs).map(async (output) => { + if (!output.endsWith(".json") && !output.endsWith(".html")) return; + let contents = await fsp.readFile(output, { encoding: "utf8" }); + for (const [file, stub] of Object.entries(combinedStubs)) { if (!stub.startsWith("mml:") && !stub.startsWith("asset:")) { continue; diff --git a/test/__snapshots__/test.ts.snap b/test/__snapshots__/test.ts.snap index bbeb31c..3a90909 100644 --- a/test/__snapshots__/test.ts.snap +++ b/test/__snapshots__/test.ts.snap @@ -345,20 +345,20 @@ exports[`mml plugin with absolute outdir path with import prefix: generated/abso " `; +exports[`mml plugin with absolute outdir path with new import and path from output processor: generated/absolute-outpath/new-import-and-path/flop/assets/duck.glb 1`] = `"duck.glb"`; + exports[`mml plugin with absolute outdir path with new import and path from output processor: generated/absolute-outpath/new-import-and-path/flump/a.html 1`] = ` "" `; -exports[`mml plugin with absolute outdir path with new import and path from output processor: generated/absolute-outpath/new-import-and-path/flump/assets/duck.glb 1`] = `"duck.glb"`; - exports[`mml plugin with absolute outdir path with new import and path from output processor: generated/absolute-outpath/new-import-and-path/flump/b.html 1`] = ` "" `; -exports[`mml plugin with absolute outdir path with new import from output processor: generated/absolute-outpath/new-import/assets/duck.glb 1`] = `"duck.glb"`; - exports[`mml plugin with absolute outdir path with new import from output processor: generated/absolute-outpath/new-import/b.html 1`] = ` "" `; -exports[`mml plugin with absolute outdir path with new path from output processor: generated/absolute-outpath/new-path/bar/assets/duck.glb 1`] = `"duck.glb"`; - exports[`mml plugin with absolute outdir path with new path from output processor: generated/absolute-outpath/new-path/bar/b.html 1`] = ` "" `; -exports[`mml plugin with relative outdir path with new import and path from output processor: generated/relative-outpath/new-import-and-path/flump/assets/duck.glb 1`] = `"duck.glb"`; - exports[`mml plugin with relative outdir path with new import and path from output processor: generated/relative-outpath/new-import-and-path/flump/b.html 1`] = ` "" `; -exports[`mml plugin with relative outdir path with new import from output processor: generated/relative-outpath/new-import/assets/duck.glb 1`] = `"duck.glb"`; - exports[`mml plugin with relative outdir path with new import from output processor: generated/relative-outpath/new-import/b.html 1`] = ` "" `; -exports[`mml plugin with relative outdir path with new path from output processor: generated/relative-outpath/new-path/bar/assets/duck.glb 1`] = `"duck.glb"`; - exports[`mml plugin with relative outdir path with new path from output processor: generated/relative-outpath/new-path/bar/b.html 1`] = ` "" @@ -18,7 +18,7 @@ exports[`context rebuild: generated/relative-outpath/context/rebuild/a.html/2 1` var b_default = "ws:///b.html"; var d_default = "ws:///c/d.html"; var c = "wat-" + d_default; - var duck_default = "assets/duck.glb"; + var duck_default = "/assets/duck.glb"; var e_default = "ws:///e.html"; console.log(b_default, d_default, duck_default); console.log(e_default); @@ -32,7 +32,7 @@ exports[`context rebuild: generated/relative-outpath/context/rebuild/a.html/3 1` var b_default = "ws:///b.html"; var d_default = "ws:///c/d.html"; var c = "wat-" + d_default; - var duck_default = "assets/duck.glb"; + var duck_default = "/assets/duck.glb"; console.log(b_default, d_default, duck_default); })(); " @@ -150,7 +150,7 @@ exports[`context watch: generated/relative-outpath/context/watch/a.html/1 1`] = var b_default = "ws:///b.html"; var d_default = "ws:///c/d.html"; var c = "wat-" + d_default; - var duck_default = "assets/duck.glb"; + var duck_default = "/assets/duck.glb"; console.log(b_default, d_default, duck_default); })(); " @@ -162,7 +162,7 @@ exports[`context watch: generated/relative-outpath/context/watch/a.html/2 1`] = var b_default = "ws:///b.html"; var d_default = "ws:///c/d.html"; var c = "wat-" + d_default; - var duck_default = "assets/duck.glb"; + var duck_default = "/assets/duck.glb"; console.log(b_default, d_default, duck_default); })(); " @@ -287,7 +287,7 @@ exports[`mml plugin with absolute outdir path with default options: generated/ab var b_default = "ws:///b.html"; var d_default = "ws:///c/d.html"; var c = "wat-" + d_default; - var duck_default = "assets/duck.glb"; + var duck_default = "/assets/duck.glb"; console.log(b_default, d_default, duck_default); })(); " @@ -319,7 +319,7 @@ exports[`mml plugin with absolute outdir path with import prefix: generated/abso var b_default = "ws:///b.html"; var d_default = "ws:///c/d.html"; var c = "wat-" + d_default; - var duck_default = "assets/duck.glb"; + var duck_default = "/assets/duck.glb"; console.log(b_default, d_default, duck_default); })(); " @@ -353,7 +353,7 @@ exports[`mml plugin with absolute outdir path with new import and path from outp var b_default = "ws:///blump/b.html"; var d_default = "ws:///blump/c/d.html"; var c = "wat-" + d_default; - var duck_default = "blip/assets/duck.glb"; + var duck_default = "/blip/assets/duck.glb"; console.log(b_default, d_default, duck_default); })(); " @@ -383,7 +383,7 @@ exports[`mml plugin with absolute outdir path with new import from output proces var b_default = "ws:///quux/b.html"; var d_default = "ws:///quux/c/d.html"; var c = "wat-" + d_default; - var duck_default = "qack/assets/duck.glb"; + var duck_default = "/qack/assets/duck.glb"; console.log(b_default, d_default, duck_default); })(); " @@ -415,7 +415,7 @@ exports[`mml plugin with absolute outdir path with new path from output processo var b_default = "ws:///bar/b.html"; var d_default = "ws:///bar/c/d.html"; var c = "wat-" + d_default; - var duck_default = "baz/assets/duck.glb"; + var duck_default = "/baz/assets/duck.glb"; console.log(b_default, d_default, duck_default); })(); " @@ -447,7 +447,7 @@ exports[`mml plugin with absolute outdir path world: generated/absolute-outpath/ var b_default = "ws:///b.html"; var d_default = "ws:///c/d.html"; var c = "wat-" + d_default; - var duck_default = "assets/duck.glb"; + var duck_default = "/assets/duck.glb"; console.log(b_default, d_default, duck_default); })(); " @@ -494,7 +494,7 @@ exports[`mml plugin with relative outdir path with default options: generated/re var b_default = "ws:///b.html"; var d_default = "ws:///c/d.html"; var c = "wat-" + d_default; - var duck_default = "assets/duck.glb"; + var duck_default = "/assets/duck.glb"; console.log(b_default, d_default, duck_default); })(); " @@ -526,7 +526,7 @@ exports[`mml plugin with relative outdir path with import prefix: generated/rela var b_default = "ws:///b.html"; var d_default = "ws:///c/d.html"; var c = "wat-" + d_default; - var duck_default = "assets/duck.glb"; + var duck_default = "/assets/duck.glb"; console.log(b_default, d_default, duck_default); })(); " @@ -560,7 +560,7 @@ exports[`mml plugin with relative outdir path with new import and path from outp var b_default = "ws:///blump/b.html"; var d_default = "ws:///blump/c/d.html"; var c = "wat-" + d_default; - var duck_default = "blip/assets/duck.glb"; + var duck_default = "/blip/assets/duck.glb"; console.log(b_default, d_default, duck_default); })(); " @@ -590,7 +590,7 @@ exports[`mml plugin with relative outdir path with new import from output proces var b_default = "ws:///quux/b.html"; var d_default = "ws:///quux/c/d.html"; var c = "wat-" + d_default; - var duck_default = "qack/assets/duck.glb"; + var duck_default = "/qack/assets/duck.glb"; console.log(b_default, d_default, duck_default); })(); " @@ -622,7 +622,7 @@ exports[`mml plugin with relative outdir path with new path from output processo var b_default = "ws:///bar/b.html"; var d_default = "ws:///bar/c/d.html"; var c = "wat-" + d_default; - var duck_default = "baz/assets/duck.glb"; + var duck_default = "/baz/assets/duck.glb"; console.log(b_default, d_default, duck_default); })(); " @@ -654,7 +654,7 @@ exports[`mml plugin with relative outdir path world: generated/relative-outpath/ var b_default = "ws:///b.html"; var d_default = "ws:///c/d.html"; var c = "wat-" + d_default; - var duck_default = "assets/duck.glb"; + var duck_default = "/assets/duck.glb"; console.log(b_default, d_default, duck_default); })(); " diff --git a/test/test.ts b/test/test.ts index ba81884..1a2de1b 100644 --- a/test/test.ts +++ b/test/test.ts @@ -115,8 +115,6 @@ describe("resultProcessor", () => { }, }); - await ctx.rebuild(); - expect.hasAssertions(); }); });