diff options
Diffstat (limited to 'packages/astro/src')
-rw-r--r-- | packages/astro/src/@types/astro.ts | 6 | ||||
-rw-r--r-- | packages/astro/src/compiler/codegen/index.ts | 106 | ||||
-rw-r--r-- | packages/astro/src/compiler/index.ts | 7 | ||||
-rw-r--r-- | packages/astro/src/compiler/utils.ts | 7 | ||||
-rw-r--r-- | packages/astro/src/config_manager.ts | 44 | ||||
-rw-r--r-- | packages/astro/src/frontend/__astro_config.ts | 8 | ||||
-rw-r--r-- | packages/astro/src/internal/__astro_component.ts | 107 | ||||
-rw-r--r-- | packages/astro/src/internal/element-registry.ts | 48 | ||||
-rw-r--r-- | packages/astro/src/internal/renderer-html.ts | 12 | ||||
-rw-r--r-- | packages/astro/src/runtime.ts | 13 |
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), |