🎉 initiate project *astro_rewrite*

This commit is contained in:
sindrekjelsrud 2023-07-19 21:31:30 +02:00
parent ffd4d5e86c
commit 2ba37bfbe3
8658 changed files with 2268794 additions and 2538 deletions

View file

@ -0,0 +1,4 @@
import type * as vite from 'vite';
import type { AstroSettings } from '../@types/astro';
import { type LogOptions } from '../core/logger/core.js';
export declare function baseMiddleware(settings: AstroSettings, logging: LogOptions): vite.Connect.NextHandleFunction;

View file

@ -0,0 +1,56 @@
import * as fs from "node:fs";
import { warn } from "../core/logger/core.js";
import notFoundTemplate, { subpathNotUsedTemplate } from "../template/4xx.js";
import { log404 } from "./common.js";
import { writeHtmlResponse } from "./response.js";
function baseMiddleware(settings, logging) {
const { config } = settings;
const site = config.site ? new URL(config.base, config.site) : void 0;
const devRootURL = new URL(config.base, "http://localhost");
const devRoot = site ? site.pathname : devRootURL.pathname;
const devRootReplacement = devRoot.endsWith("/") ? "/" : "";
return function devBaseMiddleware(req, res, next) {
var _a;
const url = req.url;
const pathname = decodeURI(new URL(url, "http://localhost").pathname);
if (pathname.startsWith(devRoot)) {
req.url = url.replace(devRoot, devRootReplacement);
return next();
}
if (pathname === "/" || pathname === "/index.html") {
log404(logging, pathname);
const html = subpathNotUsedTemplate(devRoot, pathname);
return writeHtmlResponse(res, 404, html);
}
if ((_a = req.headers.accept) == null ? void 0 : _a.includes("text/html")) {
log404(logging, pathname);
const html = notFoundTemplate({
statusCode: 404,
title: "Not found",
tabTitle: "404: Not Found",
pathname
});
return writeHtmlResponse(res, 404, html);
}
const publicPath = new URL("." + req.url, config.publicDir);
fs.stat(publicPath, (_err, stats) => {
if (stats) {
const expectedLocation = new URL("." + url, devRootURL).pathname;
warn(
logging,
"dev",
`Requests for items in your public folder must also include your base. ${url} should be ${expectedLocation}. Omitting the base will break in production.`
);
res.writeHead(301, {
Location: expectedLocation
});
res.end();
} else {
next();
}
});
};
}
export {
baseMiddleware
};

View file

@ -0,0 +1,2 @@
import { type LogOptions } from '../core/logger/core.js';
export declare function log404(logging: LogOptions, pathname: string): void;

View file

@ -0,0 +1,8 @@
import { info } from "../core/logger/core.js";
import * as msg from "../core/messages.js";
function log404(logging, pathname) {
info(logging, "serve", msg.req({ url: pathname, statusCode: 404 }));
}
export {
log404
};

View file

@ -0,0 +1,26 @@
import type { LoaderEvents, ModuleLoader } from '../core/module-loader/index';
import type { ServerState } from './server-state';
type ReloadFn = () => void;
export interface DevServerController {
state: ServerState;
onFileChange: LoaderEvents['file-change'];
onHMRError: LoaderEvents['hmr-error'];
}
export type CreateControllerParams = {
loader: ModuleLoader;
} | {
reload: ReloadFn;
};
export declare function createController(params: CreateControllerParams): DevServerController;
export declare function createBaseController({ reload }: {
reload: ReloadFn;
}): DevServerController;
export declare function createLoaderController(loader: ModuleLoader): DevServerController;
export interface RunWithErrorHandlingParams {
controller: DevServerController;
pathname: string;
run: () => Promise<any>;
onError: (error: unknown) => Error;
}
export declare function runWithErrorHandling({ controller: { state }, pathname, run, onError, }: RunWithErrorHandlingParams): Promise<void>;
export {};

View file

