@astrojs/compiler 0.11.1 → 0.12.0-next.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @astrojs/compiler
2
2
 
3
+ ## 0.11.3
4
+
5
+ ### Patch Changes
6
+
7
+ - dcf15bf: Fixes bug causing a crash when using Astro.resolve on a hoisted script
8
+
9
+ ## 0.11.2
10
+
11
+ ### Patch Changes
12
+
13
+ - 41cc6ef: Fix memory issue caused by duplicate WASM instantiations
14
+
3
15
  ## 0.11.1
4
16
 
5
17
  ### Patch Changes
@@ -31,6 +43,14 @@
31
43
  - 5fa9e53: Fix handling of special characters inside of expressions
32
44
  - 8aaa956: Formalize support for magic `data-astro-raw` attribute with new, official `is:raw` directive
33
45
  - c698350: Improve MathML support. `{}` inside of `<math>` is now treated as raw text rather than an expression construct.
46
+ ## 0.11.0-next.0
47
+
48
+ ### Minor Changes
49
+
50
+ - 083d604: Do not render implicit tags created during the parsing process
51
+ - d188c74: Remove "as" option, treats all documents as fragments that generate no implicit tags
52
+ - c4b6b05: Add `parse` function which generates an AST
53
+ - d84c3a2: Adds support for `Astro.self` (as accepted in the [Recursive Components RFC](https://github.com/withastro/rfcs/blob/main/active-rfcs/0000-recursive-components.md)).
34
54
 
35
55
  ## 0.10.1
36
56
 
package/README.md CHANGED
@@ -12,7 +12,15 @@ npm install @astrojs/compiler
12
12
 
13
13
  ## Usage
14
14
 
15
- _Note: API will change before 1.0! Use at your own discretion._
15
+ _Note: Public APIs are likely to change before 1.0! Use at your own discretion._
16
+
17
+ #### Transform `.astro` to valid TypeScript
18
+
19
+ The Astro compiler can convert `.astro` syntax to a TypeScript Module whose default export generates HTML.
20
+
21
+ **Some notes**...
22
+ - TypeScript is valid `.astro` syntax! The output code may need an additional post-processing step to generate valid JavaScript.
23
+ - `.astro` files rely on a server implementation exposed as `astro/internal` in the Node ecosystem. Other runtimes currently need to bring their own rendering implementation and reference it via `internalURL`. This is a pain point we're looking into fixing.
16
24
 
17
25
  ```js
18
26
  import { transform } from '@astrojs/compiler';
@@ -25,6 +33,31 @@ const result = await transform(source, {
25
33
  });
26
34
  ```
27
35
 
36
+ #### Parse `.astro` and return an AST
37
+
38
+ The Astro compiler can emit an AST using the `parse` method.
39
+
40
+ **Some notes**...
41
+ - Position data is currently incomplete and in some cases incorrect. We're working on it!
42
+ - A `TextNode` can represent both HTML `text` and JavaScript/TypeScript source code.
43
+ - The `@astrojs/compiler/utils` entrypoint exposes a `walk` function that can be used to traverse the AST. It also exposes the `is` helper which can be used as guards to derive the proper types for each `node`.
44
+
45
+ ```js
46
+ import { parse } from '@astrojs/compiler';
47
+ import { walk, is } from '@astrojs/compiler/utils';
48
+
49
+ const result = await parse(source, {
50
+ position: false, // defaults to `true`
51
+ });
52
+
53
+ walk(result.ast, (node) => {
54
+ // `tag` nodes are `element` | `custom-element` | `component`
55
+ if (is.tag(node)) {
56
+ console.log(node.name);
57
+ }
58
+ })
59
+ ```
60
+
28
61
  ## Contributing
29
62
 
30
63
  [CONTRIBUTING.md](./CONTRIBUTING.md)
package/astro.wasm CHANGED
Binary file
@@ -1,3 +1,4 @@
1
1
  import type * as types from '../shared/types';
2
2
  export declare const transform: typeof types.transform;
3
+ export declare const parse: typeof types.parse;
3
4
  export declare const initialize: typeof types.initialize;
package/browser/index.js CHANGED
@@ -2,28 +2,32 @@ import Go from './wasm_exec.js';
2
2
  export const transform = (input, options) => {
3
3
  return ensureServiceIsRunning().transform(input, options);
4
4
  };
5
+ export const parse = (input, options) => {
6
+ return ensureServiceIsRunning().parse(input, options);
7
+ };
5
8
  let initializePromise;
6
9
  let longLivedService;
7
- export const initialize = (options) => {
10
+ export const initialize = async (options) => {
8
11
  let wasmURL = options.wasmURL;
9
12
  if (!wasmURL)
10
13
  throw new Error('Must provide the "wasmURL" option');
11
14
  wasmURL += '';
12
- if (initializePromise)
13
- throw new Error('Cannot call "initialize" more than once');
14
- initializePromise = startRunningService(wasmURL);
15
- initializePromise.catch(() => {
16
- // Let the caller try again if this fails
17
- initializePromise = void 0;
18
- });
19
- return initializePromise;
15
+ if (!initializePromise) {
16
+ initializePromise = startRunningService(wasmURL).catch((err) => {
17
+ // Let the caller try again if this fails.
18
+ initializePromise = void 0;
19
+ // But still, throw the error back up the caller.
20
+ throw err;
21
+ });
22
+ }
23
+ longLivedService = longLivedService || (await initializePromise);
20
24
  };
21
25
  let ensureServiceIsRunning = () => {
22
- if (longLivedService)
23
- return longLivedService;
24
- if (initializePromise)
26
+ if (!initializePromise)
27
+ throw new Error('You need to call "initialize" before calling this');
28
+ if (!longLivedService)
25
29
  throw new Error('You need to wait for the promise returned from "initialize" to be resolved before calling this');
26
- throw new Error('You need to call "initialize" before calling this');
30
+ return longLivedService;
27
31
  };
28
32
  const instantiateWASM = async (wasmURL, importObject) => {
29
33
  let response = undefined;
@@ -44,7 +48,8 @@ const startRunningService = async (wasmURL) => {
44
48
  const wasm = await instantiateWASM(wasmURL, go.importObject);
45
49
  go.run(wasm.instance);
46
50
  const service = globalThis['@astrojs/compiler'];
47
- longLivedService = {
51
+ return {
48
52
  transform: (input, options) => new Promise((resolve) => resolve(service.transform(input, options || {}))),
53
+ parse: (input, options) => new Promise((resolve) => resolve(service.parse(input, options || {}))).then((result) => ({ ...result, ast: JSON.parse(result.ast) })),
49
54
  };
50
55
  };
@@ -0,0 +1,20 @@
1
+ import { Node, ParentNode, RootNode, ElementNode, CustomElementNode, ComponentNode, LiteralNode, ExpressionNode, TextNode, CommentNode, DoctypeNode, FrontmatterNode } from '../shared/ast';
2
+ export interface Visitor {
3
+ (node: Node, parent?: ParentNode, index?: number): void | Promise<void>;
4
+ }
5
+ export declare const is: {
6
+ parent(node: Node): node is ParentNode;
7
+ literal(node: Node): node is LiteralNode;
8
+ tag(node: Node): node is ElementNode | ComponentNode | CustomElementNode;
9
+ whitespace(node: Node): node is TextNode;
10
+ root: (node: Node) => node is RootNode;
11
+ element: (node: Node) => node is ElementNode;
12
+ customElement: (node: Node) => node is CustomElementNode;
13
+ component: (node: Node) => node is ComponentNode;
14
+ expression: (node: Node) => node is ExpressionNode;
15
+ text: (node: Node) => node is TextNode;
16
+ doctype: (node: Node) => node is DoctypeNode;
17
+ comment: (node: Node) => node is CommentNode;
18
+ frontmatter: (node: Node) => node is FrontmatterNode;
19
+ };
20
+ export declare function walk(node: ParentNode, callback: Visitor): void;
@@ -0,0 +1,46 @@
1
+ function guard(type) {
2
+ return (node) => node.type === type;
3
+ }
4
+ export const is = {
5
+ parent(node) {
6
+ return Array.isArray(node.children);
7
+ },
8
+ literal(node) {
9
+ return typeof node.value === 'string';
10
+ },
11
+ tag(node) {
12
+ return node.type === 'element' || node.type === 'custom-element' || node.type === 'component';
13
+ },
14
+ whitespace(node) {
15
+ return node.type === 'text' && node.value.trim().length === 0;
16
+ },
17
+ root: guard('root'),
18
+ element: guard('element'),
19
+ customElement: guard('custom-element'),
20
+ component: guard('component'),
21
+ expression: guard('expression'),
22
+ text: guard('text'),
23
+ doctype: guard('doctype'),
24
+ comment: guard('comment'),
25
+ frontmatter: guard('frontmatter'),
26
+ };
27
+ class Walker {
28
+ constructor(callback) {
29
+ this.callback = callback;
30
+ }
31
+ async visit(node, parent, index) {
32
+ await this.callback(node, parent, index);
33
+ if (is.parent(node)) {
34
+ let promises = [];
35
+ for (let i = 0; i < node.children.length; i++) {
36
+ const child = node.children[i];
37
+ promises.push(this.callback(child, node, i));
38
+ }
39
+ await Promise.all(promises);
40
+ }
41
+ }
42
+ }
43
+ export function walk(node, callback) {
44
+ const walker = new Walker(callback);
45
+ walker.visit(node);
46
+ }
package/node/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  import type * as types from '../shared/types';
2
2
  export declare const transform: typeof types.transform;
3
+ export declare const parse: typeof types.parse;
3
4
  export declare const compile: (template: string) => Promise<string>;
package/node/index.js CHANGED
@@ -2,17 +2,26 @@ import { promises as fs } from 'fs';
2
2
  import Go from './wasm_exec.js';
3
3
  import { fileURLToPath } from 'url';
4
4
  export const transform = async (input, options) => {
5
- return ensureServiceIsRunning().then((service) => service.transform(input, options));
5
+ return getService().then((service) => service.transform(input, options));
6
+ };
7
+ export const parse = async (input, options) => {
8
+ return getService().then((service) => service.parse(input, options));
6
9
  };
7
10
  export const compile = async (template) => {
8
11
  const { default: mod } = await import(`data:text/javascript;charset=utf-8;base64,${Buffer.from(template).toString('base64')}`);
9
12
  return mod;
10
13
  };
11
14
  let longLivedService;
12
- let ensureServiceIsRunning = () => {
13
- if (longLivedService)
14
- return Promise.resolve(longLivedService);
15
- return startRunningService();
15
+ let getService = () => {
16
+ if (!longLivedService) {
17
+ longLivedService = startRunningService().catch((err) => {
18
+ // Let the caller try again if this fails.
19
+ longLivedService = void 0;
20
+ // But still, throw the error back up the caller.
21
+ throw err;
22
+ });
23
+ }
24
+ return longLivedService;
16
25
  };
17
26
  const instantiateWASM = async (wasmURL, importObject) => {
18
27
  let response = undefined;
@@ -27,9 +36,9 @@ const startRunningService = async () => {
27
36
  const go = new Go();
28
37
  const wasm = await instantiateWASM(fileURLToPath(new URL('../astro.wasm', import.meta.url)), go.importObject);
29
38
  go.run(wasm.instance);
30
- const service = globalThis['@astrojs/compiler'];
31
- longLivedService = {
32
- transform: (input, options) => new Promise((resolve) => resolve(service.transform(input, options || {}))),
39
+ const _service = globalThis['@astrojs/compiler'];
40
+ return {
41
+ transform: (input, options) => new Promise((resolve) => resolve(_service.transform(input, options || {}))),
42
+ parse: (input, options) => new Promise((resolve) => resolve(_service.parse(input, options || {}))).then((result) => ({ ...result, ast: JSON.parse(result.ast) })),
33
43
  };
34
- return longLivedService;
35
44
  };
@@ -0,0 +1,20 @@
1
+ import { Node, ParentNode, RootNode, ElementNode, CustomElementNode, ComponentNode, LiteralNode, ExpressionNode, TextNode, CommentNode, DoctypeNode, FrontmatterNode } from '../shared/ast';
2
+ export interface Visitor {
3
+ (node: Node, parent?: ParentNode, index?: number): void | Promise<void>;
4
+ }
5
+ export declare const is: {
6
+ parent(node: Node): node is ParentNode;
7
+ literal(node: Node): node is LiteralNode;
8
+ tag(node: Node): node is ElementNode | ComponentNode | CustomElementNode;
9
+ whitespace(node: Node): node is TextNode;
10
+ root: (node: Node) => node is RootNode;
11
+ element: (node: Node) => node is ElementNode;
12
+ customElement: (node: Node) => node is CustomElementNode;
13
+ component: (node: Node) => node is ComponentNode;
14
+ expression: (node: Node) => node is ExpressionNode;
15
+ text: (node: Node) => node is TextNode;
16
+ doctype: (node: Node) => node is DoctypeNode;
17
+ comment: (node: Node) => node is CommentNode;
18
+ frontmatter: (node: Node) => node is FrontmatterNode;
19
+ };
20
+ export declare function walk(node: ParentNode, callback: Visitor): void;
package/node/utils.js ADDED
@@ -0,0 +1,46 @@
1
+ function guard(type) {
2
+ return (node) => node.type === type;
3
+ }
4
+ export const is = {
5
+ parent(node) {
6
+ return Array.isArray(node.children);
7
+ },
8
+ literal(node) {
9
+ return typeof node.value === 'string';
10
+ },
11
+ tag(node) {
12
+ return node.type === 'element' || node.type === 'custom-element' || node.type === 'component';
13
+ },
14
+ whitespace(node) {
15
+ return node.type === 'text' && node.value.trim().length === 0;
16
+ },
17
+ root: guard('root'),
18
+ element: guard('element'),
19
+ customElement: guard('custom-element'),
20
+ component: guard('component'),
21
+ expression: guard('expression'),
22
+ text: guard('text'),
23
+ doctype: guard('doctype'),
24
+ comment: guard('comment'),
25
+ frontmatter: guard('frontmatter'),
26
+ };
27
+ class Walker {
28
+ constructor(callback) {
29
+ this.callback = callback;
30
+ }
31
+ async visit(node, parent, index) {
32
+ await this.callback(node, parent, index);
33
+ if (is.parent(node)) {
34
+ let promises = [];
35
+ for (let i = 0; i < node.children.length; i++) {
36
+ const child = node.children[i];
37
+ promises.push(this.callback(child, node, i));
38
+ }
39
+ await Promise.all(promises);
40
+ }
41
+ }
42
+ }
43
+ export function walk(node, callback) {
44
+ const walker = new Walker(callback);
45
+ walker.visit(node);
46
+ }
package/package.json CHANGED
@@ -5,12 +5,12 @@
5
5
  "type": "module",
6
6
  "bugs": "https://github.com/withastro/compiler/issues",
7
7
  "homepage": "https://astro.build",
8
- "version": "0.11.1",
8
+ "version": "0.12.0-next.0",
9
9
  "scripts": {
10
10
  "build": "tsc -p ."
11
11
  },
12
12
  "main": "./node/index.js",
13
- "types": "./shared/types.d.ts",
13
+ "types": "./node",
14
14
  "repository": {
15
15
  "type": "git",
16
16
  "url": "https://github.com/withastro/compiler.git"
@@ -21,6 +21,11 @@
21
21
  "import": "./node/index.js",
22
22
  "default": "./browser/index.js"
23
23
  },
24
+ "./utils": {
25
+ "browser": "./browser/utils.js",
26
+ "import": "./node/utils.js",
27
+ "default": "./browser/utils.js"
28
+ },
24
29
  "./astro.wasm": "./astro.wasm"
25
30
  },
26
31
  "dependencies": {
@@ -0,0 +1,71 @@
1
+ export declare type ParentNode = RootNode | ElementNode | ComponentNode | CustomElementNode | ExpressionNode;
2
+ export declare type Node = RootNode | ElementNode | ComponentNode | CustomElementNode | ExpressionNode | TextNode | FrontmatterNode | DoctypeNode | CommentNode;
3
+ export interface Position {
4
+ start: Point;
5
+ end?: Point;
6
+ }
7
+ export interface Point {
8
+ /** 1-based line number */
9
+ line: number;
10
+ /** 1-based column number, per-line */
11
+ column: number;
12
+ /** 0-based byte offset */
13
+ offset: number;
14
+ }
15
+ export interface BaseNode {
16
+ type: string;
17
+ position?: Position;
18
+ }
19
+ export interface ParentLikeNode extends BaseNode {
20
+ type: 'element' | 'component' | 'custom-element' | 'expression' | 'root';
21
+ children: Node[];
22
+ }
23
+ export interface LiteralNode extends BaseNode {
24
+ type: 'text' | 'doctype' | 'comment' | 'frontmatter';
25
+ value: string;
26
+ }
27
+ export interface RootNode extends ParentLikeNode {
28
+ type: 'root';
29
+ }
30
+ export interface AttributeNode extends BaseNode {
31
+ type: 'attribute';
32
+ kind: 'quoted' | 'empty' | 'expression' | 'spread' | 'shorthand' | 'template-literal';
33
+ name: string;
34
+ value: string;
35
+ }
36
+ export interface DirectiveNode extends Omit<AttributeNode, 'type'> {
37
+ type: 'directive';
38
+ }
39
+ export interface TextNode extends LiteralNode {
40
+ type: 'text';
41
+ }
42
+ export interface ElementNode extends ParentLikeNode {
43
+ type: 'element';
44
+ name: string;
45
+ attributes: AttributeNode[];
46
+ directives: DirectiveNode[];
47
+ }
48
+ export interface ComponentNode extends ParentLikeNode {
49
+ type: 'component';
50
+ name: string;
51
+ attributes: AttributeNode[];
52
+ directives: DirectiveNode[];
53
+ }
54
+ export interface CustomElementNode extends ParentLikeNode {
55
+ type: 'custom-element';
56
+ name: string;
57
+ attributes: AttributeNode[];
58
+ directives: DirectiveNode[];
59
+ }
60
+ export interface DoctypeNode extends LiteralNode {
61
+ type: 'doctype';
62
+ }
63
+ export interface CommentNode extends LiteralNode {
64
+ type: 'comment';
65
+ }
66
+ export interface FrontmatterNode extends LiteralNode {
67
+ type: 'frontmatter';
68
+ }
69
+ export interface ExpressionNode extends ParentLikeNode {
70
+ type: 'expression';
71
+ }
package/shared/ast.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/shared/types.d.ts CHANGED
@@ -1,13 +1,21 @@
1
+ import { RootNode } from './ast';
2
+ export * from './ast';
1
3
  export interface PreprocessorResult {
2
4
  code: string;
3
5
  map?: string;
4
6
  }
7
+ export interface ParseOptions {
8
+ position?: boolean;
9
+ }
5
10
  export interface TransformOptions {
6
11
  internalURL?: string;
7
12
  site?: string;
8
13
  sourcefile?: string;
9
14
  pathname?: string;
10
15
  sourcemap?: boolean | 'inline' | 'external' | 'both';
16
+ /**
17
+ * @deprecated "as" has been removed and no longer has any effect!
18
+ */
11
19
  as?: 'document' | 'fragment';
12
20
  projectRoot?: string;
13
21
  preprocessStyle?: (content: string, attrs: Record<string, string>) => Promise<PreprocessorResult>;
@@ -28,7 +36,11 @@ export interface TransformResult {
28
36
  code: string;
29
37
  map: string;
30
38
  }
39
+ export interface ParseResult {
40
+ ast: RootNode;
41
+ }
31
42
  export declare function transform(input: string, options?: TransformOptions): Promise<TransformResult>;
43
+ export declare function parse(input: string, options?: ParseOptions): Promise<ParseResult>;
32
44
  export declare function initialize(options: InitializeOptions): Promise<void>;
33
45
  export interface InitializeOptions {
34
46
  wasmURL?: string;
package/shared/types.js CHANGED
@@ -1 +1 @@
1
- export {};
1
+ export * from './ast';
package/utils.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './node/utils';