add component state, top-level await support (#26)
This commit is contained in:
parent
a72ab10c62
commit
30cccdf715
50 changed files with 397 additions and 258 deletions
|
@ -3,7 +3,7 @@ import Banner from './Banner.astro';
|
||||||
import Nav from './Nav.astro';
|
import Nav from './Nav.astro';
|
||||||
---
|
---
|
||||||
|
|
||||||
<Banner></Banner>
|
<Banner />
|
||||||
<Nav />
|
<Nav />
|
||||||
|
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
---
|
---
|
||||||
import BaseLayout from './BaseLayout.astro';
|
import BaseLayout from './BaseLayout.astro';
|
||||||
import Menu from './Menu.astro';
|
import Menu from './Menu.astro';
|
||||||
export function setup({ context }) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout>
|
<BaseLayout>
|
||||||
|
|
16
examples/snowpack/astro/components/PokemonLookup.astro
Normal file
16
examples/snowpack/astro/components/PokemonLookup.astro
Normal file
|
@ -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>
|
|
@ -73,7 +73,7 @@ export let content: any;
|
||||||
</h2>
|
</h2>
|
||||||
<div class="content-layout">
|
<div class="content-layout">
|
||||||
<div class="content-body">
|
<div class="content-body">
|
||||||
{content.body}
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -25,7 +25,6 @@ let guides;
|
||||||
let communityGuides;
|
let communityGuides;
|
||||||
|
|
||||||
|
|
||||||
export function setup({ /* paginate */ }) {
|
|
||||||
guides = paginate({
|
guides = paginate({
|
||||||
files: '/posts/guides/*.md',
|
files: '/posts/guides/*.md',
|
||||||
// sort: ((a, b) => new Date(b) - new Date(a)),
|
// sort: ((a, b) => new Date(b) - new Date(a)),
|
||||||
|
@ -39,8 +38,6 @@ export function setup({ /* paginate */ }) {
|
||||||
tag: 'communityGuides',
|
tag: 'communityGuides',
|
||||||
limit: 10,
|
limit: 10,
|
||||||
});
|
});
|
||||||
return {};
|
|
||||||
}
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<html>
|
<html>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
---
|
---
|
||||||
import Card from '../components/Card.jsx';
|
import Card from '../components/Card.jsx';
|
||||||
|
import PokemonLookup from '../components/PokemonLookup.astro';
|
||||||
import CompanyLogo from '../components/CompanyLogo.jsx';
|
import CompanyLogo from '../components/CompanyLogo.jsx';
|
||||||
import NewsAssets from '../components/NewsAssets.svelte';
|
import NewsAssets from '../components/NewsAssets.svelte';
|
||||||
import NewsTitle from '../components/NewsTitle.vue';
|
import NewsTitle from '../components/NewsTitle.vue';
|
||||||
|
@ -11,15 +12,8 @@ import MainLayout from '../components/MainLayout.astro';
|
||||||
import news from '../data/news.json';
|
import news from '../data/news.json';
|
||||||
import users from '../data/users.json';
|
import users from '../data/users.json';
|
||||||
|
|
||||||
let title = 'Community & News';
|
const title = 'Community & News';
|
||||||
let description = 'Snowpack community news and companies that use Snowpack.';
|
const 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 {};
|
|
||||||
}
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<html>
|
<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>
|
<a href="https://github.com/snowpackjs/snowpack/edit/main/www/_data/news.js">Submit it!</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<PokemonLookup number={1} />
|
||||||
In case you're curious, the best pokemon is <strong>{pokemonData.name}.</strong>
|
<PokemonLookup number={2} />
|
||||||
</p>
|
<PokemonLookup number={3} />
|
||||||
|
|
||||||
<div class="card-grid card-grid-3">
|
<div class="card-grid card-grid-3">
|
||||||
<article class="discord-banner">
|
<article class="discord-banner">
|
||||||
|
|
112
package-lock.json
generated
112
package-lock.json
generated
|
@ -8,11 +8,53 @@
|
||||||
"version": "7.12.13",
|
"version": "7.12.13",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
|
||||||
"integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==",
|
"integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/highlight": "^7.12.13"
|
"@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": {
|
"@babel/helper-validator-identifier": {
|
||||||
"version": "7.12.11",
|
"version": "7.12.11",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
|
||||||
|
@ -22,7 +64,6 @@
|
||||||
"version": "7.13.10",
|
"version": "7.13.10",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz",
|
||||||
"integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==",
|
"integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/helper-validator-identifier": "^7.12.11",
|
"@babel/helper-validator-identifier": "^7.12.11",
|
||||||
"chalk": "^2.0.0",
|
"chalk": "^2.0.0",
|
||||||
|
@ -33,7 +74,6 @@
|
||||||
"version": "3.2.1",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"color-convert": "^1.9.0"
|
"color-convert": "^1.9.0"
|
||||||
}
|
}
|
||||||
|
@ -42,7 +82,6 @@
|
||||||
"version": "2.4.2",
|
"version": "2.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-styles": "^3.2.1",
|
"ansi-styles": "^3.2.1",
|
||||||
"escape-string-regexp": "^1.0.5",
|
"escape-string-regexp": "^1.0.5",
|
||||||
|
@ -53,7 +92,6 @@
|
||||||
"version": "1.9.3",
|
"version": "1.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"color-name": "1.1.3"
|
"color-name": "1.1.3"
|
||||||
}
|
}
|
||||||
|
@ -61,8 +99,7 @@
|
||||||
"color-name": {
|
"color-name": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
||||||
"dev": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -71,6 +108,39 @@
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.11.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.11.tgz",
|
||||||
"integrity": "sha512-PhuoqeHoO9fc4ffMEVk4qb/w/s2iOSWohvbHxLtxui0eBg3Lg5gN1U8wp1V1u61hOWkPQJJyJzGH6Y+grwkq8Q=="
|
"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": {
|
"@babel/types": {
|
||||||
"version": "7.13.0",
|
"version": "7.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.0.tgz",
|
||||||
|
@ -162,6 +232,22 @@
|
||||||
"defer-to-connect": "^1.0.1"
|
"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": {
|
"@types/estree": {
|
||||||
"version": "0.0.46",
|
"version": "0.0.46",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.46.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.46.tgz",
|
||||||
|
@ -1220,8 +1306,7 @@
|
||||||
"escape-string-regexp": {
|
"escape-string-regexp": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"eslint": {
|
"eslint": {
|
||||||
"version": "7.22.0",
|
"version": "7.22.0",
|
||||||
|
@ -1741,8 +1826,7 @@
|
||||||
"has-flag": {
|
"has-flag": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
|
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"has-yarn": {
|
"has-yarn": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
|
@ -2039,6 +2123,11 @@
|
||||||
"esprima": "^4.0.0"
|
"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": {
|
"json-buffer": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz",
|
||||||
|
@ -3159,7 +3248,6 @@
|
||||||
"version": "5.5.0",
|
"version": "5.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"has-flag": "^3.0.0"
|
"has-flag": "^3.0.0"
|
||||||
}
|
}
|
||||||
|
|
10
package.json
10
package.json
|
@ -16,15 +16,17 @@
|
||||||
"astro": "astro.mjs"
|
"astro": "astro.mjs"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc && npm run copy-js",
|
"build": "tsc",
|
||||||
|
"dev": "tsc --watch",
|
||||||
"lint": "eslint 'src/**/*.{js,ts}'",
|
"lint": "eslint 'src/**/*.{js,ts}'",
|
||||||
"dev": "concurrently 'tsc --watch' 'npm run copy-js:watch'",
|
|
||||||
"format": "prettier -w 'src/**/*.{js,ts}'",
|
"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"
|
"test": "uvu test -i fixtures -i test-utils.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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/estree": "0.0.46",
|
||||||
"@types/node": "^14.14.31",
|
"@types/node": "^14.14.31",
|
||||||
"@types/react": "^17.0.3",
|
"@types/react": "^17.0.3",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const { readFile } = require('fs').promises;
|
const { readFile } = require('fs').promises;
|
||||||
|
|
||||||
// Snowpack plugins must be CommonJS :(
|
// Snowpack plugins must be CommonJS :(
|
||||||
const transformPromise = import('./lib/transform2.js');
|
const transformPromise = import('./lib/compiler/index.js');
|
||||||
|
|
||||||
module.exports = function (snowpackConfig, { resolve, extensions } = {}) {
|
module.exports = function (snowpackConfig, { resolve, extensions } = {}) {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -21,7 +21,7 @@ export interface JsxItem {
|
||||||
|
|
||||||
export interface TransformResult {
|
export interface TransformResult {
|
||||||
script: string;
|
script: string;
|
||||||
props: string[];
|
imports: string[];
|
||||||
items: JsxItem[];
|
items: JsxItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { TemplateNode } from '../compiler/interfaces';
|
import type { TemplateNode } from '../parser/interfaces';
|
||||||
|
|
||||||
export type VisitorFn = (node: TemplateNode) => void;
|
export type VisitorFn = (node: TemplateNode) => void;
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,20 @@
|
||||||
import type { CompileOptions } from '../@types/compiler';
|
import type { CompileOptions } from '../@types/compiler';
|
||||||
import type { ValidExtensionPlugins } from '../@types/astro';
|
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 type { JsxItem, TransformResult } from '../@types/astro';
|
||||||
|
|
||||||
import eslexer from 'es-module-lexer';
|
import eslexer from 'es-module-lexer';
|
||||||
import esbuild from 'esbuild';
|
import esbuild from 'esbuild';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { walk } from 'estree-walker';
|
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;
|
const { transformSync } = esbuild;
|
||||||
|
|
||||||
interface Attribute {
|
interface Attribute {
|
||||||
|
@ -43,8 +50,8 @@ function getAttributes(attrs: Attribute[]): Record<string, string> {
|
||||||
'(' +
|
'(' +
|
||||||
attr.value
|
attr.value
|
||||||
.map((v: TemplateNode) => {
|
.map((v: TemplateNode) => {
|
||||||
if (v.expression) {
|
if (v.content) {
|
||||||
return v.expression;
|
return v.content;
|
||||||
} else {
|
} else {
|
||||||
return JSON.stringify(getTextFromAttribute(v));
|
return JSON.stringify(getTextFromAttribute(v));
|
||||||
}
|
}
|
||||||
|
@ -60,7 +67,7 @@ function getAttributes(attrs: Attribute[]): Record<string, string> {
|
||||||
}
|
}
|
||||||
switch (val.type) {
|
switch (val.type) {
|
||||||
case 'MustacheTag':
|
case 'MustacheTag':
|
||||||
result[attr.name] = '(' + val.expression + ')';
|
result[attr.name] = '(' + val.content + ')';
|
||||||
continue;
|
continue;
|
||||||
case 'Text':
|
case 'Text':
|
||||||
result[attr.name] = JSON.stringify(getTextFromAttribute(val));
|
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> {
|
export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Promise<TransformResult> {
|
||||||
await eslexer.init;
|
await eslexer.init;
|
||||||
|
|
||||||
// Compile scripts as TypeScript, always
|
const componentImports: ImportDeclaration[] = [];
|
||||||
const script = compileScriptSafe(ast.module ? ast.module.content : '');
|
const componentProps: VariableDeclarator[] = [];
|
||||||
|
const componentExports: ExportNamedDeclaration[] = [];
|
||||||
|
|
||||||
// Collect all exported variables for props
|
let script = '';
|
||||||
const scannedExports = eslexer.parse(script)[1].filter((n) => n !== 'setup' && n !== 'layout');
|
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
|
if (ast.module) {
|
||||||
const [scriptImports] = eslexer.parse(script, 'optional-sourcename');
|
const program = babelParser.parse(ast.module.content, {
|
||||||
const components = Object.fromEntries(
|
sourceType: 'module',
|
||||||
scriptImports.map((imp) => {
|
plugins: ['jsx', 'typescript', 'topLevelAwait'],
|
||||||
const componentType = path.posix.extname(imp.n!);
|
}).program;
|
||||||
const componentName = path.posix.basename(imp.n!, componentType);
|
|
||||||
return [componentName, { type: componentType, url: imp.n! }];
|
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 items: JsxItem[] = [];
|
||||||
let collectionItem: JsxItem | undefined;
|
let collectionItem: JsxItem | undefined;
|
||||||
let currentItemName: string | undefined;
|
let currentItemName: string | undefined;
|
||||||
|
@ -238,7 +289,7 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro
|
||||||
enter(node: TemplateNode) {
|
enter(node: TemplateNode) {
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
case 'MustacheTag':
|
case 'MustacheTag':
|
||||||
let code = compileExpressionSafe(node.expression);
|
let code = compileExpressionSafe(node.content);
|
||||||
|
|
||||||
let matches: RegExpExecArray[] = [];
|
let matches: RegExpExecArray[] = [];
|
||||||
let match: RegExpExecArray | null | undefined;
|
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);
|
const { wrapper, wrapperImport } = getComponentWrapper(name, components[componentName], compileOptions);
|
||||||
if (wrapperImport) {
|
if (wrapperImport) {
|
||||||
additionalImports.add(wrapperImport);
|
importExportStatements.add(wrapperImport);
|
||||||
}
|
}
|
||||||
if (wrapper !== name) {
|
if (wrapper !== name) {
|
||||||
code = code.slice(0, match.index + 2) + wrapper + code.slice(match.index + match[0].length - 1);
|
code = code.slice(0, match.index + 2) + wrapper + code.slice(match.index + match[0].length - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
collectionItem!.jsx += `,(${code.trim().replace(/\;$/, '')})`;
|
collectionItem!.jsx += `,(${code.trim().replace(/\;$/, '')})`;
|
||||||
|
this.skip();
|
||||||
return;
|
return;
|
||||||
case 'Comment':
|
case 'Comment':
|
||||||
return;
|
return;
|
||||||
|
@ -287,11 +339,6 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro
|
||||||
currentItemName = name;
|
currentItemName = name;
|
||||||
if (!collectionItem) {
|
if (!collectionItem) {
|
||||||
collectionItem = { name, jsx: '' };
|
collectionItem = { name, jsx: '' };
|
||||||
if (node.type === 'Head') {
|
|
||||||
collectionItem.jsx += `h(Fragment, null`;
|
|
||||||
headItem = collectionItem;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
items.push(collectionItem);
|
items.push(collectionItem);
|
||||||
}
|
}
|
||||||
collectionItem.jsx += collectionItem.jsx === '' ? '' : ',';
|
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);
|
const { wrapper, wrapperImport } = getComponentWrapper(name, components[componentName], compileOptions);
|
||||||
if (wrapperImport) {
|
if (wrapperImport) {
|
||||||
additionalImports.add(wrapperImport);
|
importExportStatements.add(wrapperImport);
|
||||||
}
|
}
|
||||||
|
|
||||||
collectionItem.jsx += `h(${wrapper}, ${attributes ? generateAttributes(attributes) : 'null'}`;
|
collectionItem.jsx += `h(${wrapper}, ${attributes ? generateAttributes(attributes) : 'null'}`;
|
||||||
|
@ -381,8 +428,8 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
script: script + '\n' + Array.from(additionalImports).join('\n'),
|
script: script,
|
||||||
|
imports: Array.from(importExportStatements),
|
||||||
items,
|
items,
|
||||||
props: scannedExports,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
|
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { walk } from 'estree-walker';
|
import { walk } from 'estree-walker';
|
||||||
import type { Ast, TemplateNode } from '../compiler/interfaces';
|
import type { Ast, TemplateNode } from '../../parser/interfaces';
|
||||||
import { NodeVisitor, Optimizer, VisitorFn } from '../@types/optimizer';
|
import { NodeVisitor, Optimizer, VisitorFn } from '../../@types/optimizer';
|
||||||
import optimizeStyles from './styles.js';
|
import optimizeStyles from './styles.js';
|
||||||
|
|
||||||
interface VisitorCollection {
|
interface VisitorCollection {
|
|
@ -5,8 +5,8 @@ import postcss from 'postcss';
|
||||||
import postcssModules from 'postcss-modules';
|
import postcssModules from 'postcss-modules';
|
||||||
import findUp from 'find-up';
|
import findUp from 'find-up';
|
||||||
import sass from 'sass';
|
import sass from 'sass';
|
||||||
import { Optimizer } from '../@types/optimizer';
|
import { Optimizer } from '../../@types/optimizer';
|
||||||
import type { TemplateNode } from '../compiler/interfaces';
|
import type { TemplateNode } from '../../parser/interfaces';
|
||||||
|
|
||||||
type StyleType = 'css' | 'scss' | 'sass' | 'postcss';
|
type StyleType = 'css' | 'scss' | 'sass' | 'postcss';
|
||||||
|
|
|
@ -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 { bold, blue, red, grey, underline } from 'kleur/colors';
|
||||||
import { Writable } from 'stream';
|
import { Writable } from 'stream';
|
||||||
import { format as utilFormat } from 'util';
|
import { format as utilFormat } from 'util';
|
||||||
|
|
|
@ -28,7 +28,6 @@ export function createMarkdownHeadersCollector() {
|
||||||
this.tag(`<h${currentHeader.depth} id="${currentHeader.slug}">`);
|
this.tag(`<h${currentHeader.depth} id="${currentHeader.slug}">`);
|
||||||
this.raw(currentHeader.text);
|
this.raw(currentHeader.text);
|
||||||
this.tag(`</h${currentHeader.depth}>`);
|
this.tag(`</h${currentHeader.depth}>`);
|
||||||
// console.log(this.sliceSerialize(node));
|
|
||||||
},
|
},
|
||||||
} as any,
|
} as any,
|
||||||
} as any,
|
} as any,
|
||||||
|
|
1
src/parser/index.ts
Normal file
1
src/parser/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { default as parse } from './parse/index.js';
|
|
@ -1,5 +1,5 @@
|
||||||
import { Node, Program } from 'estree';
|
import type { Expression, Program } from '@babel/types';
|
||||||
import { SourceMap } from 'magic-string';
|
import type { SourceMap } from 'magic-string';
|
||||||
|
|
||||||
interface BaseNode {
|
interface BaseNode {
|
||||||
start: number;
|
start: number;
|
||||||
|
@ -21,7 +21,7 @@ export interface Text extends BaseNode {
|
||||||
|
|
||||||
export interface MustacheTag extends BaseNode {
|
export interface MustacheTag extends BaseNode {
|
||||||
type: 'MustacheTag';
|
type: 'MustacheTag';
|
||||||
expression: string;
|
content: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DirectiveType = 'Action' | 'Animation' | 'Binding' | 'Class' | 'EventHandler' | 'Let' | 'Ref' | 'Transition';
|
export type DirectiveType = 'Action' | 'Animation' | 'Binding' | 'Class' | 'EventHandler' | 'Let' | 'Ref' | 'Transition';
|
|
@ -397,7 +397,7 @@ export default function mustache(parser: Parser) {
|
||||||
// });
|
// });
|
||||||
throw new Error('@debug not yet supported');
|
throw new Error('@debug not yet supported');
|
||||||
} else {
|
} else {
|
||||||
const expression = read_expression(parser);
|
const content = read_expression(parser);
|
||||||
|
|
||||||
parser.allow_whitespace();
|
parser.allow_whitespace();
|
||||||
parser.eat('}', true);
|
parser.eat('}', true);
|
||||||
|
@ -407,7 +407,7 @@ export default function mustache(parser: Parser) {
|
||||||
start,
|
start,
|
||||||
end: parser.index,
|
end: parser.index,
|
||||||
type: 'MustacheTag',
|
type: 'MustacheTag',
|
||||||
expression,
|
content,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -351,7 +351,7 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
|
||||||
parser.allow_whitespace();
|
parser.allow_whitespace();
|
||||||
|
|
||||||
if (parser.eat('...')) {
|
if (parser.eat('...')) {
|
||||||
const expression = read_expression(parser);
|
const {expression} = read_expression(parser);
|
||||||
|
|
||||||
parser.allow_whitespace();
|
parser.allow_whitespace();
|
||||||
parser.eat('}', true);
|
parser.eat('}', true);
|
||||||
|
@ -549,7 +549,7 @@ function read_sequence(parser: Parser, done: () => boolean): TemplateNode[] {
|
||||||
flush();
|
flush();
|
||||||
|
|
||||||
parser.allow_whitespace();
|
parser.allow_whitespace();
|
||||||
const expression = read_expression(parser);
|
const content = read_expression(parser);
|
||||||
parser.allow_whitespace();
|
parser.allow_whitespace();
|
||||||
parser.eat('}', true);
|
parser.eat('}', true);
|
||||||
|
|
||||||
|
@ -557,7 +557,7 @@ function read_sequence(parser: Parser, done: () => boolean): TemplateNode[] {
|
||||||
start: index,
|
start: index,
|
||||||
end: parser.index,
|
end: parser.index,
|
||||||
type: 'MustacheTag',
|
type: 'MustacheTag',
|
||||||
expression,
|
content,
|
||||||
});
|
});
|
||||||
|
|
||||||
current_chunk = {
|
current_chunk = {
|
|
@ -1,7 +1,7 @@
|
||||||
import type { SnowpackDevServer, ServerRuntime as SnowpackServerRuntime, LoadResult as SnowpackLoadResult } from 'snowpack';
|
import type { SnowpackDevServer, ServerRuntime as SnowpackServerRuntime, LoadResult as SnowpackLoadResult } from 'snowpack';
|
||||||
import type { AstroConfig } from './@types/astro';
|
import type { AstroConfig } from './@types/astro';
|
||||||
import type { LogOptions } from './logger';
|
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 { info, error, parseError } from './logger.js';
|
||||||
|
|
||||||
import { existsSync, promises as fsPromises } from 'fs';
|
import { existsSync, promises as fsPromises } from 'fs';
|
||||||
|
|
|
@ -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,
|
|
||||||
};
|
|
||||||
}
|
|
Loading…
Reference in a new issue