From 30cccdf7154b6470e876464da9e412af10894dd5 Mon Sep 17 00:00:00 2001
From: "Fred K. Schott"
Date: Thu, 25 Mar 2021 00:00:22 -0700
Subject: [PATCH] add component state, top-level await support (#26)
---
.../astro/components/BaseLayout.astro | 2 +-
.../astro/components/MainLayout.astro | 3 -
.../astro/components/PokemonLookup.astro | 16 ++
.../astro/layouts/content-with-cover.astro | 2 +-
examples/snowpack/astro/pages/guides.astro | 3 -
examples/snowpack/astro/pages/news.astro | 18 +-
package-lock.json | 112 ++++++++++--
package.json | 10 +-
snowpack-plugin.cjs | 2 +-
src/@types/astro.ts | 2 +-
src/@types/optimizer.ts | 2 +-
src/{codegen/index.ts => compiler/codegen.ts} | 105 ++++++++---
src/compiler/index.ts | 172 +++++++++++++++++-
src/{ => compiler}/optimize/index.ts | 4 +-
src/{ => compiler}/optimize/styles.ts | 4 +-
src/logger.ts | 2 +-
src/micromark-collect-headers.ts | 1 -
src/{compiler => parser}/README.md | 0
src/{compiler => parser}/Stats.ts | 0
src/{compiler => parser}/config.ts | 0
src/parser/index.ts | 1 +
src/{compiler => parser}/interfaces.ts | 6 +-
src/{compiler => parser}/parse/acorn.ts | 2 +-
src/{compiler => parser}/parse/index.ts | 0
.../parse/read/context.ts | 0
.../parse/read/expression.ts | 2 +-
src/{compiler => parser}/parse/read/script.ts | 0
src/{compiler => parser}/parse/read/style.ts | 0
.../parse/state/fragment.ts | 0
.../parse/state/mustache.ts | 4 +-
src/{compiler => parser}/parse/state/setup.ts | 0
src/{compiler => parser}/parse/state/tag.ts | 6 +-
src/{compiler => parser}/parse/state/text.ts | 0
.../parse/utils/bracket.ts | 0
.../parse/utils/entities.ts | 0
src/{compiler => parser}/parse/utils/html.ts | 0
src/{compiler => parser}/parse/utils/node.ts | 0
src/{compiler => parser}/utils/error.ts | 0
.../utils/full_char_code_at.ts | 0
src/{compiler => parser}/utils/fuzzymatch.ts | 0
.../utils/get_code_frame.ts | 0
src/{compiler => parser}/utils/link.ts | 0
src/{compiler => parser}/utils/list.ts | 0
src/{compiler => parser}/utils/names.ts | 0
src/{compiler => parser}/utils/namespaces.ts | 0
src/{compiler => parser}/utils/nodes_match.ts | 0
src/{compiler => parser}/utils/patterns.ts | 0
src/{compiler => parser}/utils/trim.ts | 0
src/runtime.ts | 2 +-
src/transform2.ts | 172 ------------------
50 files changed, 397 insertions(+), 258 deletions(-)
create mode 100644 examples/snowpack/astro/components/PokemonLookup.astro
rename src/{codegen/index.ts => compiler/codegen.ts} (77%)
rename src/{ => compiler}/optimize/index.ts (94%)
rename src/{ => compiler}/optimize/styles.ts (98%)
rename src/{compiler => parser}/README.md (100%)
rename src/{compiler => parser}/Stats.ts (100%)
rename src/{compiler => parser}/config.ts (100%)
create mode 100644 src/parser/index.ts
rename src/{compiler => parser}/interfaces.ts (95%)
rename src/{compiler => parser}/parse/acorn.ts (99%)
rename src/{compiler => parser}/parse/index.ts (100%)
rename src/{compiler => parser}/parse/read/context.ts (100%)
rename src/{compiler => parser}/parse/read/expression.ts (99%)
rename src/{compiler => parser}/parse/read/script.ts (100%)
rename src/{compiler => parser}/parse/read/style.ts (100%)
rename src/{compiler => parser}/parse/state/fragment.ts (100%)
rename src/{compiler => parser}/parse/state/mustache.ts (99%)
rename src/{compiler => parser}/parse/state/setup.ts (100%)
rename src/{compiler => parser}/parse/state/tag.ts (99%)
rename src/{compiler => parser}/parse/state/text.ts (100%)
rename src/{compiler => parser}/parse/utils/bracket.ts (100%)
rename src/{compiler => parser}/parse/utils/entities.ts (100%)
rename src/{compiler => parser}/parse/utils/html.ts (100%)
rename src/{compiler => parser}/parse/utils/node.ts (100%)
rename src/{compiler => parser}/utils/error.ts (100%)
rename src/{compiler => parser}/utils/full_char_code_at.ts (100%)
rename src/{compiler => parser}/utils/fuzzymatch.ts (100%)
rename src/{compiler => parser}/utils/get_code_frame.ts (100%)
rename src/{compiler => parser}/utils/link.ts (100%)
rename src/{compiler => parser}/utils/list.ts (100%)
rename src/{compiler => parser}/utils/names.ts (100%)
rename src/{compiler => parser}/utils/namespaces.ts (100%)
rename src/{compiler => parser}/utils/nodes_match.ts (100%)
rename src/{compiler => parser}/utils/patterns.ts (100%)
rename src/{compiler => parser}/utils/trim.ts (100%)
delete mode 100644 src/transform2.ts
diff --git a/examples/snowpack/astro/components/BaseLayout.astro b/examples/snowpack/astro/components/BaseLayout.astro
index 2e141a83f..040739515 100644
--- a/examples/snowpack/astro/components/BaseLayout.astro
+++ b/examples/snowpack/astro/components/BaseLayout.astro
@@ -3,7 +3,7 @@ import Banner from './Banner.astro';
import Nav from './Nav.astro';
---
-
+
diff --git a/examples/snowpack/astro/components/MainLayout.astro b/examples/snowpack/astro/components/MainLayout.astro
index dbc714510..852a6636f 100644
--- a/examples/snowpack/astro/components/MainLayout.astro
+++ b/examples/snowpack/astro/components/MainLayout.astro
@@ -1,9 +1,6 @@
---
import BaseLayout from './BaseLayout.astro';
import Menu from './Menu.astro';
-export function setup({ context }) {
-return {};
-}
---
diff --git a/examples/snowpack/astro/components/PokemonLookup.astro b/examples/snowpack/astro/components/PokemonLookup.astro
new file mode 100644
index 000000000..0de7713e3
--- /dev/null
+++ b/examples/snowpack/astro/components/PokemonLookup.astro
@@ -0,0 +1,16 @@
+---
+export let number: number;
+
+const pokemonDataReq = await fetch(`https://pokeapi.co/api/v2/pokemon/${number}`);
+const pokemonData = await pokemonDataReq.json();
+---
+
+
+
+
+
+ Pokemon #{number} is: {pokemonData.name}
+
+
diff --git a/examples/snowpack/astro/layouts/content-with-cover.astro b/examples/snowpack/astro/layouts/content-with-cover.astro
index dd9909578..ac84f6354 100644
--- a/examples/snowpack/astro/layouts/content-with-cover.astro
+++ b/examples/snowpack/astro/layouts/content-with-cover.astro
@@ -73,7 +73,7 @@ export let content: any;
diff --git a/examples/snowpack/astro/pages/guides.astro b/examples/snowpack/astro/pages/guides.astro
index 43c1f8c5c..3febcb2f7 100644
--- a/examples/snowpack/astro/pages/guides.astro
+++ b/examples/snowpack/astro/pages/guides.astro
@@ -25,7 +25,6 @@ let guides;
let communityGuides;
-export function setup({ /* paginate */ }) {
guides = paginate({
files: '/posts/guides/*.md',
// sort: ((a, b) => new Date(b) - new Date(a)),
@@ -39,8 +38,6 @@ export function setup({ /* paginate */ }) {
tag: 'communityGuides',
limit: 10,
});
- return {};
-}
---
diff --git a/examples/snowpack/astro/pages/news.astro b/examples/snowpack/astro/pages/news.astro
index 492313d5e..2f310cc0b 100644
--- a/examples/snowpack/astro/pages/news.astro
+++ b/examples/snowpack/astro/pages/news.astro
@@ -1,5 +1,6 @@
---
import Card from '../components/Card.jsx';
+import PokemonLookup from '../components/PokemonLookup.astro';
import CompanyLogo from '../components/CompanyLogo.jsx';
import NewsAssets from '../components/NewsAssets.svelte';
import NewsTitle from '../components/NewsTitle.vue';
@@ -11,15 +12,8 @@ import MainLayout from '../components/MainLayout.astro';
import news from '../data/news.json';
import users from '../data/users.json';
-let title = 'Community & News';
-let description = 'Snowpack community news and companies that use Snowpack.';
-let pokemonData;
-
-export async function setup({ context, request, fetch }) {
- const pokemonDataReq = await fetch(`https://pokeapi.co/api/v2/pokemon/ditto`);
- pokemonData = await pokemonDataReq.json();
- return {};
-}
+const title = 'Community & News';
+const description = 'Snowpack community news and companies that use Snowpack.';
---
@@ -40,9 +34,9 @@ export async function setup({ context, request, fetch }) {
Submit it!
-
- In case you're curious, the best pokemon is {pokemonData.name}.
-
+
+
+
diff --git a/package-lock.json b/package-lock.json
index c3f2d0d1f..b46ac503c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,11 +8,53 @@
"version": "7.12.13",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
"integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==",
- "dev": true,
"requires": {
"@babel/highlight": "^7.12.13"
}
},
+ "@babel/generator": {
+ "version": "7.13.9",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.9.tgz",
+ "integrity": "sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==",
+ "requires": {
+ "@babel/types": "^7.13.0",
+ "jsesc": "^2.5.1",
+ "source-map": "^0.5.0"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
+ }
+ }
+ },
+ "@babel/helper-function-name": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz",
+ "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==",
+ "requires": {
+ "@babel/helper-get-function-arity": "^7.12.13",
+ "@babel/template": "^7.12.13",
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-get-function-arity": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz",
+ "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==",
+ "requires": {
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/helper-split-export-declaration": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz",
+ "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==",
+ "requires": {
+ "@babel/types": "^7.12.13"
+ }
+ },
"@babel/helper-validator-identifier": {
"version": "7.12.11",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
@@ -22,7 +64,6 @@
"version": "7.13.10",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz",
"integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==",
- "dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.12.11",
"chalk": "^2.0.0",
@@ -33,7 +74,6 @@
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
- "dev": true,
"requires": {
"color-convert": "^1.9.0"
}
@@ -42,7 +82,6 @@
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
- "dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
@@ -53,7 +92,6 @@
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "dev": true,
"requires": {
"color-name": "1.1.3"
}
@@ -61,8 +99,7 @@
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
- "dev": true
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
}
}
},
@@ -71,6 +108,39 @@
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.11.tgz",
"integrity": "sha512-PhuoqeHoO9fc4ffMEVk4qb/w/s2iOSWohvbHxLtxui0eBg3Lg5gN1U8wp1V1u61hOWkPQJJyJzGH6Y+grwkq8Q=="
},
+ "@babel/template": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz",
+ "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==",
+ "requires": {
+ "@babel/code-frame": "^7.12.13",
+ "@babel/parser": "^7.12.13",
+ "@babel/types": "^7.12.13"
+ }
+ },
+ "@babel/traverse": {
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.0.tgz",
+ "integrity": "sha512-xys5xi5JEhzC3RzEmSGrs/b3pJW/o87SypZ+G/PhaE7uqVQNv/jlmVIBXuoh5atqQ434LfXV+sf23Oxj0bchJQ==",
+ "requires": {
+ "@babel/code-frame": "^7.12.13",
+ "@babel/generator": "^7.13.0",
+ "@babel/helper-function-name": "^7.12.13",
+ "@babel/helper-split-export-declaration": "^7.12.13",
+ "@babel/parser": "^7.13.0",
+ "@babel/types": "^7.13.0",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0",
+ "lodash": "^4.17.19"
+ },
+ "dependencies": {
+ "globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
+ }
+ }
+ },
"@babel/types": {
"version": "7.13.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.0.tgz",
@@ -162,6 +232,22 @@
"defer-to-connect": "^1.0.1"
}
},
+ "@types/babel__generator": {
+ "version": "7.6.2",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.2.tgz",
+ "integrity": "sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ==",
+ "requires": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "@types/babel__traverse": {
+ "version": "7.11.1",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.11.1.tgz",
+ "integrity": "sha512-Vs0hm0vPahPMYi9tDjtP66llufgO3ST16WXaSTtDGEl9cewAl3AibmxWw6TINOqHPT9z0uABKAYjT9jNSg4npw==",
+ "requires": {
+ "@babel/types": "^7.3.0"
+ }
+ },
"@types/estree": {
"version": "0.0.46",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.46.tgz",
@@ -1220,8 +1306,7 @@
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
- "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
- "dev": true
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"eslint": {
"version": "7.22.0",
@@ -1741,8 +1826,7 @@
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
- "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
- "dev": true
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
},
"has-yarn": {
"version": "2.1.0",
@@ -2039,6 +2123,11 @@
"esprima": "^4.0.0"
}
},
+ "jsesc": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="
+ },
"json-buffer": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz",
@@ -3159,7 +3248,6 @@
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
- "dev": true,
"requires": {
"has-flag": "^3.0.0"
}
diff --git a/package.json b/package.json
index b62c8e7d8..fcd9944ea 100644
--- a/package.json
+++ b/package.json
@@ -16,15 +16,17 @@
"astro": "astro.mjs"
},
"scripts": {
- "build": "tsc && npm run copy-js",
+ "build": "tsc",
+ "dev": "tsc --watch",
"lint": "eslint 'src/**/*.{js,ts}'",
- "dev": "concurrently 'tsc --watch' 'npm run copy-js:watch'",
"format": "prettier -w 'src/**/*.{js,ts}'",
- "copy-js": "copyfiles -u 1 src/*.js lib/",
- "copy-js:watch": "nodemon -w src --ext js -x 'npm run copy-js'",
"test": "uvu test -i fixtures -i test-utils.js"
},
"dependencies": {
+ "@babel/generator": "^7.13.9",
+ "@babel/traverse": "^7.13.0",
+ "@types/babel__generator": "^7.6.2",
+ "@types/babel__traverse": "^7.11.1",
"@types/estree": "0.0.46",
"@types/node": "^14.14.31",
"@types/react": "^17.0.3",
diff --git a/snowpack-plugin.cjs b/snowpack-plugin.cjs
index cacd1d017..6b5e76a5b 100644
--- a/snowpack-plugin.cjs
+++ b/snowpack-plugin.cjs
@@ -1,7 +1,7 @@
const { readFile } = require('fs').promises;
// Snowpack plugins must be CommonJS :(
-const transformPromise = import('./lib/transform2.js');
+const transformPromise = import('./lib/compiler/index.js');
module.exports = function (snowpackConfig, { resolve, extensions } = {}) {
return {
diff --git a/src/@types/astro.ts b/src/@types/astro.ts
index 8a92983f8..d0f9242c9 100644
--- a/src/@types/astro.ts
+++ b/src/@types/astro.ts
@@ -21,7 +21,7 @@ export interface JsxItem {
export interface TransformResult {
script: string;
- props: string[];
+ imports: string[];
items: JsxItem[];
}
diff --git a/src/@types/optimizer.ts b/src/@types/optimizer.ts
index c62976068..b9e228f3e 100644
--- a/src/@types/optimizer.ts
+++ b/src/@types/optimizer.ts
@@ -1,4 +1,4 @@
-import type { TemplateNode } from '../compiler/interfaces';
+import type { TemplateNode } from '../parser/interfaces';
export type VisitorFn = (node: TemplateNode) => void;
diff --git a/src/codegen/index.ts b/src/compiler/codegen.ts
similarity index 77%
rename from src/codegen/index.ts
rename to src/compiler/codegen.ts
index 2eb289887..52249fd77 100644
--- a/src/codegen/index.ts
+++ b/src/compiler/codegen.ts
@@ -1,13 +1,20 @@
import type { CompileOptions } from '../@types/compiler';
import type { ValidExtensionPlugins } from '../@types/astro';
-import type { Ast, TemplateNode } from '../compiler/interfaces';
+import type { Ast, TemplateNode } from '../parser/interfaces';
import type { JsxItem, TransformResult } from '../@types/astro';
import eslexer from 'es-module-lexer';
import esbuild from 'esbuild';
import path from 'path';
import { walk } from 'estree-walker';
+import babelParser from '@babel/parser';
+import _babelGenerator from '@babel/generator';
+import traverse from '@babel/traverse';
+import { ImportDeclaration,ExportNamedDeclaration, VariableDeclarator, Identifier, VariableDeclaration } from '@babel/types';
+const babelGenerator: typeof _babelGenerator =
+ // @ts-ignore
+ _babelGenerator.default;
const { transformSync } = esbuild;
interface Attribute {
@@ -43,8 +50,8 @@ function getAttributes(attrs: Attribute[]): Record {
'(' +
attr.value
.map((v: TemplateNode) => {
- if (v.expression) {
- return v.expression;
+ if (v.content) {
+ return v.content;
} else {
return JSON.stringify(getTextFromAttribute(v));
}
@@ -60,7 +67,7 @@ function getAttributes(attrs: Attribute[]): Record {
}
switch (val.type) {
case 'MustacheTag':
- result[attr.name] = '(' + val.expression + ')';
+ result[attr.name] = '(' + val.content + ')';
continue;
case 'Text':
result[attr.name] = JSON.stringify(getTextFromAttribute(val));
@@ -211,24 +218,68 @@ function compileExpressionSafe(raw: string): string {
export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Promise {
await eslexer.init;
- // Compile scripts as TypeScript, always
- const script = compileScriptSafe(ast.module ? ast.module.content : '');
+ const componentImports: ImportDeclaration[] = [];
+ const componentProps: VariableDeclarator[] = [];
+ const componentExports: ExportNamedDeclaration[] = [];
- // Collect all exported variables for props
- const scannedExports = eslexer.parse(script)[1].filter((n) => n !== 'setup' && n !== 'layout');
+ let script = '';
+ let propsStatement: string = '';
+ const importExportStatements: Set = new Set();
+ const components: Record = {};
- // Todo: Validate that `h` and `Fragment` aren't defined in the script
- const [scriptImports] = eslexer.parse(script, 'optional-sourcename');
- const components = Object.fromEntries(
- scriptImports.map((imp) => {
- const componentType = path.posix.extname(imp.n!);
- const componentName = path.posix.basename(imp.n!, componentType);
- return [componentName, { type: componentType, url: imp.n! }];
- })
- );
+ if (ast.module) {
+ const program = babelParser.parse(ast.module.content, {
+ sourceType: 'module',
+ plugins: ['jsx', 'typescript', 'topLevelAwait'],
+ }).program;
+
+ const { body } = program;
+ let i = body.length;
+ while (--i >= 0) {
+ const node = body[i];
+ if (node.type === 'ImportDeclaration') {
+ componentImports.push(node);
+ body.splice(i, 1);
+ }
+ if (/^Export/.test(node.type)) {
+ if (node.type === 'ExportNamedDeclaration' && node.declaration?.type === 'VariableDeclaration') {
+ const declaration = node.declaration.declarations[0];
+ if ((declaration.id as Identifier).name === '__layout' || (declaration.id as Identifier).name === '__content') {
+ componentExports.push(node);
+ } else {
+ componentProps.push(declaration);
+ }
+ body.splice(i, 1);
+ }
+ // const replacement = extract_exports(node);
+ }
+ }
+
+ for (const componentImport of componentImports) {
+ const importUrl = componentImport.source.value;
+ const componentType = path.posix.extname(importUrl);
+ const componentName = path.posix.basename(importUrl, componentType);
+ components[componentName] = { type: componentType, url: importUrl };
+ importExportStatements.add(ast.module.content.slice(componentImport.start!, componentImport.end!));
+ }
+ for (const componentImport of componentExports) {
+ importExportStatements.add(ast.module.content.slice(componentImport.start!, componentImport.end!));
+ }
+
+ if (componentProps.length > 0) {
+ propsStatement = 'let {';
+ for (const componentExport of componentProps) {
+ propsStatement += `${(componentExport.id as Identifier).name}`;
+ if (componentExport.init) {
+ propsStatement += `= ${babelGenerator(componentExport.init!).code }`;
+ }
+ propsStatement += `,`;
+ }
+ propsStatement += `} = props;`;
+ }
+ script = propsStatement + babelGenerator(program).code;
+ }
- const additionalImports = new Set();
- let headItem: JsxItem | undefined;
let items: JsxItem[] = [];
let collectionItem: JsxItem | undefined;
let currentItemName: string | undefined;
@@ -238,7 +289,7 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro
enter(node: TemplateNode) {
switch (node.type) {
case 'MustacheTag':
- let code = compileExpressionSafe(node.expression);
+ let code = compileExpressionSafe(node.content);
let matches: RegExpExecArray[] = [];
let match: RegExpExecArray | null | undefined;
@@ -255,13 +306,14 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro
}
const { wrapper, wrapperImport } = getComponentWrapper(name, components[componentName], compileOptions);
if (wrapperImport) {
- additionalImports.add(wrapperImport);
+ importExportStatements.add(wrapperImport);
}
if (wrapper !== name) {
code = code.slice(0, match.index + 2) + wrapper + code.slice(match.index + match[0].length - 1);
}
}
collectionItem!.jsx += `,(${code.trim().replace(/\;$/, '')})`;
+ this.skip();
return;
case 'Comment':
return;
@@ -287,11 +339,6 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro
currentItemName = name;
if (!collectionItem) {
collectionItem = { name, jsx: '' };
- if (node.type === 'Head') {
- collectionItem.jsx += `h(Fragment, null`;
- headItem = collectionItem;
- return;
- }
items.push(collectionItem);
}
collectionItem.jsx += collectionItem.jsx === '' ? '' : ',';
@@ -311,7 +358,7 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro
}
const { wrapper, wrapperImport } = getComponentWrapper(name, components[componentName], compileOptions);
if (wrapperImport) {
- additionalImports.add(wrapperImport);
+ importExportStatements.add(wrapperImport);
}
collectionItem.jsx += `h(${wrapper}, ${attributes ? generateAttributes(attributes) : 'null'}`;
@@ -381,8 +428,8 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro
});
return {
- script: script + '\n' + Array.from(additionalImports).join('\n'),
+ script: script,
+ imports: Array.from(importExportStatements),
items,
- props: scannedExports,
};
}
diff --git a/src/compiler/index.ts b/src/compiler/index.ts
index 718199c94..e09664a19 100644
--- a/src/compiler/index.ts
+++ b/src/compiler/index.ts
@@ -1 +1,171 @@
-export { default as parse } from './parse/index.js';
+import type { LogOptions } from '../logger.js';
+
+import path from 'path';
+import micromark from 'micromark';
+import gfmSyntax from 'micromark-extension-gfm';
+import matter from 'gray-matter';
+import gfmHtml from 'micromark-extension-gfm/html.js';
+import { CompileResult, TransformResult } from '../@types/astro';
+import { parse } from '../parser/index.js';
+import { createMarkdownHeadersCollector } from '../micromark-collect-headers.js';
+import { encodeMarkdown } from '../micromark-encode.js';
+import { defaultLogOptions } from '../logger.js';
+import { optimize } from './optimize/index.js';
+import { codegen } from './codegen.js';
+
+interface CompileOptions {
+ logging: LogOptions;
+ resolve: (p: string) => string;
+}
+
+const defaultCompileOptions: CompileOptions = {
+ logging: defaultLogOptions,
+ resolve: (p: string) => p,
+};
+
+function internalImport(internalPath: string) {
+ return `/_astro_internal/${internalPath}`;
+}
+
+interface ConvertAstroOptions {
+ compileOptions: CompileOptions;
+ filename: string;
+ fileID: string;
+}
+
+async function convertAstroToJsx(template: string, opts: ConvertAstroOptions): Promise {
+ const { filename } = opts;
+
+ // 1. Parse
+ const ast = parse(template, {
+ filename,
+ });
+
+ // 2. Optimize the AST
+ await optimize(ast, opts);
+
+ // Turn AST into JSX
+ return await codegen(ast, opts);
+}
+
+async function convertMdToJsx(
+ contents: string,
+ { compileOptions, filename, fileID }: { compileOptions: CompileOptions; filename: string; fileID: string }
+): Promise {
+ const { data: frontmatterData, content } = matter(contents);
+ const { headers, headersExtension } = createMarkdownHeadersCollector();
+ const mdHtml = micromark(content, {
+ allowDangerousHtml: true,
+ extensions: [gfmSyntax()],
+ htmlExtensions: [gfmHtml, encodeMarkdown, headersExtension],
+ });
+
+ // TODO: Warn if reserved word is used in "frontmatterData"
+ const contentData: any = {
+ ...frontmatterData,
+ headers,
+ source: content,
+ };
+
+ let imports = '';
+ for (let [ComponentName, specifier] of Object.entries(frontmatterData.import || {})) {
+ imports += `import ${ComponentName} from '${specifier}';\n`;
+ }
+
+ // can't be anywhere inside of a JS string, otherwise the HTML parser fails.
+ // Break it up here so that the HTML parser won't detect it.
+ const stringifiedSetupContext = JSON.stringify(contentData).replace(/\<\/script\>/g, ``);
+
+ const raw = `---
+ ${imports}
+ ${frontmatterData.layout ? `export const __layout = ${JSON.stringify(frontmatterData.layout)};` : ''}
+ export const __content = ${stringifiedSetupContext};
+---
+`;
+
+ const convertOptions = { compileOptions, filename, fileID };
+
+ return convertAstroToJsx(raw, convertOptions);
+}
+
+type SupportedExtensions = '.astro' | '.md';
+
+async function transformFromSource(
+ contents: string,
+ { compileOptions, filename, projectRoot }: { compileOptions: CompileOptions; filename: string; projectRoot: string }
+): Promise {
+ const fileID = path.relative(projectRoot, filename);
+ switch (path.extname(filename) as SupportedExtensions) {
+ case '.astro':
+ return convertAstroToJsx(contents, { compileOptions, filename, fileID });
+ case '.md':
+ return convertMdToJsx(contents, { compileOptions, filename, fileID });
+ default:
+ throw new Error('Not Supported!');
+ }
+}
+
+
+export async function compileComponent(
+ source: string,
+ { compileOptions = defaultCompileOptions, filename, projectRoot }: { compileOptions: CompileOptions; filename: string; projectRoot: string }
+): Promise {
+ const sourceJsx = await transformFromSource(source, { compileOptions, filename, projectRoot });
+ const isPage = path.extname(filename) === '.md' || sourceJsx.items.some((item) => item.name === 'html');
+ // sort