You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Mar 25, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Mar 25, 2026. It is now read-only.
Bikeshed possible syntaxes for import statementsΒ #6
I came to this repo from the webcomponents issue I linked above, because I'm interested in the implications these decisions will have on the scripting security model. The goal that I see here is to have a static import syntax that works more or less the same as Body#json() from a security perspective -- that is, I should have a way to (statically) load content, passed through a built-in parser, that is guaranteed not to have side effects.
I'm not an expert in the current state of the various standards involved, so I could be wrong, but my understanding is that the static import keyword is currently used to load an ES module. Now, in some environments, it can also mean to perform the same kind of loader logic against a file that isn't an ES module, then run it through some built-in parser. (I'm thinking specifically of import-from-JSON, and import-from-HTML, but maybe this applies to WebASM or maybe even a WebGL shader or model, anything that might be useful to a script and that has a built-in parser in at least one execution context.)
Am I right in thinking that this second "mode" isn't actually part of a common standard? At least when I use it in my own projects today, it winds up going through a transpiler / webpack loader under the hood. But the goal, as I understand it, is to formalize this behavior so that we can have consistency across environments, with first-class support that doesn't require polyfills or transpilers. That leaves us with two distinct security models: ES modules that load via a script execution engine with global state, and a bunch of module types that load via stateless parsers that handle "passive" content like JSON, HTML, etc.
Today's transpilers implement both of these features with the same keyword, but why should that be so? Sure, it's easy to piggy-back on import because it's already reserved, but in a very real sense we're overloading it to do two different things. Other issues here debate how best to ensure that the loader treats the file as the correct type, but from a security perspective I'd argue that the only distinction that matters is which of the two bins it should fall into.
So, that's my motivation. Strictly from the perspective of security-model concerns, using two different keywords solves it and neatly sidesteps basically every concern raised in this repo -- attribute syntax, in- vs out-of-band, handling in Node vs web, interactions with MIME types, etc. None of that is necessary if we just say that import is only for ES modules, and a different keyword would be used for "everything else".
@thw0rted all JS Spec defined modules are part of an Abstract Module Record type intended to allow interoperability with other Module types potentially not in the specification. The design was to allow a single system of loading a variety of types. In addition, a variety of uses are possible without side-effects but still going through a JS module of sorts such as:
Redirecting to a different module such as export * from './foo.json';
Defining/initializing functions without executing code on initial loading export function add(a, b) { return a + b; };
I'm unclear how a split would occur naturally for these differing types and it seems like they should be left to the same mechanism for loading. We could try and force an artificial separation of types, but that seems unclear in motivation and benefits to me currently.
I always thought of export * from "whatever" as being a shorthand for import {a,b,c...} from "whatever"; export {a,b,c};. Viewed that way, the export stays the same and the import would just change to the second keyword and behave as described in my previous comment. Of course that means that you'd need a second new keyword to pair with export. (As I said in the linked comment on the webcomponents issue, I'm loathe to suggest concrete keywords, but for a very poor example, let's say passiveimport and passiveexport.) This just reflects my personal experience with the syntax so I could be missing some nuance or use-case.
As for the second bullet, I'm aware that a given JS module could be written to have no side effects, but you can't reason about whether they will or not when importing them, because they're evaluated in the same global scope -- any JS module could have side effects, from the security model's POV. The "forced, artificial" separation of types is actually a natural division between evaluating a resource that has access to the global scope (i.e. a JS module) versus evaluating a resource that does not have that access (i.e. everything else).
@phistuck while the proposal promotes a single type attribute at the moment the goal is too have arbitrary attributes. I think that your syntax suggestion is interesting
The current proposal supports arbitrary key-value syntax, as import { foo } from "module" with attr: "value", attr2: "value2". It defines constraints around the interpretation of type: "json" and type: in general. I think it is already extensible in the way @phistuck proposes, but the module specifier remains distinctly positioned.
Whatever syntax we choose, I think it is important to separate different kinds of attributes at syntax level. Otherwise, it will get harder to write import for different platforms. (See #30 (comment)#30 (comment) and #30 (comment) )
I don't have any non-assert attributes to propose. What if we say that with sets off assert-type attributes, and some other keyword is added in a follow-on proposal to enable attributes which change module interpretation?
I went through #3, #5 and a few other related issues. I agree with various sayings but the most consequential to me are the following.
Since with is not recommended and forbidden in the strict mode (@littledan in #3 (comment), MDN), it would be confusing to introduce in this scope even if it has nothing related to the actual with statement. I assume that for an experienced software engineer with would be clear, however, a junior developer may be confused and afraid to use such syntax.
Trying to combine these arguments it took some ideas from C++ and Pascal and baked it into the syntax.
Dynamic import
I thought about 2 options given in the current README#Before stage 3 I vote for the short syntax.
If we define import() as a function with 2 parameters as the following
functionimport(moduleUrl,moduleAttributes){
...
}
passing an object like { with: { ... } } does not look logical since the actual attributes are always under the single property value.
Also, defining with (or any other keyword) as a key might be confusing to a new coming developer who's just learned that certain words must be avoided for naming identifiers. Going further, some basic syntax highlighters always highlight with as a statement even if it's under the object notation what could make using a keyword as an object key even more confusing.
As a counter-argument to this may be wishing to add one more keyword to the import statement, i.e. for the following static import:
importpackagefrom'./package.json'withtype= 'json' having rel = 'fallback'
Currently, I do not see any possible need to add another keyword to the import statement, hence no need to use such keyword in the configurational object to pass attributes.
Conclusion and proposal
To conclude my arguments from above I propose the following example:
Since it's dynamic import, dynamic constructing of attributes should be allowed. I did not find any sayings about this, perhaps it's obvious.
Static import
For the static import, I propose the following syntax:
Shorthand syntax
Such syntax could be used when a single attribute is passed.
importpackagefrom'./package.json' using type='json'
Full syntax
importpackagefrom'./package.yml' using {
type ='json';syntax='YAML';}
The object-like-literal may be confusing but this is not an object constructing but more a block scope.
Also, using is a new keyword what would no make it confusing with actual with statement. On the other hand, using is not a restricted keyword what may leak to backward compatibility issues as well as other options (having, given, when) like in the following example:
importgivenfrom'./given'given('Viktor Yakubiv')// should return 'Viktor'
the engine should possibly throw SyntaxError: Unexpected token 'given' when given becomes a reserved word. A similar example could be found for any of the proposed words.
Alternatives
In my example, the assignment operator may be as confusing as the object notation. Hence I propose a few alternatives:
Two colons (::)
Similarly to C++ namespace access. Perhaps looks weird with a string literal.
importpackagefrom'./package.json' using type::'json'importconfigfrom'./config.yml' using {type:: 'json',syntax:: 'YAML',}
Pascal asignment (:=)
Logically is good but looks a little bit unusual or even weird.
importpackagefrom'./package.json' using type :='json'importconfigfrom'./config.yml' using {type :='json',syntax :='YAML',}
However, it can be generalised in way that only a static literal can go after such assignment, i.e. a clause const a := 'hello' can be optimised by an engine or a transpiler.
Singular arrow notation (->)
importpackagefrom'./package.json' using type :='json'importconfigfrom'./config.yml' using {type->'json',syntax->'YAML',}
I am not sure it's not reserved yet by any other proposal or already rejected by this one. The code may look like PHP and the syntax could be confusing too.
Sorry, if this is too long and messy. I appreciate any feedback.
The Worker constructor already use a type key in an object as its second param, in that case we need to nest the attributes. I'd prefer that we keep APIs consistent and only pass the attributes in a nested object. We suggested to use with as a key to reflect the ImportDecleration syntax.
The syntax with operators wasn't mentioned before but it looks overloaded to me.
The proposal has settled on the import [bindings] from [source] with { [key1]: [string1], [key2]: [string2], ... } syntax, with the dynamic import equivalent being import([source], { with: { [key1]: [string1], [key2]: [string2], ... } }).
Activity
bmeck commentedon Nov 8, 2019
I'd personally prefer out of band and just wanted to be sure that is left considered.
thw0rted commentedon Nov 14, 2019
Would this be the right place to consider syntaxes that don't use
importat all, or should that be a different issue?littledan commentedon Nov 14, 2019
It's pretty hard to introduce a new keyword into JS syntax-wise. What would be the motivation? (Here is a good place to discuss IMO)
thw0rted commentedon Nov 14, 2019
I came to this repo from the webcomponents issue I linked above, because I'm interested in the implications these decisions will have on the scripting security model. The goal that I see here is to have a static import syntax that works more or less the same as
Body#json()from a security perspective -- that is, I should have a way to (statically) load content, passed through a built-in parser, that is guaranteed not to have side effects.I'm not an expert in the current state of the various standards involved, so I could be wrong, but my understanding is that the static
importkeyword is currently used to load an ES module. Now, in some environments, it can also mean to perform the same kind of loader logic against a file that isn't an ES module, then run it through some built-in parser. (I'm thinking specifically of import-from-JSON, and import-from-HTML, but maybe this applies to WebASM or maybe even a WebGL shader or model, anything that might be useful to a script and that has a built-in parser in at least one execution context.)Am I right in thinking that this second "mode" isn't actually part of a common standard? At least when I use it in my own projects today, it winds up going through a transpiler / webpack loader under the hood. But the goal, as I understand it, is to formalize this behavior so that we can have consistency across environments, with first-class support that doesn't require polyfills or transpilers. That leaves us with two distinct security models: ES modules that load via a script execution engine with global state, and a bunch of module types that load via stateless parsers that handle "passive" content like JSON, HTML, etc.
Today's transpilers implement both of these features with the same keyword, but why should that be so? Sure, it's easy to piggy-back on
importbecause it's already reserved, but in a very real sense we're overloading it to do two different things. Other issues here debate how best to ensure that the loader treats the file as the correct type, but from a security perspective I'd argue that the only distinction that matters is which of the two bins it should fall into.So, that's my motivation. Strictly from the perspective of security-model concerns, using two different keywords solves it and neatly sidesteps basically every concern raised in this repo -- attribute syntax, in- vs out-of-band, handling in Node vs web, interactions with MIME types, etc. None of that is necessary if we just say that
importis only for ES modules, and a different keyword would be used for "everything else".bmeck commentedon Nov 14, 2019
@thw0rted all JS Spec defined modules are part of an Abstract Module Record type intended to allow interoperability with other Module types potentially not in the specification. The design was to allow a single system of loading a variety of types. In addition, a variety of uses are possible without side-effects but still going through a JS module of sorts such as:
export * from './foo.json';export function add(a, b) { return a + b; };I'm unclear how a split would occur naturally for these differing types and it seems like they should be left to the same mechanism for loading. We could try and force an artificial separation of types, but that seems unclear in motivation and benefits to me currently.
thw0rted commentedon Nov 15, 2019
I always thought of
export * from "whatever"as being a shorthand forimport {a,b,c...} from "whatever"; export {a,b,c};. Viewed that way, theexportstays the same and theimportwould just change to the second keyword and behave as described in my previous comment. Of course that means that you'd need a second new keyword to pair withexport. (As I said in the linked comment on the webcomponents issue, I'm loathe to suggest concrete keywords, but for a very poor example, let's saypassiveimportandpassiveexport.) This just reflects my personal experience with the syntax so I could be missing some nuance or use-case.As for the second bullet, I'm aware that a given JS module could be written to have no side effects, but you can't reason about whether they will or not when importing them, because they're evaluated in the same global scope -- any JS module could have side effects, from the security model's POV. The "forced, artificial" separation of types is actually a natural division between evaluating a resource that has access to the global scope (i.e. a JS module) versus evaluating a resource that does not have that access (i.e. everything else).
phistuck commentedon May 10, 2020
It looks weird to me that this proposal aims for a single attribute.
There are few attributes that are already needed -
How about -
This resembles
fetch(URLStringOrRequestObject)and is forward looking.xtuc commentedon May 11, 2020
@phistuck while the proposal promotes a single
typeattribute at the moment the goal is too have arbitrary attributes. I think that your syntax suggestion is interestinglittledan commentedon May 11, 2020
The current proposal supports arbitrary key-value syntax, as
import { foo } from "module" with attr: "value", attr2: "value2". It defines constraints around the interpretation oftype: "json"andtype:in general. I think it is already extensible in the way @phistuck proposes, but the module specifier remains distinctly positioned.Jack-Works commentedon May 12, 2020
Whatever syntax we choose, I think it is important to separate different kinds of attributes at syntax level. Otherwise, it will get harder to write import for different platforms. (See #30 (comment) #30 (comment) and #30 (comment) )
littledan commentedon May 13, 2020
I don't have any non-assert attributes to propose. What if we say that
withsets off assert-type attributes, and some other keyword is added in a follow-on proposal to enable attributes which change module interpretation?viktor-yakubiv commentedon Jun 6, 2020
I went through #3, #5 and a few other related issues. I agree with various sayings but the most consequential to me are the following.
Since
withis not recommended and forbidden in the strict mode (@littledan in #3 (comment), MDN), it would be confusing to introduce in this scope even if it has nothing related to the actualwithstatement. I assume that for an experienced software engineerwithwould be clear, however, a junior developer may be confused and afraid to use such syntax.Going through various @chicoxyzzy's comments in #3 (first, second, third) I generally agree with @justinfagnani in #5 that something like the object notation would look more readable. However, I do not agree that the record syntax (comment) is a good idea in this case.
Trying to combine these arguments it took some ideas from C++ and Pascal and baked it into the syntax.
Dynamic import
I thought about 2 options given in the current README#Before stage 3 I vote for the short syntax.
If we define
import()as a function with 2 parameters as the followingpassing an object like
{ with: { ... } }does not look logical since the actual attributes are always under the single property value.Also, defining
with(or any other keyword) as a key might be confusing to a new coming developer who's just learned that certain words must be avoided for naming identifiers. Going further, some basic syntax highlighters always highlightwithas a statement even if it's under the object notation what could make using a keyword as an object key even more confusing.As a counter-argument to this may be wishing to add one more keyword to the import statement, i.e. for the following static import:
the dynamic call might look like:
However, this does seem to be too complicated and can be easily blended into the current proposal as an attribute:
Currently, I do not see any possible need to add another keyword to the import statement, hence no need to use such keyword in the configurational object to pass attributes.
Conclusion and proposal
To conclude my arguments from above I propose the following example:
Since it's dynamic import, dynamic constructing of attributes should be allowed. I did not find any sayings about this, perhaps it's obvious.
Static import
For the static import, I propose the following syntax:
Shorthand syntax
Such syntax could be used when a single attribute is passed.
Full syntax
The object-like-literal may be confusing but this is not an object constructing but more a block scope.
Also,
usingis a new keyword what would no make it confusing with actualwithstatement. On the other hand,usingis not a restricted keyword what may leak to backward compatibility issues as well as other options (having,given,when) like in the following example:the engine should possibly throw
SyntaxError: Unexpected token 'given'whengivenbecomes a reserved word. A similar example could be found for any of the proposed words.Alternatives
In my example, the assignment operator may be as confusing as the object notation. Hence I propose a few alternatives:
Two colons (
::)Similarly to C++ namespace access. Perhaps looks weird with a string literal.
Pascal asignment (
:=)Logically is good but looks a little bit unusual or even weird.
However, it can be generalised in way that only a static literal can go after such assignment, i.e. a clause
const a := 'hello'can be optimised by an engine or a transpiler.Singular arrow notation (
->)I am not sure it's not reserved yet by any other proposal or already rejected by this one. The code may look like PHP and the syntax could be confusing too.
Sorry, if this is too long and messy. I appreciate any feedback.
xtuc commentedon Jun 6, 2020
The
Workerconstructor already use atypekey in an object as its second param, in that case we need to nest the attributes. I'd prefer that we keep APIs consistent and only pass the attributes in a nested object. We suggested to usewithas a key to reflect the ImportDecleration syntax.The syntax with operators wasn't mentioned before but it looks overloaded to me.
nicolo-ribaudo commentedon Mar 31, 2023
The proposal has settled on the
import [bindings] from [source] with { [key1]: [string1], [key2]: [string2], ... }syntax, with the dynamic import equivalent beingimport([source], { with: { [key1]: [string1], [key2]: [string2], ... } }).Thanks for contributing to the discussion!
Switch import assertions (`assert`) to import attributes (`with`)