summaryrefslogtreecommitdiff
path: root/src/runtime.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/runtime.ts')
-rw-r--r--src/runtime.ts365
1 files changed, 0 insertions, 365 deletions
diff --git a/src/runtime.ts b/src/runtime.ts
deleted file mode 100644
index 66a0657cf..000000000
--- a/src/runtime.ts
+++ /dev/null
@@ -1,365 +0,0 @@
-import { fileURLToPath } from 'url';
-import type { SnowpackDevServer, ServerRuntime as SnowpackServerRuntime, SnowpackConfig } from 'snowpack';
-import type { AstroConfig, CollectionResult, CollectionRSS, CreateCollection, Params, RuntimeMode } from './@types/astro';
-import type { LogOptions } from './logger';
-import type { CompileError } from './parser/utils/error.js';
-import { debug, info } from './logger.js';
-import { searchForPage } from './search.js';
-
-import { existsSync } from 'fs';
-import { loadConfiguration, logger as snowpackLogger, startServer as startSnowpackServer } from 'snowpack';
-
-// We need to use require.resolve for snowpack plugins, so create a require function here.
-import { createRequire } from 'module';
-const require = createRequire(import.meta.url);
-
-interface RuntimeConfig {
- astroConfig: AstroConfig;
- logging: LogOptions;
- mode: RuntimeMode;
- backendSnowpack: SnowpackDevServer;
- backendSnowpackRuntime: SnowpackServerRuntime;
- backendSnowpackConfig: SnowpackConfig;
- frontendSnowpack: SnowpackDevServer;
- frontendSnowpackRuntime: SnowpackServerRuntime;
- frontendSnowpackConfig: SnowpackConfig;
-}
-
-// info needed for collection generation
-interface CollectionInfo {
- additionalURLs: Set<string>;
- rss?: { data: any[] & CollectionRSS };
-}
-
-type LoadResultSuccess = {
- statusCode: 200;
- contents: string | Buffer;
- contentType?: string | false;
-};
-type LoadResultNotFound = { statusCode: 404; error: Error; collectionInfo?: CollectionInfo };
-type LoadResultRedirect = { statusCode: 301 | 302; location: string; collectionInfo?: CollectionInfo };
-type LoadResultError = { statusCode: 500 } & ({ type: 'parse-error'; error: CompileError } | { type: 'unknown'; error: Error });
-
-export type LoadResult = (LoadResultSuccess | LoadResultNotFound | LoadResultRedirect | LoadResultError) & { collectionInfo?: CollectionInfo };
-
-// Disable snowpack from writing to stdout/err.
-snowpackLogger.level = 'silent';
-
-/** Pass a URL to Astro to resolve and build */
-async function load(config: RuntimeConfig, rawPathname: string | undefined): Promise<LoadResult> {
- const { logging, backendSnowpackRuntime, frontendSnowpack } = config;
- const { astroRoot } = config.astroConfig;
-
- const fullurl = new URL(rawPathname || '/', 'https://example.org/');
-
- const reqPath = decodeURI(fullurl.pathname);
- info(logging, 'access', reqPath);
-
- const searchResult = searchForPage(fullurl, astroRoot);
- if (searchResult.statusCode === 404) {
- try {
- const result = await frontendSnowpack.loadUrl(reqPath);
- if (!result) throw new Error(`Unable to load ${reqPath}`);
- // success
- return {
- statusCode: 200,
- ...result,
- };
- } catch (err) {
- // build error
- if (err.failed) {
- return { statusCode: 500, type: 'unknown', error: err };
- }
-
- // not found
- return { statusCode: 404, error: err };
- }
- }
-
- if (searchResult.statusCode === 301) {
- return { statusCode: 301, location: searchResult.pathname };
- }
-
- const snowpackURL = searchResult.location.snowpackURL;
- let rss: { data: any[] & CollectionRSS } = {} as any;
-
- try {
- const mod = await backendSnowpackRuntime.importModule(snowpackURL);
- debug(logging, 'resolve', `${reqPath} -> ${snowpackURL}`);
-
- // handle collection
- let collection = {} as CollectionResult;
- let additionalURLs = new Set<string>();
-
- if (mod.exports.createCollection) {
- const createCollection: CreateCollection = await mod.exports.createCollection();
- for (const key of Object.keys(createCollection)) {
- if (key !== 'data' && key !== 'routes' && key !== 'permalink' && key !== 'pageSize' && key !== 'rss') {
- throw new Error(`[createCollection] unknown option: "${key}"`);
- }
- }
- let { data: loadData, routes, permalink, pageSize, rss: createRSS } = createCollection;
- if (!pageSize) pageSize = 25; // can’t be 0
- let currentParams: Params = {};
-
- // params
- if (routes || permalink) {
- if (!routes || !permalink) {
- throw new Error('createCollection() must have both routes and permalink options. Include both together, or omit both.');
- }
- let requestedParams = routes.find((p) => {
- const baseURL = (permalink as any)({ params: p });
- additionalURLs.add(baseURL);
- return baseURL === reqPath || `${baseURL}/${searchResult.currentPage || 1}` === reqPath;
- });
- if (requestedParams) {
- currentParams = requestedParams;
- collection.params = requestedParams;
- }
- }
-
- let data: any[] = await loadData({ params: currentParams });
-
- // handle RSS
- if (createRSS) {
- rss = {
- ...createRSS,
- data: [...data] as any,
- };
- }
-
- collection.start = 0;
- collection.end = data.length - 1;
- collection.total = data.length;
- collection.page = { current: 1, size: pageSize, last: 1 };
- collection.url = { current: reqPath };
-
- // paginate
- if (searchResult.currentPage) {
- const start = (searchResult.currentPage - 1) * pageSize; // currentPage is 1-indexed
- const end = Math.min(start + pageSize, data.length);
-
- collection.start = start;
- collection.end = end - 1;
- collection.page.current = searchResult.currentPage;
- collection.page.last = Math.ceil(data.length / pageSize);
- // TODO: fix the .replace() hack
- if (end < data.length) {
- collection.url.next = collection.url.current.replace(/(\/\d+)?$/, `/${searchResult.currentPage + 1}`);
- }
- if (searchResult.currentPage > 1) {
- collection.url.prev = collection.url.current
- .replace(/\d+$/, `${searchResult.currentPage - 1 || 1}`) // update page #
- .replace(/\/1$/, ''); // if end is `/1`, then just omit
- }
-
- // from page 2 to the end, add all pages as additional URLs (needed for build)
- for (let n = 1; n <= collection.page.last; n++) {
- if (additionalURLs.size) {
- // if this is a param-based collection, paginate all params
- additionalURLs.forEach((url) => {
- additionalURLs.add(url.replace(/(\/\d+)?$/, `/${n}`));
- });
- } else {
- // if this has no params, simply add page
- additionalURLs.add(reqPath.replace(/(\/\d+)?$/, `/${n}`));
- }
- }
-
- data = data.slice(start, end);
- } else if (createCollection.pageSize) {
- // TODO: fix bug where redirect doesn’t happen
- // This happens because a pageSize is set, but the user isn’t on a paginated route. Redirect:
- return {
- statusCode: 301,
- location: reqPath + '/1',
- collectionInfo: {
- additionalURLs,
- rss: rss.data ? rss : undefined,
- },
- };
- }
-
- // if we’ve paginated too far, this is a 404
- if (!data.length) {
- return {
- statusCode: 404,
- error: new Error('Not Found'),
- collectionInfo: {
- additionalURLs,
- rss: rss.data ? rss : undefined,
- },
- };
- }
-
- collection.data = data;
- }
-
- const requestURL = new URL(fullurl.toString());
-
- // For first release query params are not passed to components.
- // An exception is made for dev server specific routes.
- if(reqPath !== '/500') {
- requestURL.search = '';
- }
-
- let html = (await mod.exports.__renderPage({
- request: {
- // params should go here when implemented
- url: requestURL
- },
- children: [],
- props: { collection },
- })) as string;
-
- // inject styles
- // TODO: handle this in compiler
- const styleTags = Array.isArray(mod.css) && mod.css.length ? mod.css.reduce((markup, href) => `${markup}\n<link rel="stylesheet" type="text/css" href="${href}" />`, '') : ``;
- if (html.indexOf('</head>') !== -1) {
- html = html.replace('</head>', `${styleTags}</head>`);
- } else {
- html = styleTags + html;
- }
-
- return {
- statusCode: 200,
- contentType: 'text/html; charset=utf-8',
- contents: html,
- collectionInfo: {
- additionalURLs,
- rss: rss.data ? rss : undefined,
- },
- };
- } catch (err) {
- if (err.code === 'parse-error' || err instanceof SyntaxError) {
- return {
- statusCode: 500,
- type: 'parse-error',
- error: err,
- };
- }
- return {
- statusCode: 500,
- type: 'unknown',
- error: err,
- };
- }
-}
-
-export interface AstroRuntime {
- runtimeConfig: RuntimeConfig;
- load: (rawPathname: string | undefined) => Promise<LoadResult>;
- shutdown: () => Promise<void>;
-}
-
-interface RuntimeOptions {
- mode: RuntimeMode;
- logging: LogOptions;
-}
-
-interface CreateSnowpackOptions {
- env: Record<string, any>;
- mode: RuntimeMode;
- resolvePackageUrl?: (pkgName: string) => Promise<string>;
-}
-
-/** Create a new Snowpack instance to power Astro */
-async function createSnowpack(astroConfig: AstroConfig, options: CreateSnowpackOptions) {
- const { projectRoot, astroRoot, extensions } = astroConfig;
- const { env, mode, resolvePackageUrl } = options;
-
- const internalPath = new URL('./frontend/', import.meta.url);
-
- let snowpack: SnowpackDevServer;
- const astroPlugOptions: {
- resolvePackageUrl?: (s: string) => Promise<string>;
- extensions?: Record<string, string>;
- astroConfig: AstroConfig;
- } = {
- astroConfig,
- extensions,
- resolvePackageUrl,
- };
-
- const mountOptions = {
- [fileURLToPath(astroRoot)]: '/_astro',
- [fileURLToPath(internalPath)]: '/_astro_internal',
- };
-
- if (existsSync(astroConfig.public)) {
- mountOptions[fileURLToPath(astroConfig.public)] = '/';
- }
-
- const snowpackConfig = await loadConfiguration({
- root: fileURLToPath(projectRoot),
- mount: mountOptions,
- mode,
- plugins: [
- [fileURLToPath(new URL('../snowpack-plugin.cjs', import.meta.url)), astroPlugOptions],
- require.resolve('@snowpack/plugin-sass'),
- [require.resolve('@snowpack/plugin-svelte'), { compilerOptions: { hydratable: true } }],
- require.resolve('@snowpack/plugin-vue'),
- ],
- devOptions: {
- open: 'none',
- output: 'stream',
- port: 0,
- },
- buildOptions: {
- out: astroConfig.dist,
- },
- packageOptions: {
- knownEntrypoints: ['preact-render-to-string'],
- external: ['@vue/server-renderer', 'node-fetch', 'prismjs/components/index.js'],
- },
- });
-
- const envConfig = snowpackConfig.env || (snowpackConfig.env = {});
- Object.assign(envConfig, env);
-
- snowpack = await startSnowpackServer({
- config: snowpackConfig,
- lockfile: null,
- });
- const snowpackRuntime = snowpack.getServerRuntime();
-
- return { snowpack, snowpackRuntime, snowpackConfig };
-}
-
-/** Core Astro runtime */
-export async function createRuntime(astroConfig: AstroConfig, { mode, logging }: RuntimeOptions): Promise<AstroRuntime> {
- const resolvePackageUrl = async (pkgName: string) => frontendSnowpack.getUrlForPackage(pkgName);
-
- const { snowpack: backendSnowpack, snowpackRuntime: backendSnowpackRuntime, snowpackConfig: backendSnowpackConfig } = await createSnowpack(astroConfig, {
- env: {
- astro: true,
- },
- mode,
- resolvePackageUrl,
- });
-
- const { snowpack: frontendSnowpack, snowpackRuntime: frontendSnowpackRuntime, snowpackConfig: frontendSnowpackConfig } = await createSnowpack(astroConfig, {
- env: {
- astro: false,
- },
- mode,
- });
-
- const runtimeConfig: RuntimeConfig = {
- astroConfig,
- logging,
- mode,
- backendSnowpack,
- backendSnowpackRuntime,
- backendSnowpackConfig,
- frontendSnowpack,
- frontendSnowpackRuntime,
- frontendSnowpackConfig,
- };
-
- return {
- runtimeConfig,
- load: load.bind(null, runtimeConfig),
- shutdown: () => Promise.all([backendSnowpack.shutdown(), frontendSnowpack.shutdown()]).then(() => void 0),
- };
-}