Fix Windows dev script proxying (#2052)
* Add tests for script proxying * Fix Windows script proxying #2053
This commit is contained in:
parent
606fa81b94
commit
03cabc5171
10 changed files with 245 additions and 142 deletions
|
@ -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,
|
||||
|
|
|
@ -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<string> {
|
||||
const css = new Set<string>();
|
||||
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<string>()) {
|
||||
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);
|
||||
|
|
|
@ -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 <!doctype html> if missing (TODO: is a more robust check needed for comments, etc.?)
|
||||
|
|
|
@ -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}`;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import Hello from '../components/Hello.jsx';
|
|||
<html>
|
||||
<head><title>Solid</title></head>
|
||||
<body>
|
||||
<div><Hello /></div>
|
||||
<div>
|
||||
<Hello client:load />
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -19,7 +19,7 @@ import TypeScript from '../components/TypeScript.svelte'
|
|||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<TypeScript message="Hello, TypeScript" />
|
||||
<TypeScript message="Hello, TypeScript" client:load />
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 <astro-root>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 <astro-root>s have uid attributes
|
||||
expect($('astro-root[uid]')).to.have.lengthOf(4);
|
||||
// test 2: renders 3 <astro-root>s
|
||||
expect($('astro-root')).to.have.lengthOf(4);
|
||||
|
||||
// test 5: all <astro-root>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 <astro-root>s have uid attributes
|
||||
expect($('astro-root[uid]')).to.have.lengthOf(4);
|
||||
|
||||
// test 5: all <astro-root>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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue