Compare commits
10 commits
Author | SHA1 | Date | |
---|---|---|---|
|
a63ec9d59d | ||
|
e48f3fed4b | ||
|
a140fb0ded | ||
|
c8299b24fb | ||
|
bc437dce60 | ||
|
48efa4beda | ||
|
e12e1e61c1 | ||
|
c0febd56f9 | ||
|
26458cf4f4 | ||
|
4ce0b918b1 |
10 changed files with 320 additions and 109 deletions
5
.changeset/fresh-rivers-allow.md
Normal file
5
.changeset/fresh-rivers-allow.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Add a DevApp export
|
|
@ -44,6 +44,7 @@
|
||||||
},
|
},
|
||||||
"./app": "./dist/core/app/index.js",
|
"./app": "./dist/core/app/index.js",
|
||||||
"./app/node": "./dist/core/app/node.js",
|
"./app/node": "./dist/core/app/node.js",
|
||||||
|
"./app/dev": "./dist/core/app/dev.js",
|
||||||
"./client/*": "./dist/runtime/client/*",
|
"./client/*": "./dist/runtime/client/*",
|
||||||
"./components": "./components/index.ts",
|
"./components": "./components/index.ts",
|
||||||
"./components/*": "./components/*",
|
"./components/*": "./components/*",
|
||||||
|
|
189
packages/astro/src/core/app/dev.ts
Normal file
189
packages/astro/src/core/app/dev.ts
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
import type { ComponentInstance, RouteData } from '../../@types/astro';
|
||||||
|
import type { SSRManifest as Manifest } from './types';
|
||||||
|
import type { LogOptions } from '../logger/core';
|
||||||
|
import type http from 'http';
|
||||||
|
import { posix } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { createContainer, type CreateContainerParams } from '../dev/index.js';
|
||||||
|
import { openConfig, createSettings } from '../config/index.js';
|
||||||
|
import { createViteLoader } from '../module-loader/index.js';
|
||||||
|
import { createRouteManifest } from '../routing/index.js';
|
||||||
|
import {
|
||||||
|
createDevelopmentEnvironment,
|
||||||
|
getScriptsAndStyles,
|
||||||
|
preload,
|
||||||
|
type DevelopmentEnvironment
|
||||||
|
} from '../render/dev/index.js';
|
||||||
|
import {
|
||||||
|
renderPage as coreRenderPage
|
||||||
|
} from '../render/index.js';
|
||||||
|
import { App } from './index.js';
|
||||||
|
import { RenderContext, Environment } from '../render';
|
||||||
|
import { nodeLogDestination } from '../logger/node.js';
|
||||||
|
|
||||||
|
export const logging: LogOptions = {
|
||||||
|
dest: nodeLogDestination,
|
||||||
|
level: 'error',
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DevAppParams = Partial<CreateContainerParams> & {
|
||||||
|
root: URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DevApp extends App {
|
||||||
|
#createContainerParams: CreateContainerParams;
|
||||||
|
#manifest: Manifest;
|
||||||
|
#env: DevelopmentEnvironment | null = null;
|
||||||
|
#root: URL;
|
||||||
|
#modToRoute = new Map<ComponentInstance, RouteData>();
|
||||||
|
|
||||||
|
// TODO don't expose this entire API
|
||||||
|
container: Awaited<ReturnType<typeof createContainer>> | null = null;
|
||||||
|
constructor(params: DevAppParams) {
|
||||||
|
const { root, userConfig } = params;
|
||||||
|
const manifest: Manifest = {
|
||||||
|
adapterName: 'development',
|
||||||
|
base: userConfig?.base,
|
||||||
|
routes: [],
|
||||||
|
markdown: {
|
||||||
|
contentDir: root
|
||||||
|
},
|
||||||
|
pageMap: new Map(),
|
||||||
|
renderers: [],
|
||||||
|
// Temporary hack
|
||||||
|
entryModules: new Proxy({}, {
|
||||||
|
has() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
get(target, key) {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
assets: new Set(),
|
||||||
|
propagation: new Map(),
|
||||||
|
trailingSlash: userConfig?.trailingSlash ?? 'ignore'
|
||||||
|
};
|
||||||
|
super(manifest, true);
|
||||||
|
this.#manifest = manifest;
|
||||||
|
this.#root = root;
|
||||||
|
this.#createContainerParams = params;
|
||||||
|
}
|
||||||
|
|
||||||
|
get loaded() {
|
||||||
|
return !!this.container;
|
||||||
|
}
|
||||||
|
|
||||||
|
url(pathname: string): string | undefined {
|
||||||
|
if(!this.loaded) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const { host, port } = this.container!.settings.config.server
|
||||||
|
return new URL(pathname, `http://${host}:${port}`).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
if(this.loaded) {
|
||||||
|
await this.close();
|
||||||
|
this.container = null;
|
||||||
|
this.#env = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const params: CreateContainerParams = {
|
||||||
|
...this.#createContainerParams,
|
||||||
|
};
|
||||||
|
|
||||||
|
if(!this.#createContainerParams.userConfig) {
|
||||||
|
const configResult = await openConfig({
|
||||||
|
cmd: 'dev',
|
||||||
|
logging,
|
||||||
|
});
|
||||||
|
params.settings = createSettings(configResult.astroConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = this.container = await createContainer(params);
|
||||||
|
this.#manifest.trailingSlash = container.settings.config.trailingSlash;
|
||||||
|
|
||||||
|
const loader = createViteLoader(container.viteServer);
|
||||||
|
|
||||||
|
const routeManifest = createRouteManifest({
|
||||||
|
settings: container.settings,
|
||||||
|
fsMod: this.#createContainerParams.fs
|
||||||
|
}, container.logging);
|
||||||
|
const routes = routeManifest.routes.map(routeData => {
|
||||||
|
return {
|
||||||
|
routeData,
|
||||||
|
file: routeData.component,
|
||||||
|
links: [],
|
||||||
|
scripts: []
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.updateRoutes(routes);
|
||||||
|
this.#env = createDevelopmentEnvironment(container.settings, container.logging, loader);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
async close() {
|
||||||
|
await this.container?.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
fileChanged(path: string) {
|
||||||
|
const container = this.container!;
|
||||||
|
const fs = this.#createContainerParams.fs!;
|
||||||
|
const root = fileURLToPath(this.#root);
|
||||||
|
const fullPath = posix.join(root, path);
|
||||||
|
container.viteServer.watcher.emit('change', fullPath);
|
||||||
|
|
||||||
|
if (!fileURLToPath(container.settings.config.root).startsWith('/')) {
|
||||||
|
const drive = fileURLToPath(container.settings.config.root).slice(0, 2);
|
||||||
|
container.viteServer.watcher.emit('change', drive + fullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handle(req: http.IncomingMessage, res: http.ServerResponse) {
|
||||||
|
this.container!.handle(req, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
async render(request: Request, route?: RouteData | undefined): Promise<Response> {
|
||||||
|
if(!this.loaded) {
|
||||||
|
await this.load();
|
||||||
|
}
|
||||||
|
if(!route) {
|
||||||
|
route = this.match(request, { matchNotFound: false });
|
||||||
|
}
|
||||||
|
if(route) {
|
||||||
|
const filePath = new URL(route.component, this.#root);
|
||||||
|
|
||||||
|
// Always run preload so that if there has been a change in the file, the new
|
||||||
|
// version will run.
|
||||||
|
const [renderers, mod] = await preload({
|
||||||
|
env: this.#env!,
|
||||||
|
filePath
|
||||||
|
});
|
||||||
|
|
||||||
|
// Always reset the renderers as they might have changed.
|
||||||
|
this.#manifest.renderers.length = 0;
|
||||||
|
this.#manifest.renderers.push(...renderers);
|
||||||
|
|
||||||
|
// Save this module in the pageMap, so that super.render() finds it.
|
||||||
|
this.#manifest.pageMap.set(route.component, mod);
|
||||||
|
this.#modToRoute.set(mod, route);
|
||||||
|
}
|
||||||
|
return super.render(request, route);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPage = async (mod: ComponentInstance, ctx: RenderContext, env: Environment) => {
|
||||||
|
const route = this.#modToRoute.get(mod)!;
|
||||||
|
|
||||||
|
const { scripts, links, styles, propagationMap } = await getScriptsAndStyles({
|
||||||
|
env: this.#env!,
|
||||||
|
filePath: new URL(route.component, this.#root),
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.scripts = scripts;
|
||||||
|
ctx.links = links;
|
||||||
|
ctx.styles = styles;
|
||||||
|
ctx.propagation = propagationMap;
|
||||||
|
|
||||||
|
return coreRenderPage(mod, ctx, env);
|
||||||
|
};
|
||||||
|
}
|
|
@ -38,8 +38,8 @@ export interface MatchOptions {
|
||||||
export class App {
|
export class App {
|
||||||
#env: Environment;
|
#env: Environment;
|
||||||
#manifest: Manifest;
|
#manifest: Manifest;
|
||||||
#manifestData: ManifestData;
|
#manifestData: ManifestData = { routes: [] };
|
||||||
#routeDataToRouteInfo: Map<RouteData, RouteInfo>;
|
#routeDataToRouteInfo: Map<RouteData, RouteInfo> = new Map();
|
||||||
#encoder = new TextEncoder();
|
#encoder = new TextEncoder();
|
||||||
#logging: LogOptions = {
|
#logging: LogOptions = {
|
||||||
dest: consoleLogDestination,
|
dest: consoleLogDestination,
|
||||||
|
@ -47,13 +47,11 @@ export class App {
|
||||||
};
|
};
|
||||||
#base: string;
|
#base: string;
|
||||||
#baseWithoutTrailingSlash: string;
|
#baseWithoutTrailingSlash: string;
|
||||||
|
renderPage: typeof renderPage;
|
||||||
|
|
||||||
constructor(manifest: Manifest, streaming = true) {
|
constructor(manifest: Manifest, streaming = true) {
|
||||||
this.#manifest = manifest;
|
this.#manifest = manifest;
|
||||||
this.#manifestData = {
|
this.updateRoutes(manifest.routes);
|
||||||
routes: manifest.routes.map((route) => route.routeData),
|
|
||||||
};
|
|
||||||
this.#routeDataToRouteInfo = new Map(manifest.routes.map((route) => [route.routeData, route]));
|
|
||||||
this.#env = createEnvironment({
|
this.#env = createEnvironment({
|
||||||
adapterName: manifest.adapterName,
|
adapterName: manifest.adapterName,
|
||||||
logging: this.#logging,
|
logging: this.#logging,
|
||||||
|
@ -83,6 +81,16 @@ export class App {
|
||||||
|
|
||||||
this.#base = this.#manifest.base || '/';
|
this.#base = this.#manifest.base || '/';
|
||||||
this.#baseWithoutTrailingSlash = removeTrailingForwardSlash(this.#base);
|
this.#baseWithoutTrailingSlash = removeTrailingForwardSlash(this.#base);
|
||||||
|
this.renderPage = renderPage;
|
||||||
|
}
|
||||||
|
updateRoutes(routes: RouteInfo[]) {
|
||||||
|
this.#manifestData = {
|
||||||
|
routes: routes.map((route) => route.routeData),
|
||||||
|
};
|
||||||
|
this.#routeDataToRouteInfo = new Map(routes.map((route) => [route.routeData, route]));
|
||||||
|
}
|
||||||
|
get routes() {
|
||||||
|
return this.#manifestData.routes;
|
||||||
}
|
}
|
||||||
removeBase(pathname: string) {
|
removeBase(pathname: string) {
|
||||||
if (pathname.startsWith(this.#base)) {
|
if (pathname.startsWith(this.#base)) {
|
||||||
|
@ -96,7 +104,14 @@ export class App {
|
||||||
if (this.#manifest.assets.has(url.pathname)) {
|
if (this.#manifest.assets.has(url.pathname)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
let pathname = '/' + this.removeBase(url.pathname);
|
let hasTrailingSlash = url.pathname.endsWith('/');
|
||||||
|
let noBase = this.removeBase(url.pathname);
|
||||||
|
let pathname: string;
|
||||||
|
if(this.#manifest.trailingSlash === 'never' && noBase === '' && !hasTrailingSlash) {
|
||||||
|
pathname = noBase;
|
||||||
|
} else {
|
||||||
|
pathname = prependForwardSlash(noBase);
|
||||||
|
}
|
||||||
let routeData = matchRoute(pathname, this.#manifestData);
|
let routeData = matchRoute(pathname, this.#manifestData);
|
||||||
|
|
||||||
if (routeData) {
|
if (routeData) {
|
||||||
|
@ -200,7 +215,7 @@ export class App {
|
||||||
status,
|
status,
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await renderPage(mod, ctx, this.#env);
|
const response = await this.renderPage(mod, ctx, this.#env);
|
||||||
return response;
|
return response;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
error(this.#logging, 'ssr', err.stack || err.message || String(err));
|
error(this.#logging, 'ssr', err.stack || err.message || String(err));
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import type { MarkdownRenderingOptions } from '@astrojs/markdown-remark';
|
import type { MarkdownRenderingOptions } from '@astrojs/markdown-remark';
|
||||||
import type {
|
import type {
|
||||||
|
AstroConfig,
|
||||||
ComponentInstance,
|
ComponentInstance,
|
||||||
PropagationHint,
|
PropagationHint,
|
||||||
RouteData,
|
RouteData,
|
||||||
|
@ -37,6 +38,7 @@ export interface SSRManifest {
|
||||||
entryModules: Record<string, string>;
|
entryModules: Record<string, string>;
|
||||||
assets: Set<string>;
|
assets: Set<string>;
|
||||||
propagation: SSRResult['propagation'];
|
propagation: SSRResult['propagation'];
|
||||||
|
trailingSlash: AstroConfig['trailingSlash'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SerializedSSRManifest = Omit<SSRManifest, 'routes' | 'assets' | 'propagation'> & {
|
export type SerializedSSRManifest = Omit<SSRManifest, 'routes' | 'assets' | 'propagation'> & {
|
||||||
|
|
|
@ -215,6 +215,7 @@ function buildManifest(
|
||||||
renderers: [],
|
renderers: [],
|
||||||
entryModules,
|
entryModules,
|
||||||
assets: staticFiles.map((s) => settings.config.base + s),
|
assets: staticFiles.map((s) => settings.config.base + s),
|
||||||
|
trailingSlash: opts.settings.config.trailingSlash,
|
||||||
};
|
};
|
||||||
|
|
||||||
return ssrManifest;
|
return ssrManifest;
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export { createContainer, isStarted, runInContainer, startContainer } from './container.js';
|
export { createContainer, isStarted, runInContainer, startContainer, type CreateContainerParams } from './container.js';
|
||||||
export { default } from './dev.js';
|
export { default } from './dev.js';
|
||||||
export { createContainerWithAutomaticRestart } from './restart.js';
|
export { createContainerWithAutomaticRestart } from './restart.js';
|
||||||
|
|
|
@ -74,7 +74,7 @@ interface GetScriptsAndStylesParams {
|
||||||
filePath: URL;
|
filePath: URL;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getScriptsAndStyles({ env, filePath }: GetScriptsAndStylesParams) {
|
export async function getScriptsAndStyles({ env, filePath }: GetScriptsAndStylesParams) {
|
||||||
// Add hoisted script tags
|
// Add hoisted script tags
|
||||||
const scripts = await getScriptsForURL(filePath, env.loader);
|
const scripts = await getScriptsForURL(filePath, env.loader);
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
|
|
||||||
import { runInContainer } from '../../../dist/core/dev/index.js';
|
import { DevApp } from '../../../dist/core/app/dev.js';
|
||||||
import { createFs, createRequestAndResponse } from '../test-utils.js';
|
import { createFs } from '../test-utils.js';
|
||||||
|
|
||||||
const root = new URL('../../fixtures/alias/', import.meta.url);
|
const root = new URL('../../fixtures/alias/', import.meta.url);
|
||||||
|
|
||||||
|
@ -16,25 +16,23 @@ describe('base configuration', () => {
|
||||||
root
|
root
|
||||||
);
|
);
|
||||||
|
|
||||||
await runInContainer(
|
const app = new DevApp({
|
||||||
{
|
|
||||||
fs,
|
|
||||||
root,
|
root,
|
||||||
|
fs,
|
||||||
userConfig: {
|
userConfig: {
|
||||||
base: '/docs',
|
base: '/docs',
|
||||||
trailingSlash: 'never',
|
trailingSlash: 'never',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
async (container) => {
|
|
||||||
const { req, res, done } = createRequestAndResponse({
|
|
||||||
method: 'GET',
|
|
||||||
url: '/docs/',
|
|
||||||
});
|
});
|
||||||
container.handle(req, res);
|
|
||||||
await done;
|
try {
|
||||||
expect(res.statusCode).to.equal(404);
|
const request = new Request(`http://localhost:8080/docs/`);
|
||||||
|
const response = await app.render(request);
|
||||||
|
|
||||||
|
expect(response.status).to.equal(404);
|
||||||
|
} finally {
|
||||||
|
await app.close();
|
||||||
}
|
}
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Requests that exclude a trailing slash 200', async () => {
|
it('Requests that exclude a trailing slash 200', async () => {
|
||||||
|
@ -45,25 +43,23 @@ describe('base configuration', () => {
|
||||||
root
|
root
|
||||||
);
|
);
|
||||||
|
|
||||||
await runInContainer(
|
const app = new DevApp({
|
||||||
{
|
|
||||||
fs,
|
|
||||||
root,
|
root,
|
||||||
|
fs,
|
||||||
userConfig: {
|
userConfig: {
|
||||||
base: '/docs',
|
base: '/docs',
|
||||||
trailingSlash: 'never',
|
trailingSlash: 'never',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
async (container) => {
|
|
||||||
const { req, res, done } = createRequestAndResponse({
|
|
||||||
method: 'GET',
|
|
||||||
url: '/docs',
|
|
||||||
});
|
});
|
||||||
container.handle(req, res);
|
|
||||||
await done;
|
try {
|
||||||
expect(res.statusCode).to.equal(200);
|
const request = new Request(`http://localhost:8080/docs`);
|
||||||
|
|
||||||
|
const response = await app.render(request);
|
||||||
|
expect(response.status).to.equal(200);
|
||||||
|
} finally {
|
||||||
|
await app.close();
|
||||||
}
|
}
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -76,25 +72,23 @@ describe('base configuration', () => {
|
||||||
root
|
root
|
||||||
);
|
);
|
||||||
|
|
||||||
await runInContainer(
|
const app = new DevApp({
|
||||||
{
|
|
||||||
fs,
|
fs,
|
||||||
root,
|
root,
|
||||||
userConfig: {
|
userConfig: {
|
||||||
base: '/docs',
|
base: '/docs',
|
||||||
trailingSlash: 'never',
|
trailingSlash: 'never',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
async (container) => {
|
|
||||||
const { req, res, done } = createRequestAndResponse({
|
|
||||||
method: 'GET',
|
|
||||||
url: '/docs/sub/',
|
|
||||||
});
|
});
|
||||||
container.handle(req, res);
|
|
||||||
await done;
|
try {
|
||||||
expect(res.statusCode).to.equal(404);
|
const request = new Request(`http://localhost:8080/docs/sub/`);
|
||||||
|
|
||||||
|
const response = await app.render(request);
|
||||||
|
expect(response.status).to.equal(404);
|
||||||
|
} finally {
|
||||||
|
await app.close();
|
||||||
}
|
}
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Requests that exclude a trailing slash 200', async () => {
|
it('Requests that exclude a trailing slash 200', async () => {
|
||||||
|
@ -105,25 +99,23 @@ describe('base configuration', () => {
|
||||||
root
|
root
|
||||||
);
|
);
|
||||||
|
|
||||||
await runInContainer(
|
const app = new DevApp({
|
||||||
{
|
|
||||||
fs,
|
fs,
|
||||||
root,
|
root,
|
||||||
userConfig: {
|
userConfig: {
|
||||||
base: '/docs',
|
base: '/docs',
|
||||||
trailingSlash: 'never',
|
trailingSlash: 'never',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
async (container) => {
|
|
||||||
const { req, res, done } = createRequestAndResponse({
|
|
||||||
method: 'GET',
|
|
||||||
url: '/docs/sub',
|
|
||||||
});
|
});
|
||||||
container.handle(req, res);
|
|
||||||
await done;
|
try {
|
||||||
expect(res.statusCode).to.equal(200);
|
const request = new Request(`http://localhost:8080/docs/sub`);
|
||||||
|
|
||||||
|
const response = await app.render(request);
|
||||||
|
expect(response.status).to.equal(200);
|
||||||
|
} finally {
|
||||||
|
await app.close();
|
||||||
}
|
}
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import * as cheerio from 'cheerio';
|
import * as cheerio from 'cheerio';
|
||||||
|
|
||||||
|
import { DevApp } from '../../../dist/core/app/dev.js';
|
||||||
import { runInContainer } from '../../../dist/core/dev/index.js';
|
import { runInContainer } from '../../../dist/core/dev/index.js';
|
||||||
import { createFs, createRequestAndResponse, triggerFSEvent } from '../test-utils.js';
|
import { createFs, createRequestAndResponse, triggerFSEvent } from '../test-utils.js';
|
||||||
|
|
||||||
|
@ -25,17 +26,21 @@ describe('dev container', () => {
|
||||||
root
|
root
|
||||||
);
|
);
|
||||||
|
|
||||||
await runInContainer({ fs, root }, async (container) => {
|
const app = new DevApp({ fs, root });
|
||||||
const { req, res, text } = createRequestAndResponse({
|
try {
|
||||||
method: 'GET',
|
await app.load();
|
||||||
url: '/',
|
const request = new Request(app.url('/'));
|
||||||
});
|
|
||||||
container.handle(req, res);
|
const response = await app.render(request);
|
||||||
const html = await text();
|
expect(response.status).to.equal(200);
|
||||||
|
|
||||||
|
const html = await response.text();
|
||||||
const $ = cheerio.load(html);
|
const $ = cheerio.load(html);
|
||||||
expect(res.statusCode).to.equal(200);
|
|
||||||
expect($('h1')).to.have.a.lengthOf(1);
|
expect($('h1')).to.have.a.lengthOf(1);
|
||||||
});
|
} finally {
|
||||||
|
await app.close();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('HMR only short circuits on previously cached modules', async () => {
|
it('HMR only short circuits on previously cached modules', async () => {
|
||||||
|
@ -60,13 +65,14 @@ describe('dev container', () => {
|
||||||
root
|
root
|
||||||
);
|
);
|
||||||
|
|
||||||
await runInContainer({ fs, root }, async (container) => {
|
const app = new DevApp({ fs, root });
|
||||||
let r = createRequestAndResponse({
|
try {
|
||||||
method: 'GET',
|
await app.load();
|
||||||
url: '/',
|
|
||||||
});
|
let request = new Request(app.url('/'));
|
||||||
container.handle(r.req, r.res);
|
let response = await app.render(request);
|
||||||
let html = await r.text();
|
|
||||||
|
let html = await response.text();
|
||||||
let $ = cheerio.load(html);
|
let $ = cheerio.load(html);
|
||||||
expect($('body.one')).to.have.a.lengthOf(1);
|
expect($('body.one')).to.have.a.lengthOf(1);
|
||||||
|
|
||||||
|
@ -76,7 +82,7 @@ describe('dev container', () => {
|
||||||
<h1>{Astro.props.title}</h1>
|
<h1>{Astro.props.title}</h1>
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
triggerFSEvent(container, fs, '/src/components/Header.astro', 'change');
|
app.fileChanged('/src/components/Header.astro');
|
||||||
|
|
||||||
fs.writeFileFromRootSync(
|
fs.writeFileFromRootSync(
|
||||||
'/src/pages/index.astro',
|
'/src/pages/index.astro',
|
||||||
|
@ -93,18 +99,18 @@ describe('dev container', () => {
|
||||||
</html>
|
</html>
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
triggerFSEvent(container, fs, '/src/pages/index.astro', 'change');
|
app.fileChanged('/src/pages/index.astro');
|
||||||
|
|
||||||
r = createRequestAndResponse({
|
request = new Request(app.url('/'));
|
||||||
method: 'GET',
|
response = await app.render(request);
|
||||||
url: '/',
|
html = await response.text();
|
||||||
});
|
|
||||||
container.handle(r.req, r.res);
|
|
||||||
html = await r.text();
|
|
||||||
$ = cheerio.load(html);
|
$ = cheerio.load(html);
|
||||||
expect($('body.one')).to.have.a.lengthOf(0);
|
expect($('body.one')).to.have.a.lengthOf(0);
|
||||||
expect($('body.two')).to.have.a.lengthOf(1);
|
expect($('body.two')).to.have.a.lengthOf(1);
|
||||||
});
|
|
||||||
|
} finally {
|
||||||
|
await app.close();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Allows dynamic segments in injected routes', async () => {
|
it('Allows dynamic segments in injected routes', async () => {
|
||||||
|
|
Loading…
Add table
Reference in a new issue