@ -0,0 +1,77 @@
import {
clearRouteError,
createServerState,
setRouteError,
setServerError
} from "./server-state.js";
function createController(params) {
if ("loader" in params) {
return createLoaderController(params.loader);
} else {
return createBaseController(params);
}
}
function createBaseController({ reload }) {
const serverState = createServerState();
const onFileChange = () => {
if (serverState.state === "error") {
reload();
}
};
const onHMRError = (payload) => {
var _a, _b;
let msg = ((_a = payload == null ? void 0 : payload.err) == null ? void 0 : _a.message) ?? "Unknown error";
let stack = ((_b = payload == null ? void 0 : payload.err) == null ? void 0 : _b.stack) ?? "Unknown stack";
let error = new Error(msg);
Object.defineProperty(error, "stack", {
value: stack
});
setServerError(serverState, error);
};
return {
state: serverState,
onFileChange,
onHMRError
};
}
function createLoaderController(loader) {
const controller = createBaseController({
reload() {
loader.clientReload();
}
});
const baseOnFileChange = controller.onFileChange;
controller.onFileChange = (...args) => {
if (controller.state.state === "error") {
loader.eachModule((mod) => {
if (mod.ssrError) {
loader.invalidateModule(mod);
}
});
}
baseOnFileChange(...args);
};
loader.events.on("file-change", controller.onFileChange);
loader.events.on("hmr-error", controller.onHMRError);
return controller;
}
async function runWithErrorHandling({
controller: { state },
pathname,
run,
onError
}) {
try {
await run();
clearRouteError(state, pathname);
} catch (err) {
const error = onError(err);
setRouteError(state, pathname, error);
}
}
export {
createBaseController,
createController,
createLoaderController,
runWithErrorHandling
};

View file

@ -0,0 +1,3 @@
export { createController, runWithErrorHandling } from './controller.js';
export { default as vitePluginAstroServer } from './plugin.js';
export { handleRequest } from './request.js';

View file

@ -0,0 +1,9 @@
import { createController, runWithErrorHandling } from "./controller.js";
import { default as default2 } from "./plugin.js";
import { handleRequest } from "./request.js";
export {
createController,
handleRequest,
runWithErrorHandling,
default2 as vitePluginAstroServer
};

View file

@ -0,0 +1,19 @@
/// <reference types="node" />
import type fs from 'node:fs';
import type * as vite from 'vite';
import type { AstroSettings, SSRManifest } from '../@types/astro';
import type { LogOptions } from '../core/logger/core.js';
export interface AstroPluginOptions {
settings: AstroSettings;
logging: LogOptions;
fs: typeof fs;
}
export default function createVitePluginAstroServer({ settings, logging, fs: fsMod, }: AstroPluginOptions): vite.Plugin;
/**
* It creates a `SSRManifest` from the `AstroSettings`.
*
* Renderers needs to be pulled out from the page module emitted during the build.
* @param settings
* @param renderers
*/
export declare function createDevelopmentManifest(settings: AstroSettings): SSRManifest;

View file

@ -0,0 +1,82 @@
import { patchOverlay } from "../core/errors/overlay.js";
import { createViteLoader } from "../core/module-loader/index.js";
import { createDevelopmentEnvironment } from "../core/render/dev/index.js";
import { createRouteManifest } from "../core/routing/index.js";
import { baseMiddleware } from "./base.js";
import { createController } from "./controller.js";
import { handleRequest } from "./request.js";
function createVitePluginAstroServer({
settings,
logging,
fs: fsMod
}) {
return {
name: "astro:server",
configureServer(viteServer) {
const loader = createViteLoader(viteServer);
const manifest = createDevelopmentManifest(settings);
const env = createDevelopmentEnvironment(manifest, settings, logging, loader);
let manifestData = createRouteManifest({ settings, fsMod }, logging);
const controller = createController({ loader });
function rebuildManifest(needsManifestRebuild) {
env.routeCache.clearAll();
if (needsManifestRebuild) {
manifestData = createRouteManifest({ settings }, logging);
}
}
viteServer.watcher.on("add", rebuildManifest.bind(null, true));
viteServer.watcher.on("unlink", rebuildManifest.bind(null, true));
viteServer.watcher.on("change", rebuildManifest.bind(null, false));
return () => {
if (settings.config.base !== "/") {
viteServer.middlewares.stack.unshift({
route: "",
handle: baseMiddleware(settings, logging)
});
}
viteServer.middlewares.use(async function astroDevHandler(request, response) {
if (request.url === void 0 || !request.method) {
response.writeHead(500, "Incomplete request");
response.end();
return;
}
handleRequest({
env,
manifestData,
controller,
incomingRequest: request,
incomingResponse: response,
manifest
});
});
};
},
transform(code, id, opts = {}) {
if (opts.ssr)
return;
if (!id.includes("vite/dist/client/client.mjs"))
return;
return patchOverlay(code);
}
};
}
function createDevelopmentManifest(settings) {
return {
compressHTML: settings.config.compressHTML,
assets: /* @__PURE__ */ new Set(),
entryModules: {},
routes: [],
adapterName: "",
markdown: settings.config.markdown,
clientDirectives: settings.clientDirectives,
renderers: [],
base: settings.config.base,
assetsPrefix: settings.config.build.assetsPrefix,
site: settings.config.site ? new URL(settings.config.base, settings.config.site).toString() : settings.config.site,
componentMetadata: /* @__PURE__ */ new Map()
};
}
export {
createDevelopmentManifest,
createVitePluginAstroServer as default
};

