`Deno.emit` with provided sources fails when import does not contain file extension
It looks like, when you provide sources to Deno.emit, specifiers that don't have a file extension break something. Consider the following example:
await Deno.emit('file:///index.js', {
bundle: 'module',
importMapPath: new URL('import_map.json', import.meta.url).toString(),
importMap: {
imports: {
"external/": "https://example.com/",
},
},
sources: {
"file:///index.js": "import ok from 'external/test';\nok();",
"https://example.com/test": "export default function ok() { console.log('ok'); }",
},
});
This breaks with the following error:
Error 'Unable to output during bundling.' contains boxed error of unknown type:
Error { context: "Unable to output during bundling.", source: load_transformed failed
Caused by:
0: failed to analyze module
1: failed to resolve external/test from file:///index.js
2: Cannot resolve "external/test" from "file:///index.js". }
Error { context: "load_transformed failed", source: failed to analyze module
Caused by:
0: failed to resolve external/test from file:///index.js
1: Cannot resolve "external/test" from "file:///index.js". }
Error { context: "failed to analyze module", source: failed to resolve external/test from file:///index.js
Caused by:
Cannot resolve "external/test" from "file:///index.js". }
Error { context: "failed to resolve external/test from file:///index.js", source: Cannot resolve "external/test" from "file:///index.js". }
"Cannot resolve \"external/test\" from \"file:///index.js\"."
error: Uncaught (in promise) Error: Unable to output during bundling.
However, if we add a .js extension to the import and the external specifier, it works fine:
await Deno.emit('file:///index.js', {
bundle: 'module',
importMapPath: new URL('import_map.json', import.meta.url).toString(),
importMap: {
imports: {
"external/": "https://example.com/",
},
},
sources: {
"file:///index.js": "import ok from 'external/test.js';\nok();",
"https://example.com/test.js": "export default function ok() { console.log('ok'); }",
},
});
Output:
// deno-fmt-ignore-file
// deno-lint-ignore-file
// This code was bundled using `deno bundle` and it's not recommended to edit it manually
function ok() {
console.log('ok');
}
ok();
I know normally Deno is able to use the content-type header to know what it's parsing. Maybe the sources map could accept objects as values that contain the content type of the source, not just its contents. Something like:
interface Source {
kind?: 'js' | 'ts' | 'jsx' | 'tsx'; // or just let users specify a valid `content-type` directly.
content: string;
}
interface EmitOptions {
sources: Record<string, string | Source>;
}
Alternative solution would be to implement @kitsonk's idea in this issue. The deno_graph's createGraph API copied almost verbatim would work well (the load and resolve options).
This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. Thank you for your contributions.
This will work once #114 lands with the following code:
import { transpile } from "https://deno.land/x/emit/mod.ts";
const sources = new Map(Object.entries({
"file:///index.js": "import ok from 'external/test.js';\nok();",
"https://example.com/test.js":
"export default function ok() { console.log('ok'); }",
}));
await transpile(new URL("file:///index.js"), {
importMap: {
imports: {
"external/": "https://example.com/",
},
},
async load(specifier) {
const source = sources.get(specifier);
if (source === undefined) {
throw new Error("Unexpected specifier");
}
return {
kind: "module",
specifier,
content: source,
};
},
});