sveast 0.6.0 → 0.8.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/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # sveast
2
2
 
3
- > Zero-dependency Svelte 5 parser. svelte/compiler's exact AST, 2× faster.
3
+ > Zero-dependency Svelte 5 parser: svelte/compiler's AST, 2.4× faster.
4
4
 
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.
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 has no dependencies and uses no Node APIs, so it runs in the browser as well as in Node, Bun and Deno.
6
+
7
+ It's for tools that analyze components statically, such as documentation and type generators that read a component's props, events and slots. They need only the AST, but parse every component in a library, often on every change. [sveld](https://github.com/carbon-design-system/sveld), which generates TypeScript definitions and documentation for Svelte components, uses it.
6
8
 
7
9
  ```sh
8
10
  bun i sveast
@@ -17,6 +19,21 @@ ast.instance?.content.body; // [VariableDeclaration]
17
19
  ast.fragment.nodes; // [Text, RegularElement]
18
20
  ```
19
21
 
22
+ | Function | For | Import from |
23
+ |:---|:---|:---|
24
+ | [`parse`](#parsesource-options--astroot) | A component's AST, the same as svelte's | `sveast` |
25
+ | [`parseSections`](#parsesectionssource-options--astroot) | A component's AST without the markup: its `<script>`s, `<style>` and `<svelte:options>` | `sveast` |
26
+ | [`parseModule`](#parsemodulesource-options--program) | A `.js` or `.ts` module's AST, the same as a component's `<script>` | `sveast` |
27
+ | [`parseImportsExports`](#parseimportsexportssource-options--moduledeclaration) | A module's `import` and `export` statements, without parsing the rest | `sveast` |
28
+ | [`lexImportsExports`](#leximportsexportssource--lexedstatement) | Their offsets, sources and names, without loading a parser | `sveast/lexer` |
29
+ | [`lexComponent`](#lexcomponentsource--lexedcomponent) | A component's `<script>`s, `<style>` and `<svelte:options>`, with their attributes and offsets, without loading a parser | `sveast/lexer` |
30
+ | [`createParser`, `createModuleParser`](#smaller-bundles-sveastcore-and-sveastmodule) | The parsers without TypeScript, named HTML entities or the template parser, for a smaller bundle | `sveast/core`, `sveast/module` |
31
+ | [`isValidType`](#isvalidtypetext-options--boolean) | Whether a JSDoc type is safe to copy into a `.d.ts` | `sveast` |
32
+ | [`isRunesMode`](#isrunesmodesource--boolean) | Whether svelte compiles a component in runes mode, mostly without parsing it | `sveast` |
33
+ | [`walk`, `visitorKeys`, `markupVisitorKeys`](#walknode-visitor-keys--void) | Visit, skip, stop at or replace nodes, in the whole AST or only the markup | `sveast`, `sveast/walk` |
34
+ | [`isReference`](#isreferencenode-parent--boolean), [`extractIdentifiers`](#extractidentifierspattern--identifier) | Which identifiers are references, and which ones a pattern declares | `sveast`, `sveast/walk` |
35
+ | [`createLocator`](#createlocatorsource--offset--position) | Lines and columns for the nodes you report, without parsing with `loc` | `sveast`, `sveast/walk` |
36
+
20
37
  ## Migrating from svelte/compiler
21
38
 
22
39
  sveast is a drop-in for `parse` in most tools: change the import, and pass `loc: true` if you read line and column numbers.
@@ -34,41 +51,105 @@ sveast is a drop-in for `parse` in most tools: change the import, and pass `loc:
34
51
  | `loc`, `name_loc` | Always | With `loc: true`; otherwise only `start`/`end` offsets, for a faster parse and a smaller AST |
35
52
  | Errors | `CompileError` | `ParseError`: same `code`, `message`, `position`, `start`, `end` and `frame`; no `filename`; `reason`, the message without its link |
36
53
  | AST formats | Modern, legacy (`modern: false`), error-tolerant (`loose`) | Modern |
37
- | Scope | Parsing, `parseCss`, analysis, compilation | Parsing (`parse`, `parseModule`, `parseImportsExports`, `isValidType`), walking the AST (`walk`, `visitorKeys`), and finding references and bindings (`isReference`, `extractIdentifiers`) |
38
- | Non-ASCII identifiers | acorn's tables, Unicode 17 | The engine's own Unicode data, which is smaller to ship: the same as acorn's in Node 24 and Bun; an engine on another Unicode version differs on the letters added in between |
39
- | 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 |
54
+ | Non-ASCII identifiers | Its own Unicode 17 tables | The JavaScript engine's Unicode data, which is smaller to ship: the same in Node 24 and Bun; an engine on another Unicode version differs on the letters added in between |
55
+ | TypeScript-only errors | Reported, e.g. modifier order or initializers in ambient contexts | Not reported: code that only TypeScript's own checks reject still parses |
56
+
57
+ ## Examples
58
+
59
+ **List the components a file renders**, e.g. to build a dependency graph:
60
+
61
+ ```ts
62
+ import { parse, walk } from "sveast";
63
+
64
+ function componentsUsed(source: string): string[] {
65
+ const names = new Set<string>();
66
+ walk(parse(source, { css: false }).fragment, {
67
+ enter(node) {
68
+ if (node.type === "Component") names.add(node.name);
69
+ },
70
+ });
71
+ return [...names]; // ["Button", "Modal.Root"]
72
+ }
73
+ ```
74
+
75
+ **Report syntax errors**, e.g. in a pre-commit check, with svelte's own messages:
76
+
77
+ ```ts
78
+ import { ParseError, parse } from "sveast";
79
+
80
+ try {
81
+ parse(source, { css: false });
82
+ } catch (error) {
83
+ if (!(error instanceof ParseError)) throw error;
84
+ console.error(`${file}:${error.start?.line}:${error.start?.column} ${error.code}\n${error.frame}`);
85
+ }
86
+ ```
87
+
88
+ [`examples/`](examples) has runnable versions of these and more, each a CLI you can point at your own components: a module graph, unused imports and variables, unused CSS classes, runes-or-legacy detection, `$props()` and their defaults, `.d.ts` props from JSDoc, and styles without parsing them.
89
+
90
+ ## Parity and performance
91
+
92
+ - **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. A differential fuzzer compares the two parsers on mutated components.
93
+ - **TypeScript.** `<script lang="ts">` and `.ts` modules parse to svelte's AST, key order and `loc` included, on 6,604 real-world modules and on all but 2 of the 9,586 TypeScript conformance tests svelte parses.
94
+ - **About 2.4× faster** than svelte/compiler, and its ASTs retain about half the memory.
95
+ - **A smaller bundle and a faster load:** `parse` is less than half of svelte/compiler's size, and a fresh process imports it in about 5 ms against 45 ms.
96
+
97
+ Apple M2, medians of warm calls. The corpus is `tests/corpus`: all of carbon-components-svelte plus svelte's and sveld's test inputs.
98
+
99
+ | Input | sveast | sveast, `loc: true` | svelte/compiler |
100
+ |:---|:---|:---|:---|
101
+ | Corpus, 400 components, 1.5 MB | **64.7 ms** | 84.0 ms | 155 ms (2.4×) |
102
+ | Carbon's 5 largest components, 318 kB | **13.8 ms** | | 31.3 ms (2.3×) |
103
+ | `lang="ts"` components, 22 files | **0.79 ms** | | 2.40 ms (3.0×) |
104
+
105
+ | `parse` alone, minified for the browser | Gzipped |
106
+ |:---|:---|
107
+ | svelte/compiler | 126.7 kB |
108
+ | sveast | **58.5 kB** |
109
+ | `sveast/core`'s `createParser()`, without TypeScript and named HTML entities | **41.0 kB** |
110
+
111
+ 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`.
40
112
 
41
113
  ## API
42
114
 
43
115
  ### `parse(source, options?) => AST.Root`
44
116
 
45
- | Option | Description |
46
- |:---|:---|
47
- | `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`. |
48
- | `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`. |
49
- | `script` | Parse each `<script>`'s JavaScript or TypeScript. With `false`, `instance` and `module` keep their attributes, `start` and `end`, and `content` is a `Program` with its `start` and `end` but an empty `body`; the scripts' comments aren't in `comments`, and their syntax errors aren't reported. Expressions in the markup are still parsed. For a pass that only reads the markup, such as collecting which components a file renders and with what props; on the benchmark corpus, it parses 3.4× faster. Default `true`. |
50
- | `comments` | Collect JavaScript comments, in scripts, expressions and tags. With `false`, `comments` is empty and no node has `leadingComments` or `trailingComments`, including the HTML comment before a `<script>` that svelte copies into its `content.leadingComments`. HTML comments in the markup and CSS comments are kept. For tools that never read comments; on Carbon's components, it parses 3 to 6% faster and the ASTs take 8% less memory. Default `true`. |
117
+ | Option | Default | Description |
118
+ |:---|:---|:---|
119
+ | `loc` | `false` | Add `loc` (line and column) to script and expression nodes, and `name_loc` to elements, attributes and directives, as svelte does. They cost time and memory, and most tools only need `start`/`end`. |
120
+ | `css` | `true` | 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. It also keeps `<style lang="scss">` from failing to parse as CSS. |
121
+ | `script` | `true` | Parse each `<script>`. With `false`, `instance` and `module` keep their attributes, `start` and `end`, and `content` is a `Program` with an empty `body`; their comments aren't collected and their syntax errors aren't reported. Expressions in the markup are still parsed. For a pass that only reads the markup; 3.4× faster on the benchmark corpus. |
122
+ | `comments` | `true` | Collect JavaScript comments, in scripts, expressions and tags. With `false`, `comments` is empty and no node has `leadingComments` or `trailingComments`, including the HTML comment before a `<script>` that svelte copies into its `content.leadingComments`. HTML and CSS comments are kept. 3 to 6% faster, and 8% less memory. |
51
123
 
52
124
  With `loc: true`, the result equals svelte's. With the defaults, it's svelte's without `loc` and `name_loc`.
53
125
 
54
- 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). `message` is always the reason, a line break, then the link to the error's docs, `https://svelte.dev/e/${code}`. `reason` has the reason on its own, e.g. `"Unexpected token"`.
126
+ 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). `message` is always the reason, a line break, then the link to the error's docs, `https://svelte.dev/e/${code}`; `reason` is the reason on its own, e.g. `"Unexpected token"`.
127
+
128
+ ### `parseSections(source, options?) => AST.Root`
129
+
130
+ `parse` for a pass that only reads a component's scripts, such as its props or its module script's exports: the top-level `<script>`s, `<style>` and `<svelte:options>` are parsed as `parse` parses them, and the markup between them is skipped as [`lexComponent`](#lexcomponentsource--lexedcomponent) skips it. `fragment.nodes` is empty, `comments` has only the scripts' comments, and the markup's syntax errors aren't reported; the rest is `parse`'s, offsets and the HTML comment before a `<script>` included. It takes `parse`'s options, and on the benchmark corpus it's 1.5× faster, where most of what's left is the scripts. It's a function of its own so that a bundle with only `parse` doesn't carry the lexer; `createParser` doesn't return it.
131
+
132
+ ```ts
133
+ import { parseSections } from "sveast";
134
+
135
+ const { instance, module } = parseSections(source, { css: false });
136
+ module?.content.body; // the module script's statements, with parse's offsets
137
+ ```
55
138
 
56
139
  ### `parseModule(source, options?) => Program`
57
140
 
58
- 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`, and `comments`, default `true`; with `false`, no node has `leadingComments` or `trailingComments`.
141
+ Parses a JavaScript or TypeScript module, such as a `.ts` file a component imports, into the same AST a component's `<script>` gets, with comments attached as `leadingComments`/`trailingComments`. Options: `typescript` and `loc`, default `false`, and `comments`, default `true`.
59
142
 
60
143
  ### `parseImportsExports(source, options?) => ModuleDeclaration[]`
61
144
 
62
- A module's top-level `import` and `export` statements, without parsing the rest, e.g. to rewrite a component's imports or follow a module graph. Each node is the one `parseModule(source, { comments: false })` has for that statement, so `start`, `end`, `specifiers` (with `imported`, `local` and `importKind`) and `source` are all there: `ImportDeclaration`, `ExportNamedDeclaration`, `ExportDefaultDeclaration` and `ExportAllDeclaration`, and, in TypeScript, `TSImportEqualsDeclaration`, `TSExportAssignment` and `TSNamespaceExportDeclaration`. `import(...)` and `import.meta` are expressions, so they aren't returned. Unlike a regex, it never matches an `import` in a comment, a string, a template or a nested block such as `declare module "a" { ... }`.
63
-
64
- The rest is skipped by a tokenizer that only tracks strings, comments, templates, regular expressions and brackets, and only the statements it finds are parsed. A syntax error in one of them throws `parseModule`'s `ParseError`, including a name exported twice; errors elsewhere aren't reported.
145
+ A module's top-level `import` and `export` statements, without parsing the rest, e.g. to rewrite a component's imports or follow a module graph. Each node is the one `parseModule(source, { comments: false })` has for that statement: `ImportDeclaration`, `ExportNamedDeclaration`, `ExportDefaultDeclaration` and `ExportAllDeclaration`, and, in TypeScript, `TSImportEqualsDeclaration`, `TSExportAssignment` and `TSNamespaceExportDeclaration`. `import(...)` and `import.meta` are expressions, so they aren't returned. Unlike a regex, it never matches an `import` in a comment, a string, a template or a nested block such as `declare module "a" { ... }`.
65
146
 
66
- | Option | Description |
67
- |:---|:---|
68
- | `typescript` | Parse TypeScript: `import type`, `export type`, `import a = require("a")`. Default `false`. |
69
- | `localExports` | Return the exports of the module's own bindings, such as `export let a`, `export function b() {}`, `export { c }` and `export default d`, which are parsed in full. With `false`, only imports and the exports that have a `from` (`export * from "e"`, `export { f } from "f"`, `export import`) are returned, and the rest is skipped. Default `true`. |
147
+ It stops after the last `import` or `export` that could start a statement, so the code after a component's imports is never read: on Carbon's component scripts, it takes a third of `parseModule`'s time, and about a seventh with `localExports: false`. A syntax error in a statement it returns throws a `ParseError`, including a name exported twice; errors elsewhere aren't reported.
70
148
 
71
- On Carbon's 327 component scripts, mostly JSDoc-documented `export let` props, it takes 10.7 ms against `parseModule`'s 33.8 ms, and 4.5 ms with `localExports: false`; the largest script, 63 kB, takes 0.37 ms against 2.7 ms. It checks the TypeScript conformance tests, kit, immich and bits-ui against `parseModule`, with an `import` inserted after every top-level statement: the same nodes on all 24,354 modules.
149
+ | Option | Default | Description |
150
+ |:---|:---|:---|
151
+ | `typescript` | `false` | Parse TypeScript: `import type`, `export type`, `import a = require("a")`. |
152
+ | `localExports` | `true` | Return the exports of the module's own bindings, such as `export let a`, `export function b() {}`, `export { c }` and `export default d`, parsed in full. With `false`, only imports and the exports that have a `from` (`export * from "e"`, `export { f } from "f"`, `export import`) are returned. |
72
153
 
73
154
  ```ts
74
155
  import { parseImportsExports } from "sveast";
@@ -80,209 +161,183 @@ function dependencies(source: string, typescript = false): string[] {
80
161
  }
81
162
  ```
82
163
 
83
- ### `createParser(support?) => { parse, parseModule, parseImportsExports }` from `sveast/core`
164
+ ### `lexImportsExports(source) => LexedStatement[]`
84
165
 
85
- `parse`, `parseModule` and `parseImportsExports` without the two parts most tools can do without: the TypeScript plugin and the table of HTML's named character references. Together they're about a third of a bundle: `parse` and `parseModule` from `sveast` minify to 57.3 kB gzipped, and from `createParser()` to 40.3 kB. Pass back the parts you need; with both, the parsers are the same as `sveast`'s.
166
+ From `sveast/lexer`, and also `sveast`. A module's top-level `import` statements and `export … from` re-exports, read by hand instead of parsed, for a tool on a hot or eagerly loaded path, such as a preprocessor that rewrites a component's imports, that needs each statement's offsets, source and names, but not the AST. `sveast/lexer` loads no parser, so a fresh process imports it in 1.4 ms, and on Carbon's component scripts it takes 40% of the time `parseImportsExports` takes with `localExports: false`.
86
167
 
87
- ```ts
88
- import { createParser } from "sveast/core";
89
- import { typescript } from "sveast/typescript";
168
+ It finds the statements `parseImportsExports` returns with `localExports: false`, with the same offsets, sources and names. It doesn't check syntax and never throws: a statement it can't read, such as `import { a from "a"` or `import a = require("a")`, has `source: null` and no specifiers, and ends where it stopped reading. `import type` and `type` before a name are always read.
90
169
 
91
- const { parse, parseModule, parseImportsExports } = createParser({ typescript });
170
+ ```ts
171
+ import { lexImportsExports } from "sveast/lexer";
172
+
173
+ for (const statement of lexImportsExports(source)) {
174
+ statement.kind; // "import" | "export"
175
+ statement.start; // offset of `import` or `export`
176
+ statement.end; // after the `;`, or the last token without one
177
+ statement.source; // { value: "carbon-components-svelte", start, end } (quotes included), or null
178
+ statement.typeOnly; // `import type …` or `export type …`
179
+ statement.specifiers;
180
+ // import: { kind: "default" | "namespace" | "named", imported, local, typeOnly, start, end }
181
+ // export: { kind: "all" | "namespace" | "named", local, exported, typeOnly, start, end }
182
+ }
92
183
  ```
93
184
 
94
- | Support | Without it |
95
- |:---|:---|
96
- | `typescript` from `sveast/typescript` (+8.8 kB gzipped) | A component with `<script lang="ts">`, or `parseModule` or `parseImportsExports` with `typescript: true`, throws an `Error`, not a `ParseError`: a missing import, not a syntax error, and never a JavaScript AST of TypeScript |
97
- | `entities` from `sveast/entities` (+10.0 kB gzipped) | Text and attribute values decode numeric references, such as `&#169;`, and `&amp;`, `&apos;`, `&gt;`, `&lt;` and `&quot;`, but leave other names, such as `&copy;`, as written, so `Text.data` differs from svelte's where the source uses them |
185
+ | Specifier | `import` | `export … from` |
186
+ |:---|:---|:---|
187
+ | `a` in `import a` | `default`, `imported: "default"`, `local: "a"` | |
188
+ | `* as a` | `namespace`, `imported: "*"`, `local: "a"` | `namespace`, `local: "*"`, `exported: "a"` |
189
+ | `*` | | `all`, `local: "*"`, `exported: null` |
190
+ | `b as c`, `"b-c" as d` | `named`, `imported: "b"`, `local: "c"` | `named`, `local: "b"`, `exported: "c"` |
98
191
 
99
- `sveast/core` also exports `ParseError`, the same class as `sveast`'s, and the types.
192
+ Names are as the module sees them: escapes decoded, and a string name without its quotes. A specifier's `typeOnly` is also `true` in an `import type` or `export type`. Its `start` is that of `type` before the name, if any, and `end` is after the last name; for `export *`, after the `*`.
100
193
 
101
- ### `isValidType(text, options?) => boolean`
194
+ ### `lexComponent(source) => LexedComponent`
102
195
 
103
- Whether `text` is exactly one TypeScript type, such as a JSDoc `{"sm" | "lg"}` a tool is about to copy into a `.d.ts`. The text is parsed as a type on its own, not wrapped in a statement, so a `;`, a line break or a `}` in it can't end the type and smuggle in a statement: `string; let x = 1` and `{ a: string } }` are `false`. Whitespace and comments around the type are allowed. Results aren't cached; memoize if you check the same text often.
196
+ From `sveast/lexer`, and also `sveast`. A component's top-level `<script>`, `<script module>`, `<style>` and `<svelte:options>`, with their attributes and the offsets of their tags and content, found without parsing: e.g. to read a component's scripts and their imports, its language or its options, or to rewrite a script, on a path that can't afford a parser. On the benchmark corpus it's about 50× faster than `parse`.
104
197
 
105
- A `//` comment runs to the end of the line, so `string // the size` is a valid type but breaks `CustomEvent<${text}>`. Pass `inline: true` when the text goes before more code on the same line: it's then also `false` unless every `//` comment in the text ends with a line break. Block comments and `//` inside strings, as in `"http://a" | "https://b"`, are fine either way.
198
+ It skips the markup by tracking only tags, attribute values, blocks, and the brackets, strings, comments and templates of each `{…}` expression, so a `<script>` in `<svelte:head>`, a block, an attribute, an expression or an HTML comment isn't one, and it stops after the last `<script`, `<style` or `<svelte:options`. Where `parse` accepts the component, the sections are its `instance`, `module`, `css` and `options`, with the same offsets, into the source without a leading byte order mark. It doesn't check syntax and never throws.
106
199
 
107
- ### `walk(node, visitor) => void`
200
+ ```ts
201
+ import { lexComponent } from "sveast/lexer";
202
+
203
+ const { typescript, instance, module, css, options } = lexComponent(source);
204
+ typescript; // whether `parse` reads it as TypeScript, decided by the first <script> with a lang
205
+ instance?.content; // { start, end }: source.slice(start, end) is the script
206
+ instance?.attributes; // [{ name: "lang", value: "ts", start, end }]
207
+ module?.context; // "module"
208
+ options?.attributes; // [{ name: "runes", value: "{true}", start, end }]
209
+ ```
108
210
 
109
- Visits `node` and every node under it, depth-first and in source order, calling `visitor.enter(node, parent, key, index)` before a node's children and `visitor.leave(node, parent, key, index)` after them. `parent[key]` is the node, or `parent[key][index]` when the field is an array; all three are `null` for the node you pass. Return `SKIP` from `enter` to skip the node's children; `leave` is still called, so a stack you push in `enter` and pop in `leave` stays balanced. Return `STOP` from `enter` or `leave` to end the walk there: no more `enter` or `leave` calls, not even `leave` on the node's ancestors, e.g. to find the first use of a rune. Return another node from `enter` to put it in this one's place: `walk` writes it to `parent[key]` (or `parent[key][index]`), then calls `enter` on it and visits its children instead, and `leave` gets the replacement. So `return node.expression` on each `TSAsExpression` strips `a as B as C` down to `a`, one layer per `enter`. The node you pass to `walk` can't be replaced. Any other return value, `false` included, is ignored, so an expression-bodied `enter: (node) => node.type === "Component" && names.add(node.name)` still visits every node. Checking `node.type` narrows `node`.
211
+ An attribute's `value` is as written, without its quotes: character references aren't decoded, an expression keeps its braces, and an attribute without a value has `true`.
110
212
 
111
- It works on any node the parsers return: a component's `Root`, a `Fragment`, an expression, a `parseModule` program, a `<style>`'s rules. It only reads the fields that hold child nodes, so it never descends into `loc`, comments or strings, and it throws on a node type it doesn't know. Two exceptions to source order: a component's sections are visited in scope order, `module`, `instance`, `fragment`, then `css`; and a template literal's `quasis` come before its `expressions`. `Root.options` isn't a node, so `<svelte:options>`'s attributes aren't visited; read them from `ast.options?.attributes`. acorn shares one `Identifier` between both names of `import { a }`, so it's visited once as `imported` and once as `local`, and likewise as `local` and `exported` in `export { a }`.
213
+ `typescript` is what `createParser` needs to know to load the TypeScript parser only for the components that use it:
112
214
 
113
- `visitorKeys` is the table `walk` reads: the fields of each node type that hold child nodes, in source order, e.g. `visitorKeys.IfBlock` is `["test", "consequent", "alternate"]`. Use it with another walker, or to write your own.
215
+ ```ts
216
+ import { createParser } from "sveast/core";
217
+ import { lexComponent } from "sveast/lexer";
114
218
 
115
- ### `isReference(node, parent) => boolean`
219
+ let parser = createParser();
220
+ let loaded = false;
116
221
 
117
- Whether `node` is an `Identifier` that names a variable, function, class, import or other binding, where it's declared or where it's used, given the `parent` `walk` passes. It's the check svelte's analyzer makes, from the [`is-reference`](https://github.com/Rich-Harris/is-reference) package: `false` for a property or method name (`b` in `a.b`, `{ b: a }` and `class { b() {} }`, unless computed), the renamed side of an import or export (`b` in `import { b as a }` and `export { a as b }`), and labels. Unlike `is-reference`, it's also `false` for `import` and `new` in `import.meta` and `new.target`, an import attribute's key, and `b` in `export * as b from`, and for any node that isn't an `Identifier`.
222
+ async function parseComponent(source: string) {
223
+ if (!loaded && lexComponent(source).typescript) {
224
+ const { typescript } = await import("sveast/typescript");
225
+ parser = createParser({ typescript });
226
+ loaded = true;
227
+ }
228
+ return parser.parse(source);
229
+ }
230
+ ```
118
231
 
119
- TypeScript nodes hold types, so an `Identifier` whose parent is a TypeScript node isn't a reference (`B` in `let a: B`, `typeof b` in a type, interface and type alias names), except where it's a value: the expression of `as`, `satisfies`, `<T>a`, `a!`, `a<T>` and `export =`, an enum member's initializer, a parameter property (`a` in `constructor(private a)`), an enum's name, and the name and target of `import a = b`. Names of namespaces and of functions without a body (overloads, `declare function`) aren't references.
232
+ ### Smaller bundles: `sveast/core` and `sveast/module`
120
233
 
121
- acorn shares one `Identifier` between both names of `import { a }` and `export { a }`, and `walk` visits it twice, so it's a reference on both visits.
234
+ `createParser(support?)` from `sveast/core` returns `parse`, `parseModule` and `parseImportsExports` without the TypeScript parser and the table of HTML's named character references, which most tools can do without. `createModuleParser(support?)` from `sveast/module` returns `parseModule` and `parseImportsExports` without the template parser either, for tools that read `.js` and `.ts` files but never components, such as a bundler plugin following a module graph. Pass back the parts you need; with them, the parsers are the same as `sveast`'s.
122
235
 
123
236
  ```ts
124
- import { isReference, parseModule, walk } from "sveast";
237
+ import { createParser } from "sveast/core";
238
+ import { typescript } from "sveast/typescript";
125
239
 
126
- const used = new Set<string>();
127
- walk(parseModule("const a = b.c({ d: e });"), {
128
- enter(node, parent) {
129
- if (node.type === "Identifier" && isReference(node, parent)) used.add(node.name);
130
- },
131
- });
132
- used; // Set { "a", "b", "e" }
240
+ const { parse, parseModule, parseImportsExports } = createParser({ typescript });
133
241
  ```
134
242
 
135
- ### `extractIdentifiers(pattern) => Identifier[]`
243
+ | Support | Without it |
244
+ |:---|:---|
245
+ | `typescript` from `sveast/typescript` | A component with `<script lang="ts">`, or `typescript: true`, throws an `Error`, not a `ParseError`: a missing import, not a syntax error, and never a JavaScript AST of TypeScript |
246
+ | `entities` from `sveast/entities` (`createParser` only) | Numeric references, such as `&#169;`, and `&amp;`, `&apos;`, `&gt;`, `&lt;` and `&quot;` are decoded, but other names, such as `&copy;`, are left as written, so `Text.data` differs from svelte's where the source uses them |
136
247
 
137
- The `Identifier` nodes a pattern binds, in source order, as svelte's `extract_identifiers` returns them: `a`, `c`, `d`, `e` and `f` in `let { a, b: [c, ...d], e = 1, ...f } = x`. Property keys, defaults and type annotations aren't included. It returns nodes rather than names, so you keep their positions; `.map((node) => node.name)` for the names.
248
+ Both also export `ParseError`, the same class as `sveast`'s, and the node types.
138
249
 
139
- It takes any binding or assignment target: a declarator's `id`, a function's params, including a parameter property (`a` in `constructor(private a)`), a catch clause's `param`, an each block's `context`, a snippet's `parameters`, or an assignment's `left`. A member expression binds nothing, so `[a.b, c] = x` yields only `c`, as in svelte. On an assignment's `left`, `a!`, `a as T`, `a satisfies T` and `<T>a` yield `a`.
250
+ ### `isValidType(text, options?) => boolean`
140
251
 
141
- ```ts
142
- import { extractIdentifiers, parseModule } from "sveast";
252
+ Whether `text` is exactly one TypeScript type, such as a JSDoc `{"sm" | "lg"}` a tool is about to copy into a `.d.ts`. The text is parsed as a type on its own, not wrapped in a statement, so a `;`, a line break or a `}` in it can't end the type and smuggle in a statement: `string; let x = 1` and `{ a: string } }` are `false`. Whitespace and comments around the type are allowed. Results aren't cached.
143
253
 
144
- const [declaration] = parseModule("let { a, b: [c] } = x;").body;
145
- if (declaration.type === "VariableDeclaration") {
146
- declaration.declarations.flatMap((d) => extractIdentifiers(d.id)).map((node) => node.name); // ["a", "c"]
147
- }
148
- ```
254
+ A `//` comment runs to the end of the line, so `string // the size` is a valid type but breaks `CustomEvent<${text}>`. Pass `inline: true` when the text goes before more code on the same line: it's then also `false` unless every `//` comment in it ends with a line break.
149
255
 
150
- `walk`, `SKIP`, `STOP`, `visitorKeys`, `Visitor`, `isReference`, `extractIdentifiers` and the types are also exported from `sveast/walk`, which loads neither acorn nor the parser: for code that walks ASTs it gets from elsewhere, such as a cache, and must not pay for loading the parser. They're the same values as `sveast`'s.
256
+ ### `isRunesMode(source) => boolean`
151
257
 
152
- ```ts
153
- import { STOP, walk } from "sveast/walk";
154
- ```
258
+ Whether svelte compiles the component in runes mode, as `compile(source).metadata.runes` says: by `<svelte:options runes>` if it has one, otherwise by whether it uses a rune, such as `$state` or `$props`, or `await` outside a function in its instance script or markup. A `$state` that subscribes to a store named `state`, or that a function declares, isn't a rune, as in svelte. E.g. to track a migration to Svelte 5, or to pick the rules a linter applies.
155
259
 
156
- ### Types
260
+ Most components are decided without parsing: `<svelte:options>` is read with `lexComponent`, a component without a rune's name or `await` isn't in runes mode, and one whose scripts call a rune in a way that can't be a declaration, a method, a type or a store is. The rest is parsed, without the markup unless a rune's name or `await` is in it, and a syntax error there throws a `ParseError`. On 12,808 components from other Svelte projects, it takes a twelfth of the time parsing them does.
157
261
 
158
- `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`, `ParseModuleOptions` and `ParseImportsExportsOptions` are exported too.
262
+ ### `walk(node, visitor, keys?) => void`
159
263
 
160
- 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. Where the grammar only allows a string, such as an import's `source`, the type is `StringLiteral`, a `Literal` whose `value` is a `string`.
264
+ Visits `node` and every node under it, depth-first and in source order, calling `visitor.enter(node, parent, key, index)` before a node's children and `visitor.leave(node, parent, key, index)` after them. `parent[key]` is the node, or `parent[key][index]` when the field is an array; all three are `null` for the node you pass. Checking `node.type` narrows `node`.
161
265
 
162
- ```ts
163
- import { parseModule, type TSInterfaceDeclaration } from "sveast";
266
+ What `enter` and `leave` return controls the walk:
164
267
 
165
- const interfaces: TSInterfaceDeclaration[] = [];
166
- for (const node of parseModule(source, { typescript: true }).body) {
167
- if (node.type === "TSInterfaceDeclaration") interfaces.push(node);
168
- }
169
- ```
268
+ - **`SKIP`** from `enter` skips the node's children. `leave` is still called, so a stack you push in `enter` and pop in `leave` stays balanced.
269
+ - **`STOP`** from `enter` or `leave` ends the walk there, without `leave` on the node's ancestors, e.g. to find the first use of a rune.
270
+ - **A node** from `enter` replaces this one: `walk` writes it to `parent[key]` (or `parent[key][index]`), then enters it and visits its children instead, and `leave` gets the replacement. `return node.expression` on each `TSAsExpression` strips `a as B as C` down to `a`. The node you pass to `walk` can't be replaced.
271
+ - **Anything else**, `false` included, is ignored, so `enter: (node) => node.type === "Component" && names.add(node.name)` still visits every node.
170
272
 
171
- ## Recipes
273
+ It works on any node the parsers return: a `Root`, a `Fragment`, an expression, a `Program`, a `<style>`'s rules. It never descends into `loc`, comments or strings, and throws on a node type it doesn't know. A component's sections are visited in scope order, `module`, `instance`, `fragment`, then `css`, and a template literal's `quasis` before its `expressions`. `<svelte:options>`'s attributes aren't visited; read them from `ast.options?.attributes`. In `import { a }` and `export { a }`, one `Identifier` is both names, so it's visited twice.
172
274
 
173
- **List the components a file renders**, e.g. to build a dependency graph:
275
+ `visitorKeys` is the table `walk` reads: the fields of each node type that hold child nodes, in source order, e.g. `visitorKeys.IfBlock` is `["test", "consequent", "alternate"]`. Pass another table as `keys` to visit other fields; a node whose type it has no entry for throws. `markupVisitorKeys` visits only the markup: `Root`'s `fragment`, elements' attributes and fragments, attribute values and blocks' fragments, but no script, style or expression. For a query about the markup, such as the components a file renders or the classes it uses, it's 4.8× faster on the benchmark corpus than walking everything:
174
276
 
175
277
  ```ts
176
- import { parse, walk } from "sveast";
278
+ import { markupVisitorKeys, walk } from "sveast";
177
279
 
178
- function componentsUsed(source: string): string[] {
179
- const names = new Set<string>();
180
- walk(parse(source, { css: false }).fragment, {
280
+ walk(
281
+ ast,
282
+ {
181
283
  enter(node) {
182
284
  if (node.type === "Component") names.add(node.name);
183
285
  },
184
- });
185
- return [...names]; // ["Button", "Modal.Root"]
186
- }
286
+ },
287
+ markupVisitorKeys,
288
+ );
187
289
  ```
188
290
 
189
- **Find the classes a component's styles declare but its markup never uses**, e.g. to report dead CSS. A `class={...}` expression is reported as `dynamic` rather than guessed at:
291
+ `sveast/walk` exports `walk`, `SKIP`, `STOP`, `visitorKeys`, `markupVisitorKeys`, `isReference`, `extractIdentifiers`, `createLocator` and the types without loading a parser, for code that walks ASTs from elsewhere, such as a cache.
190
292
 
191
- ```ts
192
- import { parse, walk } from "sveast";
293
+ ### `isReference(node, parent) => boolean`
193
294
 
194
- function unusedClasses(source: string): { unused: string[]; dynamic: boolean } {
195
- const ast = parse(source);
196
- const declared = new Set<string>();
197
- const used = new Set<string>();
198
- let dynamic = false;
199
- if (ast.css) {
200
- walk(ast.css, {
201
- enter(node) {
202
- if (node.type === "ClassSelector") declared.add(node.name);
203
- },
204
- });
205
- }
206
- walk(ast.fragment, {
207
- enter(node) {
208
- if (node.type === "ClassDirective") used.add(node.name);
209
- if (node.type !== "Attribute" || node.name !== "class") return;
210
- for (const part of node.value === true ? [] : [node.value].flat()) {
211
- if (part.type === "ExpressionTag") dynamic = true;
212
- else for (const name of part.data.split(/\s+/)) if (name) used.add(name);
213
- }
214
- },
215
- });
216
- return { unused: [...declared].filter((name) => !used.has(name)), dynamic };
217
- }
218
- ```
295
+ Whether `node` is an `Identifier` that names a variable, function, class, import or other binding, where it's declared or where it's used, given the `parent` `walk` passes. It's the check svelte's analyzer makes, from [`is-reference`](https://github.com/Rich-Harris/is-reference): `false` for a property or method name (`b` in `a.b`, `{ b: a }` and `class { b() {} }`, unless computed), the renamed side of an import or export (`b` in `import { b as a }` and `export { a as b }`), and labels. Unlike `is-reference`, it's also `false` for `import.meta` and `new.target`, an import attribute's key, and `b` in `export * as b from`.
219
296
 
220
- **Read the props a runes component declares:**
297
+ In TypeScript, a name in a type isn't a reference (`B` in `let a: B`, interface and type alias names), but a value is: the expression of `as`, `satisfies`, `<T>a`, `a!`, `a<T>` and `export =`, an enum's name and member initializers, a parameter property (`a` in `constructor(private a)`), and both sides of `import a = b`. Names of namespaces and of functions without a body (overloads, `declare function`) aren't references.
221
298
 
222
299
  ```ts
223
- import { parse } from "sveast";
300
+ import { isReference, parseModule, walk } from "sveast";
224
301
 
225
- function propNames(source: string): string[] {
226
- for (const statement of parse(source).instance?.content.body ?? []) {
227
- if (statement.type !== "VariableDeclaration") continue;
228
- for (const { id, init } of statement.declarations) {
229
- const isProps =
230
- init?.type === "CallExpression" &&
231
- init.callee.type === "Identifier" &&
232
- init.callee.name === "$props";
233
- if (isProps && id.type === "ObjectPattern") {
234
- return id.properties.flatMap((p) =>
235
- p.type === "Property" && p.key.type === "Identifier" ? [p.key.name] : [],
236
- );
237
- }
238
- }
239
- }
240
- return [];
241
- }
302
+ const used = new Set<string>();
303
+ walk(parseModule("const a = b.c({ d: e });"), {
304
+ enter(node, parent) {
305
+ if (node.type === "Identifier" && isReference(node, parent)) used.add(node.name);
306
+ },
307
+ });
308
+ used; // Set { "a", "b", "e" }
242
309
  ```
243
310
 
244
- **Report syntax errors**, e.g. in a pre-commit check, with svelte's own messages:
311
+ ### `extractIdentifiers(pattern) => Identifier[]`
312
+
313
+ The `Identifier` nodes a pattern binds, in source order, as svelte's `extract_identifiers` returns them: `a`, `c`, `d`, `e` and `f` in `let { a, b: [c, ...d], e = 1, ...f } = x`. Property keys, defaults and type annotations aren't included, and a member expression binds nothing, so `[a.b, c] = x` yields only `c`. It takes any binding or assignment target: a declarator's `id`, a function's params, including a parameter property, a catch clause's `param`, an each block's `context`, a snippet's `parameters`, or an assignment's `left`, where `a!`, `a as T`, `a satisfies T` and `<T>a` yield `a`.
245
314
 
246
315
  ```ts
247
- import { ParseError, parse } from "sveast";
316
+ import { extractIdentifiers, parseModule } from "sveast";
248
317
 
249
- try {
250
- parse(source, { css: false });
251
- } catch (error) {
252
- if (!(error instanceof ParseError)) throw error;
253
- console.error(`${file}:${error.start?.line}:${error.start?.column} ${error.code}\n${error.frame}`);
318
+ const [declaration] = parseModule("let { a, b: [c] } = x;").body;
319
+ if (declaration.type === "VariableDeclaration") {
320
+ declaration.declarations.flatMap((d) => extractIdentifiers(d.id)).map((node) => node.name); // ["a", "c"]
254
321
  }
255
322
  ```
256
323
 
257
- **Get a component's styles without parsing them:**
324
+ ### `createLocator(source) => (offset) => Position`
325
+
326
+ A function from an offset to its `{ line, column }`, line from 1 and column from 0, as `loc: true` gives them: for a tool that parses without `loc`, which is faster, and needs lines only for what it reports. Lines are found on the first call, and each lookup starts its search at the line found last, so lookups in source order are fastest. Lines end at `\n`, as in the markup's `loc` and `ParseError`; acorn's `loc` in scripts and modules also ends them at a lone `\r`, U+2028 and U+2029. `parse` drops a leading byte order mark, so pass the source without one.
258
327
 
259
328
  ```ts
260
- import { parse } from "sveast";
329
+ import { createLocator, parse, walk } from "sveast";
261
330
 
262
- const css = parse(source, { css: false }).css?.content.styles ?? "";
331
+ const locate = createLocator(source);
332
+ walk(parse(source), {
333
+ enter(node) {
334
+ if (node.type === "Component") console.log(node.name, locate(node.start).line);
335
+ },
336
+ });
263
337
  ```
264
338
 
265
- ## Features
266
-
267
- - **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.
268
- - **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.
269
- - **About 2.4× faster than svelte/compiler** (2.2–3.0× depending on the input), and its ASTs retain 47% less memory without `loc`.
270
- - **Fast to load.** A fresh process imports sveast in about 5 ms, against about 45 ms for `svelte/compiler`.
271
- - **Zero dependencies,** types included. 67 kB gzipped, acorn included.
272
-
273
- ## Benchmarks
274
-
275
- 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.
276
-
277
- | Input | sveast | sveast, `loc: true` | svelte/compiler |
278
- |:---|:---|:---|:---|
279
- | Corpus, 400 components, 1.5 MB | **64.7 ms** | 84.0 ms | 155 ms (2.4×) |
280
- | Carbon's 5 largest components, 318 kB | **13.8 ms** | | 31.3 ms (2.3×) |
281
- | `lang="ts"` components, 22 files | **0.79 ms** | | 2.40 ms (3.0×) |
339
+ ### Types
282
340
 
283
- | Input | sveast's TypeScript plugin | acorn-typescript |
284
- |:---|:---|:---|
285
- | Carbon's 98 `.d.ts` modules | **3.19 ms** | 8.07 ms (2.5×) |
286
- | The corpus's `lang="ts"` scripts on their own | **0.58 ms** | 1.29 ms (2.2×) |
341
+ `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`. Each function's options and results are exported too, e.g. `ParseOptions` and `LexedStatement`.
287
342
 
288
- 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`.
343
+ The script's node types are exported as well (`Program`, `Node`, `Statement`, `Expression`, `Identifier`, ...), so you don't need a separate types package. They include `start`/`end` on every node, and the TypeScript nodes (`TSInterfaceDeclaration`, `TSTypeAnnotation`, ...; `TSNode` is their union) and fields (`typeAnnotation`, `typeParameters`, `returnType`, `importKind`, ...). The TypeScript nodes are in the `Statement`, `Declaration` and `Expression` unions, so checking `node.type` narrows to them. Where the grammar only allows a string, such as an import's `source`, the type is `StringLiteral`, a `Literal` whose `value` is a `string`.
package/core.d.ts CHANGED
@@ -109,7 +109,7 @@ export interface ParseImportsExportsOptions {
109
109
  localExports?: boolean;
110
110
  }
111
111
 
112
- /** TypeScript support for {@link createParser}, from `sveast/typescript`. */
112
+ /** TypeScript support for {@link createParser} and {@link createModuleParser}, from `sveast/typescript`. */
113
113
  export interface TypeScriptSupport {
114
114
  readonly support: "typescript";
115
115
  }
package/core.js CHANGED
@@ -1 +1 @@
1
- import{m}from"./shared-rfyyd11j.js";import"./shared-f8ty80r9.js";import{e}from"./shared-3xek95jd.js";import{Ce}from"./shared-9f69x2q9.js";import{x}from"./shared-etx1q7m5.js";import{y}from"./shared-88pt6me6.js";import{Le,Ie}from"./shared-0ehch67h.js";function P(o={}){let p=Le(o.typescript),s={typescript:p,entityNames:Ie(o.entities)??Ce};return{parse:(t,r)=>m(t,r,s),parseModule:(t,r={})=>y(t,r,p),parseImportsExports:(t,r={})=>x(t,r,p)}}export{e as ParseError,P as createParser};
1
+ import"./shared-2ahdjk78.js";import"./shared-e8rqjefv.js";import{e}from"./shared-j2msb2xj.js";import{O}from"./shared-657pk0sg.js";import{ue}from"./shared-j3kp940c.js";import"./shared-j60qxzy3.js";import{x}from"./shared-p0ybqcs7.js";import{g}from"./shared-918zqd7r.js";import{W,at}from"./shared-tscn5fn7.js";function u(o={}){let p=W(o.typescript),s={typescript:p,entityNames:at(o.entities)??ue};return{parse:(t,r)=>O(t,r,s),parseModule:(t,r={})=>x(t,r,p),parseImportsExports:(t,r={})=>g(t,r,p)}}export{e as ParseError,u as createParser};
package/entities.js CHANGED
@@ -1 +1 @@
1
- import"./shared-9f69x2q9.js";import{k}from"./shared-y4gayx8p.js";import{Pe}from"./shared-0ehch67h.js";var p=Pe(k);export{p as entities};
1
+ import"./shared-j3kp940c.js";import{y}from"./shared-e15f58b0.js";import{ot}from"./shared-tscn5fn7.js";var p=ot(y);export{p as entities};