deno_emit icon indicating copy to clipboard operation
deno_emit copied to clipboard

`Deno.emit` with provided sources fails when import does not contain file extension

Open mimbrown opened this issue 4 years ago • 2 comments

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>;
}

mimbrown avatar Feb 24 '22 20:02 mimbrown

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).

mimbrown avatar Feb 25 '22 17:02 mimbrown

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.

stale[bot] avatar Apr 27 '22 08:04 stale[bot]

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,
    };
  },
});

yacinehmito avatar May 04 '23 06:05 yacinehmito