Implements top-level Astro + Astro.resolve (#1556)
* Implements top-level Astro + Astro.resolve * Fix linting
This commit is contained in:
parent
f199c69eab
commit
5fbd05be15
11 changed files with 80 additions and 73 deletions
|
@ -40,7 +40,7 @@
|
|||
"test": "mocha --parallel --timeout 15000"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/compiler": "^0.1.13",
|
||||
"@astrojs/compiler": "^0.1.14",
|
||||
"@astrojs/language-server": "^0.7.16",
|
||||
"@astrojs/markdown-remark": "^0.3.1",
|
||||
"@astrojs/markdown-support": "0.3.1",
|
||||
|
|
|
@ -26,12 +26,15 @@ export interface AstroBuiltinProps {
|
|||
'client:visible'?: boolean;
|
||||
}
|
||||
|
||||
export interface Astro {
|
||||
export interface TopLevelAstro {
|
||||
isPage: boolean;
|
||||
fetchContent<T = any>(globStr: string): Promise<FetchContentResult<T>[]>;
|
||||
props: Record<string, number | string | any>;
|
||||
request: AstroPageRequest;
|
||||
resolve: (path: string) => string;
|
||||
site: URL;
|
||||
}
|
||||
|
||||
export interface Astro extends TopLevelAstro {
|
||||
props: Record<string, number | string | any>;
|
||||
request: AstroPageRequest;
|
||||
slots: Record<string, true | undefined>;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { Astro as AstroGlobal } from './astro-file';
|
||||
import { Astro as AstroGlobal, TopLevelAstro } from './astro-file';
|
||||
import { Renderer } from './astro';
|
||||
|
||||
export interface SSRMetadata {
|
||||
|
@ -8,6 +8,6 @@ export interface SSRMetadata {
|
|||
export interface SSRResult {
|
||||
styles: Set<string>;
|
||||
scripts: Set<string>;
|
||||
createAstro(props: Record<string, any>, slots: Record<string, any> | null): AstroGlobal;
|
||||
createAstro(Astro: TopLevelAstro, props: Record<string, any>, slots: Record<string, any> | null): AstroGlobal;
|
||||
_metadata: SSRMetadata;
|
||||
}
|
|
@ -30,6 +30,7 @@ class HydrationMap {
|
|||
|
||||
private getComponentMetadata(Component: any): ComponentMetadata | null {
|
||||
if(this.metadataCache.has(Component)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return this.metadataCache.get(Component)!;
|
||||
}
|
||||
const metadata = this.findComponentMetadata(Component);
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import type { AstroComponentMetadata } from '../@types/astro';
|
||||
import type { SSRResult } from '../@types/ssr';
|
||||
import type { TopLevelAstro } from '../@types/astro-file';
|
||||
|
||||
import { pathToFileURL } from 'url';
|
||||
import { valueToEstree } from 'estree-util-value-to-estree';
|
||||
import * as astring from 'astring';
|
||||
import shorthash from 'shorthash';
|
||||
|
@ -236,6 +238,47 @@ export async function renderComponent(result: SSRResult, displayName: string, Co
|
|||
return `<astro-root uid="${astroId}">${html}</astro-root>`;
|
||||
}
|
||||
|
||||
/** Create the Astro.fetchContent() runtime function. */
|
||||
function createFetchContentFn(url: URL) {
|
||||
const fetchContent = (importMetaGlobResult: Record<string, any>) => {
|
||||
let allEntries = [...Object.entries(importMetaGlobResult)];
|
||||
if (allEntries.length === 0) {
|
||||
throw new Error(`[${url.pathname}] Astro.fetchContent() no matches found.`);
|
||||
}
|
||||
return allEntries
|
||||
.map(([spec, mod]) => {
|
||||
// Only return Markdown files for now.
|
||||
if (!mod.frontmatter) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
content: mod.metadata,
|
||||
metadata: mod.frontmatter,
|
||||
file: new URL(spec, url),
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
};
|
||||
return fetchContent;
|
||||
}
|
||||
|
||||
export function createAstro(fileURLStr: string, site: string): TopLevelAstro {
|
||||
const url = pathToFileURL(fileURLStr);
|
||||
const fetchContent = createFetchContentFn(url) as unknown as TopLevelAstro['fetchContent'];
|
||||
return {
|
||||
// TODO I think this is no longer needed.
|
||||
isPage: false,
|
||||
site: new URL(site),
|
||||
fetchContent,
|
||||
resolve(...segments) {
|
||||
return segments.reduce(
|
||||
(u, segment) => new URL(segment, u),
|
||||
url
|
||||
).pathname
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function addAttribute(value: any, key: string) {
|
||||
if (value == null || value === false) {
|
||||
return '';
|
||||
|
|
|
@ -2,7 +2,7 @@ import type { BuildResult } from 'esbuild';
|
|||
import type { ViteDevServer } from 'vite';
|
||||
import type { AstroConfig, ComponentInstance, GetStaticPathsResult, Params, Props, Renderer, RouteCache, RouteData, RuntimeMode, SSRError } from '../@types/astro';
|
||||
import type { SSRResult } from '../@types/ssr';
|
||||
import type { Astro } from '../@types/astro-file';
|
||||
import type { Astro, TopLevelAstro } from '../@types/astro-file';
|
||||
import type { LogOptions } from '../logger';
|
||||
|
||||
import cheerio from 'cheerio';
|
||||
|
@ -80,30 +80,6 @@ async function resolveRenderers(viteServer: ViteDevServer, ids: string[]): Promi
|
|||
return renderers;
|
||||
}
|
||||
|
||||
/** Create the Astro.fetchContent() runtime function. */
|
||||
function createFetchContentFn(url: URL) {
|
||||
const fetchContent = (importMetaGlobResult: Record<string, any>) => {
|
||||
let allEntries = [...Object.entries(importMetaGlobResult)];
|
||||
if (allEntries.length === 0) {
|
||||
throw new Error(`[${url.pathname}] Astro.fetchContent() no matches found.`);
|
||||
}
|
||||
return allEntries
|
||||
.map(([spec, mod]) => {
|
||||
// Only return Markdown files for now.
|
||||
if (!mod.frontmatter) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
content: mod.metadata,
|
||||
metadata: mod.frontmatter,
|
||||
file: new URL(spec, url),
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
};
|
||||
return fetchContent;
|
||||
}
|
||||
|
||||
/** use Vite to SSR */
|
||||
export async function ssr({ astroConfig, filePath, logging, mode, origin, pathname, route, routeCache, viteServer }: SSROptions): Promise<string> {
|
||||
try {
|
||||
|
@ -128,6 +104,7 @@ export async function ssr({ astroConfig, filePath, logging, mode, origin, pathna
|
|||
routeCache[route.component] =
|
||||
routeCache[route.component] ||
|
||||
(
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
await mod.getStaticPaths!({
|
||||
paginate: generatePaginateFunction(route),
|
||||
rss: () => {
|
||||
|
@ -158,35 +135,28 @@ export async function ssr({ astroConfig, filePath, logging, mode, origin, pathna
|
|||
styles: new Set(),
|
||||
scripts: new Set(),
|
||||
/** This function returns the `Astro` faux-global */
|
||||
createAstro: (props: Record<string, any>, slots: Record<string, any> | null) => {
|
||||
createAstro(AstroGlobal: TopLevelAstro, props: Record<string, any>, slots: Record<string, any> | null) {
|
||||
const site = new URL(origin);
|
||||
const url = new URL('.' + pathname, site);
|
||||
const canonicalURL = getCanonicalURL(pathname, astroConfig.buildOptions.site || origin);
|
||||
// Cast this type because the actual fetchContent implementation relies on import.meta.globEager
|
||||
const fetchContent = createFetchContentFn(filePath) as unknown as Astro['fetchContent'];
|
||||
const canonicalURL = getCanonicalURL('.' + pathname, astroConfig.buildOptions.site || origin);
|
||||
|
||||
return {
|
||||
isPage: true,
|
||||
site,
|
||||
__proto__: AstroGlobal,
|
||||
props,
|
||||
request: {
|
||||
canonicalURL,
|
||||
params: {},
|
||||
url
|
||||
},
|
||||
props,
|
||||
fetchContent,
|
||||
slots: Object.fromEntries(
|
||||
Object.entries(slots || {}).map(([slotName]) => [slotName, true])
|
||||
),
|
||||
// Only temporary to get types working.
|
||||
resolve(_s: string) {
|
||||
throw new Error('Astro.resolve() is not currently supported in next.');
|
||||
}
|
||||
};
|
||||
)
|
||||
} as unknown as Astro;
|
||||
},
|
||||
_metadata: { renderers },
|
||||
};
|
||||
|
||||
let html = await renderPage(result, Component, {}, null);
|
||||
let html = await renderPage(result, Component, pageProps, null);
|
||||
|
||||
// 4. modify response
|
||||
if (mode === 'development') {
|
||||
|
|
|
@ -29,6 +29,7 @@ export default function astro({ config, devServer }: AstroPluginOptions): Plugin
|
|||
// 1. Transform from `.astro` to valid `.ts`
|
||||
// use `sourcemap: "inline"` so that the sourcemap is included in the "code" result that we pass to esbuild.
|
||||
tsResult = await transform(source, {
|
||||
site: config.buildOptions.site,
|
||||
sourcefile: id,
|
||||
sourcemap: 'both',
|
||||
internalURL: 'astro/internal'
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
/**
|
||||
* UNCOMMENT: add Astro.* global
|
||||
|
||||
import { expect } from 'chai';
|
||||
import cheerio from 'cheerio';
|
||||
import { loadFixture } from './test-utils.js';
|
||||
|
@ -32,15 +29,16 @@ describe('Astro.*', () => {
|
|||
it('Astro.request.canonicalURL', async () => {
|
||||
// given a URL, expect the following canonical URL
|
||||
const canonicalURLs = {
|
||||
'/': 'https://mysite.dev/blog/index.html',
|
||||
'/post/post': 'https://mysite.dev/blog/post/post/index.html',
|
||||
'/posts/1': 'https://mysite.dev/blog/posts/index.html',
|
||||
'/posts/2': 'https://mysite.dev/blog/posts/2/index.html',
|
||||
'/index.html': 'https://mysite.dev/blog/',
|
||||
'/post/post/index.html': 'https://mysite.dev/blog/post/post/',
|
||||
'/posts/1/index.html': 'https://mysite.dev/blog/posts/',
|
||||
'/posts/2/index.html': 'https://mysite.dev/blog/posts/2/',
|
||||
};
|
||||
|
||||
for (const [url, canonicalURL] of Object.entries(canonicalURLs)) {
|
||||
const result = await fixture.readFile(url);
|
||||
const $ = cheerio.load(result.contents);
|
||||
const html = await fixture.readFile(url);
|
||||
|
||||
const $ = cheerio.load(html);
|
||||
expect($('link[rel="canonical"]').attr('href')).to.equal(canonicalURL);
|
||||
}
|
||||
});
|
||||
|
@ -54,16 +52,7 @@ describe('Astro.*', () => {
|
|||
it('Astro.resolve in development', async () => {
|
||||
const html = await fixture.readFile('/resolve/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
expect($('img').attr('src')).to.equal('/_astro/src/images/penguin.png');
|
||||
expect($('#inner-child img').attr('src')).to.equal('/_astro/src/components/nested/images/penguin.png');
|
||||
expect($('img').attr('src')).to.equal('/src/images/penguin.png');
|
||||
expect($('#inner-child img').attr('src')).to.equal('/src/components/nested/images/penguin.png');
|
||||
});
|
||||
|
||||
it('Astro.resolve in the build', async () => {
|
||||
const html = await fixture.readFile('/resolve/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
expect($('img').attr('src')).to.equal('/blog/_astro/src/images/penguin.png');
|
||||
});
|
||||
});
|
||||
*/
|
||||
|
||||
it.skip('is skipped', () => {});
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
const title = 'My App';
|
||||
import '../components/my-element.js';
|
||||
const title = 'My App';
|
||||
---
|
||||
|
||||
<html>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
const title = 'My App';
|
||||
import '../components/my-element.js';
|
||||
const title = 'My App';
|
||||
---
|
||||
|
||||
<html>
|
||||
|
|
|
@ -106,10 +106,10 @@
|
|||
"@algolia/logger-common" "4.10.5"
|
||||
"@algolia/requester-common" "4.10.5"
|
||||
|
||||
"@astrojs/compiler@^0.1.13":
|
||||
version "0.1.13"
|
||||
resolved "https://registry.yarnpkg.com/@astrojs/compiler/-/compiler-0.1.13.tgz#06c810d71c7bcce1603ea77f9381940523544e69"
|
||||
integrity sha512-t/soXPJ34AQ67goh8EInCNVWd2HYOpgL5hE2xcZGwG74cIuczT4c+Guiqt50KYF5a7eNTdolPdy/dd7yipW8nQ==
|
||||
"@astrojs/compiler@^0.1.14":
|
||||
version "0.1.14"
|
||||
resolved "https://registry.yarnpkg.com/@astrojs/compiler/-/compiler-0.1.14.tgz#15bb8211312c0ef2cacb9483fdb31c5dff1013ea"
|
||||
integrity sha512-iqc8upge2o9AAL87lB5ptTDZWmrrWpiFX7jGTU3Cuo4X9+KXmJL4oKSMy8sGt7JoKd2j5yZsFfLq9R5V/nwfbQ==
|
||||
dependencies:
|
||||
typescript "^4.3.5"
|
||||
|
||||
|
|
Loading…
Reference in a new issue