Visitar URL original
feat: run .groq text imports on Node, Vite, Next.js and Jest · sanity-io/codegen@5189787 · GitHub
Skip to content

Commit 5189787

Browse files
committed
feat: run .groq text imports on Node, Vite, Next.js and Jest
The import attribute does the runtime work: import query from './posts.groq' with {type: 'text'} makes a runtime or bundler deliver the file as a string, which Bun, Deno, esbuild, webpack 5.102, Rspack 1.7 and Turbopack (Next.js 16.3) do on their own, and Node 26.5 does behind --experimental-import-text. Where a host does not, one line fills in, and none of these runs the type generator: - @sanity/codegen/register: a Node preload (node --import) for Node without native text imports, through module.registerHooks, or module.register before Node 22.15. - @sanity/codegen/vite: a load hook for Vite, whose dev server does not honor the attribute; also for Vitest and Rollup, and for a Studio or App SDK app through vite.plugins in sanity.cli.ts. - @sanity/codegen/next: withGroqFiles(nextConfig) adds webpack's own asset/source rule for the webpack Next bundles, 5.98, and runs the type check inside next build with --runExternalCode, which Next's own check does not pass. - @sanity/codegen/jest: a transformer, for tests compiled to CommonJS, since Jest's ES module loader rejects the attribute. Each entry is typed structurally, so none depends on its host's types.
1 parent ef5111b commit 5189787

12 files changed

Lines changed: 1247 additions & 378 deletions

File tree

‎README.md‎

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,15 @@ Nothing is declared globally and nothing is imported: no `SanityQueries` entry,
134134

135135
### Setup
136136

