Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

import.meta.url support #1492

Open
GenrikhFetischev opened this issue Aug 3, 2021 · 18 comments
Open

import.meta.url support #1492

GenrikhFetischev opened this issue Aug 3, 2021 · 18 comments

Comments

@GenrikhFetischev
Copy link

Hi!
I have a problem with the bundling yargs package. When I try to run bundled code I get the following error:

node:internal/url:1352
    throw new ERR_INVALID_ARG_TYPE('path', ['string', 'URL'], path);
    ^

TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string or an instance of URL. Received undefined
    at new NodeError (node:internal/errors:278:15)
    at fileURLToPath (node:internal/url:1352:11)
    at Object.<anonymous> (/dir/b.js:524035:49)
    at Module._compile (node:internal/modules/cjs/loader:1108:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1137:10)
    at Module.load (node:internal/modules/cjs/loader:973:32)
    at Function.Module._load (node:internal/modules/cjs/loader:813:14)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:76:12)
    at node:internal/main/run_main_module:17:47 {
  code: 'ERR_INVALID_ARG_TYPE'
}

Looks like it happens cause of the following code (github):

const mainFilename = fileURLToPath(import.meta.url).split('node_modules')[0]
const __dirname = fileURLToPath(import.meta.url)

Which transpiled into:

var import_meta = {};
var mainFilename = (0, import_url.fileURLToPath)(import_meta.url).split("node_modules")[0];
var __dirname = (0, import_url.fileURLToPath)(import_meta.url);

import_meta.url - obviously undefined there.

I try to make a bundle for node.js with the flag --format=cjs

I have checked out this issue - #208, but looks like my isn't duplicate because I try to bundle ESM -> CJS;

Is it possible to add support import.meta.url property for ESM -> CJS bundle?

@hyrious
Copy link

hyrious commented Aug 3, 2021

A quick plugin to replace import.meta.url to file url string:

{
  name: 'import.meta.url',
  setup({ onLoad }) {
    let fs = require('fs')
    let url = require('url')
    // TODO: change /()/ to smaller range
    onLoad({ filter: /()/, namespace: 'file' }, args => {
      let code = fs.readFileSync(args.path, 'utf8')
      code = code.replace(
        /\bimport\.meta\.url\b/g,
        JSON.stringify(url.pathToFileURL(args.path))
      )
      return { contents: code }
    })
  }
}

@GenrikhFetischev
Copy link
Author

Thanks for the advice)
But looks like with that plugin bundled code will contain the hardcoded absolute path to files. I'm not sure that bundle will work in any environment

@hyrious
Copy link

hyrious commented Aug 3, 2021

@GenrikhFetischev
how about replace them to url.pathToFileURL(__filename)

@GenrikhFetischev
Copy link
Author

GenrikhFetischev commented Aug 3, 2021

Tried to apply @hyrious plugin, but I got another error:

yargs_default.command({
              ^

TypeError: yargs_default.command is not a function
    at Object.<anonymous> (/Users/g.fetishev/Projects/sandbox/js2/out.js:4701:15)
    at Module._compile (node:internal/modules/cjs/loader:1108:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1137:10)
    at Module.load (node:internal/modules/cjs/loader:973:32)
    at Function.Module._load (node:internal/modules/cjs/loader:813:14)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:76:12)
    at node:internal/main/run_main_module:17:47

Solved case by using
const yargs = require("yargs"); instead of import yargs from "yargs";

Yargs package has two different entry points for require and import - package.json, looks like there is might be a problem with esm -> cjs compilation.

@hyrious
Copy link

hyrious commented Aug 3, 2021

looks like there is might be a problem with esm -> cjs compilation.

No, it seems a bug in yargs itself. Try this vanilla esm code:

import yargs from "yargs"
console.log(yargs, yargs.command)
// save as `a.mjs`
// run with `node a.mjs`

It gives

[Function (anonymous)] undefined

maybe you should track this issue: yargs/yargs#1844

@evanw
Copy link
Owner

evanw commented Aug 5, 2021

You can replace things like this with arbitrary run-time values using the inject and define features. Here's an example:

$ cat example.js 
console.log(import.meta.url)

$ cat import-meta-url.js 
export var import_meta_url = require('url').pathToFileURL(__filename);

$ esbuild example.js --format=cjs --inject:./import-meta-url.js --define:import.meta.url=import_meta_url
var import_meta_url = require("url").pathToFileURL(__filename);
const import_meta = {};
console.log(import_meta_url);

This defines import.meta.url to import_meta_url and then injects a file that exports import_meta_url, which causes that file to be automatically imported. The name import_meta_url is arbitrary but has to match between the define and the inject.

@TimDaub
Copy link

TimDaub commented Aug 27, 2021

thanks, @evanw. What you've laid out in your last comment seems to have fixed it for me. Out of curiosity: Will import.meta.url be available in the future without this inject and define? Asking, as when defining --target=node10.4, I actually expect esbuild to deliver this in the build autonomously.

@fregante
Copy link

Wasn't support for import.meta.url added in #208? I'm not sure why I get {} instead of {url: 'the/actual/path.js'}

Screen Shot

Is this issue specific to `--bundle? My config is:

void esbuild.build({
	entryPoints: [
		'source/background.ts',
		'source/options.tsx',
		'source/resolve-conflicts.ts',
		'source/refined-github.ts',
	],
	bundle: true,
	watch: process.argv[2] === '--watch',
	outdir: 'distribution/build',
	external: ['chrome:*'],
	plugins: [readmeLoader],
});

@hyrious
Copy link

hyrious commented Nov 20, 2021

@fregante You got empty import.meta because this thing only exist in es modules context (--format=esm), and esbuild by default bundles to iife format. Transforming the esm keyword to iife or cjs world requires some conventions, one possible solution is if the targetting environment has require and __dirname, you can follow what evan has said.

Since your context is browser extension, maybe the solution above does not work. We should look in webpack to see what it does.


Update: I looked into the original output, and find that it is just replaced to __filename (the absolute path to the source file). So maybe my first comment just did this work for you.

@fregante
Copy link

You got empty import.meta because this thing only exist in es modules context

Gotcha, but I hope it will match other bundlers' behavior though.

I already tried your plugin but it broke TypeScript support (i.e. esbuild complained about TS syntax)

@hyrious
Copy link

hyrious commented Nov 20, 2021

@fregante This is because the default loader is js, but the file content is tsx. To let esbuild choose correct loader, simply put loader: 'default'.

See my comment at your closed PR.

@xkn1ght
Copy link

xkn1ght commented Nov 26, 2021

I have encontered that error too. And thank your guys the plugin and the inject & define solution work.
But I also have a few question:
what if my esbuild config's target: 'cjs', but use import yargs from 'yargs', will the esbuild bundle file contain yargs/index.mjs or yargs/index.cjs?
In the example seems get .esm, but why?

@hyrious
Copy link

hyrious commented Nov 26, 2021

@xkn1ght The which file to be bundled is determined by how code import it, esbuild is somewhat align to Node.js's native es modules' behavior. Since you write import, it will choose the esm one.

Yargs does have a bug that the esm export is not sharing the same interface as cjs one. I'd recommend you to write a simple plugin to force redirect imports to yargs to its cjs version if you already has some code depends on that.

@xkn1ght
Copy link

xkn1ght commented Nov 26, 2021

@xkn1ght The which file to be bundled is determined by how code import it, esbuild is somewhat align to Node.js's native es modules' behavior. Since you write import, it will choose the esm one.

Yargs does have a bug that the esm export is not sharing the same interface as cjs one. I'd recommend you to write a simple plugin to force redirect imports to yargs to its cjs version if you already has some code depends on that.

thanks~

@vnues
Copy link

vnues commented May 27, 2023

JSON.stringify(url.pathToFileURL(args.path))

if in ts project:

const excludeImportMetaUrl = (): Plugin => {
  return {
    name: 'exclude-import-meta-url',
    setup(build) {
      build.onLoad({ filter: /node-runner.ts$/ }, async (args) => {
        const contents = await fs.readFile(args.path, 'utf8');
        const transformedContents = contents.replace(
          /import\.meta\.url/g,
          JSON.stringify(url.pathToFileURL(args.path)),
        );
        console.log(contents);
        return { contents: transformedContents, loader: 'ts' };
      });
    },
  };
};

atjn added a commit to atjn/usable-gun that referenced this issue Sep 10, 2023
import.meta.url is not supported in prominent build tools yet, so we use a different approach:
vitejs/vite#8427
evanw/esbuild#1492
@karlhorky
Copy link

karlhorky commented Apr 17, 2024

The new import.meta.dirname and import.meta.filename, introduced in Node.js 20.11.0 are also undefined in esbuild (eg. while using @privatenumber's tsx):

dirname-filename.ts

console.log(import.meta.dirname, import.meta.filename)

Running with tsx:

$ npx tsx dirname-filename.ts
undefined undefined

Unconventional Workaround (for tsx scripts)

Use bun instead of tsx, import.meta.dirname and import.meta.filename are implemented there:

$ bun dirname-filename.ts
/Users/k/p/project /Users/k/p/project/dirname-filename.ts

@a-x-
Copy link

a-x- commented Jun 10, 2024

It's a critical bug imho

@privatenumber
Copy link
Contributor

This issue is practically resolved via the solution explained above using --define & --inject.


@karlhorky This issue is unrelated to why it's not working for you with tsx.

The reason why it's not working in your code is because you're running the code in CommonJS mode. You have to add "type": "module" in your package.json. And import.meta.filename & import.meta.dirname not working in CommonJS is not due to a limitation in esbuild; it's simply just not implemented in tsx.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants