From 03cabc51714a8963eeacbe896da22f47159d9d59 Mon Sep 17 00:00:00 2001 From: Drew Powers <1369770+drwpow@users.noreply.github.com> Date: Tue, 30 Nov 2021 11:54:37 -0700 Subject: [PATCH] Fix Windows dev script proxying (#2052) * Add tests for script proxying * Fix Windows script proxying #2053 --- packages/astro/src/core/dev/index.ts | 10 +- packages/astro/src/core/ssr/css.ts | 20 +- packages/astro/src/core/ssr/index.ts | 3 +- packages/astro/src/core/util.ts | 6 +- .../solid-component/src/pages/index.astro | 6 +- .../src/pages/typescript.astro | 2 +- packages/astro/test/react-component.test.js | 183 ++++++++++-------- packages/astro/test/solid-component.test.js | 58 ++++-- packages/astro/test/svelte-component.test.js | 39 +++- packages/astro/test/vue-component.test.js | 60 ++++-- 10 files changed, 245 insertions(+), 142 deletions(-) diff --git a/packages/astro/src/core/dev/index.ts b/packages/astro/src/core/dev/index.ts index f1326d30f..c2f2bca94 100644 --- a/packages/astro/src/core/dev/index.ts +++ b/packages/astro/src/core/dev/index.ts @@ -21,7 +21,6 @@ import { createVite } from '../create-vite.js'; import * as msg from './messages.js'; import notFoundTemplate, { subpathNotUsedTemplate } from './template/4xx.js'; import serverErrorTemplate from './template/5xx.js'; -import { viteifyURL } from '../util.js'; export interface DevOptions { logging: LogOptions; @@ -329,14 +328,7 @@ export class AstroDevServer { res.end(); } catch (err: any) { const statusCode = 500; - const mod = filePath && this.viteServer.moduleGraph.getModuleById(viteifyURL(filePath)); - if (mod) { - for (const m of [mod, ...mod.importedModules]) { - this.viteServer.moduleGraph.invalidateModule(m); - } - } else { - this.viteServer.moduleGraph.invalidateAll(); - } + await this.viteServer.moduleGraph.invalidateAll(); this.viteServer.ws.send({ type: 'error', err }); let html = serverErrorTemplate({ statusCode, diff --git a/packages/astro/src/core/ssr/css.ts b/packages/astro/src/core/ssr/css.ts index afbd53461..0f2ffb00a 100644 --- a/packages/astro/src/core/ssr/css.ts +++ b/packages/astro/src/core/ssr/css.ts @@ -9,27 +9,23 @@ export const STYLE_EXTENSIONS = new Set(['.css', '.pcss', '.postcss', '.scss', ' /** find unloaded styles */ export function getStylesForURL(filePath: URL, viteServer: vite.ViteDevServer): Set { const css = new Set(); - const { idToModuleMap } = viteServer.moduleGraph; const rootID = viteifyURL(filePath); - const moduleGraph = idToModuleMap.get(rootID); - if (!moduleGraph) return css; // recursively crawl module graph to get all style files imported by parent id function crawlCSS(entryModule: string, scanned = new Set()) { - const moduleName = idToModuleMap.get(entryModule); - if (!moduleName) return; - if (!moduleName.id) return; + const moduleName = viteServer.moduleGraph.urlToModuleMap.get(entryModule); + if (!moduleName || !moduleName.id) return; // mark the entrypoint as scanned to avoid an infinite loop - scanned.add(moduleName.id); + scanned.add(moduleName.url); for (const importedModule of moduleName.importedModules) { - if (!importedModule.id || scanned.has(importedModule.id)) continue; - const ext = path.extname(importedModule.id.toLowerCase()); + if (!importedModule.url || scanned.has(importedModule.url)) continue; + const ext = path.extname(importedModule.url.toLowerCase()); if (STYLE_EXTENSIONS.has(ext)) { - css.add(importedModule.url || importedModule.id); // if style file, add to list + css.add(importedModule.url); // if style file, add to list } else { - crawlCSS(importedModule.id, scanned); // otherwise, crawl file to see if it imports any CSS + crawlCSS(importedModule.url, scanned); // otherwise, crawl file to see if it imports any CSS } - scanned.add(importedModule.id); + scanned.add(importedModule.url); } } crawlCSS(rootID); diff --git a/packages/astro/src/core/ssr/index.ts b/packages/astro/src/core/ssr/index.ts index 5aad47b4b..af9702c9b 100644 --- a/packages/astro/src/core/ssr/index.ts +++ b/packages/astro/src/core/ssr/index.ts @@ -266,7 +266,8 @@ export async function render(renderers: Renderer[], mod: ComponentInstance, ssrO // run transformIndexHtml() in dev to run Vite dev transformations if (mode === 'development') { - html = await viteServer.transformIndexHtml(viteifyURL(filePath), html, pathname); + const relativeURL = filePath.href.replace(astroConfig.projectRoot.href, '/'); + html = await viteServer.transformIndexHtml(relativeURL, html, pathname); } // inject if missing (TODO: is a more robust check needed for comments, etc.?) diff --git a/packages/astro/src/core/util.ts b/packages/astro/src/core/util.ts index 45ada43e2..e05a76801 100644 --- a/packages/astro/src/core/util.ts +++ b/packages/astro/src/core/util.ts @@ -77,9 +77,9 @@ export function resolveDependency(dep: string, astroConfig: AstroConfig) { * Vite-ify URL * Given a file URL, return an ID that matches Vite’s module graph. Needed for resolution and stack trace fixing. * Must match the following format: - * Linux/Mac: /Users/astro/code/my-project/src/pages/index.astro - * Windows: C:/Users/astro/code/my-project/src/pages/index.astro + * Linux/Mac: /@fs/Users/astro/code/my-project/src/pages/index.astro + * Windows: /@fs/C:/Users/astro/code/my-project/src/pages/index.astro */ export function viteifyURL(filePath: URL): string { - return slash(fileURLToPath(filePath)); + return `/@fs${filePath.pathname}`; } diff --git a/packages/astro/test/fixtures/solid-component/src/pages/index.astro b/packages/astro/test/fixtures/solid-component/src/pages/index.astro index b489bda08..73a9e2d3a 100644 --- a/packages/astro/test/fixtures/solid-component/src/pages/index.astro +++ b/packages/astro/test/fixtures/solid-component/src/pages/index.astro @@ -4,6 +4,8 @@ import Hello from '../components/Hello.jsx'; Solid -
+
+ +
- \ No newline at end of file + diff --git a/packages/astro/test/fixtures/svelte-component/src/pages/typescript.astro b/packages/astro/test/fixtures/svelte-component/src/pages/typescript.astro index 229ec763b..d6b416756 100644 --- a/packages/astro/test/fixtures/svelte-component/src/pages/typescript.astro +++ b/packages/astro/test/fixtures/svelte-component/src/pages/typescript.astro @@ -19,7 +19,7 @@ import TypeScript from '../components/TypeScript.svelte'
- +
diff --git a/packages/astro/test/react-component.test.js b/packages/astro/test/react-component.test.js index 6eba570b5..c6485460f 100644 --- a/packages/astro/test/react-component.test.js +++ b/packages/astro/test/react-component.test.js @@ -4,98 +4,125 @@ import { loadFixture } from './test-utils.js'; let fixture; -before(async () => { - fixture = await loadFixture({ - projectRoot: './fixtures/react-component/', - renderers: ['@astrojs/renderer-react', '@astrojs/renderer-vue'], - }); - await fixture.build(); -}); - describe('React Components', () => { - it('Can load React', async () => { - const html = await fixture.readFile('/index.html'); - const $ = cheerio.load(html); - - // test 1: basic component renders - expect($('#react-h2').text()).to.equal('Hello world!'); - - // test 2: no reactroot - expect($('#react-h2').attr('data-reactroot')).to.equal(undefined); - - // test 3: Can use function components - expect($('#arrow-fn-component')).to.have.lengthOf(1); - - // test 4: Can use spread for components - expect($('#component-spread-props')).to.have.lengthOf(1); - - // test 5: spread props renders - expect($('#component-spread-props').text(), 'Hello world!'); - - // test 6: Can use TS components - expect($('.ts-component')).to.have.lengthOf(1); - - // test 7: Can use Pure components - expect($('#pure')).to.have.lengthOf(1); + before(async () => { + fixture = await loadFixture({ + projectRoot: './fixtures/react-component/', + renderers: ['@astrojs/renderer-react', '@astrojs/renderer-vue'], + }); }); - // TODO: fix compiler bug - it.skip('Includes reactroot on hydrating components', async () => { - const html = await fixture.readFile('/index.html'); - const $ = cheerio.load(html); + describe('build', () => { + before(async () => { + await fixture.build(); + }); - const div = $('#research'); + it('Can load React', async () => { + const html = await fixture.readFile('/index.html'); + const $ = cheerio.load(html); - // test 1: has the hydration attr - expect(div.attr('data-reactroot')).to.be.ok; + // test 1: basic component renders + expect($('#react-h2').text()).to.equal('Hello world!'); - // test 2: renders correctly - expect(div.html()).to.equal('foo bar 1'); + // test 2: no reactroot + expect($('#react-h2').attr('data-reactroot')).to.equal(undefined); + + // test 3: Can use function components + expect($('#arrow-fn-component')).to.have.lengthOf(1); + + // test 4: Can use spread for components + expect($('#component-spread-props')).to.have.lengthOf(1); + + // test 5: spread props renders + expect($('#component-spread-props').text(), 'Hello world!'); + + // test 6: Can use TS components + expect($('.ts-component')).to.have.lengthOf(1); + + // test 7: Can use Pure components + expect($('#pure')).to.have.lengthOf(1); + }); + + it('Can load Vue', async () => { + const html = await fixture.readFile('/index.html'); + const $ = cheerio.load(html); + expect($('#vue-h2').text()).to.equal('Hasta la vista, baby'); + }); + + it('Can use a pragma comment', async () => { + const html = await fixture.readFile('/pragma-comment/index.html'); + const $ = cheerio.load(html); + + // test 1: rendered the PragmaComment component + expect($('.pragma-comment')).to.have.lengthOf(2); + }); + + // TODO: is this still a relevant test? + it.skip('Includes reactroot on hydrating components', async () => { + const html = await fixture.readFile('/index.html'); + const $ = cheerio.load(html); + + const div = $('#research'); + + // test 1: has the hydration attr + expect(div.attr('data-reactroot')).to.be.ok; + + // test 2: renders correctly + expect(div.html()).to.equal('foo bar 1'); + }); }); - // TODO: Vite does not throw a helpful error message on window SSR - it.skip('Throws helpful error message on window SSR', async () => { - const html = await fixture.readFile('/window/index.html'); - expect(html).to.include( - `[/window] + describe('dev', () => { + let devServer; + + before(async () => { + devServer = await fixture.startDevServer(); + }); + + after(async () => { + devServer && (await devServer.stop()); + }); + + it('scripts proxy correctly', async () => { + const html = await fixture.fetch('/').then((res) => res.text()); + const $ = cheerio.load(html); + + for (const script of $('script').toArray()) { + const { src } = script.attribs; + if (!src) continue; + expect((await fixture.fetch(src)).status, `404: ${src}`).to.equal(200); + } + }); + + // TODO: move this to separate dev test? + it.skip('Throws helpful error message on window SSR', async () => { + const html = await fixture.fetch('/window/index.html'); + expect(html).to.include( + `[/window] The window object is not available during server-side rendering (SSR). Try using \`import.meta.env.SSR\` to write SSR-friendly code. https://docs.astro.build/reference/api-reference/#importmeta` - ); - }); + ); + }); - it('Can load Vue', async () => { - const html = await fixture.readFile('/index.html'); - const $ = cheerio.load(html); - expect($('#vue-h2').text()).to.equal('Hasta la vista, baby'); - }); + // In moving over to Vite, the jsx-runtime import is now obscured. TODO: update the method of finding this. + it.skip('uses the new JSX transform', async () => { + const html = await fixture.fetch('/index.html'); - // TODO: fix - it('Can use a pragma comment', async () => { - const html = await fixture.readFile('/pragma-comment/index.html'); - const $ = cheerio.load(html); - - // test 1: rendered the PragmaComment component - expect($('.pragma-comment')).to.have.lengthOf(2); - }); - - // In moving over to Vite, the jsx-runtime import is now obscured. TODO: update the method of finding this. - it.skip('uses the new JSX transform', async () => { - const html = await fixture.fetch('/index.html'); - - // Grab the imports - const exp = /import\("(.+?)"\)/g; - let match, componentUrl; - while ((match = exp.exec(html))) { - if (match[1].includes('Research.js')) { - componentUrl = match[1]; - break; + // Grab the imports + const exp = /import\("(.+?)"\)/g; + let match, componentUrl; + while ((match = exp.exec(html))) { + if (match[1].includes('Research.js')) { + componentUrl = match[1]; + break; + } } - } - const component = await fixture.readFile(componentUrl); - const jsxRuntime = component.imports.filter((i) => i.specifier.includes('jsx-runtime')); + const component = await fixture.readFile(componentUrl); + const jsxRuntime = component.imports.filter((i) => i.specifier.includes('jsx-runtime')); - // test 1: react/jsx-runtime is used for the component - expect(jsxRuntime).to.be.ok; + // test 1: react/jsx-runtime is used for the component + expect(jsxRuntime).to.be.ok; + }); }); }); diff --git a/packages/astro/test/solid-component.test.js b/packages/astro/test/solid-component.test.js index da1022208..426f687bc 100644 --- a/packages/astro/test/solid-component.test.js +++ b/packages/astro/test/solid-component.test.js @@ -2,22 +2,50 @@ import { expect } from 'chai'; import cheerio from 'cheerio'; import { loadFixture } from './test-utils.js'; -let fixture; - -before(async () => { - fixture = await loadFixture({ - projectRoot: './fixtures/solid-component/', - renderers: ['@astrojs/renderer-solid'], - }); - await fixture.build(); -}); - describe('Solid component', () => { - it('Can load a component', async () => { - const html = await fixture.readFile('/index.html'); - const $ = cheerio.load(html); + let fixture; - // test 1: Works - expect($('.hello')).to.have.lengthOf(1); + before(async () => { + fixture = await loadFixture({ + projectRoot: './fixtures/solid-component/', + renderers: ['@astrojs/renderer-solid'], + }); + }); + + describe('build', () => { + before(async () => { + await fixture.build(); + }); + + it('Can load a component', async () => { + const html = await fixture.readFile('/index.html'); + const $ = cheerio.load(html); + + // test 1: Works + expect($('.hello')).to.have.lengthOf(1); + }); + }); + + describe('dev', () => { + let devServer; + + before(async () => { + devServer = await fixture.startDevServer(); + }); + + after(async () => { + devServer & devServer.stop(); + }); + + it('scripts proxy correctly', async () => { + const html = await fixture.fetch('/').then((res) => res.text()); + const $ = cheerio.load(html); + + for (const script of $('script').toArray()) { + const { src } = script.attribs; + if (!src) continue; + expect((await fixture.fetch(src)).status, `404: ${src}`).to.equal(200); + } + }); }); }); diff --git a/packages/astro/test/svelte-component.test.js b/packages/astro/test/svelte-component.test.js index 0d79be3de..f50f24e0c 100644 --- a/packages/astro/test/svelte-component.test.js +++ b/packages/astro/test/svelte-component.test.js @@ -10,13 +10,42 @@ describe('Svelte component', () => { projectRoot: './fixtures/svelte-component/', renderers: ['@astrojs/renderer-svelte'], }); - await fixture.build(); }); - it('Works with TypeScript', async () => { - const html = await fixture.readFile('/typescript/index.html'); - const $ = cheerio.load(html); + describe('build', () => { + before(async () => { + await fixture.build(); + }); - expect($('#svelte-ts').text()).to.equal('Hello, TypeScript'); + it('Works with TypeScript', async () => { + const html = await fixture.readFile('/typescript/index.html'); + const $ = cheerio.load(html); + + expect($('#svelte-ts').text()).to.equal('Hello, TypeScript'); + }); + }); + + describe('dev', () => { + let devServer; + + before(async () => { + devServer = await fixture.startDevServer(); + }); + + after(async () => { + devServer && (await devServer.stop()); + }); + + it('scripts proxy correctly', async () => { + const html = await fixture.fetch('/').then((res) => res.text()); + const $ = cheerio.load(html); + + for (const script of $('script').toArray()) { + const { src } = script.attribs; + if (!src) continue; + console.log({ src }); + expect((await fixture.fetch(src)).status, `404: ${src}`).to.equal(200); + } + }); }); }); diff --git a/packages/astro/test/vue-component.test.js b/packages/astro/test/vue-component.test.js index 8899782af..f375174bb 100644 --- a/packages/astro/test/vue-component.test.js +++ b/packages/astro/test/vue-component.test.js @@ -10,28 +10,56 @@ describe('Vue component', () => { projectRoot: './fixtures/vue-component/', renderers: ['@astrojs/renderer-vue'], }); - await fixture.build(); }); - it('Can load Vue', async () => { - const html = await fixture.readFile('/index.html'); - const $ = cheerio.load(html); + describe('build', () => { + before(async () => { + await fixture.build(); + }); - const allPreValues = $('pre') - .toArray() - .map((el) => $(el).text()); + it('Can load Vue', async () => { + const html = await fixture.readFile('/index.html'); + const $ = cheerio.load(html); - // test 1: renders all components correctly - expect(allPreValues).to.deep.equal(['0', '1', '10', '100', '1000']); + const allPreValues = $('pre') + .toArray() + .map((el) => $(el).text()); - // test 2: renders 3 s - expect($('astro-root')).to.have.lengthOf(4); + // test 1: renders all components correctly + expect(allPreValues).to.deep.equal(['0', '1', '10', '100', '1000']); - // test 3: all s have uid attributes - expect($('astro-root[uid]')).to.have.lengthOf(4); + // test 2: renders 3 s + expect($('astro-root')).to.have.lengthOf(4); - // test 5: all s have unique uid attributes - const uniqueRootUIDs = $('astro-root').map((i, el) => $(el).attr('uid')); - expect(new Set(uniqueRootUIDs).size).to.equal(4); + // test 3: all s have uid attributes + expect($('astro-root[uid]')).to.have.lengthOf(4); + + // test 5: all s have unique uid attributes + const uniqueRootUIDs = $('astro-root').map((i, el) => $(el).attr('uid')); + expect(new Set(uniqueRootUIDs).size).to.equal(4); + }); + }); + + describe('dev', () => { + let devServer; + + before(async () => { + devServer = await fixture.startDevServer(); + }); + + after(async () => { + devServer && (await devServer.stop()); + }); + + it('scripts proxy correctly', async () => { + const html = await fixture.fetch('/').then((res) => res.text()); + const $ = cheerio.load(html); + + for (const script of $('script').toArray()) { + const { src } = script.attribs; + if (!src) continue; + expect((await fixture.fetch(src)).status, `404: ${src}`).to.equal(200); + } + }); }); });