diff options
-rw-r--r-- | examples/snowpack/astro/components/BaseLayout.astro | 2 | ||||
-rw-r--r-- | examples/snowpack/astro/components/MainLayout.astro | 3 | ||||
-rw-r--r-- | examples/snowpack/astro/components/PokemonLookup.astro | 16 | ||||
-rw-r--r-- | examples/snowpack/astro/layouts/content-with-cover.astro | 2 | ||||
-rw-r--r-- | examples/snowpack/astro/pages/guides.astro | 3 | ||||
-rw-r--r-- | examples/snowpack/astro/pages/news.astro | 18 | ||||
-rw-r--r-- | package-lock.json | 112 | ||||
-rw-r--r-- | package.json | 10 | ||||
-rw-r--r-- | snowpack-plugin.cjs | 2 | ||||
-rw-r--r-- | src/@types/astro.ts | 2 | ||||
-rw-r--r-- | src/@types/optimizer.ts | 2 | ||||
-rw-r--r-- | src/compiler/codegen.ts (renamed from src/codegen/index.ts) | 105 | ||||
-rw-r--r-- | src/compiler/index.ts | 172 | ||||
-rw-r--r-- | src/compiler/optimize/index.ts (renamed from src/optimize/index.ts) | 4 | ||||
-rw-r--r-- | src/compiler/optimize/styles.ts (renamed from src/optimize/styles.ts) | 4 | ||||
-rw-r--r-- | src/logger.ts | 2 | ||||
-rw-r--r-- | src/micromark-collect-headers.ts | 1 | ||||
-rw-r--r-- | src/parser/README.md (renamed from src/compiler/README.md) | 0 | ||||
-rw-r--r-- | src/parser/Stats.ts (renamed from src/compiler/Stats.ts) | 0 | ||||
-rw-r--r-- | src/parser/config.ts (renamed from src/compiler/config.ts) | 0 | ||||
-rw-r--r-- | src/parser/index.ts | 1 | ||||
-rw-r--r-- | src/parser/interfaces.ts (renamed from src/compiler/interfaces.ts) | 6 | ||||
-rw-r--r-- | src/parser/parse/acorn.ts (renamed from src/compiler/parse/acorn.ts) | 2 | ||||
-rw-r--r-- | src/parser/parse/index.ts (renamed from src/compiler/parse/index.ts) | 0 | ||||
-rw-r--r-- | src/parser/parse/read/context.ts (renamed from src/compiler/parse/read/context.ts) | 0 | ||||
-rw-r--r-- | src/parser/parse/read/expression.ts (renamed from src/compiler/parse/read/expression.ts) | 2 | ||||
-rw-r--r-- | src/parser/parse/read/script.ts (renamed from src/compiler/parse/read/script.ts) | 0 | ||||
-rw-r--r-- | src/parser/parse/read/style.ts (renamed from src/compiler/parse/read/style.ts) | 0 | ||||
-rw-r--r-- | src/parser/parse/state/fragment.ts (renamed from src/compiler/parse/state/fragment.ts) | 0 | ||||
-rw-r--r-- | src/parser/parse/state/mustache.ts (renamed from src/compiler/parse/state/mustache.ts) | 4 | ||||
-rw-r--r-- | src/parser/parse/state/setup.ts (renamed from src/compiler/parse/state/setup.ts) | 0 | ||||
-rw-r--r-- | src/parser/parse/state/tag.ts (renamed from src/compiler/parse/state/tag.ts) | 6 | ||||
-rw-r--r-- | src/parser/parse/state/text.ts (renamed from src/compiler/parse/state/text.ts) | 0 | ||||
-rw-r--r-- | src/parser/parse/utils/bracket.ts (renamed from src/compiler/parse/utils/bracket.ts) | 0 | ||||
-rw-r--r-- | src/parser/parse/utils/entities.ts (renamed from src/compiler/parse/utils/entities.ts) | 0 | ||||
-rw-r--r-- | src/parser/parse/utils/html.ts (renamed from src/compiler/parse/utils/html.ts) | 0 | ||||
-rw-r--r-- | src/parser/parse/utils/node.ts (renamed from src/compiler/parse/utils/node.ts) | 0 | ||||
-rw-r--r-- | src/parser/utils/error.ts (renamed from src/compiler/utils/error.ts) | 0 | ||||
-rw-r--r-- | src/parser/utils/full_char_code_at.ts (renamed from src/compiler/utils/full_char_code_at.ts) | 0 | ||||
-rw-r--r-- | src/parser/utils/fuzzymatch.ts (renamed from src/compiler/utils/fuzzymatch.ts) | 0 | ||||
-rw-r--r-- | src/parser/utils/get_code_frame.ts (renamed from src/compiler/utils/get_code_frame.ts) | 0 | ||||
-rw-r--r-- | src/parser/utils/link.ts (renamed from src/compiler/utils/link.ts) | 0 | ||||
-rw-r--r-- | src/parser/utils/list.ts (renamed from src/compiler/utils/list.ts) | 0 | ||||
-rw-r--r-- | src/parser/utils/names.ts (renamed from src/compiler/utils/names.ts) | 0 | ||||
-rw-r--r-- | src/parser/utils/namespaces.ts (renamed from src/compiler/utils/namespaces.ts) | 0 | ||||
-rw-r--r-- | src/parser/utils/nodes_match.ts (renamed from src/compiler/utils/nodes_match.ts) | 0 | ||||
-rw-r--r-- | src/parser/utils/patterns.ts (renamed from src/compiler/utils/patterns.ts) | 0 | ||||
-rw-r--r-- | src/parser/utils/trim.ts (renamed from src/compiler/utils/trim.ts) | 0 | ||||
-rw-r--r-- | src/runtime.ts | 2 | ||||
-rw-r--r-- | src/transform2.ts | 172 |
50 files changed, 397 insertions, 258 deletions
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'; --- -<Banner></Banner> +<Banner /> <Nav /> <slot></slot> 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 {}; -} --- <BaseLayout> 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(); +--- + +<style> +.mb1 { margin-bottom: 1rem; } +</style> + +<div class="notification mb1"> + <div class="container"> + Pokemon #{number} is: {pokemonData.name} + </div> +</div> 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; </h2> <div class="content-layout"> <div class="content-body"> - {content.body} + <slot></slot> </div> </div> </div> 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 {}; -} --- <html> 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.'; --- <html> @@ -40,9 +34,9 @@ export async function setup({ context, request, fetch }) { <a href="https://github.com/snowpackjs/snowpack/edit/main/www/_data/news.js">Submit it!</a> </p> - <p> - In case you're curious, the best pokemon is <strong>{pokemonData.name}.</strong> - </p> + <PokemonLookup number={1} /> + <PokemonLookup number={2} /> + <PokemonLookup number={3} /> <div class="card-grid card-grid-3"> <article class="discord-banner"> 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 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<string, string> { '(' + 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<string, string> { } 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<TransformResult> { 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<string> = new Set(); + const components: Record<string, { type: string; url: string }> = {}; - // 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<string>(); - 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<TransformResult> { + 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<TransformResult> { + 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`; + } + + // </script> 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, `</scrip" + "t>`); + + const raw = `--- + ${imports} + ${frontmatterData.layout ? `export const __layout = ${JSON.stringify(frontmatterData.layout)};` : ''} + export const __content = ${stringifiedSetupContext}; +--- +<section>${mdHtml}</section>`; + + 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<TransformResult> { + 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<CompileResult> { + const sourceJsx = await transformFromSource(source, { compileOptions, filename, projectRoot }); + const isPage = path.extname(filename) === '.md' || sourceJsx.items.some((item) => item.name === 'html'); + // sort <style> tags first + sourceJsx.items.sort((a, b) => (a.name === 'style' && b.name !== 'style' ? -1 : 0)); + + // return template + let modJsx = ` +import fetch from 'node-fetch'; + +// <script astro></script> +${sourceJsx.imports.join('\n')} + +// \`__render()\`: Render the contents of the Astro module. +import { h, Fragment } from '${internalImport('h.js')}'; +async function __render(props, ...children) { + ${sourceJsx.script} + return h(Fragment, null, ${sourceJsx.items.map(({ jsx }) => jsx).join(',')}); +} +export default __render; +`; + + if (isPage) { + modJsx += ` +// \`__renderPage()\`: Render the contents of the Astro module as a page. This is a special flow, +// triggered by loading a component directly by URL. +export async function __renderPage({request, children, props}) { + + const currentChild = { + setup: typeof setup === 'undefined' ? (passthrough) => passthrough : setup, + layout: typeof __layout === 'undefined' ? undefined : __layout, + content: typeof __content === 'undefined' ? undefined : __content, + __render, + }; + + await currentChild.setup({request}); + const childBodyResult = await currentChild.__render(props, children); + + // find layout, if one was given. + if (currentChild.layout) { + const layoutComponent = (await import('/_astro/layouts/' + currentChild.layout.replace(/.*layouts\\//, "").replace(/\.astro$/, '.js'))); + return layoutComponent.__renderPage({ + request, + props: {content: currentChild.content}, + children: [childBodyResult], + }); + } + + return childBodyResult; +};\n`; + } else { + modJsx += ` +export async function __renderPage() { throw new Error("No <html> page element found!"); }\n`; + } + + return { + result: sourceJsx, + contents: modJsx, + }; +} diff --git a/src/optimize/index.ts b/src/compiler/optimize/index.ts index 9f8ec2f05..4f6e54fa5 100644 --- a/src/optimize/index.ts +++ b/src/compiler/optimize/index.ts @@ -1,6 +1,6 @@ import { walk } from 'estree-walker'; -import type { Ast, TemplateNode } from '../compiler/interfaces'; -import { NodeVisitor, Optimizer, VisitorFn } from '../@types/optimizer'; +import type { Ast, TemplateNode } from '../../parser/interfaces'; +import { NodeVisitor, Optimizer, VisitorFn } from '../../@types/optimizer'; import optimizeStyles from './styles.js'; interface VisitorCollection { diff --git a/src/optimize/styles.ts b/src/compiler/optimize/styles.ts index c69bee504..691300067 100644 --- a/src/optimize/styles.ts +++ b/src/compiler/optimize/styles.ts @@ -5,8 +5,8 @@ import postcss from 'postcss'; import postcssModules from 'postcss-modules'; import findUp from 'find-up'; import sass from 'sass'; -import { Optimizer } from '../@types/optimizer'; -import type { TemplateNode } from '../compiler/interfaces'; +import { Optimizer } from '../../@types/optimizer'; +import type { TemplateNode } from '../../parser/interfaces'; type StyleType = 'css' | 'scss' | 'sass' | 'postcss'; diff --git a/src/logger.ts b/src/logger.ts index 4bdac162a..6634c5092 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,4 +1,4 @@ -import type { CompileError } from './compiler/utils/error.js'; +import type { CompileError } from './parser/utils/error.js'; import { bold, blue, red, grey, underline } from 'kleur/colors'; import { Writable } from 'stream'; import { format as utilFormat } from 'util'; diff --git a/src/micromark-collect-headers.ts b/src/micromark-collect-headers.ts index d69a0a358..78567699c 100644 --- a/src/micromark-collect-headers.ts +++ b/src/micromark-collect-headers.ts @@ -28,7 +28,6 @@ export function createMarkdownHeadersCollector() { this.tag(`<h${currentHeader.depth} id="${currentHeader.slug}">`); this.raw(currentHeader.text); this.tag(`</h${currentHeader.depth}>`); - // console.log(this.sliceSerialize(node)); }, } as any, } as any, diff --git a/src/compiler/README.md b/src/parser/README.md index f44d45ecf..f44d45ecf 100644 --- a/src/compiler/README.md +++ b/src/parser/README.md diff --git a/src/compiler/Stats.ts b/src/parser/Stats.ts index 33802a42b..33802a42b 100644 --- a/src/compiler/Stats.ts +++ b/src/parser/Stats.ts diff --git a/src/compiler/config.ts b/src/parser/config.ts index e6d0f65a7..e6d0f65a7 100644 --- a/src/compiler/config.ts +++ b/src/parser/config.ts diff --git a/src/parser/index.ts b/src/parser/index.ts new file mode 100644 index 000000000..718199c94 --- /dev/null +++ b/src/parser/index.ts @@ -0,0 +1 @@ +export { default as parse } from './parse/index.js'; diff --git a/src/compiler/interfaces.ts b/src/parser/interfaces.ts index b77357d23..89c99aa20 100644 --- a/src/compiler/interfaces.ts +++ b/src/parser/interfaces.ts @@ -1,5 +1,5 @@ -import { Node, Program } from 'estree'; -import { SourceMap } from 'magic-string'; +import type { Expression, Program } from '@babel/types'; +import type { SourceMap } from 'magic-string'; interface BaseNode { start: number; @@ -21,7 +21,7 @@ export interface Text extends BaseNode { export interface MustacheTag extends BaseNode { type: 'MustacheTag'; - expression: string; + content: string; } export type DirectiveType = 'Action' | 'Animation' | 'Binding' | 'Class' | 'EventHandler' | 'Let' | 'Ref' | 'Transition'; diff --git a/src/compiler/parse/acorn.ts b/src/parser/parse/acorn.ts index c70756d79..7fc0f8f67 100644 --- a/src/compiler/parse/acorn.ts +++ b/src/parser/parse/acorn.ts @@ -39,4 +39,4 @@ export const parse_expression_at = (source: string, index: number): number => { // sourceType: 'module', // ecmaVersion: 2020, // locations: true, -// }); +// });
\ No newline at end of file diff --git a/src/compiler/parse/index.ts b/src/parser/parse/index.ts index 052cf0317..052cf0317 100644 --- a/src/compiler/parse/index.ts +++ b/src/parser/parse/index.ts diff --git a/src/compiler/parse/read/context.ts b/src/parser/parse/read/context.ts index 4d8f12060..4d8f12060 100644 --- a/src/compiler/parse/read/context.ts +++ b/src/parser/parse/read/context.ts diff --git a/src/compiler/parse/read/expression.ts b/src/parser/parse/read/expression.ts index 1fe5f05f1..f691f4772 100644 --- a/src/compiler/parse/read/expression.ts +++ b/src/parser/parse/read/expression.ts @@ -39,4 +39,4 @@ export default function read_expression(parser: Parser): string { } catch (err) { parser.acorn_error(err); } -} +}
\ No newline at end of file diff --git a/src/compiler/parse/read/script.ts b/src/parser/parse/read/script.ts index 7afbfb08f..7afbfb08f 100644 --- a/src/compiler/parse/read/script.ts +++ b/src/parser/parse/read/script.ts diff --git a/src/compiler/parse/read/style.ts b/src/parser/parse/read/style.ts index f23d7b10e..f23d7b10e 100644 --- a/src/compiler/parse/read/style.ts +++ b/src/parser/parse/read/style.ts diff --git a/src/compiler/parse/state/fragment.ts b/src/parser/parse/state/fragment.ts index 97398b227..97398b227 100644 --- a/src/compiler/parse/state/fragment.ts +++ b/src/parser/parse/state/fragment.ts diff --git a/src/compiler/parse/state/mustache.ts b/src/parser/parse/state/mustache.ts index 79372d8d9..8ffac4f85 100644 --- a/src/compiler/parse/state/mustache.ts +++ b/src/parser/parse/state/mustache.ts @@ -397,7 +397,7 @@ export default function mustache(parser: Parser) { // }); throw new Error('@debug not yet supported'); } else { - const expression = read_expression(parser); + const content = read_expression(parser); parser.allow_whitespace(); parser.eat('}', true); @@ -407,7 +407,7 @@ export default function mustache(parser: Parser) { start, end: parser.index, type: 'MustacheTag', - expression, + content, }); } } diff --git a/src/compiler/parse/state/setup.ts b/src/parser/parse/state/setup.ts index f64d8c52b..f64d8c52b 100644 --- a/src/compiler/parse/state/setup.ts +++ b/src/parser/parse/state/setup.ts diff --git a/src/compiler/parse/state/tag.ts b/src/parser/parse/state/tag.ts index c6684874c..1003be14c 100644 --- a/src/compiler/parse/state/tag.ts +++ b/src/parser/parse/state/tag.ts @@ -351,7 +351,7 @@ function read_attribute(parser: Parser, unique_names: Set<string>) { parser.allow_whitespace(); if (parser.eat('...')) { - const expression = read_expression(parser); + const {expression} = read_expression(parser); parser.allow_whitespace(); parser.eat('}', true); @@ -549,7 +549,7 @@ function read_sequence(parser: Parser, done: () => boolean): TemplateNode[] { flush(); parser.allow_whitespace(); - const expression = read_expression(parser); + const content = read_expression(parser); parser.allow_whitespace(); parser.eat('}', true); @@ -557,7 +557,7 @@ function read_sequence(parser: Parser, done: () => boolean): TemplateNode[] { start: index, end: parser.index, type: 'MustacheTag', - expression, + content, }); current_chunk = { diff --git a/src/compiler/parse/state/text.ts b/src/parser/parse/state/text.ts index cca83f2d4..cca83f2d4 100644 --- a/src/compiler/parse/state/text.ts +++ b/src/parser/parse/state/text.ts diff --git a/src/compiler/parse/utils/bracket.ts b/src/parser/parse/utils/bracket.ts index 7e885ad78..7e885ad78 100644 --- a/src/compiler/parse/utils/bracket.ts +++ b/src/parser/parse/utils/bracket.ts diff --git a/src/compiler/parse/utils/entities.ts b/src/parser/parse/utils/entities.ts index e554664eb..e554664eb 100644 --- a/src/compiler/parse/utils/entities.ts +++ b/src/parser/parse/utils/entities.ts diff --git a/src/compiler/parse/utils/html.ts b/src/parser/parse/utils/html.ts index 3b406c9cc..3b406c9cc 100644 --- a/src/compiler/parse/utils/html.ts +++ b/src/parser/parse/utils/html.ts diff --git a/src/compiler/parse/utils/node.ts b/src/parser/parse/utils/node.ts index 45769f96e..45769f96e 100644 --- a/src/compiler/parse/utils/node.ts +++ b/src/parser/parse/utils/node.ts diff --git a/src/compiler/utils/error.ts b/src/parser/utils/error.ts index 3c1b23e4c..3c1b23e4c 100644 --- a/src/compiler/utils/error.ts +++ b/src/parser/utils/error.ts diff --git a/src/compiler/utils/full_char_code_at.ts b/src/parser/utils/full_char_code_at.ts index fea5151b6..fea5151b6 100644 --- a/src/compiler/utils/full_char_code_at.ts +++ b/src/parser/utils/full_char_code_at.ts diff --git a/src/compiler/utils/fuzzymatch.ts b/src/parser/utils/fuzzymatch.ts index d24d0fd0a..d24d0fd0a 100644 --- a/src/compiler/utils/fuzzymatch.ts +++ b/src/parser/utils/fuzzymatch.ts diff --git a/src/compiler/utils/get_code_frame.ts b/src/parser/utils/get_code_frame.ts index a0c296672..a0c296672 100644 --- a/src/compiler/utils/get_code_frame.ts +++ b/src/parser/utils/get_code_frame.ts diff --git a/src/compiler/utils/link.ts b/src/parser/utils/link.ts index 0dc5af1b7..0dc5af1b7 100644 --- a/src/compiler/utils/link.ts +++ b/src/parser/utils/link.ts diff --git a/src/compiler/utils/list.ts b/src/parser/utils/list.ts index ba1ef9f4c..ba1ef9f4c 100644 --- a/src/compiler/utils/list.ts +++ b/src/parser/utils/list.ts diff --git a/src/compiler/utils/names.ts b/src/parser/utils/names.ts index f2e1dfc8e..f2e1dfc8e 100644 --- a/src/compiler/utils/names.ts +++ b/src/parser/utils/names.ts diff --git a/src/compiler/utils/namespaces.ts b/src/parser/utils/namespaces.ts index 5f61beff9..5f61beff9 100644 --- a/src/compiler/utils/namespaces.ts +++ b/src/parser/utils/namespaces.ts diff --git a/src/compiler/utils/nodes_match.ts b/src/parser/utils/nodes_match.ts index 563742635..563742635 100644 --- a/src/compiler/utils/nodes_match.ts +++ b/src/parser/utils/nodes_match.ts diff --git a/src/compiler/utils/patterns.ts b/src/parser/utils/patterns.ts index 317a7c199..317a7c199 100644 --- a/src/compiler/utils/patterns.ts +++ b/src/parser/utils/patterns.ts diff --git a/src/compiler/utils/trim.ts b/src/parser/utils/trim.ts index 406a8c97f..406a8c97f 100644 --- a/src/compiler/utils/trim.ts +++ b/src/parser/utils/trim.ts diff --git a/src/runtime.ts b/src/runtime.ts index aff5ee7d2..4b5d51f07 100644 --- a/src/runtime.ts +++ b/src/runtime.ts @@ -1,7 +1,7 @@ import type { SnowpackDevServer, ServerRuntime as SnowpackServerRuntime, LoadResult as SnowpackLoadResult } from 'snowpack'; import type { AstroConfig } from './@types/astro'; import type { LogOptions } from './logger'; -import type { CompileError } from './compiler/utils/error.js'; +import type { CompileError } from './parser/utils/error.js'; import { info, error, parseError } from './logger.js'; import { existsSync, promises as fsPromises } from 'fs'; diff --git a/src/transform2.ts b/src/transform2.ts deleted file mode 100644 index 47c3659e7..000000000 --- a/src/transform2.ts +++ /dev/null @@ -1,172 +0,0 @@ -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 './compiler/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/index.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<TransformResult> { - 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<TransformResult> { - 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, - html: mdHtml, - }; - - let imports = ''; - for (let [ComponentName, specifier] of Object.entries(frontmatterData.import || {})) { - imports += `import ${ComponentName} from '${specifier}';\n`; - } - - // </script> 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, `</scrip" + "t>`); - - const raw = `--- - ${imports} - ${frontmatterData.layout ? `const __layout = ${JSON.stringify(frontmatterData.layout)};` : ''} - const __content = ${stringifiedSetupContext}; ---- -<section>${mdHtml}</section>`; - - 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<TransformResult> { - 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<CompileResult> { - const sourceJsx = await transformFromSource(source, { compileOptions, filename, projectRoot }); - - // sort <style> tags first - // TODO: remove these and inject in <head> - const isPage = path.extname(filename) === '.md' || sourceJsx.items.some((item) => item.name === 'html'); - sourceJsx.items.sort((a, b) => (a.name === 'style' && b.name !== 'style' ? -1 : 0)); - - // return template - let modJsx = ` -// <script astro></script> -${sourceJsx.script} - -// \`__render()\`: Render the contents of the Astro module. -import { h, Fragment } from '${internalImport('h.js')}'; -function __render(props, ...children) { - ${sourceJsx.props.map((p) => `${p} = props.${p} ?? ${p};`).join('\n')} - return h(Fragment, null, ${sourceJsx.items.map(({ jsx }) => jsx).join(',')}); -} -export default __render; -`; - - if (isPage) { - modJsx += ` -// \`__renderPage()\`: Render the contents of the Astro module as a page. This is a special flow, -// triggered by loading a component directly by URL. -export async function __renderPage({request, children, props}) { - - const currentChild = { - setup: typeof setup === 'undefined' ? (passthrough) => passthrough : setup, - layout: typeof __layout === 'undefined' ? undefined : __layout, - content: typeof __content === 'undefined' ? undefined : __content, - __render, - }; - - const fetch = (await import('node-fetch')).default; - await currentChild.setup({request, fetch}); - const childBodyResult = await currentChild.__render(props, children); - - // find layout, if one was given. - if (currentChild.layout) { - const layoutComponent = (await import('/_astro/layouts/' + currentChild.layout.replace(/.*layouts\\//, "").replace(/\.astro$/, '.js'))); - return layoutComponent.__renderPage({ - request, - props: {content: currentChild.content}, - children: [childBodyResult], - }); - } - - return childBodyResult; -};\n`; - } else { - modJsx += ` -export async function __renderPage() { throw new Error("No <html> page element found!"); }\n`; - } - - return { - result: sourceJsx, - contents: modJsx, - }; -} |