View file

@ -0,0 +1,16 @@
/// <reference types="node" />
import type http from 'node:http';
import type { ManifestData, SSRManifest } from '../@types/astro';
import type { DevelopmentEnvironment } from '../core/render/dev/index';
import type { DevServerController } from './controller';
type HandleRequest = {
env: DevelopmentEnvironment;
manifestData: ManifestData;
controller: DevServerController;
incomingRequest: http.IncomingMessage;
incomingResponse: http.ServerResponse;
manifest: SSRManifest;
};
/** The main logic to route dev server requests to pages in Astro. */
export declare function handleRequest({ env, manifestData, controller, incomingRequest, incomingResponse, manifest, }: HandleRequest): Promise<void>;
export {};

View file

@ -0,0 +1,83 @@
import { collectErrorMetadata } from "../core/errors/dev/index.js";
import { createSafeError } from "../core/errors/index.js";
import { error } from "../core/logger/core.js";
import * as msg from "../core/messages.js";
import { removeTrailingForwardSlash } from "../core/path.js";
import { eventError, telemetry } from "../events/index.js";
import { isServerLikeOutput } from "../prerender/utils.js";
import { runWithErrorHandling } from "./controller.js";
import { handle500Response } from "./response.js";
import { handleRoute, matchRoute } from "./route.js";
async function handleRequest({
env,
manifestData,
controller,
incomingRequest,
incomingResponse,
manifest
}) {
const { settings, loader: moduleLoader } = env;
const { config } = settings;
const origin = `${moduleLoader.isHttps() ? "https" : "http"}://${incomingRequest.headers.host}`;
const buildingToSSR = isServerLikeOutput(config);
const url = new URL(origin + incomingRequest.url);
let pathname;
if (config.trailingSlash === "never" && !incomingRequest.url) {
pathname = "";
} else {
pathname = decodeURI(url.pathname);
}
url.pathname = removeTrailingForwardSlash(config.base) + url.pathname;
if (!buildingToSSR && pathname !== "/_image") {
const allSearchParams = Array.from(url.searchParams);
for (const [key] of allSearchParams) {
url.searchParams.delete(key);
}
}
let body = void 0;
if (!(incomingRequest.method === "GET" || incomingRequest.method === "HEAD")) {
let bytes = [];
await new Promise((resolve) => {
incomingRequest.on("data", (part) => {
bytes.push(part);
});
incomingRequest.on("end", resolve);
});
body = Buffer.concat(bytes);
}
await runWithErrorHandling({
controller,
pathname,
async run() {
const matchedRoute = await matchRoute(pathname, env, manifestData);
const resolvedPathname = (matchedRoute == null ? void 0 : matchedRoute.resolvedPathname) ?? pathname;
return await handleRoute({
matchedRoute,
url,
pathname: resolvedPathname,
body,
origin,
env,
manifestData,
incomingRequest,
incomingResponse,
manifest
});
},
onError(_err) {
const err = createSafeError(_err);
try {
env.loader.fixStacktrace(err);
} catch {
}
const errorWithMetadata = collectErrorMetadata(err, config.root);
telemetry.record(eventError({ cmd: "dev", err: errorWithMetadata, isFatal: false }));
error(env.logging, null, msg.formatErrorMessage(errorWithMetadata));
handle500Response(moduleLoader, incomingResponse, errorWithMetadata);
return err;
}
});
}
export {
handleRequest
};

