@sveltejs/sv-utils is an add-on utility for parsing, transforming, and generating code..
npminstall-D @sveltejs/sv-utils
transforms
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:
sv-utils = what to do to content, sv = where and when to do it.
Each transform wraps: parse -> callback({ast/data, utils}) -> generateCode().
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.
Transforms are curried: call with the callback to get a (content: string) => string | false
function that plugs directly into sv.file().
import{transforms}from'@sveltejs/sv-utils';// use with sv.file() - curried form plugs in directlysv.file(files.viteConfig,transforms.script(({ast,js})=>{js.vite.addPlugin(ast,{code:'kitRoutes()'});}));// standalone usage / testingconstresult=transforms.script(({ast,js})=>{js.imports.addDefault(ast,{as:'foo',from:'foo'});})(fileContent);
sv-utils = what to do to content, sv = where and when to do it.
Each transform wraps: parse -> callback({ast/data, utils}) -> generateCode().
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.
Transforms are curried: call with the callback to get a (content: string) => string | false
function that plugs directly into sv.file().
import{transforms}from'@sveltejs/sv-utils';// use with sv.file() - curried form plugs in directlysv.file(files.viteConfig,transforms.script(({ast,js})=>{js.vite.addPlugin(ast,{code:'kitRoutes()'});}));// standalone usage / testingconstresult=transforms.script(({ast,js})=>{js.imports.addDefault(ast,{as:'foo',from:'foo'});})(fileContent);
sv-utils = what to do to content, sv = where and when to do it.
Each transform wraps: parse -> callback({ast/data, utils}) -> generateCode().
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.
Transforms are curried: call with the callback to get a (content: string) => string | false
function that plugs directly into sv.file().
import{transforms}from'@sveltejs/sv-utils';// use with sv.file() - curried form plugs in directlysv.file(files.viteConfig,transforms.script(({ast,js})=>{js.vite.addPlugin(ast,{code:'kitRoutes()'});}));// standalone usage / testingconstresult=transforms.script(({ast,js})=>{js.imports.addDefault(ast,{as:'foo',from:'foo'});})(fileContent);
sv-utils = what to do to content, sv = where and when to do it.
Each transform wraps: parse -> callback({ast/data, utils}) -> generateCode().
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.
Transforms are curried: call with the callback to get a (content: string) => string | false
function that plugs directly into sv.file().
import{transforms}from'@sveltejs/sv-utils';// use with sv.file() - curried form plugs in directlysv.file(files.viteConfig,transforms.script(({ast,js})=>{js.vite.addPlugin(ast,{code:'kitRoutes()'});}));// standalone usage / testingconstresult=transforms.script(({ast,js})=>{js.imports.addDefault(ast,{as:'foo',from:'foo'});})(fileContent);
sv-utils = what to do to content, sv = where and when to do it.
Each transform wraps: parse -> callback({ast/data, utils}) -> generateCode().
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.
Transforms are curried: call with the callback to get a (content: string) => string | false
function that plugs directly into sv.file().
import{transforms}from'@sveltejs/sv-utils';// use with sv.file() - curried form plugs in directlysv.file(files.viteConfig,transforms.script(({ast,js})=>{js.vite.addPlugin(ast,{code:'kitRoutes()'});}));// standalone usage / testingconstresult=transforms.script(({ast,js})=>{js.imports.addDefault(ast,{as:'foo',from:'foo'});})(fileContent);
sv-utils = what to do to content, sv = where and when to do it.
Each transform wraps: parse -> callback({ast/data, utils}) -> generateCode().
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.
Transforms are curried: call with the callback to get a (content: string) => string | false
function that plugs directly into sv.file().
import{transforms}from'@sveltejs/sv-utils';// use with sv.file() - curried form plugs in directlysv.file(files.viteConfig,transforms.script(({ast,js})=>{js.vite.addPlugin(ast,{code:'kitRoutes()'});}));// standalone usage / testingconstresult=transforms.script(({ast,js})=>{js.imports.addDefault(ast,{as:'foo',from:'foo'});})(fileContent);
sv-utils = what to do to content, sv = where and when to do it.
Each transform wraps: parse -> callback({ast/data, utils}) -> generateCode().
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.
Transforms are curried: call with the callback to get a (content: string) => string | false
function that plugs directly into sv.file().
import{transforms}from'@sveltejs/sv-utils';// use with sv.file() - curried form plugs in directlysv.file(files.viteConfig,transforms.script(({ast,js})=>{js.vite.addPlugin(ast,{code:'kitRoutes()'});}));// standalone usage / testingconstresult=transforms.script(({ast,js})=>{js.imports.addDefault(ast,{as:'foo',from:'foo'});})(fileContent);
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.
sv-utils = what to do to content, sv = where and when to do it.
Each transform wraps: parse -> callback({ast/data, utils}) -> generateCode().
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.
Transforms are curried: call with the callback to get a (content: string) => string | false
function that plugs directly into sv.file().
import{transforms}from'@sveltejs/sv-utils';// use with sv.file() - curried form plugs in directlysv.file(files.viteConfig,transforms.script(({ast,js})=>{js.vite.addPlugin(ast,{code:'kitRoutes()'});}));// standalone usage / testingconstresult=transforms.script(({ast,js})=>{js.imports.addDefault(ast,{as:'foo',from:'foo'});})(fileContent);
sv-utils = what to do to content, sv = where and when to do it.
Each transform wraps: parse -> callback({ast/data, utils}) -> generateCode().
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.
Transforms are curried: call with the callback to get a (content: string) => string | false
function that plugs directly into sv.file().
import{transforms}from'@sveltejs/sv-utils';// use with sv.file() - curried form plugs in directlysv.file(files.viteConfig,transforms.script(({ast,js})=>{js.vite.addPlugin(ast,{code:'kitRoutes()'});}));// standalone usage / testingconstresult=transforms.script(({ast,js})=>{js.imports.addDefault(ast,{as:'foo',from:'foo'});})(fileContent);
Transform a Svelte component file with a script block guaranteed.
Calls ensureScript before invoking your callback, so ast.instance is always non-null.
Pass { language } as the first argument to set the script language.
sv-utils = what to do to content, sv = where and when to do it.
Each transform wraps: parse -> callback({ast/data, utils}) -> generateCode().
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.
Transforms are curried: call with the callback to get a (content: string) => string | false
function that plugs directly into sv.file().
import{transforms}from'@sveltejs/sv-utils';// use with sv.file() - curried form plugs in directlysv.file(files.viteConfig,transforms.script(({ast,js})=>{js.vite.addPlugin(ast,{code:'kitRoutes()'});}));// standalone usage / testingconstresult=transforms.script(({ast,js})=>{js.imports.addDefault(ast,{as:'foo',from:'foo'});})(fileContent);
sv-utils = what to do to content, sv = where and when to do it.
Each transform wraps: parse -> callback({ast/data, utils}) -> generateCode().
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.
Transforms are curried: call with the callback to get a (content: string) => string | false
function that plugs directly into sv.file().
import{transforms}from'@sveltejs/sv-utils';// use with sv.file() - curried form plugs in directlysv.file(files.viteConfig,transforms.script(({ast,js})=>{js.vite.addPlugin(ast,{code:'kitRoutes()'});}));// standalone usage / testingconstresult=transforms.script(({ast,js})=>{js.imports.addDefault(ast,{as:'foo',from:'foo'});})(fileContent);
sv-utils = what to do to content, sv = where and when to do it.
Each transform wraps: parse -> callback({ast/data, utils}) -> generateCode().
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.
Transforms are curried: call with the callback to get a (content: string) => string | false
function that plugs directly into sv.file().
import{transforms}from'@sveltejs/sv-utils';// use with sv.file() - curried form plugs in directlysv.file(files.viteConfig,transforms.script(({ast,js})=>{js.vite.addPlugin(ast,{code:'kitRoutes()'});}));// standalone usage / testingconstresult=transforms.script(({ast,js})=>{js.imports.addDefault(ast,{as:'foo',from:'foo'});})(fileContent);
sv-utils = what to do to content, sv = where and when to do it.
Each transform wraps: parse -> callback({ast/data, utils}) -> generateCode().
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.
Transforms are curried: call with the callback to get a (content: string) => string | false
function that plugs directly into sv.file().
import{transforms}from'@sveltejs/sv-utils';// use with sv.file() - curried form plugs in directlysv.file(files.viteConfig,transforms.script(({ast,js})=>{js.vite.addPlugin(ast,{code:'kitRoutes()'});}));// standalone usage / testingconstresult=transforms.script(({ast,js})=>{js.imports.addDefault(ast,{as:'foo',from:'foo'});})(fileContent);
sv-utils = what to do to content, sv = where and when to do it.
Each transform wraps: parse -> callback({ast/data, utils}) -> generateCode().
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.
Transforms are curried: call with the callback to get a (content: string) => string | false
function that plugs directly into sv.file().
import{transforms}from'@sveltejs/sv-utils';// use with sv.file() - curried form plugs in directlysv.file(files.viteConfig,transforms.script(({ast,js})=>{js.vite.addPlugin(ast,{code:'kitRoutes()'});}));// standalone usage / testingconstresult=transforms.script(({ast,js})=>{js.imports.addDefault(ast,{as:'foo',from:'foo'});})(fileContent);
sv-utils = what to do to content, sv = where and when to do it.
Each transform wraps: parse -> callback({ast/data, utils}) -> generateCode().
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.
Transforms are curried: call with the callback to get a (content: string) => string | false
function that plugs directly into sv.file().
import{transforms}from'@sveltejs/sv-utils';// use with sv.file() - curried form plugs in directlysv.file(files.viteConfig,transforms.script(({ast,js})=>{js.vite.addPlugin(ast,{code:'kitRoutes()'});}));// standalone usage / testingconstresult=transforms.script(({ast,js})=>{js.imports.addDefault(ast,{as:'foo',from:'foo'});})(fileContent);
Transform a plain text file (.env, .gitignore, etc.).
Unlike other transforms there's no AST here - just string in, string out.
Return the new content, or false to abort (original content is returned unchanged).
sv-utils = what to do to content, sv = where and when to do it.
Each transform wraps: parse -> callback({ast/data, utils}) -> generateCode().
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.
Transforms are curried: call with the callback to get a (content: string) => string | false
function that plugs directly into sv.file().
import{transforms}from'@sveltejs/sv-utils';// use with sv.file() - curried form plugs in directlysv.file(files.viteConfig,transforms.script(({ast,js})=>{js.vite.addPlugin(ast,{code:'kitRoutes()'});}));// standalone usage / testingconstresult=transforms.script(({ast,js})=>{js.imports.addDefault(ast,{as:'foo',from:'foo'});})(fileContent);
sv-utils = what to do to content, sv = where and when to do it.
Each transform wraps: parse -> callback({ast/data, utils}) -> generateCode().
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.
Transforms are curried: call with the callback to get a (content: string) => string | false
function that plugs directly into sv.file().
import{transforms}from'@sveltejs/sv-utils';// use with sv.file() - curried form plugs in directlysv.file(files.viteConfig,transforms.script(({ast,js})=>{js.vite.addPlugin(ast,{code:'kitRoutes()'});}));// standalone usage / testingconstresult=transforms.script(({ast,js})=>{js.imports.addDefault(ast,{as:'foo',from:'foo'});})(fileContent);
sv-utils = what to do to content, sv = where and when to do it.
Each transform wraps: parse -> callback({ast/data, utils}) -> generateCode().
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.
Transforms are curried: call with the callback to get a (content: string) => string | false
function that plugs directly into sv.file().
import{transforms}from'@sveltejs/sv-utils';// use with sv.file() - curried form plugs in directlysv.file(files.viteConfig,transforms.script(({ast,js})=>{js.vite.addPlugin(ast,{code:'kitRoutes()'});}));// standalone usage / testingconstresult=transforms.script(({ast,js})=>{js.imports.addDefault(ast,{as:'foo',from:'foo'});})(fileContent);
sv-utils = what to do to content, sv = where and when to do it.
Each transform wraps: parse -> callback({ast/data, utils}) -> generateCode().
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.
Transforms are curried: call with the callback to get a (content: string) => string | false
function that plugs directly into sv.file().
import{transforms}from'@sveltejs/sv-utils';// use with sv.file() - curried form plugs in directlysv.file(files.viteConfig,transforms.script(({ast,js})=>{js.vite.addPlugin(ast,{code:'kitRoutes()'});}));// standalone usage / testingconstresult=transforms.script(({ast,js})=>{js.imports.addDefault(ast,{as:'foo',from:'foo'});})(fileContent);
sv-utils = what to do to content, sv = where and when to do it.
Each transform wraps: parse -> callback({ast/data, utils}) -> generateCode().
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.
Transforms are curried: call with the callback to get a (content: string) => string | false
function that plugs directly into sv.file().
import{transforms}from'@sveltejs/sv-utils';// use with sv.file() - curried form plugs in directlysv.file(files.viteConfig,transforms.script(({ast,js})=>{js.vite.addPlugin(ast,{code:'kitRoutes()'});}));// standalone usage / testingconstresult=transforms.script(({ast,js})=>{js.imports.addDefault(ast,{as:'foo',from:'foo'});})(fileContent);
}from'@sveltejs/sv-utils';// reusable - export from your package
exportconstaddFooImportconstaddFooImport:TransformFn=transforms(alias)consttransforms:{script(cb:(file:{ast:Program;comments:Comments;content:string;js:typeofindex_d_exports$3;})=>void|false,options?:TransformOptions):TransformFn;svelte(cb:(file:{ast:AST.Root;content:string;svelte:typeofindex_d_exports$4;js:typeofindex_d_exports$3;})=>void|false,options?:TransformOptions):TransformFn;...6more...;text(cb:(file:{content:string;text:typeoftext_d_exports;})=>string|false):TransformFn;}importtransforms
File transform primitives that know their format.
sv-utils = what to do to content, sv = where and when to do it.
Each transform wraps: parse -> callback({ast/data, utils}) -> generateCode().
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.
Transforms are curried: call with the callback to get a (content: string) => string | false
function that plugs directly into sv.file().
import{transforms}from'@sveltejs/sv-utils';// use with sv.file() - curried form plugs in directlysv.file(files.viteConfig,transforms.script(({ast,js})=>{js.vite.addPlugin(ast,{code:'kitRoutes()'});}));// standalone usage / testingconstresult=transforms.script(({ast,js})=>{js.imports.addDefault(ast,{as:'foo',from:'foo'});})(fileContent);
text.* - 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.
Helpers for the svelte/kit config, which can live either in a svelte.config.{js,ts} default
export or in the object passed to sveltekit() in a vite.config.{js,ts}.
}from'@sveltejs/sv-utils';// inside an add-on's `run({ sv, cwd })`:
svelteConfig(alias)constsvelteConfig:{edit:(target:{sv:SvFileApi;cwd:string;},editFn:SvelteConfEdit)=>void;find:(source:ConfigSource)=>SvelteConfigLocation|null;read:(source:ConfigSource)=>SvelteConfigObjects|null;}importsvelteConfig
Helpers for the svelte/kit config, which can live either in a svelte.config.{js,ts} default
export or in the object passed to sveltekit() in a vite.config.{js,ts}.
Get-or-create a top-level config option's value, placed in the correct location for its name
(kit-level options end up under kit in a svelte.config, flattened in a vite.config).
Set/override top-level config options, each routed to the correct location by its name.
Pass dropLeadingComments with option names whose now-stale leading comments should be removed
(e.g. the adapter-auto note when switching adapters).
,js(parameter)js:typeofindex_d_exports$3})=>{ // svelte-level option - get-or-create its value, then mutate in place:
js(parameter)js:typeofindex_d_exports$3.array(alias)namespaceindex_d_exports$3.arrayexportindex_d_exports$3.array.append(alias)array_d_exports.append(node:ArrayExpression,element:string|Expression|SpreadElement):voidexportarray_d_exports.append(property(parameter)property:<ArrayExpression>(name:string,opts:{fallback:ArrayExpression;})=>ArrayExpression
Get-or-create a top-level config option's value, placed in the correct location for its name
(kit-level options end up under kit in a svelte.config, flattened in a vite.config).
('extensions',{fallback(property)fallback:ArrayExpression:js(parameter)js:typeofindex_d_exports$3.array(alias)namespaceindex_d_exports$3.arrayexportindex_d_exports$3.array.create(alias)array_d_exports.create():ArrayExpressionexportarray_d_exports.create()}),'.svx'); // kit option - routed automatically, no `kit` nesting to think about:
js(parameter)js:typeofindex_d_exports$3.imports(alias)namespaceindex_d_exports$3.importsexportindex_d_exports$3.imports.addDefault(alias)imports_d_exports.addDefault(node:Program,options:{from:string;as:string;}):voidexportimports_d_exports.addDefault(ast(parameter)ast:Program,{from(property)from:string:'@sveltejs/adapter-node',as(property)as:string:'adapter'});override(parameter)override:(props:ObjectMap$1,opts?:{dropLeadingComments?:string[];})=>void
Set/override top-level config options, each routed to the correct location by its name.
Pass dropLeadingComments with option names whose now-stale leading comments should be removed
(e.g. the adapter-auto note when switching adapters).
property(name, { fallback }) - get-or-create an option’s value to mutate in place (arrays, nested objects).
override(props, { dropLeadingComments }) - set/replace options; dropLeadingComments clears 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 } or null (kind is 'vite' or 'svelte'; svelte.config wins when both are present).
svelteConfig.read(read) - locates and parses in one pass, returning { location, config, kit } (the object expressions) or null.
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.
cwd is the target project: the pnpm version that would run there (via pnpm --version) decides the shape, so the invoker’s packageManager pin is not used.
pnpm >= 11: writes to the unified allowBuilds map ({ pkg: true }), migrating any legacy onlyBuiltDependencies list into the map.
pnpm < 11: writes to the legacy onlyBuiltDependencies list.
Wires an add-on demo into a SvelteKit project. Every demo is listed in a floating DemoLinks post-it rendered from the root layout, whatever the template. It returns the pieces you spread into sv.file():
Upserts the add-on link into the floating DemoLinks component.
);// adds `/demo/my-addon` to `<routes>/demo/DemoLinks.svelte`svany.fileany(...democonstdemo:DemoPage.layout(property)layout:[path:`${KitRoutes}/+layout.svelte`,transform:TransformFn]
Upserts <DemoLinks /> into the root layout.
);// renders `<DemoLinks />` in `<routes>/+layout.svelte`svany.fileany(`${democonstdemo:DemoPage.addonPath(property)addonPath:`${KitRoutes}/demo/${AddonName}`
Where the add-on's own demo route belongs.
}/+page.svelte`/* your demo route */);
addonPath - <routes>/demo/<name>, where your own demo route belongs.
links - a [path, transform] pair for <routes>/demo/DemoLinks.svelte (created if missing).
layout - a [path, transform] pair for <routes>/+layout.svelte (created if missing).
Both transforms are idempotent, so re-running an add-on won’t duplicate entries. Only call them when the user asked for a demo (e.g. a demo option), so projects without demos don’t get the post-it. To opt out, delete DemoLinks.svelte and its usage in the layout.
Browser usage
The package root pulls in Node-only APIs (file system, package manager detection, shell lookups, terminal colors). For browser bundles - in-browser playgrounds, sandboxes, ... - import @sveltejs/sv-utils/browser instead, which exposes the environment-agnostic subset: parse, transforms, the language namespaces (js, svelte, css, html, json, text), Walker, dedent, the version helpers, sanitizeName, minimizeDiff, createPrinter and downloadJson.
sv-utils = what to do to content, sv = where and when to do it.
Each transform wraps: parse -> callback({ast/data, utils}) -> generateCode().
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.
Transforms are curried: call with the callback to get a (content: string) => string | false
function that plugs directly into sv.file().
import{transforms}from'@sveltejs/sv-utils';// use with sv.file() - curried form plugs in directlysv.file(files.viteConfig,transforms.script(({ast,js})=>{js.vite.addPlugin(ast,{code:'kitRoutes()'});}));// standalone usage / testingconstresult=transforms.script(({ast,js})=>{js.imports.addDefault(ast,{as:'foo',from:'foo'});})(fileContent);