feat: improve handling of undefined components (#650)

This commit is contained in:
Nate Moore 2021-07-09 17:02:39 -05:00 committed by GitHub
parent fb78b76cc6
commit 5077ff2e11
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 41 additions and 11 deletions

View file

@ -309,6 +309,7 @@ interface CodegenState {
markers: { markers: {
insideMarkdown: boolean | Record<string, any>; insideMarkdown: boolean | Record<string, any>;
}; };
declarations: Set<string>;
exportStatements: Set<string>; exportStatements: Set<string>;
importStatements: Set<string>; importStatements: Set<string>;
customElementCandidates: Map<string, string>; customElementCandidates: Map<string, string>;
@ -375,6 +376,9 @@ function compileModule(ast: Ast, module: Script, state: CodegenState, compileOpt
break; break;
} }
case 'FunctionDeclaration': { case 'FunctionDeclaration': {
if (node.id?.name) {
state.declarations.add(node.id?.name);
}
break; break;
} }
case 'ImportDeclaration': { case 'ImportDeclaration': {
@ -384,8 +388,14 @@ function compileModule(ast: Ast, module: Script, state: CodegenState, compileOpt
} }
case 'VariableDeclaration': { case 'VariableDeclaration': {
for (const declaration of node.declarations) { for (const declaration of node.declarations) {
// only select Astro.fetchContent() calls here. this utility filters those out for us. // only select Astro.fetchContent() calls for more processing,
if (!isFetchContent(declaration)) continue; // otherwise just push name to declarations
if (!isFetchContent(declaration)) {
if (declaration.id.type === 'Identifier') {
state.declarations.add(declaration.id.name);
}
continue;
}
// remove node // remove node
body.splice(i, 1); body.splice(i, 1);
@ -687,7 +697,7 @@ async function compileHtml(enterNode: TemplateNode, state: CodegenState, compile
const [componentNamespace] = componentName.split('.'); const [componentNamespace] = componentName.split('.');
componentInfo = components.get(componentNamespace); componentInfo = components.get(componentNamespace);
} }
if (!componentInfo && !isCustomElementTag(componentName)) { if (state.declarations.has(componentName) && !componentInfo && !isCustomElementTag(componentName)) {
if (hydrationAttributes.method) { if (hydrationAttributes.method) {
throw new Error( throw new Error(
`Unable to hydrate "${componentName}" because it is statically defined in the frontmatter script. Hydration directives may only be used on imported components.` `Unable to hydrate "${componentName}" because it is statically defined in the frontmatter script. Hydration directives may only be used on imported components.`
@ -705,6 +715,8 @@ async function compileHtml(enterNode: TemplateNode, state: CodegenState, compile
paren++; paren++;
buffers[curr] += `h(${componentName}, ${attributes ? generateAttributes(attributes) : 'null'}`; buffers[curr] += `h(${componentName}, ${attributes ? generateAttributes(attributes) : 'null'}`;
return; return;
} else if (!state.declarations.has(componentName) && !componentInfo && !isCustomElementTag(componentName)) {
throw new Error(`Unable to render "${componentName}" because it is undefined\n ${state.filename}`)
} }
if (componentName === 'Markdown') { if (componentName === 'Markdown') {
const { $scope } = attributes ?? {}; const { $scope } = attributes ?? {};
@ -870,6 +882,7 @@ export async function codegen(ast: Ast, { compileOptions, filename, fileID }: Co
markers: { markers: {
insideMarkdown: false, insideMarkdown: false,
}, },
declarations: new Set(),
importStatements: new Set(), importStatements: new Set(),
exportStatements: new Set(), exportStatements: new Set(),
customElementCandidates: new Map(), customElementCandidates: new Map(),

View file

@ -95,12 +95,4 @@ Basics('Allows spread attributes with TypeScript (#521)', async ({ runtime }) =>
assert.equal($('#spread-ts').attr('c'), '2'); assert.equal($('#spread-ts').attr('c'), '2');
}); });
Basics('Allows Components defined in frontmatter', async ({ runtime }) => {
const result = await runtime.load('/frontmatter-component');
const html = result.contents;
const $ = doc(html);
assert.equal($('h1').length, 1);
});
Basics.run(); Basics.run();

View file

@ -26,4 +26,18 @@ Components('Astro components are able to render framework components', async ({
assert.not.type($svelte, 'undefined', 'Renders Svelte component'); assert.not.type($svelte, 'undefined', 'Renders Svelte component');
}); });
Components('Allows Components defined in frontmatter', async ({ runtime }) => {
const result = await runtime.load('/frontmatter-component');
const html = result.contents;
const $ = doc(html);
assert.equal($('h1').length, 1);
});
Components('Still throws an error for undefined components', async ({ runtime }) => {
const result = await runtime.load('/undefined-component');
assert.equal(result.statusCode, 500);
});
Components.run(); Components.run();

View file

@ -0,0 +1,11 @@
---
function FnComponent() {
const lame = 'ugh';
return <h2>Hey</h2>
}
const Defined = 'h1'
---
<FnComponent />
<Defined>Hello world!</Defined>
<Undefined />