Skip to content
Draft
29 changes: 21 additions & 8 deletions packages/addons/common.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { imports, exports, common } from '@sveltejs/cli-core/js';
import { toSvelteFragment, type SvelteAst } from '@sveltejs/cli-core/html';
import { parseScript, parseSvelte } from '@sveltejs/cli-core/parsers';
import process from 'node:process';

Expand Down Expand Up @@ -64,17 +65,29 @@ export function addEslintConfigPrettier(content: string): string {
}

export function addToDemoPage(content: string, path: string): string {
const { template, generateCode } = parseSvelte(content);

for (const node of template.ast.childNodes) {
if (node.type === 'tag' && node.attribs['href'] === `/demo/${path}`) {
return content;
const { ast, generateCode } = parseSvelte(content);

for (const node of ast.fragment.nodes) {
if (node.type === 'RegularElement') {
const hrefAttribute = node.attributes.find(
(x) => x.type === 'Attribute' && x.name === 'href'
) as SvelteAst.Attribute;
if (!hrefAttribute || !hrefAttribute.value) continue;

if (!Array.isArray(hrefAttribute.value)) continue;

const hasDemo = hrefAttribute.value.find(
(x) => x.type === 'Text' && x.data === `/demo/${path}`
);
if (hasDemo) {
return content;
}
}
}

const newLine = template.source ? '\n' : '';
const src = template.source + `${newLine}<a href="/demo/${path}">${path}</a>`;
return generateCode({ template: src });
ast.fragment.nodes.push(...toSvelteFragment(`<a href="/demo/${path}">${path}</a>`));

return generateCode();
}

/**
Expand Down
16 changes: 10 additions & 6 deletions packages/addons/eslint/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export default defineAddon({
});

sv.file('eslint.config.js', (content) => {
const { ast, generateCode } = parseScript(content);
const { ast, additionalComments, generateCode } = parseScript(content);

const eslintConfigs: Array<AstTypes.Expression | AstTypes.SpreadElement> = [];
imports.addDefault(ast, { from: './svelte.config.js', as: 'svelteConfig' });
Expand Down Expand Up @@ -85,18 +85,20 @@ export default defineAddon({
if (rules.properties[0].type !== 'Property') {
throw new Error('rules.properties[0].type !== "Property"');
}
rules.properties[0].key.leadingComments = [
additionalComments.set(rules.properties[0].key, [
{
type: 'Line',
value:
' typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.'
' typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.',
position: 'leading'
},
{
type: 'Line',
value:
' see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors'
' see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors',
position: 'leading'
}
];
]);

const globalsConfig = object.create({
languageOptions: {
Expand Down Expand Up @@ -153,7 +155,9 @@ export default defineAddon({

// type annotate config
if (!typescript)
common.addJsDocTypeComment(astNode, { type: "import('eslint').Linter.Config[]" });
common.addJsDocTypeComment(astNode, additionalComments, {
type: "import('eslint').Linter.Config[]"
});

if (typescript) imports.addDefault(ast, { from: 'typescript-eslint', as: 'ts' });
imports.addDefault(ast, { from: 'globals', as: 'globals' });
Expand Down
52 changes: 31 additions & 21 deletions packages/addons/paraglide/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import MagicString from 'magic-string';
import { colors, defineAddon, defineAddonOptions, log } from '@sveltejs/cli-core';
import { common, imports, variables, exports, kit as kitJs, vite } from '@sveltejs/cli-core/js';
import * as html from '@sveltejs/cli-core/html';
Expand Down Expand Up @@ -183,35 +182,46 @@ export default defineAddon({

// add usage example
sv.file(`${kit.routesDirectory}/demo/paraglide/+page.svelte`, (content) => {
const { script, template, generateCode } = parseSvelte(content, { typescript });
imports.addNamed(script.ast, { from: '$lib/paraglide/messages.js', imports: ['m'] });
imports.addNamed(script.ast, { from: '$app/navigation', imports: ['goto'] });
imports.addNamed(script.ast, { from: '$app/state', imports: ['page'] });
imports.addNamed(script.ast, { from: '$lib/paraglide/runtime', imports: ['setLocale'] });

const scriptCode = new MagicString(script.generateCode());
const { ast, generateCode } = parseSvelte(content);

let scriptAst = ast.instance?.content;
if (!scriptAst) {
scriptAst = parseScript('').ast;
ast.instance = {
type: 'Script',
start: 0,
end: 0,
context: 'default',
attributes: [],
content: scriptAst
};
}

const templateCode = new MagicString(template.source);
imports.addNamed(scriptAst, { imports: { m: 'm' }, from: '$lib/paraglide/messages.js' });
imports.addNamed(scriptAst, {
imports: {
setLocale: 'setLocale'
},
from: '$lib/paraglide/runtime'
});

// add localized message
templateCode.append("\n\n<h1>{m.hello_world({ name: 'SvelteKit User' })}</h1>\n");
let templateCode = "<h1>{m.hello_world({ name: 'SvelteKit User' })}</h1>";

// add links to other localized pages, the first one is the default
// language, thus it does not require any localized route
const { validLanguageTags } = parseLanguageTagInput(options.languageTags);
const links = validLanguageTags
.map(
(x) =>
`${templateCode.getIndentString()}<button onclick={() => setLocale('${x}')}>${x}</button>`
)
.join('\n');
templateCode.append(`<div>\n${links}\n</div>`);

templateCode.append(
'<p>\nIf you use VSCode, install the <a href="https://marketplace.visualstudio.com/items?itemName=inlang.vs-code-extension" target="_blank">Sherlock i18n extension</a> for a better i18n experience.\n</p>'
);
.map((x) => `<button onclick={() => setLocale('${x}')}>${x}</button>`)
.join('');
templateCode += `<div>${links}</div>`;

return generateCode({ script: scriptCode.toString(), template: templateCode.toString() });
templateCode +=
'<p>If you use VSCode, install the <a href="https://marketplace.visualstudio.com/items?itemName=inlang.vs-code-extension" target="_blank">Sherlock i18n extension</a> for a better i18n experience.</p>';

ast.fragment.nodes.push(...html.toSvelteFragment(templateCode));

return generateCode();
});
}

Expand Down
19 changes: 16 additions & 3 deletions packages/addons/sveltekit-adapter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export default defineAddon({
sv.devDependency(adapter.package, adapter.version);

sv.file('svelte.config.js', (content) => {
const { ast, generateCode } = parseScript(content);
const { ast, comments, generateCode } = parseScript(content);

// finds any existing adapter's import declaration
const importDecls = ast.body.filter((n) => n.type === 'ImportDeclaration');
Expand Down Expand Up @@ -86,8 +86,21 @@ export default defineAddon({
if (adapter.package !== '@sveltejs/adapter-auto') {
const fallback = object.create({});
const cfgKitValue = object.property(config, { name: 'kit', fallback });
const cfgAdapter = object.propertyNode(cfgKitValue, { name: 'adapter', fallback });
cfgAdapter.leadingComments = [];

// removes any existing adapter auto comments
const adapterAutoComments = comments.filter(
(c) =>
c.loc &&
cfgKitValue.loc &&
c.loc.start.line >= cfgKitValue.loc.start.line &&
c.loc.end.line <= cfgKitValue.loc.end.line
);
// modify the array in place
comments.splice(
0,
comments.length,
...comments.filter((c) => !adapterAutoComments.includes(c))
);
}

return generateCode();
Expand Down
3 changes: 2 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,13 @@
"dom-serializer": "^2.0.0",
"domhandler": "^5.0.3",
"domutils": "^3.2.2",
"esrap": "^1.4.9",
"esrap": "https://pkg.pr.new/sveltejs/esrap@af12b38",
"htmlparser2": "^9.1.0",
"magic-string": "^0.30.17",
"picocolors": "^1.1.1",
"postcss": "^8.5.6",
"silver-fleece": "^1.2.1",
"svelte": "https://pkg.pr.new/sveltejs/svelte@1377c40",
"yaml": "^2.8.1",
"zimmerframe": "^1.1.2"
},
Expand Down
6 changes: 3 additions & 3 deletions packages/core/tests/js/common/jsdoc-comment/run.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { common, type AstTypes } from '@sveltejs/cli-core/js';
import { common, type AdditionalCommentMap, type AstTypes } from '@sveltejs/cli-core/js';

export function run(ast: AstTypes.Program): void {
export function run(ast: AstTypes.Program, additionalComments: AdditionalCommentMap): void {
const functionDeclaration = ast.body[0] as AstTypes.FunctionDeclaration;

common.addJsDocComment(functionDeclaration, {
common.addJsDocComment(functionDeclaration, additionalComments, {
params: { 'import("$lib/paraglide/runtime").AvailableLanguageTag': 'newLanguage' }
});
}
3 changes: 1 addition & 2 deletions packages/core/tests/js/common/jsdoc-type-comment/output.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
/** @type {number} */
const foo = 42;
/** @type {number} */ const foo = 42;
6 changes: 3 additions & 3 deletions packages/core/tests/js/common/jsdoc-type-comment/run.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { common, variables, type AstTypes } from '@sveltejs/cli-core/js';
import { common, variables, type AdditionalCommentMap, type AstTypes } from '@sveltejs/cli-core/js';

export function run(ast: AstTypes.Program): void {
export function run(ast: AstTypes.Program, additionalComments: AdditionalCommentMap): void {
const declaration = variables.declaration(ast, {
kind: 'const',
name: 'foo',
value: { type: 'Literal', value: 42 }
});

common.addJsDocTypeComment(declaration, {
common.addJsDocTypeComment(declaration, additionalComments, {
type: 'number'
});

Expand Down
6 changes: 3 additions & 3 deletions packages/core/tests/js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ for (const categoryDirectory of categoryDirectories) {

const inputFilePath = join(testDirectoryPath, 'input.ts');
const input = fs.existsSync(inputFilePath) ? fs.readFileSync(inputFilePath, 'utf8') : '';
const ast = parseScript(input);
const { ast, comments, additionalComments } = parseScript(input);

// dynamic imports always need to provide the path inline for static analysis
const module = await import(`./${categoryDirectory}/${testName}/run.ts`);
module.run(ast);
module.run(ast, additionalComments);

let output = serializeScript(ast, input);
let output = serializeScript(ast, comments, input, additionalComments);
if (!output.endsWith('\n')) output += '\n';
await expect(output).toMatchFileSnapshot(`${testDirectoryPath}/output.ts`);
});
Expand Down
8 changes: 1 addition & 7 deletions packages/core/tests/js/object/create/output.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
const empty = {};
const created = { foo: 1, bar: 'string' };

// prettier-ignore
const created2 = {
foo: 1,
bar: 'string',
object: { foo: 'hello', nested: { bar: 'world' } },
array: [
123,
'hello',
{ foo: 'bar', bool: true },
[456, '789']
]
array: [123, 'hello', { foo: 'bar', bool: true }, [456, '789']]
};
1 change: 0 additions & 1 deletion packages/core/tests/js/object/create/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,5 @@ export function run(ast: AstTypes.Program): void {
name: 'created2',
value: createdObject2
});
createdVariable2.leadingComments = [{ type: 'Line', value: ' prettier-ignore' }];
ast.body.push(createdVariable2);
}
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
const test = { a: { /** a comment */ keep: 'you', b: { c: '007' } } };
const test = {
a: {
/** a comment */
keep: 'you',

b: { c: '007' }
}
};
8 changes: 7 additions & 1 deletion packages/core/tests/js/object/override-property/output.ts
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
const test = { /** a comment */ foo: 2, bar: 'string2', lorem: false };
const test = {
/** a comment */
foo: 2,

bar: 'string2',
lorem: false
};
7 changes: 6 additions & 1 deletion packages/core/tests/js/object/property-node/output.ts
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
const test = { /*a comment updated*/ foo: 1, /*aka: bond, james bond*/ james: '007' };
const test = {
/** a comment */
foo: 1,

james: '007'
};
2 changes: 2 additions & 0 deletions packages/core/tests/js/vite/add-plugin-mode/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
firstPlugin(),

// a default plugin
sveltekit(),

middlePlugin(),
lastPlugin()
]
Expand Down
11 changes: 6 additions & 5 deletions packages/core/tests/js/vite/with-satisfies/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,24 @@ const config = defineConfig({
plugins: [
// all plugins
examples,

tailwindcss(),
sveltekit(),
kitRoutes(),
myPlugin()
],

resolve: { alias: { $lib, $routes, $scripts, $actions } },
build: {
sourcemap: true,
target: 'esnext',
cssMinify: 'lightningcss'
},
build: { sourcemap: true, target: 'esnext', cssMinify: 'lightningcss' },

css: {
transformer: 'lightningcss',

lightningcss: {
targets: browserslistToTargets(browserslist('defaults, not ie 11'))
}
},

experimental: { enableNativePlugin: true }
}) satisfies UserConfig;

Expand Down
Loading
Loading