esbuild icon indicating copy to clipboard operation
esbuild copied to clipboard

`import.meta` behavior inconsistency between targets

Open sapphi-red opened this issue 2 years ago • 4 comments

import.meta has different instances when target is below es2020. But it has same instance if target is same or above es2020.

If it is intented to be unmodified (https://github.com/evanw/esbuild/issues/208#issuecomment-652691781) (import.meta's scope is the output file), I think it needs to point the same instance.

current behaviors

Without bundle

main.mjs

import { sub } from './sub.mjs'

import.meta.url = 'main'

const main = () => {
  console.log(import.meta.url)
}

main()
sub()

sub.mjs

import.meta.url = 'sub'

export const sub = () => {
  console.log(import.meta.url)
}

When I run node main.mjs, it outputs:

main
sub

With bundle (es2020)

After I bundle this with esbuild --bundle --format=esm --target=es2020 main.mjs, it becomes:

// sub.mjs
import.meta.url = "sub";
var sub = () => {
  console.log(import.meta.url);
};

// main.mjs
import.meta.url = "main";
var main = () => {
  console.log(import.meta.url);
};
main();
sub();

When I run this, it outputs:

main
main

With bundle (es2015)

After I bundle this with esbuild --bundle --format=esm --target=es2015 main.mjs, it becomes:

// sub.mjs
var import_meta = {};
import_meta.url = "sub";
var sub = () => {
  console.log(import_meta.url);
};

// main.mjs
var import_meta2 = {};
import_meta2.url = "main";
var main = () => {
  console.log(import_meta2.url);
};
main();
sub();

When I run this, it outputs:

main
sub

sapphi-red avatar Jun 02 '22 12:06 sapphi-red

https://github.com/evanw/esbuild/commit/ac97be74c3b5453740f84fc40098ccae95cde19f You can see the reason in change log

susiwen8 avatar Jun 03 '22 16:06 susiwen8

Thanks. I know the reason. I think the output of es2015 bundle would be better to be the code below. This has more consistency with es2020 bundle output.

// sub.mjs
var import_meta = {};
import_meta.url = "sub";
var sub = () => {
  console.log(import_meta.url);
};

// main.mjs
import_meta.url = "main";
var main = () => {
  console.log(import_meta.url);
};
main();
sub();

sapphi-red avatar Jun 03 '22 16:06 sapphi-red

Oh, sorry, I see

susiwen8 avatar Jun 03 '22 16:06 susiwen8

There is an existing workaround in tsup/cjs_shims.js by injecting import.meta.url manually:

esbuild main.mjs --bundle --define:import.meta.url=importMetaUrl --inject:cjs_shims.js --format=esm

// cjs_shims.js
var getImportMetaUrl = () => typeof document === "undefined" ? new URL("file:" + __filename).href : document.currentScript && document.currentScript.src || new URL("main.js", document.baseURI).href;
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();

// sub.mjs
importMetaUrl = "sub";
var sub = () => {
  console.log(importMetaUrl);
};

// main.mjs
importMetaUrl = "main";
var main = () => {
  console.log(importMetaUrl);
};
main();
sub();

But in fact import.meta is from ES2020. I'm not sure what's the correct behavior before that target. esbuild is replacing it with a simple object to prevent syntax error (likewise import() becomes new Promise((r) => r(require()))). But we still have to handle it at the most of the time, and some of them cannot be handled easily.

hyrious avatar Jun 04 '22 11:06 hyrious