137-
TypeScript 7.1 can ask a package to turn a file it does not understand into a TypeScript module, through a [content mapper](https://devblogs.microsoft.com/typescript/announcing-typescript-7-1-beta/). This package is one for `.groq` files. Name it in `tsconfig.json`, next to `compilerOptions`, with the extension it should own and the schema to type against, and type-check with `--runExternalCode`, the flag that lets `tsc` run content mappers. The mapper then types every `.groq` import from the live file, with nothing written to disk, and reports a GROQ syntax error at the `.groq` file. The feature is this mapper; there is no generated declaration file and nothing for earlier TypeScript releases.
137+
This needs TypeScript 7.1 beta or later, the first release with [content mappers](https://devblogs.microsoft.com/typescript/announcing-typescript-7-1-beta/), and Node 22.12 or later, or Bun.
138+
139+
```sh
140+
npm install -D @sanity/codegen typescript@beta
141+
```
142+
143+
An app that runs on Node under the preload below, rather than as a bundle or on a Node with native text imports, loads its `.groq` files when it runs, so it installs `@sanity/codegen` as a dependency: `npm install @sanity/codegen`.
144+
145+
Name the package in `tsconfig.json`, next to `compilerOptions`, with the extension it should own and the schema to type against, and type-check with `--runExternalCode`, the flag that lets `tsc` run content mappers. The mapper then types every `.groq` import from the live file, with nothing written to disk, and reports a GROQ syntax error at the `.groq` file. The feature is this mapper; there is no generated declaration file and nothing for earlier TypeScript releases.
138146

139147
```jsonc
140148
// tsconfig.json
@@ -161,7 +169,36 @@ TypeScript 7.1 can ask a package to turn a file it does not understand into a Ty
161169
}
162170
```
163171

164-
Editors ask for `--runExternalCode` through workspace trust.
172+
At run time the import attribute does the work: `import query from './posts.groq' with {type: 'text'}` makes the runtime or bundler deliver the file as a string, which most of them do on their own. Where one does not, one line fills in. None of these runs the type generator, and each delivers the file as it is, which is what the mapper types.
173+
174+
| Runtime or bundler | `with {type: 'text'}` | Setup |
175+
| --------------------------------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
176+
| Node 26.5 and later | behind `--experimental-import-text` | the flag, or the preload below |
177+
| Node 22.12 and later | no | `node --import @sanity/codegen/register main.ts` |
178+
| Bun | yes | nothing |
179+
| Deno | yes | nothing; `deno check` types the import as `string`, since Deno's TypeScript has no content mappers |
180+
| esbuild, `Bun.build` | yes | nothing |
181+
| webpack 5.102 and later, Rspack 1.7 and later | yes, as `asset/source` | nothing |
182+
| webpack before 5.102 | no | `{test: /\.groq$/, type: 'asset/source'}` in `module.rules` |
183+
| Rolldown | with `moduleTypes: {'.groq': 'text'}` | that option |
184+
| Vite, Vitest, Rollup | not in Vite's dev server | `import groqFiles from '@sanity/codegen/vite'`, then `groqFiles()` in `plugins` |
185+
| Sanity Studio, App SDK apps | as Vite | the same plugin, in `vite: {plugins: [groqFiles()]}` of `sanity.cli.ts` |
186+
| Next.js 16.3 and later | yes under Turbopack; its webpack is 5.98 | `import {withGroqFiles} from '@sanity/codegen/next'`, then `export default withGroqFiles(nextConfig)` (below) |
187+
| Jest | rejected by Jest's ES module loader | `'@sanity/codegen/jest'` in `transform`, for `'\\.groq$'`, with tests compiled to CommonJS, where Babel drops the attribute |
188+
189+
`withGroqFiles` adds the `asset/source` rule for the webpack Next bundles and takes over the type check `next build` runs: Next runs the project's own `tsc` for it, but without `--runExternalCode`, which TypeScript takes from the command line only, so that check cannot start the mapper and fails on every `.groq` import. The wrapper turns Next's check off and runs the same check with the flag in `compiler.runAfterProductionCompile`, after the compile under either bundler; on a first build it runs `next typegen` first, since Next writes `next-env.d.ts` only later. A project that set `typescript.ignoreBuildErrors` itself gets no check.
190+
191+
In VS Code or Cursor, install the TypeScript 7 extension, `TypeScriptTeam.native-preview`, and point it at the project's TypeScript, since the extension bundles 7.0:
192+
193+
```jsonc
194+
// .vscode/settings.json
195+
{
196+
"js/ts.experimental.useTsgo": true,
197+
"js/ts.tsdk.path": "./node_modules/typescript",
198+
}
199+
```
200+
201+
Then trust the workspace, and allow the extension to use the workspace's TypeScript when it asks; editors ask for `--runExternalCode` that way.
165202

166203
### The schema
167204

@@ -188,4 +225,4 @@ A codebase that queries more than one project or dataset has one schema per fold
188225

189226
The mapper is also exported as `@sanity/codegen/content-mapper`, as `ContentMapper`, for a host that wants to drive it in-process.
190227

191-
Two things are not inferred: a comparison against `^` is `unknown`, because the probe that resolves field types does not reproduce the parent scope, and groq-js does not yet model `score()`, so `_score` is `null` in a result and `[_score > 0]` empties it. A bundler plugin that loads the file as its string at runtime is a separate, following change; until then the import needs a loader that evaluates a `.groq` file to `export default "<its text>"`.
228+
Two things are not inferred: a comparison against `^` is `unknown`, because the probe that resolves field types does not reproduce the parent scope, and groq-js does not yet model `score()`, so `_score` is `null` in a result and `[_score > 0]` empties it.

‎dev/sanity.cli.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
// eslint-disable-next-line import-x/no-extraneous-dependencies -- a build-time plugin, a devDependency as in any project
2+
import groqFiles from '@sanity/codegen/vite'
13
import {defineCliConfig} from 'sanity/cli'
24

3-
export default defineCliConfig({})
5+
export default defineCliConfig({
6+
// `sanity dev` and `sanity build` bundle with Vite; this makes a `.groq` import evaluate to its text.
7+
vite: {plugins: [groqFiles()]},
8+
})

‎package.json‎

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
},
2121
"license": "MIT",
2222
"author": "Sanity.io <hello@sanity.io>",
23-
"sideEffects": false,
23+
"sideEffects": [
24+
"./dist/_exports/register.js"
25+
],
2426
"type": "module",
2527
"exports": {
2628
".": {
@@ -31,6 +33,22 @@
3133
"source": "./src/_exports/content-mapper.ts",
3234
"default": "./dist/_exports/content-mapper.js"
3335
},
36+
"./jest": {
37+
"source": "./src/_exports/jest.ts",
38+
"default": "./dist/_exports/jest.js"
39+
},
40+
"./next": {
41+
"source": "./src/_exports/next.ts",
42+
"default": "./dist/_exports/next.js"
43+
},
44+
"./register": {
45+
"source": "./src/_exports/register.ts",
46+
"default": "./dist/_exports/register.js"
47+
},
48+
"./vite": {
49+
"source": "./src/_exports/vite.ts",
50+
"default": "./dist/_exports/vite.js"
51+
},
3452
"./package.json": "./package.json"
3553
},
3654
"main": "./dist/_exports/index.js",
@@ -89,6 +107,7 @@
89107
"@types/lodash-es": "^4.17.12",
90108
"@types/node": "^24.13.3",
91109
"@vitest/coverage-istanbul": "^4.1.8",
110+
"esbuild": "^0.28.2",
92111
"eslint": "^9.39.4",
93112
"husky": "^9.1.7",
94113
"lint-staged": "^16.4.0",
@@ -100,7 +119,8 @@
100119
"tinyglobby": "^0.2.17",
101120
"typescript": "^5.9.3",
102121
"vite": "^8.2.2",
103-
"vitest": "^4.1.8"
122+
"vitest": "^4.1.8",
123+
"webpack": "^5.112.0"
104124
},
105125
"peerDependencies": {
106126
"oxfmt": "*"

0 commit comments

Comments
 (0)