tc39 / proposal-import-attributes

Proposal for syntax to import ES modules with assertions
https://tc39.es/proposal-import-attributes/
Apache License 2.0
591 stars 26 forks source link

Using attributes to support different types of runtime ES Module implementations. #73

Closed trusktr closed 1 year ago

trusktr commented 4 years ago

Currently, there are two types of ES Module implementations (as far as I'm aware):

Tools like NW.js currently have an issue, whereby it is possible to use ESM in the browser context (because it wraps Chromium, so it comes for free with browser-based ESM).

However, to import a Node modules in NW.js, one is required to require (hehe) any Node module that they want to import.

For example:

const fs = require("fs") // Node-based import using require
import value from "./file.js"; // regular browser-based ESM import here

The NW.js user can not use import to import Node modules.

Probably a similar issues exists with Electron.

So what I'm thinking is maybe there can be a way to disambiguate between module implementations using import attributes. Then it would be possible to patch a browser context engine (f.e. Chromium), to allow things like this using an official syntax:

import fs from "fs" with { type: "node-esm" }; // Node-based ESM import
import value from "./file.js"; // regular browser-based ESM import.

Of course, the naming (with, type, node-esm) is from a shed full of bikes to choose from.

The alternative for a project like NW.js would be that it would need to patch the browser engine to make it so if it encounters an identifier like fs, it will delegate to Node ESM for that, otherwise follow the code path of normal browser-based ESM.

littledan commented 4 years ago

This is an interesting idea for a use case. Thanks for filing an issue. I take it that this is something that would be done within hosts, not decided in this proposal, right?

xtuc commented 4 years ago

That sounds like a evaluator attribute, not a condition for the module to be loaded at all.

littledan commented 4 years ago

I guess that depends on some details; I could imagine a 'check' version of this in the context of something related to Node.js/ESM integration.

mikeal commented 4 years ago

This would be really nice actually. I’ve been writing ESM modules that run in Node.js, browser and Deno w/o a compiler. Right now, I end up never using relative imports and instead using named imports so that they pass through the export map in Node.js and then I have to generate import maps for the browser and for Deno.

This would allow me to actually get rid of the last build/compile step. However, I’m not sure the syntax above is ideal.

What you likely want is an import to a single constant with a variation of the relative path on a per-platform basis.

import fs from "./fs-node.js" if { env: "node" }
import fs from "./fs-browser.js" if { env: "browser" }
import fs from "./fs-deno.js" if { env: “deno” }

This form probably isn’t possible given the existing syntax constraints, but it illustrates something closer to the ideal usage pattern.

xtuc commented 4 years ago

It won't work with the assertion semantics because the first import that wouldn't met the env conditions will result in a failure.

While it's less elegant I can imagine that it will work with evaluator/transform attributes:

import fs_node from "./fs-node.js" with { env: "node" }
import fs_browser from "./fs-browser.js" with { env: "browser" }
import fs_deno from "./fs-deno.js" with { env: “deno” }

const fs = fs_node || fs_browser || fs_deno

env would return undefined if the env isn't met, It's just an idea, not really a fan of it.

mikeal commented 4 years ago

@xtuc that’s totally acceptable 😁

SamB commented 1 year ago

But wouldn't that fail the whole module graph? I think you would either need a new optional/weak import facility or dynamic imports to do it that way, and if you're stuck using dynamic imports you may as well use normal control flow rather than this env assertion. (It would obviously be helpful to establish conventions for how to detect the nature of the environment, but this doesn't seem like the right place to suggest that?)

nicolo-ribaudo commented 1 year ago

The proposal has been updated to allow attributes to affect module loading. As such, hosts could implement attributes such as:

import "fs" with { cjsDefaultInterop: "module.exports" }
import "fs" with { cjsDefaultInterop: "exports.default" }

Thanks for contributing to the discussion!