sveast 0.0.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Eric Liu
3
+ Copyright (c) 2026-present Eric Liu
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -19,3 +19,49 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
22
+
23
+ ---
24
+
25
+ The parser is adapted from Svelte's (https://github.com/sveltejs/svelte),
26
+ and its type declarations include Svelte's AST types. The published bundle
27
+ includes acorn (https://github.com/acornjs/acorn), and the type
28
+ declarations include @types/estree
29
+ (https://github.com/DefinitelyTyped/DefinitelyTyped). All are MIT licensed:
30
+
31
+ Svelte
32
+
33
+ Copyright (c) 2016-2025 Svelte Contributors
34
+ (https://github.com/sveltejs/svelte/graphs/contributors)
35
+
36
+ acorn
37
+
38
+ Copyright (C) 2012-2022 by various contributors (see AUTHORS)
39
+
40
+ @types/estree
41
+
42
+ Copyright (c) Microsoft Corporation.
43
+
44
+ Permission is hereby granted, free of charge, to any person obtaining a copy
45
+ of this software and associated documentation files (the "Software"), to deal
46
+ in the Software without restriction, including without limitation the rights
47
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
48
+ copies of the Software, and to permit persons to whom the Software is
49
+ furnished to do so, subject to the following conditions:
50
+
51
+ The above copyright notice and this permission notice shall be included in all
52
+ copies or substantial portions of the Software.
53
+
54
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
55
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
56
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
57
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
58
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
59
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
60
+ SOFTWARE.
61
+
62
+ ---
63
+
64
+ The named character reference table in the bundle is generated from the
65
+ HTML Living Standard's entities.json (https://html.spec.whatwg.org/),
66
+ Copyright WHATWG (Apple, Google, Mozilla, Microsoft), licensed under
67
+ CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/).
package/README.md CHANGED
@@ -1,5 +1,160 @@
1
1
  # sveast
2
2
 
3
- Placeholder package reserving the "sveast" name.
3
+ > Zero-dependency Svelte 5 parser. svelte/compiler's exact AST, 2× faster.
4
4
 
5
- This package name is currently reserved and not yet implemented.
5
+ Parse `.svelte` components, including `<script lang="ts">`, into the same AST, and the same errors, as `svelte/compiler`'s `parse(source, { modern: true })`. sveast bundles acorn and its own TypeScript plugin, so it has no dependencies, and it runs anywhere: no Node APIs.
6
+
7
+ ```sh
8
+ bun i sveast
9
+ ```
10
+
11
+ ```ts
12
+ import { parse } from "sveast";
13
+
14
+ const ast = parse(`<script>let name = "world";</script>\n<h1>Hello {name}!</h1>`);
15
+
16
+ ast.instance?.content.body; // [VariableDeclaration]
17
+ ast.fragment.nodes; // [Text, RegularElement]
18
+ ```
19
+
20
+ ## Migrating from svelte/compiler
21
+
22
+ sveast is a drop-in for `parse` in most tools: change the import, and pass `loc: true` if you read line and column numbers.
23
+
24
+ ```diff
25
+ - import { type AST, parse } from "svelte/compiler";
26
+ + import { type AST, parse } from "sveast";
27
+
28
+ - const ast = parse(source, { modern: true });
29
+ + const ast = parse(source, { loc: true });
30
+ ```
31
+
32
+ | | svelte/compiler | sveast |
33
+ |:---|:---|:---|
34
+ | `loc`, `name_loc` | Always | With `loc: true`; otherwise only `start`/`end` offsets, for a faster parse and a smaller AST |
35
+ | Errors | `CompileError` | `ParseError`: same `code`, `message`, `position`, `start`, `end` and `frame`; no `filename` |
36
+ | AST formats | Modern, legacy (`modern: false`), error-tolerant (`loose`) | Modern |
37
+ | Scope | Parsing, `parseCss`, analysis, compilation | Parsing (`parse`, `parseModule`) |
38
+ | TypeScript-only errors | Reported, e.g. modifier order or initializers in ambient contexts | Not reported: 108 of the 2,449 TypeScript conformance tests acorn-typescript rejects still parse |
39
+
40
+ ## API
41
+
42
+ ### `parse(source, options?) => AST.Root`
43
+
44
+ | Option | Description |
45
+ |:---|:---|
46
+ | `loc` | Add `loc` (line and column) to script and expression nodes, and `name_loc` to elements, attributes and directives, as svelte does. Default `false`: they cost time and memory, and most tools only need `start`/`end`. |
47
+ | `css` | Parse `<style>` into rules and selectors. With `false`, `css` keeps its `start`, `end` and `content`, but `children` and `comments` are empty and CSS syntax errors aren't reported. Default `true`. |
48
+
49
+ With `loc: true`, the result equals svelte's. With the defaults, it's svelte's without `loc` and `name_loc`.
50
+
51
+ A syntax error throws a `ParseError` with svelte's `code` (e.g. `"block_unclosed"`), `message`, `position` (`[start, end]` offsets), `start`/`end` (`{ line, column, character }`) and `frame` (the source around the error).
52
+
53
+ ### `parseModule(source, options?) => Program`
54
+
55
+ Parses a JavaScript or TypeScript module, such as a `.ts` file a component imports, the way a component's `<script>` is parsed: estree plus TypeScript nodes, with comments attached as `leadingComments`/`trailingComments`. Options: `typescript` and `loc`, both default `false`.
56
+
57
+ ### Types
58
+
59
+ `AST` is svelte's `AST` namespace (`AST.Root`, `AST.RegularElement`, `AST.CSS.Rule`, ...), corrected to match what the parser returns: `name_loc` and a comment's `loc` are optional, `Root.instance`/`module` are absent rather than `null` when there's no such `<script>`, `Root.js` is declared, and every directive has `modifiers`. `ParseOptions` is exported too.
60
+
61
+ The estree node types are exported as well (`Program`, `Node`, `Statement`, `Expression`, `Identifier`, ...), so you don't need `@types/estree`. They're estree's, plus what the parser adds: `start`/`end` on every node, and the TypeScript plugin's nodes (`TSInterfaceDeclaration`, `TSTypeAnnotation`, `TSTypeReference`, ...; `TSNode` is their union) and fields (`typeAnnotation`, `typeParameters`, `typeArguments`, `returnType`, `importKind`/`exportKind`, ...). The TypeScript nodes are in the `Statement`, `Declaration` and `Expression` unions, so checking `node.type` narrows to them.
62
+
63
+ ```ts
64
+ import { parseModule, type TSInterfaceDeclaration } from "sveast";
65
+
66
+ const interfaces: TSInterfaceDeclaration[] = [];
67
+ for (const node of parseModule(source, { typescript: true }).body) {
68
+ if (node.type === "TSInterfaceDeclaration") interfaces.push(node);
69
+ }
70
+ ```
71
+
72
+ ## Recipes
73
+
74
+ **List the components a file renders**, e.g. to build a dependency graph:
75
+
76
+ ```ts
77
+ import { parse } from "sveast";
78
+
79
+ function componentsUsed(source: string): string[] {
80
+ const names = new Set<string>();
81
+ const visit = (node: unknown): void => {
82
+ if (!node || typeof node !== "object") return;
83
+ const { type, name } = node as { type?: string; name?: string };
84
+ if (type === "Component" && name) names.add(name);
85
+ for (const child of Object.values(node)) visit(child);
86
+ };
87
+ visit(parse(source, { css: false }).fragment);
88
+ return [...names]; // ["Button", "Modal.Root"]
89
+ }
90
+ ```
91
+
92
+ **Read the props a runes component declares:**
93
+
94
+ ```ts
95
+ import { parse } from "sveast";
96
+
97
+ function propNames(source: string): string[] {
98
+ for (const statement of parse(source).instance?.content.body ?? []) {
99
+ if (statement.type !== "VariableDeclaration") continue;
100
+ for (const { id, init } of statement.declarations) {
101
+ const isProps =
102
+ init?.type === "CallExpression" &&
103
+ init.callee.type === "Identifier" &&
104
+ init.callee.name === "$props";
105
+ if (isProps && id.type === "ObjectPattern") {
106
+ return id.properties.flatMap((p) =>
107
+ p.type === "Property" && p.key.type === "Identifier" ? [p.key.name] : [],
108
+ );
109
+ }
110
+ }
111
+ }
112
+ return [];
113
+ }
114
+ ```
115
+
116
+ **Report syntax errors**, e.g. in a pre-commit check, with svelte's own messages:
117
+
118
+ ```ts
119
+ import { ParseError, parse } from "sveast";
120
+
121
+ try {
122
+ parse(source, { css: false });
123
+ } catch (error) {
124
+ if (!(error instanceof ParseError)) throw error;
125
+ console.error(`${file}:${error.start?.line}:${error.start?.column} ${error.code}\n${error.frame}`);
126
+ }
127
+ ```
128
+
129
+ **Get a component's styles without parsing them:**
130
+
131
+ ```ts
132
+ import { parse } from "sveast";
133
+
134
+ const css = parse(source, { css: false }).css?.content.styles ?? "";
135
+ ```
136
+
137
+ ## Features
138
+
139
+ - **svelte/compiler parity.** Tested on over 12,000 components: carbon-components-svelte, svelte's own test suite, bits-ui, shadcn-svelte, skeleton, flowbite-svelte, svelte.dev, immich, SvelteKit, melt-ui, layerchart and paneforge. Every one either parses to svelte's AST, `loc` included, or throws svelte's error with the same code, message, position and frame. CI runs 442 of them, the smallest set that covers every parser line, AST shape and error code the full set does, and a differential fuzzer compares the two parsers on mutated components.
140
+ - **TypeScript without acorn-typescript.** The built-in plugin produces the same AST as `@sveltejs/acorn-typescript`, key order and `loc` included, on 6,604 real-world modules and on all but 2 of the 9,586 TypeScript conformance tests that acorn-typescript parses. It rejects the same redeclarations, and supports decorators.
141
+ - **About 2.4× faster than svelte/compiler** (2.2–3.0× depending on the input), and its ASTs retain 47% less memory without `loc`.
142
+ - **Fast to load.** A fresh process imports sveast in about 5 ms, against about 45 ms for `svelte/compiler`.
143
+ - **Zero dependencies,** types included. 64 kB gzipped, acorn included.
144
+
145
+ ## Benchmarks
146
+
147
+ Apple M2, medians of warm calls. Each task parses every file in the set once. The corpus is `tests/corpus`: all of carbon-components-svelte plus svelte's and sveld's test inputs.
148
+
149
+ | Input | sveast | sveast, `loc: true` | svelte/compiler |
150
+ |:---|:---|:---|:---|
151
+ | Corpus, 400 components, 1.5 MB | **64.7 ms** | 84.0 ms | 155 ms (2.4×) |
152
+ | Carbon's 5 largest components, 318 kB | **13.8 ms** | | 31.3 ms (2.3×) |
153
+ | `lang="ts"` components, 22 files | **0.79 ms** | | 2.40 ms (3.0×) |
154
+
155
+ | Input | sveast's TypeScript plugin | acorn-typescript |
156
+ |:---|:---|:---|
157
+ | Carbon's 98 `.d.ts` modules | **3.19 ms** | 8.07 ms (2.5×) |
158
+ | The corpus's `lang="ts"` scripts on their own | **0.58 ms** | 1.29 ms (2.2×) |
159
+
160
+ In a fresh process, importing the parser takes 5.3 ms with sveast and 44.7 ms with `svelte/compiler`, and a first parse of the whole corpus takes 65 ms against 161 ms. Keeping ten parses of the corpus alive retains 154 MB with sveast, 268 MB with `loc: true`, and 293 MB with `svelte/compiler`.