v155 · javascript · modules

Import Text (text modules)

A shader, a SQL schema, a mail template — content your code needs as a string, currently reached by fetch() at runtime or inlined by a bundler at build time. import shader from "./frag.glsl" with { type: "text" } makes it what it always was: a dependency, resolved by the module graph, with the default export being the file's text.

concepts

  1. Import a file

    Import four real files from this repository as text — plain text, GLSL, SQL and a template — and see exactly what the default export is. Falls back to fetch() when the browser has no text modules, and says which path ran.

  2. Against fetch()

    The same file both ways, measured. The interesting differences are not speed: they are when the failure happens, whether a second import costs anything, and what the code around the call has to look like.

  3. A template you can edit

    The practical shape. A receipt template imported as text, rendered against editable data, with the option to reload it — showing what the module cache does and does not do when the file behind an import changes.

why it shipped

Every project reaches for text at some point, and the two existing answers are both awkward. A bundler plugin turns the file into JavaScript at build time, which works but ties the source to a toolchain and makes the file invisible to anything that reads the module graph. fetch() works without a build step but pushes the load to runtime, into an async function, with its own error handling and cache semantics — for something that is, conceptually, a dependency.

An import attribute puts it in the graph: the loader resolves the specifier, applies the same integrity and CSP rules as any module, and gives you a string. The attribute is required rather than inferred, so a server that starts returning JavaScript for a URL you import as text cannot get that code executed — which is why the type is checked rather than trusted.

the API

// Static: resolved with the module graph, before your code runs.
import fragmentSource from "./shaders/frag.glsl" with { type: "text" };
gl.shaderSource(shader, fragmentSource);

// Dynamic: same attribute, in the options bag.
const schema = await import("./db/schema.sql", { with: { type: "text" } });
db.exec(schema.default);

The export is the default export and it is a string. A browser without text modules throws a TypeError on the import rather than silently handing back a module object, which is what makes the fallback in these demos possible to write correctly.

references