8000 utils · sveltejs/cli@3c6c63a · GitHub
[go: up one dir, main page]

Skip to content

Commit 3c6c63a

Browse files
committed
utils
1 parent 365e296 commit 3c6c63a

File tree

6 files changed

+84
-170
lines changed

6 files changed

+84
-170
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,4 @@
4646
"esbuild"
4747
]
4848
}
49-
}
49+
}

packages/core/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,9 @@
5757
"picocolors": "^1.1.1",
5858
"postcss": "^8.4.49",
5959
"silver-fleece": "^1.2.1",
60-
"zimmerframe": "^1.1.2"
60+
"zimmerframe": "^1.1.2",
61+
"svelte": "^5.30.2",
62+
"svelte-ast-print": "^1.0.1"
6163
},
6264
"keywords": [
6365
"create",

packages/core/tooling/html/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { AST as SvelteAst } from 'svelte/compiler';
12
import {
23
type AstTypes,
34
type HtmlChildNode,
@@ -7,9 +8,10 @@ import {
78
parseHtml
89
} from '../index.ts';
910
import { addFromString } from '../js/common.ts';
11+
import { parseSvelte } from '../parsers.ts';
1012

1113
export { HtmlElement, HtmlElementType };
12-
export type { HtmlDocument };
14+
export type { HtmlDocument, SvelteAst };
1315

1416
export function div(attributes: Record<string, string> = {}): HtmlElement {
1517
return element('div', attributes);
@@ -53,3 +55,9 @@ export function addSlot(
5355
addFromString(jsAst, 'let { children } = $props();');
5456
addFromRawHtml(htmlAst.childNodes, '{@render children()}');
5557
}
58+
59+
export function toSvelteFragment(content: string): SvelteAst.Fragment['nodes'] {
60+
// TODO write test
61+
const { ast } = parseSvelte(content);
62+
return ast.fragment.nodes;
63+
}

packages/core/tooling/index.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import * as fleece from 'silver-fleece';
1616
import { print as esrapPrint } from 'esrap';
1717
import * as acorn from 'acorn';
1818
import { tsPlugin } from '@sveltejs/acorn-typescript';
19+
import { parse as svelteParse, type AST as SvelteAst } from 'svelte/compiler';
20+
import { print as sveltePrint } from 'svelte-ast-print';
1921

2022
export {
2123
// html
@@ -37,11 +39,12 @@ export {
3739
export type {
3840
// html
3941
ChildNode as HtmlChildNode,
42+
SvelteAst,
4043

4144
// js
4245
TsEstree as AstTypes,
4346

44-
//css
47+
// css
4548
CssChildNode
4649
};
4750

@@ -240,3 +243,11 @@ export function guessQuoteStyle(ast: TsEstree.Node): 'single' | 'double' | undef
240243

241244
return singleCount > doubleCount ? 'single' : 'double';
242245
}
246+
247+
export function parseSvelte(content: string): SvelteAst.Root {
248+
return svelteParse(content, { modern: true });
249+
}
250+
251+
export function serializeSvelte(ast: SvelteAst.Root): string {
252+
return sveltePrint(ast).code;
253+
}

packages/core/tooling/parsers.ts

Lines changed: 5 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import * as utils from './index.ts';
2-
import MagicString from 'magic-string';
32

43
type ParseBase = {
54
source: string;
@@ -35,136 +34,13 @@ export function parseJson(source: string): { data: any } & ParseBase {
3534
return { data, source, generateCode };
3635
}
3736

38-
type SvelteGenerator = (code: {
39-
script?: string;
40-
module?: string;
41-
css?: string;
42-
template?: string;
43-
}) => string;
44-
export function parseSvelte(
45-
source: string,
46-
options?: { typescript?: boolean }
47-
): {
48-
script: ReturnType<typeof parseScript>;
49-
module: ReturnType<typeof parseScript>;
50-
css: ReturnType<typeof parseCss>;
51-
template: ReturnType<typeof parseHtml>;
52-
generateCode: SvelteGenerator;
53-
} {
54-
// `xTag` captures the whole tag block (ex: <script>...</script>)
55-
// `xSource` is the contents within the tags
56-
const scripts = extractScripts(source);
57-
// instance block
58-
const { tag: scriptTag = '', src: scriptSource = '' } =
59-
scripts.find(({ attrs }) => !attrs.includes('module')) ?? {};
60-
// module block
61-
const { tag: moduleScriptTag = '', src: moduleSource = '' } =
62-
scripts.find(({ attrs }) => attrs.includes('module')) ?? {};
63-
// style block
64-
const { styleTag, cssSource } = extractStyle(source);
65-
// rest of the template
66-
// TODO: needs more testing
67-
const templateSource = source
68-
.replace(moduleScriptTag, '')
69-
.replace(scriptTag, '')
70-
.replace(styleTag, '')
71-
.trim();
72-
73-
const script = parseScript(scriptSource);
74-
const module = parseScript(moduleSource);
75-
const css = parseCss(cssSource);
76-
const template = parseHtml(templateSource);
77-
78-
const generateCode: SvelteGenerator = (code) => {
79-
const ms = new MagicString(source);
80-
// TODO: this is imperfect and needs adjustments
81-
if (code.script !== undefined) {
82-
if (scriptSource.length === 0) {
83-
const ts = options?.typescript ? ' lang="ts"' : '';
84-
const indented = code.script.split('\n').join('\n\t');
85-
const script = `<script${ts}>\n\t${indented}\n</script>\n\n`;
86-
ms.prepend(script);
87-
} else {
88-
const { start, end } = locations(source, scriptSource);
89-
const formatted = indent(code.script, ms.getIndentString());
90-
ms.update(start, end, formatted);
91-
}
92-
}
93-
if (code.module !== undefined) {
94-
if (moduleSource.length === 0) {
95-
const ts = options?.typescript ? ' lang="ts"' : '';
96-
const indented = code.module.split('\n').join('\n\t');
97-
// TODO: make a svelte 5 variant
98-
const module = `<script${ts} context="module">\n\t${indented}\n</script>\n\n`;
99-
ms.prepend(module);
100-
} else {
101-
const { start, end } = locations(source, moduleSource);
102-
const formatted = indent(code.module, ms.getIndentString());
103-
ms.update(start, end, formatted);
104-
}
105-
}
106-
if (code.css !== undefined) {
107-
if (cssSource.length === 0) {
108-
const indented = code.css.split('\n').join('\n\t');
109-
const style = `\n<style>\n\t${indented}\n</style>\n`;
110-
ms.append(style);
111-
} else {
112-
const { start, end } = locations(source, cssSource);
113-
const formatted = indent(code.css, ms.getIndentString());
114-
ms.update(start, end, formatted);
115-
}
116-
}
117-
if (code.template !== undefined) {
118-
if (templateSource.length === 0) {
119-
ms.appendLeft(0, code.template);
120-
} else {
121-
const { start, end } = locations(source, templateSource);
122-
ms.update(start, end, code.template);
123-
}
124-
}
125-
return ms.toString();
126-
};
37+
export function parseSvelte(source: string): { ast: utils.SvelteAst.Root } & ParseBase {
38+
const ast = utils.parseSvelte(source);
39+
const generateCode = () => utils.serializeSvelte(ast);
12740

12841
return {
129-
script: { ...script, source: scriptSource },
130-
module: { ...module, source: moduleSource },
131-
css: { ...css, source: cssSource },
132-
template: { ...template, source: templateSource },
42+
ast,
43+
source,
13344
generateCode
13445
};
13546
}
136-
137-
function locations(source: string, search: string): { start: number; end: number } {
138-
const start = source.indexOf(search);
139-
const end = start + search.length;
140-
return { start, end };
141-
}
142-
143-
function indent(content: string, indent: string): string {
144-
const indented = indent + content.split('\n').join(`\n${indent}`);
145-
return `\n${indented}\n`;
146-
}
147-
1 1241 48-
// sourced from Svelte: https://github.com/sveltejs/svelte/blob/0d3d5a2a85c0f9eccb2c8dbbecc0532ec918b157/packages/svelte/src/compiler/preprocess/index.js#L253-L256
149-
const regexScriptTags =
150-
/<!--[^]*?-->|<script((?:\s+[^=>'"/\s]+=(?:"[^"]*"|'[^']*'|[^>\s]+)|\s+[^=>'"/\s]+)*\s*)(?:\/>|>([\S\s]*?)<\/script>)/;
151-
const regexStyleTags =
152-
/<!--[^]*?-->|<style((?:\s+[^=>'"/\s]+=(?:"[^"]*"|'[^']*'|[^>\s]+)|\s+[^=>'"/\s]+)*\s*)(?:\/>|>([\S\s]*?)<\/style>)/;
153-
154-
type Script = { tag: string; attrs: string; src: string };
155-
function extractScripts(source: string): Script[] {
156-
const scripts = [];
157-
const [tag = '', attrs = '', src = ''] = regexScriptTags.exec(source) ?? [];
158-
if (tag) {
159-
const stripped = source.replace(tag, '');
160-
scripts.push({ tag, attrs, src }, ...extractScripts(stripped));
161-
return scripts;
162-
}
163-
164-
return [];
165-
}
166-
167-
function extractStyle(source: string) {
168-
const [styleTag = '', attributes = '', cssSource = ''] = regexStyleTags.exec(source) ?? [];
169-
return { styleTag, attributes, cssSource };
170-
}

0 commit comments

Comments
 (0)
0