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