CLI • API
sv-utils
On this page
- transforms
- transforms.script
- transforms.svelte
- transforms.svelteScript
- transforms.css
- transforms.json
- transforms.yaml / transforms.toml
- transforms.text
- Aborting a transform
- Standalone usage & testing
- Composability
- Parsers (low-level)
- Language tooling
- Svelte config
- svelteConfig.edit
- svelteConfig.find / svelteConfig.read
- Package manager helpers
- pnpm.allowBuilds
@sveltejs/sv-utilsis currently experimental. The API may change.
@sveltejs/sv-utils is an add-on utility for parsing, transforming, and generating code..
npm install -D @sveltejs/sv-utilstransforms
transforms is a collection of parser-aware functions that lets you modify the files via abstract syntax tree (AST). It accepts a callback function. The return value is designed to be be passed directly into sv.file(). The parser choice is baked into the transform type - you can’t accidentally parse a vite config as Svelte because you never call a parser yourself.
Each transform injects relevant utilities into the callback, so you only need one import:
import { transforms } from '@sveltejs/sv-utils';
transforms.script(/* ... */);
transforms.svelte(/* ... */);
// ...transforms.script
Transform a JavaScript/TypeScript file. The callback receives { ast, comments, content, js }.
import { import transformstransforms } from '@sveltejs/sv-utils';
sv.file(
file.viteConfig,
import transformstransforms.script(({ ast: anyast, js: anyjs }) => {
js: anyjs.imports.addDefault(ast: anyast, { as: stringas: 'foo', from: stringfrom: 'foo' });
js: anyjs.vite.addPlugin(ast: anyast, { code: stringcode: 'foo()' });
})
);transforms.svelte
Transform a Svelte component. The callback receives { ast, content, svelte, js }.
import { import transformstransforms } from '@sveltejs/sv-utils';
sv.file(
layoutPath,
import transformstransforms.svelte(({ ast: anyast, svelte: anysvelte }) => {
svelte: anysvelte.addFragment(ast: anyast, '<Foo />');
})
);transforms.svelteScript
Transform a Svelte component with a <script> block guaranteed. Pass { language } as the first argument. The callback receives { ast, content, svelte, js } where ast.instance is always non-null.
import { import transformstransforms } from '@sveltejs/sv-utils';
sv.file(
layoutPath,
import transformstransforms.svelteScript({ language: stringlanguage: 'ts' }, ({ ast: anyast, svelte: anysvelte, js: anyjs }) => {
js: anyjs.imports.addDefault(ast: anyast.instance.content, { as: stringas: 'Foo', from: stringfrom: './Foo.svelte' });
svelte: anysvelte.addFragment(ast: anyast, '<Foo />');
})
);transforms.css
Transform a CSS file. The callback receives { ast, content, css }.
import { import transformstransforms } from '@sveltejs/sv-utils';
sv.file(
file.stylesheet,
import transformstransforms.css(({ ast: anyast, css: anycss }) => {
css: anycss.addAtRule(ast: anyast, { name: stringname: 'import', params: stringparams: "'tailwindcss'" });
})
);transforms.json
Transform a JSON file. Mutate the data object directly. The callback receives { data, content, json }.
import { import transformstransforms } from '@sveltejs/sv-utils';
sv.file(
file.typeConfig,
import transformstransforms.json(({ data: anydata }) => {
data: anydata.compilerOptions ??= {};
data: anydata.compilerOptions.strict = true;
})
);transforms.yaml / transforms.toml
Same pattern as transforms.json, for YAML and TOML files respectively. The callback receives { data, content }.
transforms.text
Transform a plain text file (.env, .gitignore, etc.). No parser - string in, string out. The callback receives { content, text }.
import { import transformstransforms } from '@sveltejs/sv-utils';
sv.file(
'.env',
import transformstransforms.text(({ content: anycontent }) => {
return content: anycontent + '\nDATABASE_URL="file:local.db"';
})
);Aborting a transform
Return false from any transform callback to abort - the original content is returned unchanged.
import { import transformstransforms } from '@sveltejs/sv-utils';
sv.file(
'eslint.config.js',
import transformstransforms.script(({ ast: anyast, js: anyjs }) => {
const { value: const existing: anyexisting } = js: anyjs.exports.createDefault(ast: anyast, { fallback: anyfallback: myConfig });
if (const existing: anyexisting !== myConfig) {
// config already exists, don't touch it
return false;
}
// ... continue modifying ast
})
);Standalone usage & testing
Transforms are curried functions - call them with the callback, then apply to content:
import { transforms } from '@sveltejs/sv-utils';
const transform = transforms.script(({ ast, js }) => {
js.imports.addDefault(ast, { as: 'foo', from: 'foo' });
});
const result = transform('export default {}');Composability
For cases where you need to mix and match transforms and raw edits, use sv.file with a content callback and invoke the curried transform manually:
sv.file(path, (content: anycontent) => {
// curried
const const transform: anytransform = transforms.script(({ ast: anyast, js: anyjs }) => {
js: anyjs.imports.addDefault(ast: anyast, { as: stringas: 'foo', from: stringfrom: 'bar' });
});
// parser manipulation
content: anycontent = const transform: anytransform(content: anycontent);
// raw string manipulation
content: anycontent = content: anycontent.replace('foo', 'baz');
return content: anycontent;
});Add-ons can also export reusable transform functions:
// @errors: 7006
import { transforms } from '@sveltejs/sv-utils';
// reusable - export from your package
export const addFooImport = transforms.svelte(({ ast, svelte, js }) => {
svelte.ensureScript(ast, { language });
js.imports.addDefault(ast.instance.content, { as: 'Foo', from: './Foo.svelte' });
});sv.file('+page.svelte', addFooImport);
sv.file('index.svelte', addFooImport);Parsers (low-level)
transforms will fit most users needs (e.g., conditional parsing, error handling around the parser). If not, parse is a low-level API available to you:
import { import parseparse } from '@sveltejs/sv-utils';
const { const ast: anyast, const generateCode: anygenerateCode } = import parseparse.script(content);
const { const ast: anyast, const generateCode: anygenerateCode } = import parseparse.svelte(content);
const { const ast: anyast, const generateCode: anygenerateCode } = import parseparse.css(content);
const { const data: anydata, const generateCode: anygenerateCode } = import parseparse.json(content);
const { const data: anydata, const generateCode: anygenerateCode } = import parseparse.yaml(content);
const { const data: anydata, const generateCode: anygenerateCode } = import parseparse.toml(content);
const { const ast: anyast, const generateCode: anygenerateCode } = import parseparse.html(content);Language tooling
Namespaced helpers for AST manipulation:
js.*- imports, exports, objects, arrays, variables, functions, vite config helpers, SvelteKit helperscss.*- rules, declarations, at-rules, importssvelte.*- ensureScript, addSlot, addFragmentjson.*- arrayUpsert, packageScriptsUpserthtml.*- attribute manipulationtext.*- upsert lines in flat files (.env, .gitignore)
Svelte config
The svelte/kit config can live in two places: passed straight to the sveltekit() plugin in vite.config.{js,ts}, or as a default export in a separate svelte.config.{js,ts}. Projects created by sv keep their config inside vite.config.js and ship no svelte.config.js.
svelteConfig lets add-ons read and edit that config wherever it lives - the sveltekit() argument in vite.config.{js,ts}, or a svelte.config.{js,ts} default export - without having to know which.
svelteConfig.edit
You address options by name and the helper writes each one to the right place, so you never deal with the kit nesting yourself. Svelte-level options (compilerOptions, preprocess, extensions, vitePlugin) sit on the config object; everything else (adapter, alias, files, typescript, …) is a kit option, which means flattened onto the sveltekit() argument in a vite config, or nested under kit in a svelte.config.
import { import svelteConfigsvelteConfig } from '@sveltejs/sv-utils';
// inside an add-on's `run({ sv, cwd })`:
import svelteConfigsvelteConfig.edit({ sv: anysv, cwd: anycwd }, ({ ast: anyast, property: anyproperty, override: anyoverride, js: anyjs }) => {
// svelte-level option - get-or-create its value, then mutate in place:
js: anyjs.array.append(property: anyproperty('extensions', { fallback: anyfallback: js: anyjs.array.create() }), '.svx');
// kit option - routed automatically, no `kit` nesting to think about:
js: anyjs.imports.addDefault(ast: anyast, { from: stringfrom: '@sveltejs/adapter-node', as: stringas: 'adapter' });
override: anyoverride({
adapter: anyadapter: js: anyjs.functions.createCall({ name: stringname: 'adapter', args: never[]args: [], useIdentifiers: booleanuseIdentifiers: true })
});
});property(name, { fallback })- get-or-create an option’s value to mutate in place (arrays, nested objects).override(props, { dropLeadingComments })- set/replace options;dropLeadingCommentsclears a now-stale leading comment (e.g. the adapter-auto note when switching adapters).
It writes through sv.file, so the edit is tracked like any other. If the project has neither config file, a svelte.config.js is created.
svelteConfig.find / svelteConfig.read
Lower-level building blocks, both reading candidate files through an injected read(path) (returns the file contents or null) so detection stays static - the config is never executed:
svelteConfig.find(read)- returns{ path, kind }ornull(kindis'vite'or'svelte';svelte.configwins when both are present).svelteConfig.read(read)- locates and parses in one pass, returning{ location, config, kit }(the object expressions) ornull.
Package manager helpers
pnpm.allowBuilds
Returns a transform for pnpm-workspace.yaml that adds packages to the pnpm “allow builds” config. Use with sv.file when the project uses pnpm.
The helper detects the installed pnpm version via pnpm --version:
- pnpm
>= 11: writes to the unifiedallowBuildsmap ({ pkg: true }), migrating any legacyonlyBuiltDependencieslist into the map. - pnpm
< 11: writes to the legacyonlyBuiltDependencieslist.
import { import pnpmpnpm } from '@sveltejs/sv-utils';
if (packageManager === 'pnpm') {
sv.file(file.findUp('pnpm-workspace.yaml'), import pnpmpnpm.allowBuilds('my-native-dep'));
}