Repository navigation
Ambient Module Declarations for Import Attributes (formerly known as Import Assertions) #46135
Description
Suggestion
🔍 Search Terms
import attributes, import assertions, ambient module
✅ Viability Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g., library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.
⭐ Suggestion
Following the suggestion at #40694 (comment)
Allow projects to declare ambient modules based on the import attributes. Such as:
declare module '*' with {type: 'css'} {
const stylesheet: CSSStyleSheet;
export default stylesheet;
}📃 Motivating Example
Currently frontend build tools use file extensions and URL query strings to support all kinds of custom modules that are eventually compiled into JavaScript.
For example, in Vite (webpack supports these features in a similar way), we support JS modules importing .module.css files as CSS modules, and adding ?url postfixes to the imported path means we are to import the asset's real URL after bundling.
That is:
// get the exported classes from a CSS module
import classes from './example.module.css'
// Explicitly load assets as URL
import assetAsURL from './asset.js?url'But:
- The extension approach may collide with new browser native features, such as the native CSS modules. So it's better to have a way to differentiate them. Import attributes could help in this case.
- The query string approach is not a standard.
- It alters the standard semantics of an import statement
- Different tools vary on their choices of queries
- Modules with such syntaxes won't be able to run separately without the specific bundler
- So, we hope to migrate away from this approach and base the feature upon a standard. Import attributes seems to be the way to go.
One thing that blocks us from doing so is the ability to declare ambient modules based on import assertions in TypeScript.
We can easily have type definitions for files based on their file extensions and query strings. In vite we have: https://github.com/vitejs/vite/blob/b690810f555549e9eed4b03600b27fe7649d6b07/packages/vite/client.d.ts
But currently, I don't see a way to migrate these type definitions when we move to import assertions.
💻 Use Cases
- The abovementioned frontend build tools.
- Type definitions for browsers' built-in CSS & JSON modules support. We can have
declare module '*' assert {type: 'json'}anddeclare module '*' assert {type: 'css'}in the DOM lib.
Activity
justinfagnani commentedon Sep 30, 2021
Thanks for opening this sodatea!
I think the important point with import assertions is that for the standard assertions (
type: 'css'andtype: 'json') it's the assertions themselves and nothing about the specifier that determine the type of module. (well, technically it's the MIME type, but if the MIME type and assertion disagree the module will fail, so we can ignore that case)So for native module types it only makes sense to declare ambient modules by assertion, never by name.
NuckChorris commentedon Nov 9, 2021
Just wanted to mention that this would also be a boon for stuff like imagetools which provide complex types currently via query params, but could potentially use assertions instead (and have much better syntax and types as a result!)
jakearchibald commentedon Nov 22, 2023
Thoughts on this, following discussion in #56359
Import attributes are a great way to tell a bundler how to interpret/preprocess a file being imported. Eg "load this as plain text", "load this as an ImageBitmap", "bundle this file and give me a URL to it".
The same file may be imported using different types:
This is much better than the current pattern bundlers/TypeScript uses, where anything ending
.pngis assumed to be consumed in a particular way, or uses some kind of module prefix/suffix.Some types, such as
{ type: 'json' }and{ type: 'css' }, may have built-in support on some platforms. It should be possible to use these in conjunction with custom bundler types.In a case like:
The build tool may wish to create a definition file specific for
styles.csswhen it's loaded with{ type: 'rollup-css-module' }, so it exposes CSS module entries specific to that file, probably by generating astyles.css.d.tsof sorts.I don't mind how the use-cases are solved, but here are some loosely-held thoughts/ideas.
I know there are two types of
.d.tsfile, but I cannot remember their official names, and can't find a page in the TypeScript docs that distinguishes between the two. So here's the definitions I'm using for this post:.d.tsfile that describes the exports of a particular file. This is the mode that TypeScript uses when a.d.tscontains animportorexportstatement..d.tsfile that applies across the whole project. This is the mode that TypeScript uses when a.d.tsdoes not contain animportorexportstatement.Taking from #56359, I assume this syntax is for ambient definition files:
Open question, would it be valid to use types in the above? For example:
And I assume this syntax is for use in sidecar definition files:
It feels like exports outside one of these blocks should only apply to imports without attributes.
I know this is hand-wavy and incomplete, but the process of picking a definition for an import with attributes could be something like:
declare withblock, use those types.declare … with, use those types.justinfagnani commentedon Nov 22, 2023
For non-ambient declarations I think we'll also need a way to specify that there's no support for an import with no type attribute, as is true for CSS and JSON.
jakearchibald commentedon Nov 22, 2023
declare with undefined never? 🫤fatcerberus commentedon Nov 23, 2023
I'd just like to point out that "sidecar" vs. "ambient"
.d.tsis not a distinction that exists per se - as far as TS is concerned, a.d.tsis always a companion to a corresponding JavaScript file. What you're calling an "ambient" declaration simply describes a.jsfile that runs as a script (i.e. in global scope, like classic<script>tags) rather than as a module.justinfagnani commentedon Aug 26, 2024
#56359 has a number of questions from the design meeting on this issue.
I'll attempt to give my personal responses on them here to keep things collocated:
Declaring modules with attributes
👍 (even though this wasn't a question)
Allowing module declarations to key off of import attributes is in general a great feature that will work with all kinds of tool-specific attributes. I'd assume that lib.dom.d.ts would include a declaration like the above.
Handling multiple matching module declarations
Presumably there can already be multiple matching module declarations with path wildcards. How does that work today? Merging?
For CSS and JSON, I think there are likely three common cases where merging might happen:
A library includes a declaration for
{type: 'json'}or{type: 'css'}, and then a new TypeScript version also includes those in lib.dom.d.ts.Those should be compatible. I'd personally want some kind of warning if they weren't compatible.
A tool adds a new feature, like named exports for CSS, on top of the standard type.
Ideally the tool would use a new attribute or new type value, but I'm sure we'll see tools that don't. In this case maybe they'll generate a
foo.d.css.ts(with Add--allowArbitraryExtensions, a flag for allowing arbitrary extensions on import paths #51435) declaration file so that it has the specific named exports for the specific CSS file. It would be most accurate if this declaration file could somehow say if the file must be imported with the{type: 'css'}attribute, and then that declaration was merged with the one inlib.dom.d.ts.A tool or library includes a module declaration for a non-standard import, like
declare module "*.css" ...which also matches a standard CSS/JSON module declaration keyed off the import attribute.This is the trickiest case because these declarations are most likely not compatible. I would want the declaration keyed off the import attribute to take precedence instead of merging.
Maybe what we can do here say that for a declaration to be merged with other declarations that include import attributes, that the declaration has to have an attributes declaration, even if it's empty. ie:
would match all *.css imports regardless of the
typeattribute.Are import attributed even needed for module declarations?
The bundler vs browser issue is one reason, but I hesitate to call one behavior "bundler" because there are now bundler plugins that support the standard behavior.
The real core reason is that import attributes are the way that browsers determine/verify the module type - they don't do so based on file name - so the type system should work the same way.
You absolutely do want the files to be resolved on disk. One of the problems with today's wildcard declarations is that jump-to-definition goes to the declaration and not the file. That might be useful sometimes when trying to debug your types, but usually you want to open the .css or .json file in your editor.
Path-less declarations
Seems fine, though is this really different than
declare module "*" with?Resolution
No. At least for the standard module types (json and css) the specifier should resolve exactly the same as without a type attribute.
Those files might not be in the same root tree as the .ts files, but this can be handled with multiple root dirs. Even better, .json and .css files should be emitted if they are in the root dir and references as an import.
There could conceivably be tools that alter specifiers of imports with certain attributes. Those will just have to use their own declarations?
Emit
I think these files are part of the source tree and should be copied over. Projects shouldn't need to add extra tools to get a project that only uses standard module types to build and run.
I haven't ever seen JSON files emitted. Is this true of plain
tscusage?I hope that helps. I have a bias towards standard module types and semantics just working, but I think that leads to mostly obvious answers to the questions here - differentiating standard from non-standard behavior being possibly the trickiest question.
[-]Ambient Module Declarations for Import Assertions[/-][+]Ambient Module Declarations for Import Attributes (formerly known as Import Assertions)[/+]Remove @ts-ignore:next-line when using CSS import attribute.
fregante commentedon Feb 21, 2025
Without having to already decide what should be part of the standard lib and how, it would be great to get the syntax working since it's the perfect way to define how a file should be interpreted.
This only means one thing: the file is not JavaScript, so TypeScript should have a way to define the TS type of
xTypeScript already ignores all unsupported
typeattributes, so we only really need a way to define their types. The rest can follow.I just really want to do away with
?url-based webpack hacks, and webpack already supports import attribute-based loaderswell that was fun. but its not gonna work.
ezzatron commentedon Jul 10, 2025
Small correction, these are the docs for Webpack's
withsupport: https://webpack.js.org/configuration/module/#rulewithalii commentedon Aug 18, 2025
With increasingly more runtimes and bundlers implementing things like
{type: 'text'}and the import bytes proposal, what can we do to help see this land in TypeScript?AFatNiBBa commentedon Sep 12, 2025
Is there anything new on this?
otomad commentedon Dec 26, 2025
Note that we should also support the inline grab module type. Maybe like this.
justinfagnani commentedon Jan 13, 2026
As of Firefox 147, we now have two major browser engines that support CSS modules with import attributes: https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/147#javascript
And we've had all three major engines supporting import attributes with JSON modules since Firefox 138 in April 2025. Node, Deno, and Bun also all support JSON modules, and at least Node allows adding other attribute support via loader hooks. Rollup and Webpack support import attributes too.
Is that enough platform support to get support going in TypeScript?
jonathantneal commentedon Jan 22, 2026
I’ve put a PR up for this.
#63036
tomaswrobel commentedon Aug 31, 2026
Will the issue be implemented in a way that "fit the merge criteria for post-6.0 patches"?
jonathantneal commentedon Sep 1, 2026
Thank you so incredibly much, Gabriela Araujo Britto (@gabritto) . 🙏❤️
RyanCavanaugh commentedon Sep 1, 2026
This will not be backported to 6.0, no
krutoo commentedon Sep 27, 2026
Can someone share docs for this syntax?