From 7296e0b0a2d5564b2847bdf8d407e54cb3a4be0b Mon Sep 17 00:00:00 2001 From: Drew Powers Date: Fri, 10 Sep 2021 18:36:16 -0600 Subject: [PATCH] Update compiler, improve tests --- packages/astro/package.json | 2 +- packages/astro/src/internal/index.ts | 8 +- packages/astro/src/runtime/ssr.ts | 157 +++++++++++----------- packages/astro/src/runtime/vite/config.ts | 8 +- packages/astro/test/astro-doctype.test.js | 2 +- packages/astro/test/astro-hmr.test.js | 4 +- packages/astro/test/astro-scripts.test.js | 6 +- packages/astro/test/lit-element.test.js | 4 +- yarn.lock | 8 +- 9 files changed, 105 insertions(+), 94 deletions(-) diff --git a/packages/astro/package.json b/packages/astro/package.json index 438627460..76ab7ba0e 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -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" }, diff --git a/packages/astro/src/internal/index.ts b/packages/astro/src/internal/index.ts index 5b9592d88..0252294c6 100644 --- a/packages/astro/src/internal/index.ts +++ b/packages/astro/src/internal/index.ts @@ -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): { hydrationDirective: [string, any] | null; props: Record } { let props: Record = {}; @@ -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; } diff --git a/packages/astro/src/runtime/ssr.ts b/packages/astro/src/runtime/ssr.ts index 8f0e5146c..522221d62 100644 --- a/packages/astro/src/runtime/ssr.ts +++ b/packages/astro/src/runtime/ssr.ts @@ -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 ``); + const styles = Array.from(result.styles).map((style) => ``); const scripts = Array.from(result.scripts); - return template.replace("", styles.join('\n') + scripts.join('\n') + ""); + return template.replace('', styles.join('\n') + scripts.join('\n') + ''); } 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 = {}; 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') { diff --git a/packages/astro/src/runtime/vite/config.ts b/packages/astro/src/runtime/vite/config.ts index c46fa3799..dcc3715f7 100644 --- a/packages/astro/src/runtime/vite/config.ts +++ b/packages/astro/src/runtime/vite/config.ts @@ -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 { +export async function loadViteConfig( + viteConfig: ViteConfigWithSSR, + { astroConfig, logging, devServer }: { astroConfig: AstroConfig; logging: LogOptions; devServer?: AstroDevServer } +): Promise { const optimizedDeps = new Set(); // dependencies that must be bundled for the client (Vite may not detect all of these) const dedupe = new Set(); // 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 } diff --git a/packages/astro/test/astro-doctype.test.js b/packages/astro/test/astro-doctype.test.js index 2a89a40e8..092d6a352 100644 --- a/packages/astro/test/astro-doctype.test.js +++ b/packages/astro/test/astro-doctype.test.js @@ -66,7 +66,7 @@ describe('Doctype', () => { expect(html).toEqual(expect.stringMatching(/^/)); // test 2: A link inside of the head - const $ = doc(html); + const $ = cheerio.load(html); expect($('head link')).toHaveLength(1); }); diff --git a/packages/astro/test/astro-hmr.test.js b/packages/astro/test/astro-hmr.test.js index 19b027e21..74b89a431 100644 --- a/packages/astro/test/astro-hmr.test.js +++ b/packages/astro/test/astro-hmr.test.js @@ -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'); }); diff --git a/packages/astro/test/astro-scripts.test.js b/packages/astro/test/astro-scripts.test.js index bb1780d73..d0c5db388 100644 --- a/packages/astro/test/astro-scripts.test.js +++ b/packages/astro/test/astro-scripts.test.js @@ -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); diff --git a/packages/astro/test/lit-element.test.js b/packages/astro/test/lit-element.test.js index d2abcbe85..541b20f65 100644 --- a/packages/astro/test/lit-element.test.js +++ b/packages/astro/test/lit-element.test.js @@ -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'); diff --git a/yarn.lock b/yarn.lock index d969c7cbd..af33092b9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"