View file

@ -0,0 +1,9 @@
/// <reference types="node" />
import type http from 'node:http';
import type { ErrorWithMetadata } from '../core/errors/index.js';
import type { ModuleLoader } from '../core/module-loader/index';
export declare function handle404Response(origin: string, req: http.IncomingMessage, res: http.ServerResponse): Promise<void>;
export declare function handle500Response(loader: ModuleLoader, res: http.ServerResponse, err: ErrorWithMetadata): Promise<void>;
export declare function writeHtmlResponse(res: http.ServerResponse, statusCode: number, html: string): void;
export declare function writeWebResponse(res: http.ServerResponse, webResponse: Response): Promise<void>;
export declare function writeSSRResult(webRequest: Request, webResponse: Response, res: http.ServerResponse): Promise<void>;

View file

@ -0,0 +1,89 @@
import { Readable } from "stream";
import { getSetCookiesFromResponse } from "../core/cookies/index.js";
import { getViteErrorPayload } from "../core/errors/dev/index.js";
import notFoundTemplate from "../template/4xx.js";
async function handle404Response(origin, req, res) {
const pathname = decodeURI(new URL(origin + req.url).pathname);
const html = notFoundTemplate({
statusCode: 404,
title: "Not found",
tabTitle: "404: Not Found",
pathname
});
writeHtmlResponse(res, 404, html);
}
async function handle500Response(loader, res, err) {
res.on(
"close",
async () => setTimeout(async () => loader.webSocketSend(await getViteErrorPayload(err)), 200)
);
if (res.headersSent) {
res.write(`<script type="module" src="/@vite/client"></script>`);
res.end();
} else {
writeHtmlResponse(
res,
500,
`<title>${err.name}</title><script type="module" src="/@vite/client"></script>`
);
}
}
function writeHtmlResponse(res, statusCode, html) {
res.writeHead(statusCode, {
"Content-Type": "text/html; charset=utf-8",
"Content-Length": Buffer.byteLength(html, "utf-8")
});
res.write(html);
res.end();
}
async function writeWebResponse(res, webResponse) {
const { status, headers, body } = webResponse;
const setCookieHeaders = Array.from(getSetCookiesFromResponse(webResponse));
if (setCookieHeaders.length) {
res.setHeader("set-cookie", setCookieHeaders);
}
const _headers = Object.fromEntries(headers.entries());
if (headers.has("set-cookie")) {
if ("getSetCookie" in headers && typeof headers.getSetCookie === "function") {
_headers["set-cookie"] = headers.getSetCookie();
} else {
_headers["set-cookie"] = headers.get("set-cookie");
}
}
res.writeHead(status, _headers);
if (body) {
if (Symbol.for("astro.responseBody") in webResponse) {
let stream = webResponse[Symbol.for("astro.responseBody")];
for await (const chunk of stream) {
res.write(chunk.toString());
}
} else if (body instanceof Readable) {
body.pipe(res);
return;
} else if (typeof body === "string") {
res.write(body);
} else {
const reader = body.getReader();
while (true) {
const { done, value } = await reader.read();
if (done)
break;
if (value) {
res.write(value);
}
}
}
}
res.end();
}
async function writeSSRResult(webRequest, webResponse, res) {
Reflect.set(webRequest, Symbol.for("astro.responseSent"), true);
return writeWebResponse(res, webResponse);
}
export {
handle404Response,
handle500Response,
writeHtmlResponse,
writeSSRResult,
writeWebResponse
};

View file

