summaryrefslogtreecommitdiff
path: root/packages/astro/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/astro/src')
-rw-r--r--packages/astro/src/@types/astro.ts6
-rw-r--r--packages/astro/src/compiler/codegen/index.ts106
-rw-r--r--packages/astro/src/compiler/index.ts7
-rw-r--r--packages/astro/src/compiler/utils.ts7
-rw-r--r--packages/astro/src/config_manager.ts44
-rw-r--r--packages/astro/src/frontend/__astro_config.ts8
-rw-r--r--packages/astro/src/internal/__astro_component.ts107
-rw-r--r--packages/astro/src/internal/element-registry.ts48
-rw-r--r--packages/astro/src/internal/renderer-html.ts12
-rw-r--r--packages/astro/src/runtime.ts13
10 files changed, 272 insertions, 86 deletions
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index 6fc0404a8..1fbdd6282 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -65,6 +65,8 @@ export interface TransformResult {
css?: string;
/** If this page exports a collection, the JS to be executed as a string */
createCollection?: string;
+ hasCustomElements: boolean;
+ customElementCandidates: Map<string, string>;
}
export interface CompileResult {
@@ -180,11 +182,11 @@ export interface ComponentInfo {
export type Components = Map<string, ComponentInfo>;
-type AsyncRendererComponentFn<U> = (Component: any, props: any, children: string | undefined) => Promise<U>;
+type AsyncRendererComponentFn<U> = (Component: any, props: any, children: string | undefined, options?: any) => Promise<U>;
export interface Renderer {
check: AsyncRendererComponentFn<boolean>;
renderToStaticMarkup: AsyncRendererComponentFn<{
html: string;
}>;
-}
+} \ No newline at end of file
diff --git a/packages/astro/src/compiler/codegen/index.ts b/packages/astro/src/compiler/codegen/index.ts
index ea094a32c..0b9780e16 100644
--- a/packages/astro/src/compiler/codegen/index.ts
+++ b/packages/astro/src/compiler/codegen/index.ts
@@ -7,7 +7,7 @@ import 'source-map-support/register.js';
import eslexer from 'es-module-lexer';
import esbuild from 'esbuild';
import path from 'path';
-import { parse } from '@astrojs/parser';
+import { parse, FEATURE_CUSTOM_ELEMENT } from '@astrojs/parser';
import { walk, asyncWalk } from 'estree-walker';
import _babelGenerator from '@babel/generator';
import babelParser from '@babel/parser';
@@ -17,12 +17,14 @@ import { error, warn, parseError } from '../../logger.js';
import { fetchContent } from './content.js';
import { isFetchContent } from './utils.js';
import { yellow } from 'kleur/colors';
-import { isComponentTag, positionAt } from '../utils.js';
+import { isComponentTag, isCustomElementTag, positionAt } from '../utils.js';
import { renderMarkdown } from '@astrojs/markdown-support';
+import { camelCase } from 'camel-case';
import { transform } from '../transform/index.js';
import { PRISM_IMPORT } from '../transform/prism.js';
import { nodeBuiltinsSet } from '../../node_builtins.js';
import { readFileSync } from 'fs';
+import { pathToFileURL } from 'url';
const traverse: typeof babelTraverse.default = (babelTraverse.default as any).default;
@@ -142,6 +144,13 @@ function generateAttributes(attrs: Record<string, string>): string {
return result + '}';
}
+function getComponentUrl(astroConfig: AstroConfig, url: string, parentUrl: string | URL){
+ const componentExt = path.extname(url);
+ const ext = PlainExtensions.has(componentExt) ? '.js' : `${componentExt}.js`;
+ const outUrl = new URL(url, parentUrl);
+ return '/_astro/' + outUrl.href.replace(astroConfig.projectRoot.href, '').replace(/\.[^.]+$/, ext);
+}
+
interface GetComponentWrapperOptions {
filename: string;
astroConfig: AstroConfig;
@@ -151,36 +160,43 @@ const PlainExtensions = new Set(['.js', '.jsx', '.ts', '.tsx']);
/** Generate Astro-friendly component import */
function getComponentWrapper(_name: string, { url, importSpecifier }: ComponentInfo, opts: GetComponentWrapperOptions) {
const { astroConfig, filename } = opts;
- const currFileUrl = new URL(`file://${filename}`);
const [name, kind] = _name.split(':');
- const getComponentUrl = () => {
- const componentExt = path.extname(url);
- const ext = PlainExtensions.has(componentExt) ? '.js' : `${componentExt}.js`;
- const outUrl = new URL(url, currFileUrl);
- return '/_astro/' + outUrl.href.replace(astroConfig.projectRoot.href, '').replace(/\.[^.]+$/, ext);
- };
- const getComponentExport = () => {
- switch (importSpecifier.type) {
- case 'ImportDefaultSpecifier':
- return { value: 'default' };
- case 'ImportSpecifier': {
- if (importSpecifier.imported.type === 'Identifier') {
- return { value: importSpecifier.imported.name };
+
+ // Special flow for custom elements
+ if (isCustomElementTag(name)) {
+ return {
+ wrapper: `__astro_component(...__astro_element_registry.astroComponentArgs("${name}", ${JSON.stringify({ hydrate: kind, displayName: _name })}))`,
+ wrapperImports: [`import {AstroElementRegistry} from 'astro/dist/internal/element-registry.js';`,`import {__astro_component} from 'astro/dist/internal/__astro_component.js';`],
+ };
+
+ } else {
+ const getComponentExport = () => {
+ switch (importSpecifier.type) {
+ case 'ImportDefaultSpecifier':
+ return { value: 'default' };
+ case 'ImportSpecifier': {
+ if (importSpecifier.imported.type === 'Identifier') {
+ return { value: importSpecifier.imported.name };
+ }
+ return { value: importSpecifier.imported.value };
+ }
+ case 'ImportNamespaceSpecifier': {
+ const [_, value] = name.split('.');
+ return { value };
}
- return { value: importSpecifier.imported.value };
- }
- case 'ImportNamespaceSpecifier': {
- const [_, value] = name.split('.');
- return { value };
}
- }
- };
+ };
- const importInfo = kind ? { componentUrl: getComponentUrl(), componentExport: getComponentExport() } : {};
- return {
- wrapper: `__astro_component(${name}, ${JSON.stringify({ hydrate: kind, displayName: _name, ...importInfo })})`,
- wrapperImport: `import {__astro_component} from 'astro/dist/internal/__astro_component.js';`,
- };
+ const importInfo = kind ? {
+ componentUrl: getComponentUrl(astroConfig, url, pathToFileURL(filename)),
+ componentExport: getComponentExport()
+ } : {};
+
+ return {
+ wrapper: `__astro_component(${name}, ${JSON.stringify({ hydrate: kind, displayName: _name, ...importInfo })})`,
+ wrapperImports: [`import {__astro_component} from 'astro/dist/internal/__astro_component.js';`],
+ };
+ }
}
/**
@@ -251,19 +267,22 @@ interface CompileResult {
}
interface CodegenState {
- filename: string;
- fileID: string;
components: Components;
css: string[];
+ filename: string;
+ fileID: string;
markers: {
insideMarkdown: boolean | Record<string, any>;
};
exportStatements: Set<string>;
importStatements: Set<string>;
+ customElementCandidates: Map<string, string>;
}
/** Compile/prepare Astro frontmatter scripts */
-function compileModule(module: Script, state: CodegenState, compileOptions: CompileOptions): CompileResult {
+function compileModule(ast: Ast, module: Script, state: CodegenState, compileOptions: CompileOptions): CompileResult {
+ const { astroConfig } = compileOptions;
+ const { filename } = state;
const componentImports: ImportDeclaration[] = [];
const componentProps: VariableDeclarator[] = [];
const componentExports: ExportNamedDeclaration[] = [];
@@ -373,7 +392,14 @@ function compileModule(module: Script, state: CodegenState, compileOptions: Comp
});
}
const { start, end } = componentImport;
- state.importStatements.add(module.content.slice(start || undefined, end || undefined));
+ if(ast.meta.features & FEATURE_CUSTOM_ELEMENT && componentImport.specifiers.length === 0) {
+ // Add possible custom element, but only if the AST says there are custom elements.
+ const moduleImportName = camelCase(importUrl+ 'Module');
+ state.importStatements.add(`import * as ${moduleImportName} from '${importUrl}';\n`);
+ state.customElementCandidates.set(moduleImportName, getComponentUrl(astroConfig, importUrl, pathToFileURL(filename)));
+ } else {
+ state.importStatements.add(module.content.slice(start || undefined, end || undefined));
+ }
}
// TODO: actually expose componentExports other than __layout and __content
@@ -385,7 +411,6 @@ function compileModule(module: Script, state: CodegenState, compileOptions: Comp
if (componentProps.length > 0) {
const shortname = path.posix.relative(compileOptions.astroConfig.projectRoot.pathname, state.filename);
const props = componentProps.map((prop) => (prop.id as Identifier)?.name).filter((v) => v);
- console.log();
warn(
compileOptions.logging,
shortname,
@@ -627,7 +652,7 @@ async function compileHtml(enterNode: TemplateNode, state: CodegenState, compile
const [componentNamespace] = componentName.split('.');
componentInfo = components.get(componentNamespace);
}
- if (!componentInfo) {
+ if (!componentInfo && !isCustomElementTag(componentName)) {
throw new Error(`Unknown Component: ${componentName}`);
}
if (componentName === 'Markdown') {
@@ -643,9 +668,11 @@ async function compileHtml(enterNode: TemplateNode, state: CodegenState, compile
curr = 'markdown';
return;
}
- const { wrapper, wrapperImport } = getComponentWrapper(name, componentInfo, { astroConfig, filename });
- if (wrapperImport) {
- importStatements.add(wrapperImport);
+ const { wrapper, wrapperImports } = getComponentWrapper(name, componentInfo ?? ({} as any), { astroConfig, filename });
+ if (wrapperImports) {
+ for(let wrapperImport of wrapperImports) {
+ importStatements.add(wrapperImport);
+ }
}
if (curr === 'markdown') {
await pushMarkdownToBuffer();
@@ -794,9 +821,10 @@ export async function codegen(ast: Ast, { compileOptions, filename, fileID }: Co
},
importStatements: new Set(),
exportStatements: new Set(),
+ customElementCandidates: new Map()
};
- const { script, createCollection } = compileModule(ast.module, state, compileOptions);
+ const { script, createCollection } = compileModule(ast, ast.module, state, compileOptions);
compileCss(ast.css, state);
@@ -809,5 +837,7 @@ export async function codegen(ast: Ast, { compileOptions, filename, fileID }: Co
html,
css: state.css.length ? state.css.join('\n\n') : undefined,
createCollection,
+ hasCustomElements: Boolean(ast.meta.features & FEATURE_CUSTOM_ELEMENT),
+ customElementCandidates: state.customElementCandidates
};
}
diff --git a/packages/astro/src/compiler/index.ts b/packages/astro/src/compiler/index.ts
index 4a3b359ce..0f12cc7f0 100644
--- a/packages/astro/src/compiler/index.ts
+++ b/packages/astro/src/compiler/index.ts
@@ -106,7 +106,7 @@ interface CompileComponentOptions {
isPage?: boolean;
}
/** Compiles an Astro component */
-export async function compileComponent(source: string, { compileOptions, filename, projectRoot, isPage }: CompileComponentOptions): Promise<CompileResult> {
+export async function compileComponent(source: string, { compileOptions, filename, projectRoot }: CompileComponentOptions): Promise<CompileResult> {
const result = await transformFromSource(source, { compileOptions, filename, projectRoot });
const site = compileOptions.astroConfig.buildOptions.site || `http://localhost:${compileOptions.astroConfig.devOptions.port}`;
@@ -116,6 +116,11 @@ import fetch from 'node-fetch';
// <script astro></script>
${result.imports.join('\n')}
+${result.hasCustomElements ? `
+const __astro_element_registry = new AstroElementRegistry({
+ candidates: new Map([${Array.from(result.customElementCandidates).map(([identifier, url]) => `[${identifier}, '${url}']`).join(', ')}])
+});
+`.trim() : ''}
// \`__render()\`: Render the contents of the Astro module.
import { h, Fragment } from 'astro/dist/internal/h.js';
diff --git a/packages/astro/src/compiler/utils.ts b/packages/astro/src/compiler/utils.ts
index 232f1b747..7d545531a 100644
--- a/packages/astro/src/compiler/utils.ts
+++ b/packages/astro/src/compiler/utils.ts
@@ -1,6 +1,11 @@
+/** Is the given string a custom-element tag? */
+export function isCustomElementTag(tag: string) {
+ return /[-]/.test(tag);
+}
+
/** Is the given string a valid component tag */
export function isComponentTag(tag: string) {
- return /^[A-Z]/.test(tag) || /^[a-z]+\./.test(tag);
+ return /^[A-Z]/.test(tag) || /^[a-z]+\./.test(tag) || isCustomElementTag(tag);
}
export interface Position {
diff --git a/packages/astro/src/config_manager.ts b/packages/astro/src/config_manager.ts
index 2d8d384ee..e4a4fa8e1 100644
--- a/packages/astro/src/config_manager.ts
+++ b/packages/astro/src/config_manager.ts
@@ -9,10 +9,13 @@ type RendererSnowpackPlugin = string | [string, any] | undefined;
interface RendererInstance {
name: string;
+ options: any;
snowpackPlugin: RendererSnowpackPlugin;
- client: string;
+ client: string | null;
server: string;
knownEntrypoints: string[] | undefined;
+ external: string[] | undefined;
+ polyfills: string[];
}
const CONFIG_MODULE_BASE_NAME = '__astro_config.js';
@@ -65,15 +68,25 @@ export class ConfigManager {
const rendererInstances = (
await Promise.all(
- rendererNames.map((rendererName) => {
+ rendererNames.map(async (rendererName) => {
+ let _options: any = null;
+ if (Array.isArray(rendererName)) {
+ _options = rendererName[1];
+ rendererName = rendererName[0];
+ }
+
const entrypoint = pathToFileURL(resolveDependency(rendererName)).toString();
- return import(entrypoint);
+ const r = await import(entrypoint);
+ return {
+ raw: r.default,
+ options: _options
+ };
})
)
- ).map(({ default: raw }, i) => {
+ ).map(({ raw, options }, i) => {
const { name = rendererNames[i], client, server, snowpackPlugin: snowpackPluginName, snowpackPluginOptions } = raw;
- if (typeof client !== 'string') {
+ if (typeof client !== 'string' && client != null) {
throw new Error(`Expected "client" from ${name} to be a relative path to the client-side renderer!`);
}
@@ -92,12 +105,17 @@ export class ConfigManager {
throw new Error(`Expected the snowpackPlugin from ${name} to be a "string" but encountered "${typeof snowpackPluginName}"!`);
}
+ const polyfillsNormalized = (raw.polyfills || []).map((p: string) => p.startsWith('.') ? path.join(name, p) : p);
+
return {
name,
+ options,
snowpackPlugin,
- client: path.join(name, raw.client),
+ client: raw.client ? path.join(name, raw.client) : null,
server: path.join(name, raw.server),
knownEntrypoints: raw.knownEntrypoints,
+ external: raw.external,
+ polyfills: polyfillsNormalized
};
});
@@ -107,16 +125,24 @@ export class ConfigManager {
async buildSource(contents: string): Promise<string> {
const renderers = await this.buildRendererInstances();
const rendererServerPackages = renderers.map(({ server }) => server);
- const rendererClientPackages = await Promise.all(renderers.map(({ client }) => this.resolvePackageUrl(client)));
+ const rendererClientPackages = await Promise.all(renderers.filter(({client}) => client).map(({ client }) => this.resolvePackageUrl(client!)));
+ const rendererPolyfills = await Promise.all(renderers.map(({ polyfills }) => Promise.all(polyfills.map(src => this.resolvePackageUrl(src)))));
+
+
const result = /* js */ `${rendererServerPackages.map((pkg, i) => `import __renderer_${i} from "${pkg}";`).join('\n')}
import { setRenderers } from 'astro/dist/internal/__astro_component.js';
-let rendererSources = [${rendererClientPackages.map((pkg) => `"${pkg}"`).join(', ')}];
-let renderers = [${rendererServerPackages.map((_, i) => `__renderer_${i}`).join(', ')}];
+let rendererInstances = [${renderers.map((r, i) => `{
+ source: ${rendererClientPackages[i] ? `"${rendererClientPackages[i]}"` : 'null'},
+ renderer: __renderer_${i},
+ options: ${r.options ? JSON.stringify(r.options) : 'null'},
+ polyfills: ${JSON.stringify(rendererPolyfills[i])}
+}`).join(', ')}];
${contents}
`;
+
return result;
}
diff --git a/packages/astro/src/frontend/__astro_config.ts b/packages/astro/src/frontend/__astro_config.ts
index 11d99ee3b..212f49c38 100644
--- a/packages/astro/src/frontend/__astro_config.ts
+++ b/packages/astro/src/frontend/__astro_config.ts
@@ -1,6 +1,6 @@
-declare function setRenderers(sources: string[], renderers: any[]): void;
+import type { RendererInstance } from '../internal/__astro_component';
-declare let rendererSources: string[];
-declare let renderers: any[];
+declare function setRenderers(instances: RendererInstance[]): void;
+declare let rendererInstances: RendererInstance[];
-setRenderers(rendererSources, renderers);
+setRenderers(rendererInstances);
diff --git a/packages/astro/src/internal/__astro_component.ts b/packages/astro/src/internal/__astro_component.ts
index 1e0a75c16..2e98d55dc 100644
--- a/packages/astro/src/internal/__astro_component.ts
+++ b/packages/astro/src/internal/__astro_component.ts
@@ -3,38 +3,64 @@ import hash from 'shorthash';
import { valueToEstree, Value } from 'estree-util-value-to-estree';
import { generate } from 'astring';
import * as astro from './renderer-astro';
+import * as astroHtml from './renderer-html';
// A more robust version alternative to `JSON.stringify` that can handle most values
// see https://github.com/remcohaszing/estree-util-value-to-estree#readme
const serialize = (value: Value) => generate(valueToEstree(value));
-let rendererSources: string[] = [];
-let renderers: Renderer[] = [];
+export interface RendererInstance {
+ source: string | null;
+ renderer: Renderer;
+ options: any;
+ polyfills: string[];
+}
+
+const astroRendererInstance: RendererInstance = {
+ source: '',
+ renderer: astro as Renderer,
+ options: null,
+ polyfills: []
+};
+
+const astroHtmlRendererInstance: RendererInstance = {
+ source: '',
+ renderer: astroHtml as Renderer,
+ options: null,
+ polyfills: []
+};
-export function setRenderers(_rendererSources: string[], _renderers: Renderer[]) {
- rendererSources = [''].concat(_rendererSources);
- renderers = [astro as Renderer].concat(_renderers);
+let rendererInstances: RendererInstance[] = [];
+
+export function setRenderers(_rendererInstances: RendererInstance[]) {
+ rendererInstances = [astroRendererInstance].concat(_rendererInstances);
+}
+
+function isCustomElementTag(name: string | Function) {
+ return typeof name === 'string' && /-/.test(name);
}
-const rendererCache = new WeakMap();
+const rendererCache = new Map<any, RendererInstance>();
/** For a given component, resolve the renderer. Results are cached if this instance is encountered again */
-async function resolveRenderer(Component: any, props: any = {}, children?: string) {
+async function resolveRenderer(Component: any, props: any = {}, children?: string): Promise<RendererInstance | undefined> {
if (rendererCache.has(Component)) {
- return rendererCache.get(Component);
+ return rendererCache.get(Component)!;
}
const errors: Error[] = [];
- for (const __renderer of renderers) {
+ for (const instance of rendererInstances) {
+ const { renderer, options } = instance;
+
// Yes, we do want to `await` inside of this loop!
// __renderer.check can't be run in parallel, it
// returns the first match and skips any subsequent checks
try {
- const shouldUse: boolean = await __renderer.check(Component, props, children);
+ const shouldUse: boolean = await renderer.check(Component, props, children, options);
if (shouldUse) {
- rendererCache.set(Component, __renderer);
- return __renderer;
+ rendererCache.set(Component, instance);
+ return instance;
}
} catch (err) {
errors.push(err);
@@ -47,26 +73,39 @@ async function resolveRenderer(Component: any, props: any = {}, children?: strin
}
}
-interface AstroComponentProps {
+export interface AstroComponentProps {
displayName: string;
hydrate?: 'load' | 'idle' | 'visible';
componentUrl?: string;
componentExport?: { value: string; namespace?: boolean };
}
+interface HydrateScriptOptions {
+ instance: RendererInstance;
+ astroId: string;
+ props: any;
+}
+
/** For hydrated components, generate a <script type="module"> to load the component */
-async function generateHydrateScript({ renderer, astroId, props }: any, { hydrate, componentUrl, componentExport }: Required<AstroComponentProps>) {
- const rendererSource = rendererSources[renderers.findIndex((r) => r === renderer)];
+async function generateHydrateScript({ instance, astroId, props }: HydrateScriptOptions, { hydrate, componentUrl, componentExport }: Required<AstroComponentProps>) {
+ const { source } = instance;
+
+ const hydrationSource = source ? `
+ const [{ ${componentExport.value}: Component }, { default: hydrate }] = await Promise.all([import("${componentUrl}"), import("${source}")]);
+ return (el, children) => hydrate(el)(Component, ${serialize(props)}, children);
+`.trim() : `
+ await import("${componentUrl}");
+ return () => {};
+`.trim()
- const script = `<script type="module">
+ const hydrationScript = `<script type="module">
import setup from '/_astro_frontend/hydrate/${hydrate}.js';
setup("${astroId}", async () => {
- const [{ ${componentExport.value}: Component }, { default: hydrate }] = await Promise.all([import("${componentUrl}"), import("${rendererSource}")]);
- return (el, children) => hydrate(el)(Component, ${serialize(props)}, children);
+ ${hydrationSource}
});
</script>`;
- return script;
+ return hydrationScript;
}
const getComponentName = (Component: any, componentProps: any) => {
@@ -85,25 +124,35 @@ const getComponentName = (Component: any, componentProps: any) => {
export const __astro_component = (Component: any, componentProps: AstroComponentProps = {} as any) => {
if (Component == null) {
throw new Error(`Unable to render ${componentProps.displayName} because it is ${Component}!\nDid you forget to import the component or is it possible there is a typo?`);
- } else if (typeof Component === 'string') {
+ } else if (typeof Component === 'string' && !isCustomElementTag(Component)) {
throw new Error(`Astro is unable to render ${componentProps.displayName}!\nIs there a renderer to handle this type of component defined in your Astro config?`);
}
return async (props: any, ..._children: string[]) => {
const children = _children.join('\n');
- let renderer = await resolveRenderer(Component, props, children);
-
- if (!renderer) {
- // If the user only specifies a single renderer, but the check failed
- // for some reason... just default to their preferred renderer.
- renderer = rendererSources.length === 2 ? renderers[1] : null;
+ let instance = await resolveRenderer(Component, props, children);
+
+ if (!instance) {
+ if(isCustomElementTag(Component)) {
+ instance = astroHtmlRendererInstance;
+ } else {
+ // If the user only specifies a single renderer, but the check failed
+ // for some reason... just default to their preferred renderer.
+ instance = rendererInstances.length === 2 ? rendererInstances[1] : undefined;
+ }
- if (!renderer) {
+ if (!instance) {
const name = getComponentName(Component, componentProps);
throw new Error(`No renderer found for ${name}! Did you forget to add a renderer to your Astro config?`);
}
}
- const { html } = await renderer.renderToStaticMarkup(Component, props, children);
+ let { html } = await instance.renderer.renderToStaticMarkup(Component, props, children, instance.options);
+
+ if(instance.polyfills.length) {
+ let polyfillScripts = instance.polyfills.map(src => `<script type="module" src="${src}"></script>`).join('');
+ html = html + polyfillScripts;
+ }
+
// If we're NOT hydrating this component, just return the HTML
if (!componentProps.hydrate) {
// It's safe to remove <astro-fragment>, static content doesn't need the wrapper
@@ -112,7 +161,7 @@ export const __astro_component = (Component: any, componentProps: AstroComponent
// If we ARE hydrating this component, let's generate the hydration script
const astroId = hash.unique(html);
- const script = await generateHydrateScript({ renderer, astroId, props }, componentProps as Required<AstroComponentProps>);
+ const script = await generateHydrateScript({ instance, astroId, props }, componentProps as Required<AstroComponentProps>);
const astroRoot = `<astro-root uid="${astroId}">${html}</astro-root>`;
return [astroRoot, script].join('\n');
};
diff --git a/packages/astro/src/internal/element-registry.ts b/packages/astro/src/internal/element-registry.ts
new file mode 100644
index 000000000..b9ac35f29
--- /dev/null
+++ b/packages/astro/src/internal/element-registry.ts
@@ -0,0 +1,48 @@
+import type { AstroComponentProps } from './__astro_component';
+
+type ModuleCandidates = Map<any, string>;
+
+interface RegistryOptions {
+ candidates: ModuleCandidates;
+}
+class AstroElementRegistry {
+ private candidates: ModuleCandidates;
+ private cache: Map<string, string> = new Map();
+
+ constructor(options: RegistryOptions) {
+ this.candidates = options.candidates;
+ }
+
+ find(tagName: string) {
+ for(let [module, importSpecifier] of this.candidates) {
+ if(module && typeof module.tagName === 'string') {
+ if(module.tagName === tagName) {
+ // Found!
+ return importSpecifier;
+ }
+ }
+ }
+ }
+
+ findCached(tagName: string) {
+ if(this.cache.has(tagName)) {
+ return this.cache.get(tagName)!;
+ }
+ let specifier = this.find(tagName);
+ if(specifier) {
+ this.cache.set(tagName, specifier);
+ }
+ return specifier;
+ }
+
+ astroComponentArgs(tagName: string, props: AstroComponentProps) {
+ const specifier = this.findCached(tagName);
+ const outProps: AstroComponentProps = {
+ ...props,
+ componentUrl: specifier || props.componentUrl
+ };
+ return [tagName, outProps];
+ }
+}
+
+export { AstroElementRegistry }; \ No newline at end of file
diff --git a/packages/astro/src/internal/renderer-html.ts b/packages/astro/src/internal/renderer-html.ts
new file mode 100644
index 000000000..e80239b1c
--- /dev/null
+++ b/packages/astro/src/internal/renderer-html.ts
@@ -0,0 +1,12 @@
+import { h } from './h';
+
+async function renderToStaticMarkup(tag: string, props: Record<string, any>, children: string) {
+ const html = await h(tag, props, Promise.resolve(children));
+ return {
+ html
+ };
+}
+
+export {
+ renderToStaticMarkup
+}; \ No newline at end of file
diff --git a/packages/astro/src/runtime.ts b/packages/astro/src/runtime.ts
index bf1ad7235..d6ef89a5c 100644
--- a/packages/astro/src/runtime.ts
+++ b/packages/astro/src/runtime.ts
@@ -366,11 +366,20 @@ async function createSnowpack(astroConfig: AstroConfig, options: CreateSnowpackO
const rendererInstances = await configManager.buildRendererInstances();
const knownEntrypoints: string[] = ['astro/dist/internal/__astro_component.js'];
for (const renderer of rendererInstances) {
- knownEntrypoints.push(renderer.server, renderer.client);
+ knownEntrypoints.push(renderer.server);
+ if(renderer.client) {
+ knownEntrypoints.push(renderer.client);
+ }
if (renderer.knownEntrypoints) {
knownEntrypoints.push(...renderer.knownEntrypoints);
}
}
+ const external = snowpackExternals.concat([]);
+ for(const renderer of rendererInstances) {
+ if(renderer.external) {
+ external.push(...renderer.external);
+ }
+ }
const rendererSnowpackPlugins = rendererInstances.filter((renderer) => renderer.snowpackPlugin).map((renderer) => renderer.snowpackPlugin) as string | [string, any];
const snowpackConfig = await loadConfiguration({
@@ -406,7 +415,7 @@ async function createSnowpack(astroConfig: AstroConfig, options: CreateSnowpackO
},
packageOptions: {
knownEntrypoints,
- external: snowpackExternals,
+ external,
},
alias: {
...Object.fromEntries(nodeBuiltinsMap),