Skip to content

loader: add experimental text import#62300

Open
efekrskl wants to merge 7 commits intonodejs:mainfrom
efekrskl:loader/experimental-raw-imports
Open

loader: add experimental text import#62300
efekrskl wants to merge 7 commits intonodejs:mainfrom
efekrskl:loader/experimental-raw-imports

Conversation

@efekrskl
Copy link
Copy Markdown
Contributor

@efekrskl efekrskl commented Mar 17, 2026

Closes #62082

Adds support for text import under --experimental-import-text flag.

Example:

import text from './file.txt' with { type: 'text' };

Import text is a Stage 3 TC39 proposal

Deno supports text imports with --unstable-raw-imports.
Bun supports text imports directly.

Not entirely sure what the current stance is on not-yet-landed proposals, but putting this up for discussion and feedback.

@nodejs-github-bot
Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/config
  • @nodejs/loaders

@nodejs-github-bot nodejs-github-bot added lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. labels Mar 17, 2026
@bakkot
Copy link
Copy Markdown
Contributor

bakkot commented Mar 18, 2026

Note that the import-bytes proposal requires producing Uint8Arrays backed by immutable ArrayBuffers, which are not yet supported in V8. I would be hesitant to ship this with mutable backing buffers, even flagged as experimental, lest people come to rely on that.

Text imports don't have the same problem.

@efekrskl efekrskl changed the title loader: add experimental text and bytes import loader: add experimental text import Mar 20, 2026
@efekrskl efekrskl marked this pull request as ready for review March 20, 2026 18:40
@efekrskl
Copy link
Copy Markdown
Contributor Author

Note that the import-bytes proposal requires producing Uint8Arrays backed by immutable ArrayBuffers, which are not yet supported in V8. I would be hesitant to ship this with mutable backing buffers, even flagged as experimental, lest people come to rely on that.

Text imports don't have the same problem.

That's a great point, looks like I've missed that. I've removed bytes related part of the code. Thank you!

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 20, 2026

Codecov Report

❌ Patch coverage is 94.33962% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.70%. Comparing base (06a8240) to head (4a5953c).
⚠️ Report is 76 commits behind head on main.

Files with missing lines Patch % Lines
lib/internal/modules/esm/load.js 78.57% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #62300      +/-   ##
==========================================
+ Coverage   89.68%   89.70%   +0.01%     
==========================================
  Files         676      692      +16     
  Lines      206578   214212    +7634     
  Branches    39555    41123    +1568     
==========================================
+ Hits       185267   192154    +6887     
- Misses      13446    14112     +666     
- Partials     7865     7946      +81     
Files with missing lines Coverage Δ
lib/internal/modules/esm/assert.js 100.00% <100.00%> (ø)
lib/internal/modules/esm/resolve.js 99.05% <100.00%> (+<0.01%) ⬆️
lib/internal/modules/esm/translators.js 97.70% <100.00%> (+0.03%) ⬆️
src/node_options.cc 76.49% <100.00%> (+0.04%) ⬆️
src/node_options.h 97.96% <100.00%> (+0.03%) ⬆️
lib/internal/modules/esm/load.js 90.67% <78.57%> (-0.81%) ⬇️

... and 110 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@GeoffreyBooth
Copy link
Copy Markdown
Member

Note that the import-bytes proposal requires producing Uint8Arrays backed by immutable ArrayBuffers, which are not yet supported in V8. I would be hesitant to ship this with mutable backing buffers, even flagged as experimental, lest people come to rely on that.
Text imports don't have the same problem.

That's a great point, looks like I've missed that. I've removed bytes related part of the code. Thank you!

This is really good knowledge that would be good to preserve somewhere. Maybe add a comment near the most appropriate spot in the new code, such as where the bytes support would be, to note that we shouldn’t add bytes support before immutable ArrayBuffers exist in V8? That way if someone later on comes by to add bytes, they'll stumble upon the comment in the code.

Comment on lines 27 to +42
const formatTypeMap = {
'__proto__': null,
'builtin': kImplicitTypeAttribute,
'commonjs': kImplicitTypeAttribute,
'json': 'json',
'module': kImplicitTypeAttribute,
'wasm': kImplicitTypeAttribute, // It's unclear whether the HTML spec will require an type attribute or not for Wasm; see https://github.com/WebAssembly/esm-integration/issues/42
};

function getFormatType(format) {
if (format === 'text' && getOptionValue('--experimental-import-text')) {
return 'text';
}

return formatTypeMap[format];
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the past when we've added experimental types, we conditionally add them to the map. GitHub has a bug that won't let me suggest a change, but basically (psuedocode):

formatTypeMap = { ... }

if (getOptionValue('--experimental-import-text') {
  formatTypeMap.text = 'text';
}

Then you don't need the other changes farther down in this file.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this particular example would work as we can't call getOptionValue at module top level during bootstrap. Maybe we could do it lazily when validateAttributes is called but then both formatTypeMap and supportedTypeAttributes has to be patched.

I'm not very happy about this part of the change, but looking into old commits couldn't come up with something better

Comment on lines +81 to +98
function getFormatFromImportAttributes(importAttributes) {
if (
!importAttributes ||
!ObjectPrototypeHasOwnProperty(importAttributes, 'type') ||
typeof importAttributes.type !== 'string'
) {
return undefined;
}

if (
getOptionValue('--experimental-import-text') &&
importAttributes.type === 'text'
) {
return 'text';
}

return undefined;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already support with { type: 'json' }. This seems to introduce a new pattern, rather than following the existing one?

Copy link
Copy Markdown
Contributor Author

@efekrskl efekrskl Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, the reason is JSON imports are format led (.json/MIME), whereas for text imports anything can be imported as text (regardless of .txt/MIME). So we needed to have a separate mapping

I'm open for ideas though. Maybe we can make this more clear with a comment

Copy link
Copy Markdown
Member

@GeoffreyBooth GeoffreyBooth Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed that it should have a new mapping, but in general we want to follow the existing patterns of the cocebase. Don’t create a new code path for handling modules of varying formats; we already have a path for with { type: 'json' } so text should work the same way, only diverging where it needs to because of the type. For example, JSON didn’t require getFormatFromImportAttributes so this function shouldn’t be needed to add support for text.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, thanks for the guidance.

I've updated the patch, wdyt?

Copy link
Copy Markdown

@pdeveltere pdeveltere left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in loader.js, you can add:

/**
 * @typedef {'builtin'|'commonjs'|'json'|'module'|'text'|'wasm'} ModuleFormat
 */

function getFileProtocolModuleFormat(url, context = { __proto__: null }, ignoreErrors) {
const { source } = context;
const ext = extname(url);

Copy link
Copy Markdown

@pdeveltere pdeveltere Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// If the caller explicitly requests text format via import attributes, honour it regardless of file extension.
if (context.importAttributes?.type === 'text') {
return 'text';
}

) !== null
) { return 'module'; }
if (mime === 'application/json') { return 'json'; }
if (mime === 'application/wasm') { return 'wasm'; }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (mime === 'application/wasm') { return 'wasm'; }
if (RegExpPrototypeExec(/^\s*text\/plain\s*(;\s*charset=utf-?8\s*)?$/i, mime) !== null) { return 'text'; }

@efekrskl
Copy link
Copy Markdown
Contributor Author

Note that the import-bytes proposal requires producing Uint8Arrays backed by immutable ArrayBuffers, which are not yet supported in V8. I would be hesitant to ship this with mutable backing buffers, even flagged as experimental, lest people come to rely on that.
Text imports don't have the same problem.

That's a great point, looks like I've missed that. I've removed bytes related part of the code. Thank you!

This is really good knowledge that would be good to preserve somewhere. Maybe add a comment near the most appropriate spot in the new code, such as where the bytes support would be, to note that we shouldn’t add bytes support before immutable ArrayBuffers exist in V8? That way if someone later on comes by to add bytes, they'll stumble upon the comment in the code.

Yeah makes sense, I've added a comment right below formatTypeMap

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support for importing text modules

5 participants