@ -0,0 +1,28 @@
/// <reference types="node" />
import type http from 'node:http';
import type { ComponentInstance, ManifestData, RouteData, SSRManifest } from '../@types/astro';
import type { DevelopmentEnvironment } from '../core/render/dev/index';
type AsyncReturnType<T extends (...args: any) => Promise<any>> = T extends (...args: any) => Promise<infer R> ? R : any;
export interface MatchedRoute {
route: RouteData;
filePath: URL;
resolvedPathname: string;
preloadedComponent: ComponentInstance;
mod: ComponentInstance;
}
export declare function matchRoute(pathname: string, env: DevelopmentEnvironment, manifest: ManifestData): Promise<MatchedRoute | undefined>;
type HandleRoute = {
matchedRoute: AsyncReturnType<typeof matchRoute>;
url: URL;
pathname: string;
body: ArrayBuffer | undefined;
origin: string;
env: DevelopmentEnvironment;
manifestData: ManifestData;
incomingRequest: http.IncomingMessage;
incomingResponse: http.ServerResponse;
manifest: SSRManifest;
status?: number;
};
export declare function handleRoute({ matchedRoute, url, pathname, status, body, origin, env, manifestData, incomingRequest, incomingResponse, manifest, }: HandleRoute): Promise<void>;
export {};

View file

@ -0,0 +1,211 @@
import mime from "mime";
import { attachToResponse } from "../core/cookies/index.js";
import { call as callEndpoint } from "../core/endpoint/dev/index.js";
import { throwIfRedirectNotAllowed } from "../core/endpoint/index.js";
import { AstroErrorData, isAstroError } from "../core/errors/index.js";
import { warn } from "../core/logger/core.js";
import { loadMiddleware } from "../core/middleware/loadMiddleware.js";
import { preload, renderPage } from "../core/render/dev/index.js";
import { getParamsAndProps } from "../core/render/index.js";
import { createRequest } from "../core/request.js";
import { matchAllRoutes } from "../core/routing/index.js";
import { getSortedPreloadedMatches } from "../prerender/routing.js";
import { isServerLikeOutput } from "../prerender/utils.js";
import { log404 } from "./common.js";
import { handle404Response, writeSSRResult, writeWebResponse } from "./response.js";
const clientLocalsSymbol = Symbol.for("astro.locals");
function getCustom404Route(manifest) {
const route404 = /^\/404\/?$/;
return manifest.routes.find((r) => route404.test(r.route));
}
async function matchRoute(pathname, env, manifest) {
const { logging, settings, routeCache } = env;
const matches = matchAllRoutes(pathname, manifest);
const preloadedMatches = await getSortedPreloadedMatches({ env, matches, settings });
for await (const { preloadedComponent, route: maybeRoute, filePath } of preloadedMatches) {
try {
await getParamsAndProps({
mod: preloadedComponent,
route: maybeRoute,
routeCache,
pathname,
logging,
ssr: isServerLikeOutput(settings.config)
});
return {
route: maybeRoute,
filePath,
resolvedPathname: pathname,
preloadedComponent,
mod: preloadedComponent
};
} catch (e) {
if (isAstroError(e) && e.title === AstroErrorData.NoMatchingStaticPathFound.title) {
continue;
}
throw e;
}
}
const altPathname = pathname.replace(/(index)?\.html$/, "");
if (altPathname !== pathname) {
return await matchRoute(altPathname, env, manifest);
}
if (matches.length) {
const possibleRoutes = matches.flatMap((route) => route.component);
warn(
logging,
"getStaticPaths",
`${AstroErrorData.NoMatchingStaticPathFound.message(
pathname
)}
${AstroErrorData.NoMatchingStaticPathFound.hint(possibleRoutes)}`
);
}
log404(logging, pathname);
const custom404 = getCustom404Route(manifest);
if (custom404) {
const filePath = new URL(`./${custom404.component}`, settings.config.root);
const preloadedComponent = await preload({ env, filePath });
return {
route: custom404,
filePath,
resolvedPathname: pathname,
preloadedComponent,
mod: preloadedComponent
};
}
return void 0;
}
async function handleRoute({
matchedRoute,
url,
pathname,
status = getStatus(matchedRoute),
body,
origin,
env,
manifestData,
incomingRequest,
incomingResponse,
manifest
}) {
const { logging, settings } = env;
if (!matchedRoute) {
return handle404Response(origin, incomingRequest, incomingResponse);
}
if (matchedRoute.route.type === "redirect" && !settings.config.experimental.redirects) {
writeWebResponse(
incomingResponse,
new Response(`To enable redirect set experimental.redirects to \`true\`.`, {
status: 400
})
);
return;
}
const { config } = settings;
const filePath = matchedRoute.filePath;
const { route, preloadedComponent } = matchedRoute;
const buildingToSSR = isServerLikeOutput(config);
const request = createRequest({
url,
headers: buildingToSSR ? incomingRequest.headers : new Headers(),
method: incomingRequest.method,
body,
logging,
ssr: buildingToSSR,
clientAddress: buildingToSSR ? incomingRequest.socket.remoteAddress : void 0,
locals: Reflect.get(incomingRequest, clientLocalsSymbol)
// Allows adapters to pass in locals in dev mode.
});
for (const [name, value] of Object.entries(config.server.headers ?? {})) {
if (value)
incomingResponse.setHeader(name, value);
}
const options = {
env,
filePath,
preload: preloadedComponent,
pathname,
request,
route
};
const middleware = await loadMiddleware(env.loader, env.settings.config.srcDir);
if (middleware) {
options.middleware = middleware;
}
if (route.type === "endpoint") {
const result = await callEndpoint(options);
if (result.type === "response") {
if (result.response.headers.get("X-Astro-Response") === "Not-Found") {
const fourOhFourRoute = await matchRoute("/404", env, manifestData);
return handleRoute({
matchedRoute: fourOhFourRoute,
url: new URL("/404", url),
pathname: "/404",
status: 404,
body,
origin,
env,
manifestData,
incomingRequest,
incomingResponse,
manifest
});
}
throwIfRedirectNotAllowed(result.response, config);
await writeWebResponse(incomingResponse, result.response);
} else {
let contentType = "text/plain";
const filepath = route.pathname || route.segments.map((segment) => segment.map((p) => p.content).join("")).join("/");
const computedMimeType = mime.getType(filepath);
if (computedMimeType) {
contentType = computedMimeType;
}
const response = new Response(Buffer.from(result.body, result.encoding), {
status: 200,
headers: {
"Content-Type": `${contentType};charset=utf-8`
}
});
attachToResponse(response, result.cookies);
await writeWebResponse(incomingResponse, response);
}
} else {
const result = await renderPage(options);
if (result.status === 404) {
const fourOhFourRoute = await matchRoute("/404", env, manifestData);
return handleRoute({
...options,
matchedRoute: fourOhFourRoute,
url: new URL(pathname, url),
status: 404,
body,
origin,
env,
manifestData,
incomingRequest,
incomingResponse,
manifest
});
}
throwIfRedirectNotAllowed(result, config);
let response = result;
if (status && response.status !== status) {
response = new Response(result.body, { ...result, status });
}
return await writeSSRResult(request, response, incomingResponse);
}
}
function getStatus(matchedRoute) {
if (!matchedRoute)
return 404;
if (matchedRoute.route.route === "/404")
return 404;
if (matchedRoute.route.route === "/500")
return 500;
}
export {
handleRoute,
matchRoute
};

