Update compiler, improve tests
This commit is contained in:
parent
c3e7b7f37d
commit
7296e0b0a2
9 changed files with 105 additions and 94 deletions
|
@ -89,7 +89,7 @@
|
|||
"srcset-parse": "^1.1.0",
|
||||
"string-width": "^5.0.0",
|
||||
"supports-esm": "^1.0.0",
|
||||
"vite": "^2.5.2",
|
||||
"vite": "^2.5.7",
|
||||
"yargs-parser": "^20.2.9",
|
||||
"zod": "^3.8.1"
|
||||
},
|
||||
|
|
|
@ -42,7 +42,7 @@ async function _render(child: any) {
|
|||
export class AstroComponent {
|
||||
private htmlParts: string[];
|
||||
private expressions: TemplateStringsArray;
|
||||
|
||||
|
||||
constructor(htmlParts: string[], expressions: TemplateStringsArray) {
|
||||
this.htmlParts = htmlParts;
|
||||
this.expressions = expressions;
|
||||
|
@ -74,7 +74,7 @@ export const createComponent = (cb: AstroComponentFactory) => {
|
|||
// Add a flag to this callback to mark it as an Astro component
|
||||
(cb as any).isAstroComponentFactory = true;
|
||||
return cb;
|
||||
}
|
||||
};
|
||||
|
||||
function extractHydrationDirectives(inputProps: Record<string | number, any>): { hydrationDirective: [string, any] | null; props: Record<string | number, any> } {
|
||||
let props: Record<string | number, any> = {};
|
||||
|
@ -101,7 +101,7 @@ async function generateHydrateScript(scriptOptions: HydrateScriptOptions, metada
|
|||
const { hydrate, componentUrl, componentExport } = metadata;
|
||||
|
||||
if (!componentExport) {
|
||||
throw new Error(`Unable to resolve a componentExport for "${metadata.displayName}"! Please open an issue.`)
|
||||
throw new Error(`Unable to resolve a componentExport for "${metadata.displayName}"! Please open an issue.`);
|
||||
}
|
||||
|
||||
let hydrationSource = '';
|
||||
|
@ -133,7 +133,7 @@ export const renderComponent = async (result: any, displayName: string, Componen
|
|||
// children = await renderGenerator(children);
|
||||
const { renderers } = result._metadata;
|
||||
if (Component && (Component as any).isAstroComponentFactory) {
|
||||
const output = await renderAstroComponent(await (Component as any)(result, Component, _props, children))
|
||||
const output = await renderAstroComponent(await (Component as any)(result, Component, _props, children));
|
||||
return output;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import type { AstroComponent, AstroComponentFactory } from '../internal';
|
|||
|
||||
interface SSROptions {
|
||||
/** an instance of the AstroConfig */
|
||||
astroConfig: AstroConfig,
|
||||
astroConfig: AstroConfig;
|
||||
/** location of file on disk */
|
||||
filePath: URL;
|
||||
/** logging options */
|
||||
|
@ -51,14 +51,14 @@ export async function renderAstroComponent(component: InstanceType<typeof AstroC
|
|||
export async function renderToString(result: any, componentFactory: AstroComponentFactory, props: any, children: any) {
|
||||
const Component = await componentFactory(result, props, children);
|
||||
let template = await renderAstroComponent(Component);
|
||||
return template
|
||||
return template;
|
||||
}
|
||||
|
||||
async function renderPage(result: any, Component: AstroComponentFactory, props: any, children: any) {
|
||||
const template = await renderToString(result, Component, props, children);
|
||||
const styles = Array.from(result.styles).map(style => `<style>${style}</style>`);
|
||||
const styles = Array.from(result.styles).map((style) => `<style>${style}</style>`);
|
||||
const scripts = Array.from(result.scripts);
|
||||
return template.replace("</head>", styles.join('\n') + scripts.join('\n') + "</head>");
|
||||
return template.replace('</head>', styles.join('\n') + scripts.join('\n') + '</head>');
|
||||
}
|
||||
|
||||
const cache = new Map();
|
||||
|
@ -66,44 +66,46 @@ const cache = new Map();
|
|||
// TODO: improve validation and error handling here.
|
||||
async function resolveRenderers(viteServer: ViteDevServer, ids: string[]) {
|
||||
const resolve = viteServer.config.createResolver();
|
||||
const renderers = await Promise.all(ids.map(async renderer => {
|
||||
if (cache.has(renderer)) return cache.get(renderer);
|
||||
const resolvedRenderer: any = {};
|
||||
const renderers = await Promise.all(
|
||||
ids.map(async (renderer) => {
|
||||
if (cache.has(renderer)) return cache.get(renderer);
|
||||
const resolvedRenderer: any = {};
|
||||
|
||||
// We can dynamically import the renderer by itself because it shouldn't have
|
||||
// any non-standard imports, the index is just meta info.
|
||||
// The other entrypoints need to be loaded through Vite.
|
||||
const { default: instance } = await import(renderer);
|
||||
|
||||
// This resolves the renderer's entrypoints to a final URL through Vite
|
||||
const getPath = async (src: string) => {
|
||||
const spec = path.posix.join(instance.name, src);
|
||||
const resolved = await resolve(spec);
|
||||
if (!resolved) {
|
||||
throw new Error(`Unable to resolve "${spec}" to a package!`)
|
||||
// We can dynamically import the renderer by itself because it shouldn't have
|
||||
// any non-standard imports, the index is just meta info.
|
||||
// The other entrypoints need to be loaded through Vite.
|
||||
const { default: instance } = await import(renderer);
|
||||
|
||||
// This resolves the renderer's entrypoints to a final URL through Vite
|
||||
const getPath = async (src: string) => {
|
||||
const spec = path.posix.join(instance.name, src);
|
||||
const resolved = await resolve(spec);
|
||||
if (!resolved) {
|
||||
throw new Error(`Unable to resolve "${spec}" to a package!`);
|
||||
}
|
||||
return resolved;
|
||||
};
|
||||
|
||||
resolvedRenderer.name = instance.name;
|
||||
if (instance.client) {
|
||||
resolvedRenderer.source = await getPath(instance.client);
|
||||
}
|
||||
if (Array.isArray(instance.hydrationPolyfills)) {
|
||||
resolvedRenderer.hydrationPolyfills = await Promise.all(instance.hydrationPolyfills.map((src: string) => getPath(src)));
|
||||
}
|
||||
if (Array.isArray(instance.polyfills)) {
|
||||
resolvedRenderer.polyfills = await Promise.all(instance.polyfills.map((src: string) => getPath(src)));
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
resolvedRenderer.name = instance.name;
|
||||
if (instance.client) {
|
||||
resolvedRenderer.source = await getPath(instance.client);
|
||||
}
|
||||
if (Array.isArray(instance.hydrationPolyfills)) {
|
||||
resolvedRenderer.hydrationPolyfills = await Promise.all(instance.hydrationPolyfills.map((src: string) => getPath(src)));
|
||||
}
|
||||
if (Array.isArray(instance.polyfills)) {
|
||||
resolvedRenderer.polyfills = await Promise.all(instance.polyfills.map((src: string) => getPath(src)));
|
||||
}
|
||||
|
||||
const { url } = await viteServer.moduleGraph.ensureEntryFromUrl(await getPath(instance.server));
|
||||
const { default: server } = await viteServer.ssrLoadModule(url);
|
||||
resolvedRenderer.ssr = server;
|
||||
|
||||
cache.set(renderer, resolvedRenderer);
|
||||
return resolvedRenderer
|
||||
}));
|
||||
|
||||
return resolvedRenderer;
|
||||
})
|
||||
);
|
||||
|
||||
return renderers;
|
||||
}
|
||||
|
||||
|
@ -116,35 +118,39 @@ async function resolveImportedModules(viteServer: ViteDevServer, file: string) {
|
|||
|
||||
let importedModules: Record<string, any> = {};
|
||||
const moduleNodes = Array.from(modulesByFile);
|
||||
|
||||
|
||||
// Loop over the importedModules and grab the exports from each one.
|
||||
// We'll pass these to the shared $$result so renderers can match
|
||||
// components to their exported identifier and URL
|
||||
// NOTE: Important that this is parallelized as much as possible!
|
||||
await Promise.all(moduleNodes.map(moduleNode => {
|
||||
const entries = Array.from(moduleNode.importedModules);
|
||||
|
||||
return Promise.all(entries.map(entry => {
|
||||
// Skip our internal import that every module will have
|
||||
if (entry.id?.endsWith('astro/dist/internal/index.js')) {
|
||||
return;
|
||||
}
|
||||
|
||||
return viteServer.moduleGraph.ensureEntryFromUrl(entry.url).then(mod => {
|
||||
if (mod.ssrModule) {
|
||||
importedModules[mod.url] = mod.ssrModule;
|
||||
return;
|
||||
} else {
|
||||
return viteServer.ssrLoadModule(mod.url).then(result => {
|
||||
importedModules[mod.url] = result.ssrModule;
|
||||
return;
|
||||
})
|
||||
}
|
||||
})
|
||||
}))
|
||||
}))
|
||||
await Promise.all(
|
||||
moduleNodes.map((moduleNode) => {
|
||||
const entries = Array.from(moduleNode.importedModules);
|
||||
|
||||
return importedModules
|
||||
return Promise.all(
|
||||
entries.map((entry) => {
|
||||
// Skip our internal import that every module will have
|
||||
if (entry.id?.endsWith('astro/dist/internal/index.js')) {
|
||||
return;
|
||||
}
|
||||
|
||||
return viteServer.moduleGraph.ensureEntryFromUrl(entry.url).then((mod) => {
|
||||
if (mod.ssrModule) {
|
||||
importedModules[mod.url] = mod.ssrModule;
|
||||
return;
|
||||
} else {
|
||||
return viteServer.ssrLoadModule(mod.url).then((result) => {
|
||||
importedModules[mod.url] = result.ssrModule;
|
||||
return;
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
return importedModules;
|
||||
}
|
||||
|
||||
/** use Vite to SSR */
|
||||
|
@ -154,10 +160,7 @@ export async function ssr({ astroConfig, filePath, logging, mode, origin, pathna
|
|||
|
||||
// 1.5. resolve renderers and imported modules.
|
||||
// important that this happens _after_ ssrLoadModule, otherwise `importedModules` would be empty
|
||||
const [renderers, importedModules] = await Promise.all([
|
||||
resolveRenderers(viteServer, astroConfig.renderers),
|
||||
resolveImportedModules(viteServer, fileURLToPath(filePath))
|
||||
]);
|
||||
const [renderers, importedModules] = await Promise.all([resolveRenderers(viteServer, astroConfig.renderers), resolveImportedModules(viteServer, fileURLToPath(filePath))]);
|
||||
|
||||
// 2. handle dynamic routes
|
||||
let params: Params = {};
|
||||
|
@ -192,22 +195,26 @@ export async function ssr({ astroConfig, filePath, logging, mode, origin, pathna
|
|||
const fullURL = new URL(pathname, origin);
|
||||
|
||||
const Component = await mod.default;
|
||||
if (!Component)
|
||||
throw new Error(`Expected an exported Astro component but recieved typeof ${typeof Component}`);
|
||||
if (!Component) throw new Error(`Expected an exported Astro component but recieved typeof ${typeof Component}`);
|
||||
if (!Component.isAstroComponentFactory) throw new Error(`Unable to SSR non-Astro component (${route?.component})`);
|
||||
|
||||
let html = await renderPage({
|
||||
styles: new Set(),
|
||||
scripts: new Set(),
|
||||
/** This function returns the `Astro` faux-global */
|
||||
createAstro(props: any) {
|
||||
const site = new URL(origin);
|
||||
const url = new URL('.' + pathname, site);
|
||||
const canonicalURL = getCanonicalURL(pathname, astroConfig.buildOptions.site || origin)
|
||||
return { isPage: true, site, request: { url, canonicalURL }, props };
|
||||
let html = await renderPage(
|
||||
{
|
||||
styles: new Set(),
|
||||
scripts: new Set(),
|
||||
/** This function returns the `Astro` faux-global */
|
||||
createAstro(props: any) {
|
||||
const site = new URL(origin);
|
||||
const url = new URL('.' + pathname, site);
|
||||
const canonicalURL = getCanonicalURL(pathname, astroConfig.buildOptions.site || origin);
|
||||
return { isPage: true, site, request: { url, canonicalURL }, props };
|
||||
},
|
||||
_metadata: { importedModules, renderers },
|
||||
},
|
||||
_metadata: { importedModules, renderers },
|
||||
}, Component, { }, null);
|
||||
Component,
|
||||
{},
|
||||
null
|
||||
);
|
||||
|
||||
// 4. modify response
|
||||
if (mode === 'development') {
|
||||
|
|
|
@ -18,7 +18,10 @@ const require = createRequire(import.meta.url);
|
|||
type ViteConfigWithSSR = InlineConfig & { ssr?: { external?: string[]; noExternal?: string[] } };
|
||||
|
||||
/** Return a common starting point for all Vite actions */
|
||||
export async function loadViteConfig(viteConfig: ViteConfigWithSSR, { astroConfig, logging, devServer }: { astroConfig: AstroConfig; logging: LogOptions, devServer?: AstroDevServer }): Promise<ViteConfigWithSSR> {
|
||||
export async function loadViteConfig(
|
||||
viteConfig: ViteConfigWithSSR,
|
||||
{ astroConfig, logging, devServer }: { astroConfig: AstroConfig; logging: LogOptions; devServer?: AstroDevServer }
|
||||
): Promise<ViteConfigWithSSR> {
|
||||
const optimizedDeps = new Set<string>(); // dependencies that must be bundled for the client (Vite may not detect all of these)
|
||||
const dedupe = new Set<string>(); // dependencies that can’t be duplicated (e.g. React & SolidJS)
|
||||
const plugins: Plugin[] = []; // Vite plugins
|
||||
|
@ -41,7 +44,8 @@ export async function loadViteConfig(viteConfig: ViteConfigWithSSR, { astroConfi
|
|||
optimizedDeps.add(name + renderer.client.substr(1));
|
||||
}
|
||||
// 2. knownEntrypoints and polyfills need to be added to the client
|
||||
for (const dep of [...(renderer.knownEntrypoints || []), ...(renderer.polyfills || [])]) {
|
||||
for (let dep of [...(renderer.knownEntrypoints || []), ...(renderer.polyfills || [])]) {
|
||||
if (dep[0] === '.') dep = name + dep.substr(1); // if local polyfill, use full path
|
||||
optimizedDeps.add(dep);
|
||||
dedupe.add(dep); // we can try and dedupe renderers by default
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ describe('Doctype', () => {
|
|||
expect(html).toEqual(expect.stringMatching(/^<!doctype html>/));
|
||||
|
||||
// test 2: A link inside of the head
|
||||
const $ = doc(html);
|
||||
const $ = cheerio.load(html);
|
||||
expect($('head link')).toHaveLength(1);
|
||||
});
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ describe.skip('HMR tests', () => {
|
|||
const result = await runtime.load('/static');
|
||||
assert.ok(!result.error, `build error: ${result.error}`);
|
||||
const html = result.contents;
|
||||
const $ = doc(html);
|
||||
const $ = cheerio.load(html);
|
||||
assert.equal($('[src="/_snowpack/hmr-client.js"]').length, 1);
|
||||
assert.ok(/window\.HMR_WEBSOCKET_PORT/.test(html), 'websocket port added');
|
||||
});
|
||||
|
@ -39,7 +39,7 @@ describe.skip('HMR tests', () => {
|
|||
const result = await runtime.load('/no-elements');
|
||||
assert.ok(!result.error, `build error: ${result.error}`);
|
||||
const html = result.contents;
|
||||
const $ = doc(html);
|
||||
const $ = cheerio.load(html);
|
||||
assert.equal($('[src="/_snowpack/hmr-client.js"]').length, 1);
|
||||
assert.ok(/window\.HMR_WEBSOCKET_PORT/.test(html), 'websocket port added');
|
||||
});
|
||||
|
|
|
@ -18,7 +18,7 @@ describe('Hoisted scripts', () => {
|
|||
|
||||
test('Moves external scripts up', async () => {
|
||||
const html = await fixture.fetch('/external').then((res) => res.text());
|
||||
const $ = doc(html);
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
expect($('head script[type="module"][data-astro="hoist"]')).toHaveLength(2);
|
||||
expect($('body script')).toHaveLength(0);
|
||||
|
@ -26,7 +26,7 @@ describe('Hoisted scripts', () => {
|
|||
|
||||
test('Moves inline scripts up', async () => {
|
||||
const html = await fixture.fetch('/inline').then((res) => res.text());
|
||||
const $ = doc(html);
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
expect($('head script[type="module"][data-astro="hoist"]')).toHaveLength(1);
|
||||
expect($('body script')).toHaveLength(0);
|
||||
|
@ -63,7 +63,7 @@ describe('Hoisted scripts', () => {
|
|||
|
||||
test('External page builds the scripts to a single bundle', async () => {
|
||||
let external = await fixture.readFile('/external/index.html');
|
||||
$ = doc(external);
|
||||
$ = cheerio.load(external);
|
||||
|
||||
// test 1: there are two scripts
|
||||
assert.equal($('script')).toHaveLength(2);
|
||||
|
|
|
@ -19,7 +19,7 @@ describe('LitElement test', () => {
|
|||
return;
|
||||
}
|
||||
const html = await fixture.fetch('/').then((res) => res.text());
|
||||
const $ = doc(html);
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
// test 1: attributes rendered
|
||||
expect($('my-element').attr('foo')).toBe('bar');
|
||||
|
@ -31,7 +31,7 @@ describe('LitElement test', () => {
|
|||
// Skipped because not supported by Lit
|
||||
test.skip('Renders a custom element by the constructor', async () => {
|
||||
const html = await fixture.fetch('/ctr').then((res) => res.text());
|
||||
const $ = doc(html);
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
// test 1: attributes rendered
|
||||
expect($('my-element').attr('foo')).toBe('bar');
|
||||
|
|
|
@ -11679,10 +11679,10 @@ vfile@^5.0.0:
|
|||
unist-util-stringify-position "^3.0.0"
|
||||
vfile-message "^3.0.0"
|
||||
|
||||
vite@^2.5.2:
|
||||
version "2.5.2"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-2.5.2.tgz#3963a4ec1e6ecae49359eddfdd67f6cb1e6e07a1"
|
||||
integrity sha512-JK5uhiVyMqHiAJbgBa8rCvpP8bEhAE9dKDv1gCmP+EUP2FSPmEeW3WXlCXauPB3MDa8behPW+ntyNXqnGaxslg==
|
||||
vite@^2.5.7:
|
||||
version "2.5.7"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-2.5.7.tgz#e495be9d8bcbf9d30c7141efdccacde746ee0125"
|
||||
integrity sha512-hyUoWmRPhjN1aI+ZSBqDINKdIq7aokHE2ZXiztOg4YlmtpeQtMwMeyxv6X9YxHZmvGzg/js/eATM9Z1nwyakxg==
|
||||
dependencies:
|
||||
esbuild "^0.12.17"
|
||||
postcss "^8.3.6"
|
||||
|
|
Loading…
Reference in a new issue