swc-project / swc

Rust-based platform for the Web
https://swc.rs
Apache License 2.0
31.21k stars 1.23k forks source link

swc_bundler: handling of import.meta in modules #1115

Closed kitsonk closed 4 years ago

kitsonk commented 4 years ago

Describe the bug

swc_bundler does not appear to handle import.meta well.

Input code

example7.ts

import { isMain, modUrl } from "./f.ts";

import { isMain, modUrl } from "./f.ts";

console.log(isMain, modUrl);
console.log(import.meta.main, import.meta.url);

f.ts

export const isMain = import.meta.main;
export const modUrl = import.meta.url;

Expected behavior

I am not actually sure what is possible here, but it is something that we struggled with in Deno with the existing bundling, how to deal with these things. The down-emit to SystemJS did allow is to support setting these and our "loader" could provide them.

When you do deno run example7.ts at the moment you get:

false file:///bundle-examples/f.ts
true file:///bundle-examples/example7.ts

But bundling outputs:

const isMain = import.meta.main;
const modUrl = import.meta.url;
console.log(isMain, modUrl);
console.log(import.meta.main, import.meta.url);

Which results in this:

true file:///bundle-examples/example7.bundle.js
true file:///bundle-examples/example7.bundle.js

Version

Version: swc_bundler 0.8.1 (#1005 branch)

Additional context

Working to integrate swc_bundler into Deno.

kdy1 commented 4 years ago

Is hardcoding okay? I'm not sure about the way to implement it.

kitsonk commented 4 years ago

@bartlomieju @nayeemrmn thoughts/opinions?

I don't know how hard it is, but if there was a way to supply it during the loader, and it would be up to the loader to supply the value? Like the whole of import.meta, since it is specifically designed to be platform specific?

nayeemrmn commented 4 years ago

For the behaviour of deno bundle, I think we want import.meta.url to always be hardcoded, import.meta.main to be preserved in the "entrypoint" module (the one whose exports are passed on) of the bundle, and hardcoded to false in others.

I agree that swc should structure a way to call back to the loader for import.meta references so the above is all implementable in Deno. It shouldn't itself handle any concrete import.meta.* field since that's beyond tc39.

kitsonk commented 4 years ago

I think we want import.meta.url to always be hardcoded, import.meta.main to be preserved in the "entrypoint" module (the one whose exports are passed on) of the bundle, and hardcoded to false in others.

Yes, we would be able to do that in Deno if at bundle time we had a way to supply their values.

(For clarification, though we can certainly talk about it over in Deno, import.meta.main should be true for the "root" module when import.meta.main is true for the bundled module at runtime, otherwise it should be false and always false for all other modules. And with import.meta.url, for remote modules, we can hard code it, but for local modules, we will want to change the value to something that does not leak local system information, so we would want to find the common root for all local files and change it to something like bundle:///a/b/mod.ts)

bartlomieju commented 4 years ago

I'm not really sure how we could handle import.meta in bundles - IIRC they will be plain, old scripts and import.meta is only set for modules. And even then there's only a single file, so the import.meta object provided by the runtime couldn't differentiate between different parts of bundle.

kdy1 commented 4 years ago

@bartlomieju Do you mean it should be handled at compile (bundle) time?

For the behaviour of deno bundle, I think we want import.meta.url to always be hardcoded, import.meta.main to be preserved in the "entrypoint" module (the one whose exports are passed on) of the bundle, and hardcoded to false in others.

I agree that swc should structure a way to call back to the loader for import.meta references so the above is all implementable in Deno. It shouldn't itself handle any concrete import.meta.* field since that's beyond tc39.

I was thinking for a while to make the api clean. I have some questions.

I'm considering creating each api for each meta property. This is mainly because ecmascript spec may add some meta properties, and information required to handle them may differ. How do you think about this?

Also, which information is required to import.meta.url? Only swc_common::FileName?

Finally, is it okay for bundler to handle import.meta.main without hook?

bartlomieju commented 4 years ago

Do you mean it should be handled at compile (bundle) time?

What I'm saying is that it's most likely not possible to handle import.meta during runtime for bundles, because V8's callback for import.meta is only invoked in modules. Even if it is possible there would be only a single callback since SWC outputs a single bundle file.

Also, which information is required to import.meta.url? Only swc_common::FileName?

Correct, in Deno we might want to do some mangling for local URLs as pointed by https://github.com/swc-project/swc/issues/1115#issuecomment-700420312.

Finally, is it okay for bundler to handle import.meta.main without hook?

So it would be handled by SWC itself without need for intervention from the runtime?

kdy1 commented 4 years ago

@bartlomieju

So it would be handled by SWC itself without need for intervention from the runtime?

Yes. I think it makes sense, and more importantly, import.meta.main cannot be implemented by runtime if it's bundled. It's a kind of information stripped out by a bundler (just like compiler's job).

nayeemrmn commented 4 years ago

I'm not really sure how we could handle import.meta in bundles - IIRC they will be plain, old scripts and import.meta is only set for modules.

@bartlomieju Right now our bundles are valid ES modules which re-export the symbols exported in the entrypoint module. Are you saying we will no longer have that?

And even then there's only a single file, so the import.meta object provided by the runtime couldn't differentiate between different parts of bundle.

I specified the behaviour above. In most cases it would be hardcoded, in other cases it would preserved literally as import.meta (this is what we mean by letting the behaviour change at runtime).

kitsonk commented 4 years ago

Right now our bundles are valid ES modules which re-export the symbols exported in the entrypoint module. Are you saying we will no longer have that?

The entry modules symbols will be re-exported.

I'm considering creating each api for each meta property. This is mainly because ecmascript spec may add some meta properties, and information required to handle them may differ.

I think having either an API for any access to import.meta or dealing with each individually would be fine, as long as it is determinable what property needs a value for. Thinking more about it, I would expect we would want the Deno behaviour to be something like this:

bartlomieju commented 4 years ago

@bartlomieju Right now our bundles are valid ES modules which re-export the symbols exported in the entrypoint module. Are you saying we will no longer have that?

I think I misunderstood something in discussion with @kdy1, bundles should be ES modules after all.

I think having either an API for any access to import.meta or dealing with each individually would be fine, as long as it is determinable what property needs a value for. Thinking more about it, I would expect we would want the Deno behaviour to be something like this:

Can import.meta be shimmed in user code? If not we'd have to add special handling for those bundles in deno_core which seems undesirable.

kdy1 commented 4 years ago

Will deno support runtime detection of import.meta.main?

Adn even if it will be supported, I think it's ok to compile it as false on imported modules, correct?

nayeemrmn commented 4 years ago

If it were the entry point module, we would want to replace it with something like this:

  import?.meta?.main ?? false

This would ensure that if it were run under Deno, and was the main module, its value would reflect the correct runtime value, but would not be true when running in other places.

@kitsonk The current idea behind import.meta.main is that it resolves to undefined in browsers which will normally just work as false. It's already compatible in that way. We can just preserve it as import.meta.main for the entrypoint module.

nayeemrmn commented 4 years ago

Only whole import.meta references can be statically analysed correctly (import.meta["url"]). Substituting it with a host-provided object would be proper. This means we can't vary the "hardcode vs preserve" for individual properties...

Here's how we would have to do it -- always hardcode import.meta by a host-provided object:

That would work perfectly.

TomasHubelbauer commented 4 years ago

Is this released yet?

I am getting this error:

error: Expected (, got .
  --> index.js:68:33
   |
68 |       const url = new URL(import.meta.url);
   |                                 ^

I am using these versions:

  "devDependencies": {
    "@swc/cli": "^0.1.27",
    "@swc/core": "^1.2.37"
  }

This is my config:

{
  "env": {
    "targets": {
      "chrome": "79"
    },
    "mode": "entry",
    "coreJs": 3
  },
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "dynamicImport": true
    }
  }
}
kdy1 commented 4 years ago

@TomasHubelbauer You should set jsc.parser.importMeta to true like

{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "importMeta": true
    }
  }
}
TomasHubelbauer commented 4 years ago

Thanks, I missed that there was a flag for this. It works now!

swc-bot commented 2 years ago

This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.