View file

@ -0,0 +1,15 @@
export type ErrorState = 'fresh' | 'error';
export interface RouteState {
state: ErrorState;
error?: Error;
}
export interface ServerState {
routes: Map<string, RouteState>;
state: ErrorState;
error?: Error;
}
export declare function createServerState(): ServerState;
export declare function hasAnyFailureState(serverState: ServerState): boolean;
export declare function setRouteError(serverState: ServerState, pathname: string, error: Error): void;
export declare function setServerError(serverState: ServerState, error: Error): void;
export declare function clearRouteError(serverState: ServerState, pathname: string): void;

View file

@ -0,0 +1,42 @@
function createServerState() {
return {
routes: /* @__PURE__ */ new Map(),
state: "fresh"
};
}
function hasAnyFailureState(serverState) {
return serverState.state !== "fresh";
}
function setRouteError(serverState, pathname, error) {
if (serverState.routes.has(pathname)) {
const routeState = serverState.routes.get(pathname);
routeState.state = "error";
routeState.error = error;
} else {
const routeState = {
state: "error",
error
};
serverState.routes.set(pathname, routeState);
}
serverState.state = "error";
serverState.error = error;
}
function setServerError(serverState, error) {
serverState.state = "error";
serverState.error = error;
}
function clearRouteError(serverState, pathname) {
if (serverState.routes.has(pathname)) {
serverState.routes.delete(pathname);
}
serverState.state = "fresh";
serverState.error = void 0;
}
export {
clearRouteError,
createServerState,
hasAnyFailureState,
setRouteError,
setServerError
};