ember-content-mapper
v0.3.1
Published
A TypeScript content mapper for Ember's .gts and .gjs files
Readme
ember-content-mapper
Type-check .gts and .gjs files with native TypeScript 7.
This package is a TypeScript content mapper.
Requirements
- TypeScript 7.1 nightly or newer (
typescript@next) - Node 22.21.1 or newer, or Node 24.10.0 or newer
- imports must specify the file extensions
Install
pnpm add -D ember-content-mapper @glint/ember-tscUse
Add the mapper to tsconfig.json:
{
"contentMappers": [
{
"package": "ember-content-mapper",
"extensions": [".gts", ".gjs"],
},
],
"include": ["src"],
}Type-check:
tsc --noEmit --runExternalCodeOptions
options accepts the options of Glint's ember-template-imports environment:
{
"contentMappers": [
{
"package": "ember-content-mapper",
"extensions": [".gts", ".gjs"],
"options": {
"additionalGlobals": ["t"],
"additionalSpecialForms": {},
},
},
],
}Directives
Glint's directives work as before:
{{! @glint-expect-error }}suppresses the diagnostics on the next line. If there are none, TypeScript reportsglint2578: Unused '@glint-expect-error' directive.{{! @glint-ignore }}suppresses the diagnostics on the next line.{{! @glint-nocheck }}suppresses the diagnostics in the whole template.
Declaration files
A sibling declaration file wins over transforming the module, matching Glint: counter.gjs is
typed from counter.d.gjs.ts (TypeScript 7's arbitrary-extension convention) or counter.gjs.d.ts
(Glint's) when one exists. The declaration is parsed as a .ts module, so anything unbodied or
uninitialized in it must use ambient (declare) syntax.
Migrating from TS6
On TypeScript 6, Glint 2 type-checks .gts and .gjs with its own compiler, ember-tsc, and
serves editors from its own language server. TypeScript 7 does both itself and calls this mapper
for the transform. So most of the migration is deleting configuration.
Install the TypeScript 7 nightly and the mapper:
pnpm add -D typescript@next ember-content-mapperKeep @glint/ember-tsc and @glint/template installed. The mapper transforms with
@glint/ember-tsc and references its types, and @glint/template types the signatures you write
by hand.
tsconfig.json
Add the contentMappers entry from Use. Then remove
{ "name": "@glint/tsserver-plugin" } from compilerOptions.plugins. TypeScript 7 does not
load tsserver plugins. The contentMappers entry replaces it.
"ember-source/types" and "@glint/ember-tsc/types" in compilerOptions.types become
redundant: the mapper references both from the transformed text. Removing them is optional.
Keep them while another tool that builds a program from the same tsconfig.json
(for example eslint) still runs against the project; without them those tools
lose the @ember/* and Glint types. Keep every other entry, for example
"@embroider/core/virtual" or "vite/client".
If the project still has a Glint 1 glint key, its environment options move to the mapper's
options. See Options.
package.json
- Replace
ember-tscin your scripts withtsc --noEmit --runExternalCode. Without--runExternalCode, TypeScript reportsTS100024: Content mappers require the '--runExternalCode' command line flag to be enabled. - Remove
@glint/tsserver-plugin.
Source
Add the extension to relative imports of .gts and .gjs modules:
-export { default as Counter } from './counter';
+export { default as Counter } from './counter.gts';TypeScript resolves a content-mapped file only when the specifier has the extension. Glint resolved it either way, so this is usually the only source change the migration needs.
A project that is clean under ember-tsc can report a few diagnostics that Glint dropped
because they landed on unmapped generated text. See
Diagnostics that Glint drops.
Glint's {{! @glint-expect-error }}, {{! @glint-ignore }}, and {{! @glint-nocheck }}
directives keep working. See Directives.
Editor
Glint's language server is no longer in the loop. See Editors for what replaces it.
Known issues
TypeScript 7 behavior changes that surface through the mapper. These are compiler behaviors, not mapper bugs; each links to the upstream report.
- JSDoc
@extendsover expression heritage is dropped (TypeScript#64058). On TypeScript 6, a classicclass Foo extends Component.extend(SomeMixin) {}with a/** @extends {Component<FooSignature>} */tag keeps its signature. TypeScript 7 rejects the tag (TS8023/TS8026) and the signature is lost, so every invocation types the component as taking no arguments, blocks, or element. Workaround: a sibling declaration file (see Declaration files). - Closure-style
function(...)JSDoc types no longer parse, and the parse error silently drops every later@propertyin the same typedef — arguments vanish from the signature with no error at the definition. Rewrite them as arrow types:{function(string)=}becomes{((s: string) => void)=}. Documented as intentional in typescript-go's CHANGES.md. - Declaration builds emit
foo.d.gts.tsrather thanfoo.d.ts, which complicatespackage.json#exportsfor published packages (TypeScript#64053).
Tracked in #23, along with a request for a mapper debug mode (TypeScript#64055).
Editors
- VS Code: TypeScript (Native Preview) plus Glint 2 1.4.0 or newer. Glint registers
.gtsand.gjswith TypeScript and stands down its own language server. - Neovim: ember.nvim attaches TypeScript 7's LSP
when
tsconfig.jsonhascontentMappers.
examples/README.md has the details.
Debug
TS_CONTENT_MAPPER_DEBUG=1 logs the JSON-RPC traffic between tsc and the mapper.
TS_CONTENT_MAPPER_WORKERS=<n> sets the number of transform workers (default: cores − 1, at most 8;
1 = main thread). tsc --extendedDiagnostics shows the wait as Content mapper request wait time.
Repository
examples/: two Ember apps that use the mapper.test/test-packages/: copies of Glint's test packages, with the known differences recorded in test/test-packages/README.md.test/: snapshot tests of the transform, tests of the server process, LSP tests against the example app (hover, definition, completion, diagnostics, rename), and compiler mode tests (declaration emit,--buildup-to-date checks, option diagnostics).
Prior art
- mdx-content-mapper, which this package follows.
- Vue's content mapper.
