diff --git a/.changeset/chatty-cows-attack.md b/.changeset/chatty-cows-attack.md new file mode 100644 index 000000000..7fe9fb8c2 --- /dev/null +++ b/.changeset/chatty-cows-attack.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Allow components to return a Response diff --git a/.changeset/odd-swans-walk.md b/.changeset/odd-swans-walk.md new file mode 100644 index 000000000..0c406d7e0 --- /dev/null +++ b/.changeset/odd-swans-walk.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes non-GET API routes in dev with Node 14 diff --git a/.changeset/sour-eggs-wink.md b/.changeset/sour-eggs-wink.md new file mode 100644 index 000000000..5c2c0fc44 --- /dev/null +++ b/.changeset/sour-eggs-wink.md @@ -0,0 +1,6 @@ +--- +'astro': patch +'@astrojs/deno': patch +--- + +Add a Deno adapter for SSR diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b2673975..56aff2e63 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -134,6 +134,11 @@ jobs: node-version: ${{ matrix.node_version }} cache: 'pnpm' + - name: Use Deno + uses: denoland/setup-deno@v1 + with: + deno-version: v1.19.3 + - name: Download Build Artifacts uses: actions/download-artifact@v3 diff --git a/examples/blog-multiple-authors/.gitignore b/examples/blog-multiple-authors/.gitignore index c7de34ef2..7329a851d 100644 --- a/examples/blog-multiple-authors/.gitignore +++ b/examples/blog-multiple-authors/.gitignore @@ -9,6 +9,8 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* + # environment variables .env diff --git a/examples/blog/.gitignore b/examples/blog/.gitignore index c7de34ef2..7329a851d 100644 --- a/examples/blog/.gitignore +++ b/examples/blog/.gitignore @@ -9,6 +9,8 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* + # environment variables .env diff --git a/examples/component/.gitignore b/examples/component/.gitignore index c7de34ef2..7329a851d 100644 --- a/examples/component/.gitignore +++ b/examples/component/.gitignore @@ -9,6 +9,8 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* + # environment variables .env diff --git a/examples/docs/.gitignore b/examples/docs/.gitignore index c7de34ef2..7329a851d 100644 --- a/examples/docs/.gitignore +++ b/examples/docs/.gitignore @@ -9,6 +9,8 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* + # environment variables .env diff --git a/examples/env-vars/.gitignore b/examples/env-vars/.gitignore index c7de34ef2..7329a851d 100644 --- a/examples/env-vars/.gitignore +++ b/examples/env-vars/.gitignore @@ -9,6 +9,8 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* + # environment variables .env diff --git a/examples/framework-alpine/.gitignore b/examples/framework-alpine/.gitignore index c7de34ef2..7329a851d 100644 --- a/examples/framework-alpine/.gitignore +++ b/examples/framework-alpine/.gitignore @@ -9,6 +9,8 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* + # environment variables .env diff --git a/examples/framework-lit/.gitignore b/examples/framework-lit/.gitignore index c7de34ef2..7329a851d 100644 --- a/examples/framework-lit/.gitignore +++ b/examples/framework-lit/.gitignore @@ -9,6 +9,8 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* + # environment variables .env diff --git a/examples/framework-multiple/.gitignore b/examples/framework-multiple/.gitignore index c7de34ef2..7329a851d 100644 --- a/examples/framework-multiple/.gitignore +++ b/examples/framework-multiple/.gitignore @@ -9,6 +9,8 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* + # environment variables .env diff --git a/examples/framework-preact/.gitignore b/examples/framework-preact/.gitignore index c7de34ef2..7329a851d 100644 --- a/examples/framework-preact/.gitignore +++ b/examples/framework-preact/.gitignore @@ -9,6 +9,8 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* + # environment variables .env diff --git a/examples/framework-react/.gitignore b/examples/framework-react/.gitignore index c7de34ef2..7329a851d 100644 --- a/examples/framework-react/.gitignore +++ b/examples/framework-react/.gitignore @@ -9,6 +9,8 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* + # environment variables .env diff --git a/examples/framework-solid/.gitignore b/examples/framework-solid/.gitignore index c7de34ef2..7329a851d 100644 --- a/examples/framework-solid/.gitignore +++ b/examples/framework-solid/.gitignore @@ -9,6 +9,8 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* + # environment variables .env diff --git a/examples/framework-svelte/.gitignore b/examples/framework-svelte/.gitignore index c7de34ef2..7329a851d 100644 --- a/examples/framework-svelte/.gitignore +++ b/examples/framework-svelte/.gitignore @@ -9,6 +9,8 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* + # environment variables .env diff --git a/examples/framework-vue/.gitignore b/examples/framework-vue/.gitignore index c7de34ef2..7329a851d 100644 --- a/examples/framework-vue/.gitignore +++ b/examples/framework-vue/.gitignore @@ -9,6 +9,8 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* + # environment variables .env diff --git a/examples/integrations-playground/.gitignore b/examples/integrations-playground/.gitignore index c7de34ef2..7329a851d 100644 --- a/examples/integrations-playground/.gitignore +++ b/examples/integrations-playground/.gitignore @@ -9,6 +9,8 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* + # environment variables .env diff --git a/examples/minimal/.gitignore b/examples/minimal/.gitignore index c7de34ef2..7329a851d 100644 --- a/examples/minimal/.gitignore +++ b/examples/minimal/.gitignore @@ -9,6 +9,8 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* + # environment variables .env diff --git a/examples/non-html-pages/.gitignore b/examples/non-html-pages/.gitignore index c7de34ef2..7329a851d 100644 --- a/examples/non-html-pages/.gitignore +++ b/examples/non-html-pages/.gitignore @@ -9,6 +9,8 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* + # environment variables .env diff --git a/examples/portfolio/.gitignore b/examples/portfolio/.gitignore index c7de34ef2..7329a851d 100644 --- a/examples/portfolio/.gitignore +++ b/examples/portfolio/.gitignore @@ -9,6 +9,8 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* + # environment variables .env diff --git a/examples/ssr/astro.config.mjs b/examples/ssr/astro.config.mjs index 448d5829d..ddb265905 100644 --- a/examples/ssr/astro.config.mjs +++ b/examples/ssr/astro.config.mjs @@ -1,9 +1,9 @@ import { defineConfig } from 'astro/config'; import svelte from '@astrojs/svelte'; -import nodejs from '@astrojs/node'; +import deno from '@astrojs/deno'; // https://astro.build/config export default defineConfig({ - adapter: nodejs(), + adapter: deno(), integrations: [svelte()], }); diff --git a/examples/starter/.gitignore b/examples/starter/.gitignore index c7de34ef2..7329a851d 100644 --- a/examples/starter/.gitignore +++ b/examples/starter/.gitignore @@ -9,6 +9,8 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* + # environment variables .env diff --git a/examples/subpath/.gitignore b/examples/subpath/.gitignore index c7de34ef2..7329a851d 100644 --- a/examples/subpath/.gitignore +++ b/examples/subpath/.gitignore @@ -9,6 +9,8 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* + # environment variables .env diff --git a/examples/with-markdown-plugins/.gitignore b/examples/with-markdown-plugins/.gitignore index c7de34ef2..7329a851d 100644 --- a/examples/with-markdown-plugins/.gitignore +++ b/examples/with-markdown-plugins/.gitignore @@ -9,6 +9,8 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* + # environment variables .env diff --git a/examples/with-markdown-shiki/.gitignore b/examples/with-markdown-shiki/.gitignore index c7de34ef2..7329a851d 100644 --- a/examples/with-markdown-shiki/.gitignore +++ b/examples/with-markdown-shiki/.gitignore @@ -9,6 +9,8 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* + # environment variables .env diff --git a/examples/with-markdown/.gitignore b/examples/with-markdown/.gitignore index c7de34ef2..7329a851d 100644 --- a/examples/with-markdown/.gitignore +++ b/examples/with-markdown/.gitignore @@ -9,6 +9,8 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* + # environment variables .env diff --git a/examples/with-nanostores/.gitignore b/examples/with-nanostores/.gitignore index c7de34ef2..7329a851d 100644 --- a/examples/with-nanostores/.gitignore +++ b/examples/with-nanostores/.gitignore @@ -9,6 +9,8 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* + # environment variables .env diff --git a/examples/with-tailwindcss/.gitignore b/examples/with-tailwindcss/.gitignore index c7de34ef2..7329a851d 100644 --- a/examples/with-tailwindcss/.gitignore +++ b/examples/with-tailwindcss/.gitignore @@ -9,6 +9,8 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* + # environment variables .env diff --git a/examples/with-vite-plugin-pwa/.gitignore b/examples/with-vite-plugin-pwa/.gitignore index c7de34ef2..7329a851d 100644 --- a/examples/with-vite-plugin-pwa/.gitignore +++ b/examples/with-vite-plugin-pwa/.gitignore @@ -9,6 +9,8 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* + # environment variables .env diff --git a/package.json b/package.json index 60d98b009..ef2da5a23 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "build:ci": "turbo run build:ci --no-deps --scope=astro --scope=create-astro --scope=\"@astrojs/*\"", "build:examples": "turbo run build --scope=\"@example/*\"", "dev": "turbo run dev --no-deps --no-cache --parallel --scope=astro --scope=create-astro --scope=\"@astrojs/*\"", - "test": "pnpm run test --filter astro --filter @astrojs/webapi", + "test": "pnpm run test --filter astro --filter @astrojs/webapi --filter @astrojs/deno", "test:match": "cd packages/astro && pnpm run test:match", "test:templates": "pnpm run test --filter create-astro", "test:smoke": "node scripts/smoke/index.js", diff --git a/packages/astro/package.json b/packages/astro/package.json index 70d26a3d2..8ca558d6b 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -107,6 +107,7 @@ "mime": "^3.0.0", "ora": "^6.1.0", "parse5": "^6.0.1", + "path-browserify": "^1.0.1", "path-to-regexp": "^6.2.0", "postcss": "^8.4.12", "postcss-load-config": "^3.1.4", @@ -148,6 +149,7 @@ "@types/mime": "^2.0.3", "@types/mocha": "^9.1.0", "@types/parse5": "^6.0.3", + "@types/path-browserify": "^1.0.0", "@types/prettier": "^2.4.4", "@types/resolve": "^1.20.1", "@types/rimraf": "^3.0.2", diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index f3108d390..013a6f366 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -4,6 +4,7 @@ import type * as vite from 'vite'; import { z } from 'zod'; import type { AstroConfigSchema } from '../core/config'; import type { AstroComponentFactory, Metadata } from '../runtime/server'; +import type { ViteConfigWithSSR } from '../core/create-vite'; export type { SSRManifest } from '../core/app/types'; export interface AstroBuiltinProps { @@ -171,7 +172,6 @@ export interface AstroUserConfig { integrations?: Array; /** - * @docs * @name adapter * @type {AstroIntegration} * @default `undefined` @@ -679,8 +679,9 @@ export interface AstroIntegration { 'astro:config:done'?: (options: { config: AstroConfig; setAdapter: (adapter: AstroAdapter) => void }) => void | Promise; 'astro:server:setup'?: (options: { config: Readonly; server: vite.ViteDevServer }) => void | Promise; 'astro:server:start'?: (options: { config: Readonly; address: AddressInfo }) => void | Promise; - 'astro:server:done'?: (options: { config: Readonly }) => void | Promise; + 'astro:server:done'?: (options: {config: Readonly;}) => void | Promise; 'astro:build:start'?: (options: { config: Readonly; buildConfig: BuildConfig }) => void | Promise; + 'astro:build:setup'?: (options: { config: Readonly; vite: ViteConfigWithSSR; target: 'client' | 'server' }) => void; 'astro:build:done'?: (options: { config: Readonly; pages: { pathname: string }[]; dir: URL; routes: RouteData[] }) => void | Promise; }; } diff --git a/packages/astro/src/cli/index.ts b/packages/astro/src/cli/index.ts index 5b4f2abbc..534937d8c 100644 --- a/packages/astro/src/cli/index.ts +++ b/packages/astro/src/cli/index.ts @@ -1,12 +1,12 @@ /* eslint-disable no-console */ import type { AstroConfig } from '../@types/astro'; -import { enableVerboseLogging, LogOptions } from '../core/logger.js'; +import { LogOptions } from '../core/logger/core.js'; import * as colors from 'kleur/colors'; import yargs from 'yargs-parser'; import { z } from 'zod'; -import { defaultLogDestination } from '../core/logger.js'; +import { nodeLogDestination, enableVerboseLogging } from '../core/logger/node.js'; import build from '../core/build/index.js'; import add from '../core/add/index.js'; import devServer from '../core/dev/index.js'; @@ -87,7 +87,7 @@ export async function cli(args: string[]) { // logLevel let logging: LogOptions = { - dest: defaultLogDestination, + dest: nodeLogDestination, level: 'info', }; if (flags.verbose) { diff --git a/packages/astro/src/core/add/index.ts b/packages/astro/src/core/add/index.ts index d1f63a9cf..c181d6aae 100644 --- a/packages/astro/src/core/add/index.ts +++ b/packages/astro/src/core/add/index.ts @@ -10,7 +10,7 @@ import preferredPM from 'preferred-pm'; import ora from 'ora'; import { resolveConfigURL } from '../config.js'; import { apply as applyPolyfill } from '../polyfill.js'; -import { error, info, debug, LogOptions } from '../logger.js'; +import { error, info, debug, LogOptions } from '../logger/core.js'; import { printHelp } from '../messages.js'; import * as msg from '../messages.js'; import * as CONSTS from './consts.js'; diff --git a/packages/astro/src/core/app/index.ts b/packages/astro/src/core/app/index.ts index df3c94a68..ee66df9d1 100644 --- a/packages/astro/src/core/app/index.ts +++ b/packages/astro/src/core/app/index.ts @@ -1,8 +1,9 @@ import type { ComponentInstance, EndpointHandler, ManifestData, RouteData } from '../../@types/astro'; import type { SSRManifest as Manifest, RouteInfo } from './types'; +import type { LogOptions } from '../logger/core.js'; import mime from 'mime'; -import { defaultLogOptions } from '../logger.js'; +import { consoleLogDestination } from '../logger/console.js'; export { deserializeManifest } from './common.js'; import { matchRoute } from '../routing/match.js'; import { render } from '../render/core.js'; @@ -18,7 +19,10 @@ export class App { #routeDataToRouteInfo: Map; #routeCache: RouteCache; #encoder = new TextEncoder(); - #logging = defaultLogOptions; + #logging: LogOptions = { + dest: consoleLogDestination, + level: 'info', + }; constructor(manifest: Manifest) { this.#manifest = manifest; @@ -26,7 +30,7 @@ export class App { routes: manifest.routes.map((route) => route.routeData), }; this.#routeDataToRouteInfo = new Map(manifest.routes.map((route) => [route.routeData, route])); - this.#routeCache = new RouteCache(defaultLogOptions); + this.#routeCache = new RouteCache(this.#logging); } match(request: Request): RouteData | undefined { const url = new URL(request.url); @@ -105,7 +109,7 @@ export class App { const url = new URL(request.url); const handler = mod as unknown as EndpointHandler; const result = await callEndpoint(handler, { - logging: defaultLogOptions, + logging: this.#logging, origin: url.origin, pathname: url.pathname, request, diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index f18f7b2bb..7c8e92ce5 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -5,7 +5,7 @@ import type { OutputAsset, OutputChunk, RollupOutput } from 'rollup'; import { fileURLToPath } from 'url'; import type { AstroConfig, ComponentInstance, EndpointHandler, SSRLoadedRenderer } from '../../@types/astro'; import type { BuildInternals } from '../../core/build/internal.js'; -import { debug, info } from '../../core/logger.js'; +import { debug, info } from '../logger/core.js'; import { appendForwardSlash, prependForwardSlash } from '../../core/path.js'; import type { RenderOptions } from '../../core/render/core'; import { BEFORE_HYDRATION_SCRIPT_ID } from '../../vite-plugin-scripts/index.js'; diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts index 4962ce1c7..02e1898f8 100644 --- a/packages/astro/src/core/build/index.ts +++ b/packages/astro/src/core/build/index.ts @@ -1,5 +1,5 @@ import type { AstroConfig, BuildConfig, ManifestData } from '../../@types/astro'; -import type { LogOptions } from '../logger'; +import type { LogOptions } from '../logger/core'; import fs from 'fs'; import * as colors from 'kleur/colors'; @@ -7,7 +7,8 @@ import { apply as applyPolyfill } from '../polyfill.js'; import { performance } from 'perf_hooks'; import * as vite from 'vite'; import { createVite, ViteConfigWithSSR } from '../create-vite.js'; -import { debug, defaultLogOptions, info, levels, timerMessage, warn, warnIfUsingExperimentalSSR } from '../logger.js'; +import { debug, info, levels, timerMessage, warn, warnIfUsingExperimentalSSR } from '../logger/core.js'; +import { nodeLogOptions } from '../logger/node.js'; import { createRouteManifest } from '../routing/index.js'; import { generateSitemap } from '../render/sitemap.js'; import { collectPagesData } from './page-data.js'; @@ -25,7 +26,7 @@ export interface BuildOptions { } /** `astro build` */ -export default async function build(config: AstroConfig, options: BuildOptions = { logging: defaultLogOptions }): Promise { +export default async function build(config: AstroConfig, options: BuildOptions = { logging: nodeLogOptions }): Promise { applyPolyfill(); const builder = new AstroBuilder(config, options); await builder.run(); diff --git a/packages/astro/src/core/build/page-data.ts b/packages/astro/src/core/build/page-data.ts index 7681c0664..707c89fd7 100644 --- a/packages/astro/src/core/build/page-data.ts +++ b/packages/astro/src/core/build/page-data.ts @@ -1,12 +1,12 @@ import type { AstroConfig, ComponentInstance, ManifestData, RouteData } from '../../@types/astro'; import type { AllPagesData } from './types'; -import type { LogOptions } from '../logger'; -import { info } from '../logger.js'; +import type { LogOptions } from '../logger/core'; +import { info } from '../logger/core.js'; import type { ViteDevServer } from 'vite'; import { fileURLToPath } from 'url'; import * as colors from 'kleur/colors'; -import { debug } from '../logger.js'; +import { debug } from '../logger/core.js'; import { preload as ssrPreload } from '../render/dev/index.js'; import { generateRssFunction } from '../render/rss.js'; import { callGetStaticPaths, RouteCache, RouteCacheEntry } from '../render/route-cache.js'; diff --git a/packages/astro/src/core/build/scan-based-build.ts b/packages/astro/src/core/build/scan-based-build.ts index afb2a5393..a4e9009e9 100644 --- a/packages/astro/src/core/build/scan-based-build.ts +++ b/packages/astro/src/core/build/scan-based-build.ts @@ -2,7 +2,7 @@ import type { ViteDevServer } from 'vite'; import type { RollupOutput, RollupWatcher } from 'rollup'; import type { AstroConfig, RouteType } from '../../@types/astro'; import type { AllPagesData, PageBuildData } from './types'; -import type { LogOptions } from '../logger'; +import type { LogOptions } from '../logger/core'; import type { ViteConfigWithSSR } from '../create-vite.js'; import { fileURLToPath } from 'url'; diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts index b166948f1..481d51cfa 100644 --- a/packages/astro/src/core/build/static-build.ts +++ b/packages/astro/src/core/build/static-build.ts @@ -9,7 +9,7 @@ import npath from 'path'; import { fileURLToPath } from 'url'; import * as vite from 'vite'; import { createBuildInternals } from '../../core/build/internal.js'; -import { info } from '../../core/logger.js'; +import { info } from '../logger/core.js'; import { appendForwardSlash, prependForwardSlash } from '../../core/path.js'; import { emptyDir, removeDir } from '../../core/util.js'; import { rollupPluginAstroBuildCSS } from '../../vite-plugin-build-css/index.js'; @@ -20,6 +20,7 @@ import { vitePluginPages } from './vite-plugin-pages.js'; import { generatePages } from './generate.js'; import { trackPageData } from './internal.js'; import { isBuildingToSSR } from '../util.js'; +import { runHookBuildSetup } from '../../integrations/index.js'; import { getTimeStat } from './util.js'; export async function staticBuild(opts: StaticBuildOptions) { @@ -112,8 +113,8 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp const { astroConfig, viteConfig } = opts; const ssr = astroConfig.buildOptions.experimentalSsr; const out = ssr ? opts.buildConfig.server : astroConfig.dist; - // TODO: use vite.mergeConfig() here? - return await vite.build({ + + const viteBuildConfig = { logLevel: 'error', mode: 'production', css: viteConfig.css, @@ -122,7 +123,6 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp emptyOutDir: false, manifest: false, outDir: fileURLToPath(out), - ssr: true, rollupOptions: { input: [], output: { @@ -132,6 +132,7 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp assetFileNames: 'assets/asset.[hash][extname]', }, }, + ssr: true, // must match an esbuild target target: 'esnext', // improve build performance @@ -156,7 +157,13 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp server: viteConfig.server, base: astroConfig.buildOptions.site ? new URL(astroConfig.buildOptions.site).pathname : '/', ssr: viteConfig.ssr, - } as ViteConfigWithSSR); + resolve: viteConfig.resolve, + } as ViteConfigWithSSR; + + await runHookBuildSetup({ config: astroConfig, vite: viteBuildConfig, target: 'server' }); + + // TODO: use vite.mergeConfig() here? + return await vite.build(viteBuildConfig); } async function clientBuild(opts: StaticBuildOptions, internals: BuildInternals, input: Set) { @@ -173,7 +180,7 @@ async function clientBuild(opts: StaticBuildOptions, internals: BuildInternals, const out = isBuildingToSSR(astroConfig) ? opts.buildConfig.client : astroConfig.dist; - const buildResult = await vite.build({ + const viteBuildConfig = { logLevel: 'info', mode: 'production', css: viteConfig.css, @@ -207,7 +214,11 @@ async function clientBuild(opts: StaticBuildOptions, internals: BuildInternals, envPrefix: 'PUBLIC_', server: viteConfig.server, base: appendForwardSlash(astroConfig.buildOptions.site ? new URL(astroConfig.buildOptions.site).pathname : '/'), - }); + } as ViteConfigWithSSR; + + await runHookBuildSetup({ config: astroConfig, vite: viteBuildConfig, target: 'client' }); + + const buildResult = await vite.build(viteBuildConfig); info(opts.logging, null, dim(`Completed in ${getTimeStat(timer, performance.now())}.\n`)); return buildResult; } diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index 65cf269b7..8564290e2 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -1,5 +1,5 @@ import type { AstroConfig } from '../@types/astro'; -import type { LogOptions } from './logger'; +import type { LogOptions } from './logger/core'; import { builtinModules } from 'module'; import { fileURLToPath } from 'url'; @@ -81,6 +81,13 @@ export async function createVite(commandConfig: ViteConfigWithSSR, { astroConfig css: { postcss: astroConfig.styleOptions.postcss || {}, }, + resolve: { + alias: { + // This is needed for Deno compatibility, as the non-browser version + // of this module depends on Node `crypto` + randombytes: 'randombytes/browser', + }, + }, // Note: SSR API is in beta (https://vitejs.dev/guide/ssr.html) ssr: { external: [...ALWAYS_EXTERNAL], diff --git a/packages/astro/src/core/dev/index.ts b/packages/astro/src/core/dev/index.ts index cc1e7bde2..dcb4a333b 100644 --- a/packages/astro/src/core/dev/index.ts +++ b/packages/astro/src/core/dev/index.ts @@ -4,7 +4,8 @@ import * as vite from 'vite'; import type { AstroConfig } from '../../@types/astro'; import { runHookConfigDone, runHookConfigSetup, runHookServerDone, runHookServerSetup, runHookServerStart } from '../../integrations/index.js'; import { createVite } from '../create-vite.js'; -import { defaultLogOptions, info, LogOptions, warn, warnIfUsingExperimentalSSR } from '../logger.js'; +import { info, LogOptions, warn, warnIfUsingExperimentalSSR } from '../logger/core.js'; +import { nodeLogOptions } from '../logger/node.js'; import * as msg from '../messages.js'; import { apply as applyPolyfill } from '../polyfill.js'; import { getResolvedHostForVite } from '../util.js'; @@ -19,7 +20,7 @@ export interface DevServer { } /** `astro dev` */ -export default async function dev(config: AstroConfig, options: DevOptions = { logging: defaultLogOptions }): Promise { +export default async function dev(config: AstroConfig, options: DevOptions = { logging: nodeLogOptions }): Promise { const devStart = performance.now(); applyPolyfill(); config = await runHookConfigSetup({ config, command: 'dev' }); diff --git a/packages/astro/src/core/logger.ts b/packages/astro/src/core/logger.ts deleted file mode 100644 index f1bce5d6f..000000000 --- a/packages/astro/src/core/logger.ts +++ /dev/null @@ -1,229 +0,0 @@ -import type { AstroConfig } from '../@types/astro'; -import { bold, cyan, dim, red, yellow, reset } from 'kleur/colors'; -import { performance } from 'perf_hooks'; -import { Writable } from 'stream'; -import stringWidth from 'string-width'; -import * as readline from 'readline'; -import debugPackage from 'debug'; -import { format as utilFormat } from 'util'; -import { isBuildingToSSR } from './util.js'; - -type ConsoleStream = Writable & { - fd: 1 | 2; -}; - -function getLoggerLocale(): string { - const defaultLocale = 'en-US'; - if (process.env.LANG) { - const extractedLocale = process.env.LANG.split('.')[0].replace(/_/g, '-'); - // Check if language code is atleast two characters long (ie. en, es). - // NOTE: if "c" locale is encountered, the default locale will be returned. - if (extractedLocale.length < 2) return defaultLocale; - else return extractedLocale.substring(0, 5); - } else return defaultLocale; -} - -const dt = new Intl.DateTimeFormat(getLoggerLocale(), { - hour: '2-digit', - minute: '2-digit', - second: '2-digit', -}); - -let lastMessage: string; -let lastMessageCount = 1; -export const defaultLogDestination = new Writable({ - objectMode: true, - write(event: LogMessage, _, callback) { - let dest: ConsoleStream = process.stderr; - if (levels[event.level] < levels['error']) { - dest = process.stdout; - } - - function getPrefix() { - let prefix = ''; - let type = event.type; - if (type) { - // hide timestamp when type is undefined - prefix += dim(dt.format(new Date()) + ' '); - if (event.level === 'info') { - type = bold(cyan(`[${type}]`)); - } else if (event.level === 'warn') { - type = bold(yellow(`[${type}]`)); - } else if (event.level === 'error') { - type = bold(red(`[${type}]`)); - } - - prefix += `${type} `; - } - return reset(prefix); - } - - let message = utilFormat(...event.args); - // For repeat messages, only update the message counter - if (message === lastMessage) { - lastMessageCount++; - if (levels[event.level] < levels['error']) { - let lines = 1; - let len = stringWidth(`${getPrefix()}${message}`); - let cols = (dest as typeof process.stdout).columns; - if (len > cols) { - lines = Math.ceil(len / cols); - } - for (let i = 0; i < lines; i++) { - readline.clearLine(dest, 0); - readline.cursorTo(dest, 0); - readline.moveCursor(dest, 0, -1); - } - } - message = `${message} ${yellow(`(x${lastMessageCount})`)}`; - } else { - lastMessage = message; - lastMessageCount = 1; - } - dest.write(getPrefix()); - dest.write(message); - dest.write('\n'); - - callback(); - }, -}); - -interface LogWritable extends Writable { - write: (chunk: T) => boolean; -} - -export type LoggerLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent'; // same as Pino -export type LoggerEvent = 'info' | 'warn' | 'error'; - -export interface LogOptions { - dest?: LogWritable; - level?: LoggerLevel; -} - -export const defaultLogOptions: Required = { - dest: defaultLogDestination, - level: 'info', -}; - -export interface LogMessage { - type: string | null; - level: LoggerLevel; - message: string; - args: Array; -} - -export const levels: Record = { - debug: 20, - info: 30, - warn: 40, - error: 50, - silent: 90, -}; - -export function enableVerboseLogging() { - debugPackage.enable('*,-babel'); - debug('cli', '--verbose flag enabled! Enabling: DEBUG="*,-babel"'); - debug('cli', 'Tip: Set the DEBUG env variable directly for more control. Example: "DEBUG=astro:*,vite:* astro build".'); -} - -/** Full logging API */ -export function log(opts: LogOptions = {}, level: LoggerLevel, type: string | null, ...args: Array) { - const logLevel = opts.level ?? defaultLogOptions.level; - const dest = opts.dest ?? defaultLogOptions.dest; - const event: LogMessage = { - type, - level, - args, - message: '', - }; - - // test if this level is enabled or not - if (levels[logLevel] > levels[level]) { - return; // do nothing - } - - dest.write(event); -} - -const debuggers: Record = {}; -/** - * Emit a message only shown in debug mode. - * Astro (along with many of its dependencies) uses the `debug` package for debug logging. - * You can enable these logs with the `DEBUG=astro:*` environment variable. - * More info https://github.com/debug-js/debug#environment-variables - */ -export function debug(type: string, ...messages: Array) { - const namespace = `astro:${type}`; - debuggers[namespace] = debuggers[namespace] || debugPackage(namespace); - return debuggers[namespace](...messages); -} - -/** Emit a user-facing message. Useful for UI and other console messages. */ -export function info(opts: LogOptions, type: string | null, ...messages: Array) { - return log(opts, 'info', type, ...messages); -} - -/** Emit a warning message. Useful for high-priority messages that aren't necessarily errors. */ -export function warn(opts: LogOptions, type: string | null, ...messages: Array) { - return log(opts, 'warn', type, ...messages); -} - -/** Emit a error message, Useful when Astro can't recover from some error. */ -export function error(opts: LogOptions, type: string | null, ...messages: Array) { - return log(opts, 'error', type, ...messages); -} - -type LogFn = typeof info | typeof warn | typeof error; - -export function table(opts: LogOptions, columns: number[]) { - return function logTable(logFn: LogFn, ...input: Array) { - const messages = columns.map((len, i) => padStr(input[i].toString(), len)); - logFn(opts, null, ...messages); - }; -} - -// A default logger for when too lazy to pass LogOptions around. -export const logger = { - info: info.bind(null, defaultLogOptions), - warn: warn.bind(null, defaultLogOptions), - error: error.bind(null, defaultLogOptions), -}; - -function padStr(str: string, len: number) { - const strLen = stringWidth(str); - if (strLen > len) { - return str.substring(0, len - 3) + '...'; - } - const spaces = Array.from({ length: len - strLen }, () => ' ').join(''); - return str + spaces; -} - -export let defaultLogLevel: LoggerLevel; -if (process.argv.includes('--verbose')) { - defaultLogLevel = 'debug'; -} else if (process.argv.includes('--silent')) { - defaultLogLevel = 'silent'; -} else { - defaultLogLevel = 'info'; -} - -/** Print out a timer message for debug() */ -export function timerMessage(message: string, startTime: number = performance.now()) { - let timeDiff = performance.now() - startTime; - let timeDisplay = timeDiff < 750 ? `${Math.round(timeDiff)}ms` : `${(timeDiff / 1000).toFixed(1)}s`; - return `${message} ${dim(timeDisplay)}`; -} - -/** - * A warning that SSR is experimental. Remove when we can. - */ -export function warnIfUsingExperimentalSSR(opts: LogOptions, config: AstroConfig) { - if (isBuildingToSSR(config)) { - warn( - opts, - 'warning', - bold(`Warning:`), - ` SSR support is still experimental and subject to API changes. If using in production pin your dependencies to prevent accidental breakage.` - ); - } -} diff --git a/packages/astro/src/core/logger/console.ts b/packages/astro/src/core/logger/console.ts new file mode 100644 index 000000000..eb62cf9a4 --- /dev/null +++ b/packages/astro/src/core/logger/console.ts @@ -0,0 +1,51 @@ +import type { AstroConfig } from '../../@types/astro'; +import type { LogMessage } from './core.js'; +import { bold, cyan, dim, red, yellow, reset } from 'kleur/colors'; +import stringWidth from 'string-width'; +import { format as utilFormat } from 'util'; +import { levels, dateTimeFormat } from './core.js'; + +let lastMessage: string; +let lastMessageCount = 1; +export const consoleLogDestination = { + write(event: LogMessage) { + // eslint-disable-next-line no-console + let dest = console.error; + if (levels[event.level] < levels['error']) { + // eslint-disable-next-line no-console + dest = console.log; + } + + function getPrefix() { + let prefix = ''; + let type = event.type; + if (type) { + // hide timestamp when type is undefined + prefix += dim(dateTimeFormat.format(new Date()) + ' '); + if (event.level === 'info') { + type = bold(cyan(`[${type}]`)); + } else if (event.level === 'warn') { + type = bold(yellow(`[${type}]`)); + } else if (event.level === 'error') { + type = bold(red(`[${type}]`)); + } + + prefix += `${type} `; + } + return reset(prefix); + } + + let message = utilFormat(...event.args); + // For repeat messages, only update the message counter + if (message === lastMessage) { + lastMessageCount++; + message = `${message} ${yellow(`(x${lastMessageCount})`)}`; + } else { + lastMessage = message; + lastMessageCount = 1; + } + const outMessage = getPrefix() + message; + dest(outMessage); + return true; + }, +}; diff --git a/packages/astro/src/core/logger/core.ts b/packages/astro/src/core/logger/core.ts new file mode 100644 index 000000000..0ac93e59b --- /dev/null +++ b/packages/astro/src/core/logger/core.ts @@ -0,0 +1,139 @@ +import type { AstroConfig } from '../../@types/astro'; +import { bold, dim } from 'kleur/colors'; +import stringWidth from 'string-width'; + +interface LogWritable { + write: (chunk: T) => boolean; +} + +export type LoggerLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent'; // same as Pino +export type LoggerEvent = 'info' | 'warn' | 'error'; + +export interface LogOptions { + dest: LogWritable; + level: LoggerLevel; +} + +function getLoggerLocale(): string { + const defaultLocale = 'en-US'; + if (process.env.LANG) { + const extractedLocale = process.env.LANG.split('.')[0].replace(/_/g, '-'); + // Check if language code is atleast two characters long (ie. en, es). + // NOTE: if "c" locale is encountered, the default locale will be returned. + if (extractedLocale.length < 2) return defaultLocale; + else return extractedLocale.substring(0, 5); + } else return defaultLocale; +} + +export const dateTimeFormat = new Intl.DateTimeFormat(getLoggerLocale(), { + hour: '2-digit', + minute: '2-digit', + second: '2-digit', +}); + +export interface LogMessage { + type: string | null; + level: LoggerLevel; + message: string; + args: Array; +} + +export const levels: Record = { + debug: 20, + info: 30, + warn: 40, + error: 50, + silent: 90, +}; + +/** Full logging API */ +export function log(opts: LogOptions, level: LoggerLevel, type: string | null, ...args: Array) { + const logLevel = opts.level; + const dest = opts.dest; + const event: LogMessage = { + type, + level, + args, + message: '', + }; + + // test if this level is enabled or not + if (levels[logLevel] > levels[level]) { + return; // do nothing + } + + dest.write(event); +} + +/** Emit a user-facing message. Useful for UI and other console messages. */ +export function info(opts: LogOptions, type: string | null, ...messages: Array) { + return log(opts, 'info', type, ...messages); +} + +/** Emit a warning message. Useful for high-priority messages that aren't necessarily errors. */ +export function warn(opts: LogOptions, type: string | null, ...messages: Array) { + return log(opts, 'warn', type, ...messages); +} + +/** Emit a error message, Useful when Astro can't recover from some error. */ +export function error(opts: LogOptions, type: string | null, ...messages: Array) { + return log(opts, 'error', type, ...messages); +} + +type LogFn = typeof info | typeof warn | typeof error; + +export function table(opts: LogOptions, columns: number[]) { + return function logTable(logFn: LogFn, ...input: Array) { + const messages = columns.map((len, i) => padStr(input[i].toString(), len)); + logFn(opts, null, ...messages); + }; +} + +export function debug(...args: any[]) { + if ('_astroGlobalDebug' in globalThis) { + (globalThis as any)._astroGlobalDebug(...args); + } +} + +function padStr(str: string, len: number) { + const strLen = stringWidth(str); + if (strLen > len) { + return str.substring(0, len - 3) + '...'; + } + const spaces = Array.from({ length: len - strLen }, () => ' ').join(''); + return str + spaces; +} + +export let defaultLogLevel: LoggerLevel; +if (typeof process !== 'undefined') { + if (process.argv.includes('--verbose')) { + defaultLogLevel = 'debug'; + } else if (process.argv.includes('--silent')) { + defaultLogLevel = 'silent'; + } else { + defaultLogLevel = 'info'; + } +} else { + defaultLogLevel = 'info'; +} + +/** Print out a timer message for debug() */ +export function timerMessage(message: string, startTime: number = Date.now()) { + let timeDiff = Date.now() - startTime; + let timeDisplay = timeDiff < 750 ? `${Math.round(timeDiff)}ms` : `${(timeDiff / 1000).toFixed(1)}s`; + return `${message} ${dim(timeDisplay)}`; +} + +/** + * A warning that SSR is experimental. Remove when we can. + */ +export function warnIfUsingExperimentalSSR(opts: LogOptions, config: AstroConfig) { + if (config._ctx.adapter?.serverEntrypoint) { + warn( + opts, + 'warning', + bold(`Warning:`), + ` SSR support is still experimental and subject to API changes. If using in production pin your dependencies to prevent accidental breakage.` + ); + } +} diff --git a/packages/astro/src/core/logger/node.ts b/packages/astro/src/core/logger/node.ts new file mode 100644 index 000000000..f83483859 --- /dev/null +++ b/packages/astro/src/core/logger/node.ts @@ -0,0 +1,131 @@ +import { bold, cyan, dim, red, yellow, reset } from 'kleur/colors'; +import stringWidth from 'string-width'; +import debugPackage from 'debug'; +import { format as utilFormat } from 'util'; +import * as readline from 'readline'; +import { Writable } from 'stream'; +import { info, warn, error, dateTimeFormat } from './core.js'; + +type ConsoleStream = Writable & { + fd: 1 | 2; +}; + +let lastMessage: string; +let lastMessageCount = 1; +export const nodeLogDestination = new Writable({ + objectMode: true, + write(event: LogMessage, _, callback) { + let dest: ConsoleStream = process.stderr; + if (levels[event.level] < levels['error']) { + dest = process.stdout; + } + + function getPrefix() { + let prefix = ''; + let type = event.type; + if (type) { + // hide timestamp when type is undefined + prefix += dim(dateTimeFormat.format(new Date()) + ' '); + if (event.level === 'info') { + type = bold(cyan(`[${type}]`)); + } else if (event.level === 'warn') { + type = bold(yellow(`[${type}]`)); + } else if (event.level === 'error') { + type = bold(red(`[${type}]`)); + } + + prefix += `${type} `; + } + return reset(prefix); + } + + let message = utilFormat(...event.args); + // For repeat messages, only update the message counter + if (message === lastMessage) { + lastMessageCount++; + if (levels[event.level] < levels['error']) { + let lines = 1; + let len = stringWidth(`${getPrefix()}${message}`); + let cols = (dest as unknown as typeof process.stdout).columns; + if (len > cols) { + lines = Math.ceil(len / cols); + } + for (let i = 0; i < lines; i++) { + readline.clearLine(dest, 0); + readline.cursorTo(dest, 0); + readline.moveCursor(dest, 0, -1); + } + } + message = `${message} ${yellow(`(x${lastMessageCount})`)}`; + } else { + lastMessage = message; + lastMessageCount = 1; + } + + dest.write(getPrefix()); + dest.write(message); + dest.write('\n'); + callback(); + }, +}); + +interface LogWritable { + write: (chunk: T) => boolean; +} + +export type LoggerLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent'; // same as Pino +export type LoggerEvent = 'info' | 'warn' | 'error'; + +export interface LogOptions { + dest?: LogWritable; + level?: LoggerLevel; +} + +export const nodeLogOptions: Required = { + dest: nodeLogDestination, + level: 'info', +}; + +export interface LogMessage { + type: string | null; + level: LoggerLevel; + message: string; + args: Array; +} + +export const levels: Record = { + debug: 20, + info: 30, + warn: 40, + error: 50, + silent: 90, +}; + +const debuggers: Record = {}; +/** + * Emit a message only shown in debug mode. + * Astro (along with many of its dependencies) uses the `debug` package for debug logging. + * You can enable these logs with the `DEBUG=astro:*` environment variable. + * More info https://github.com/debug-js/debug#environment-variables + */ +export function debug(type: string, ...messages: Array) { + const namespace = `astro:${type}`; + debuggers[namespace] = debuggers[namespace] || debugPackage(namespace); + return debuggers[namespace](...messages); +} + +// This is gross, but necessary since we are depending on globals. +(globalThis as any)._astroGlobalDebug = debug; + +// A default logger for when too lazy to pass LogOptions around. +export const logger = { + info: info.bind(null, nodeLogOptions), + warn: warn.bind(null, nodeLogOptions), + error: error.bind(null, nodeLogOptions), +}; + +export function enableVerboseLogging() { + //debugPackage.enable('*,-babel'); + debug('cli', '--verbose flag enabled! Enabling: DEBUG="*,-babel"'); + debug('cli', 'Tip: Set the DEBUG env variable directly for more control. Example: "DEBUG=astro:*,vite:* astro build".'); +} diff --git a/packages/astro/src/core/preview/index.ts b/packages/astro/src/core/preview/index.ts index 71bdba82e..48d077800 100644 --- a/packages/astro/src/core/preview/index.ts +++ b/packages/astro/src/core/preview/index.ts @@ -1,12 +1,12 @@ import type { AstroConfig } from '../../@types/astro'; -import type { LogOptions } from '../logger'; +import type { LogOptions } from '../logger/core'; import type { AddressInfo } from 'net'; import http from 'http'; import sirv from 'sirv'; import { performance } from 'perf_hooks'; import { fileURLToPath } from 'url'; import * as msg from '../messages.js'; -import { error, info } from '../logger.js'; +import { error, info } from '../logger/core.js'; import { subpathNotUsedTemplate, notFoundTemplate } from '../../template/4xx.js'; import { getResolvedHostForHttpServer } from './util.js'; diff --git a/packages/astro/src/core/render/core.ts b/packages/astro/src/core/render/core.ts index 3cb109b76..383128ad0 100644 --- a/packages/astro/src/core/render/core.ts +++ b/packages/astro/src/core/render/core.ts @@ -1,8 +1,8 @@ import type { ComponentInstance, EndpointHandler, MarkdownRenderOptions, Params, Props, SSRLoadedRenderer, RouteData, SSRElement } from '../../@types/astro'; -import type { LogOptions } from '../logger.js'; +import type { LogOptions } from '../logger/core.js'; import { renderHead, renderPage } from '../../runtime/server/index.js'; -import { getParams } from '../routing/index.js'; +import { getParams } from '../routing/params.js'; import { createResult } from './result.js'; import { findPathItemByKey, RouteCache, callGetStaticPaths } from './route-cache.js'; diff --git a/packages/astro/src/core/render/dev/css.ts b/packages/astro/src/core/render/dev/css.ts index baded71a9..ed3f01bab 100644 --- a/packages/astro/src/core/render/dev/css.ts +++ b/packages/astro/src/core/render/dev/css.ts @@ -2,16 +2,7 @@ import type * as vite from 'vite'; import path from 'path'; import { unwrapId, viteID } from '../../util.js'; - -// https://vitejs.dev/guide/features.html#css-pre-processors -export const STYLE_EXTENSIONS = new Set(['.css', '.pcss', '.postcss', '.scss', '.sass', '.styl', '.stylus', '.less']); - -const cssRe = new RegExp( - `\\.(${Array.from(STYLE_EXTENSIONS) - .map((s) => s.slice(1)) - .join('|')})($|\\?)` -); -export const isCSSRequest = (request: string): boolean => cssRe.test(request); +import { STYLE_EXTENSIONS } from '../util.js'; /** Given a filePath URL, crawl Vite’s module graph to find all style imports. */ export function getStylesForURL(filePath: URL, viteServer: vite.ViteDevServer): Set { diff --git a/packages/astro/src/core/render/dev/index.ts b/packages/astro/src/core/render/dev/index.ts index b96a431ae..1ced45a40 100644 --- a/packages/astro/src/core/render/dev/index.ts +++ b/packages/astro/src/core/render/dev/index.ts @@ -1,7 +1,7 @@ import { fileURLToPath } from 'url'; import type * as vite from 'vite'; import type { AstroConfig, AstroRenderer, ComponentInstance, RouteData, RuntimeMode, SSRElement, SSRLoadedRenderer } from '../../../@types/astro'; -import { LogOptions } from '../../logger.js'; +import { LogOptions } from '../../logger/core.js'; import { render as coreRender } from '../core.js'; import { prependForwardSlash } from '../../../core/path.js'; import { RouteCache } from '../route-cache.js'; diff --git a/packages/astro/src/core/render/pretty-feed.ts b/packages/astro/src/core/render/pretty-feed.ts new file mode 100644 index 000000000..ed3168fec --- /dev/null +++ b/packages/astro/src/core/render/pretty-feed.ts @@ -0,0 +1,102 @@ +// Vendored from https://github.com/genmon/aboutfeeds/blob/main/tools/pretty-feed-v3.xsl +/** Basic stylesheet for RSS feeds */ +export const PRETTY_FEED_V3 = ` + + + + + + + <xsl:value-of select="/rss/channel/title"/> Web Feed + + + + + + +
+
+

+ + + + + + + + + + + + + + + + + + + Web Feed Preview +

+

+

+ + + + + Visit Website → + +
+

Recent Items

+ +
+

+ + + + + + +

+ + Published: + +
+
+
+ + +
+
`; diff --git a/packages/astro/src/core/render/result.ts b/packages/astro/src/core/render/result.ts index 6f03d4806..c3552be3d 100644 --- a/packages/astro/src/core/render/result.ts +++ b/packages/astro/src/core/render/result.ts @@ -1,9 +1,8 @@ import { bold } from 'kleur/colors'; import type { AstroGlobal, AstroGlobalPartial, MarkdownParser, MarkdownRenderOptions, Params, SSRElement, SSRLoadedRenderer, SSRResult } from '../../@types/astro'; import { renderSlot } from '../../runtime/server/index.js'; -import { LogOptions, warn } from '../logger.js'; -import { isCSSRequest } from './dev/css.js'; -import { canonicalURL as utilCanonicalURL } from '../util.js'; +import { LogOptions, warn } from '../logger/core.js'; +import { createCanonicalURL, isCSSRequest } from './util.js'; import { isScriptRequest } from './script.js'; function onlyAvailableInSSR(name: string) { @@ -71,10 +70,10 @@ class Slots { } export function createResult(args: CreateResultArgs): SSRResult { - const { legacyBuild, markdownRender, origin, params, pathname, renderers, request, resolve, site } = args; + const { legacyBuild, markdownRender, params, pathname, renderers, request, resolve, site } = args; const url = new URL(request.url); - const canonicalURL = utilCanonicalURL('.' + pathname, site ?? url.origin); + const canonicalURL = createCanonicalURL('.' + pathname, site ?? url.origin); // Create the result object that will be passed into the render function. // This object starts here as an empty shell (not yet the result) but then diff --git a/packages/astro/src/core/render/route-cache.ts b/packages/astro/src/core/render/route-cache.ts index d181d1b1c..1b60bd0db 100644 --- a/packages/astro/src/core/render/route-cache.ts +++ b/packages/astro/src/core/render/route-cache.ts @@ -1,8 +1,8 @@ import type { ComponentInstance, GetStaticPathsItem, GetStaticPathsResult, GetStaticPathsResultKeyed, Params, RouteData, RSS } from '../../@types/astro'; -import { LogOptions, warn, debug } from '../logger.js'; +import { LogOptions, warn, debug } from '../logger/core.js'; import { generatePaginateFunction } from './paginate.js'; -import { validateGetStaticPathsModule, validateGetStaticPathsResult } from '../routing/index.js'; +import { validateGetStaticPathsModule, validateGetStaticPathsResult } from '../routing/validation.js'; type RSSFn = (...args: any[]) => any; diff --git a/packages/astro/src/core/render/rss.ts b/packages/astro/src/core/render/rss.ts index 1e77dff35..e02a6155e 100644 --- a/packages/astro/src/core/render/rss.ts +++ b/packages/astro/src/core/render/rss.ts @@ -1,7 +1,8 @@ import type { RSSFunction, RSS, RSSResult, RouteData } from '../../@types/astro'; import { XMLValidator } from 'fast-xml-parser'; -import { canonicalURL, isValidURL, PRETTY_FEED_V3 } from '../util.js'; +import { PRETTY_FEED_V3 } from './pretty-feed.js'; +import { createCanonicalURL, isValidURL } from './util.js'; /** Validates getStaticPaths.rss */ export function validateRSS(args: GenerateRSSArgs): void { @@ -38,7 +39,7 @@ export function generateRSS(args: GenerateRSSArgs): string { // title, description, customData xml += `<![CDATA[${rssData.title}]]>`; xml += ``; - xml += `${canonicalURL(site).href}`; + xml += `${createCanonicalURL(site).href}`; if (typeof rssData.customData === 'string') xml += rssData.customData; // items for (const result of rssData.items) { @@ -49,7 +50,7 @@ export function generateRSS(args: GenerateRSSArgs): string { if (!result.link) throw new Error(`[${srcFile}] rss.items required "link" property is missing. got: "${JSON.stringify(result)}"`); xml += `<![CDATA[${result.title}]]>`; // If the item's link is already a valid URL, don't mess with it. - const itemLink = isValidURL(result.link) ? result.link : canonicalURL(result.link, site).href; + const itemLink = isValidURL(result.link) ? result.link : createCanonicalURL(result.link, site).href; xml += `${itemLink}`; xml += `${itemLink}`; if (result.description) xml += ``; diff --git a/packages/astro/src/core/render/ssr-element.ts b/packages/astro/src/core/render/ssr-element.ts index 0e834398a..88c387db1 100644 --- a/packages/astro/src/core/render/ssr-element.ts +++ b/packages/astro/src/core/render/ssr-element.ts @@ -1,6 +1,6 @@ import type { SSRElement } from '../../@types/astro'; -import npath from 'path'; +import npath from 'path-browserify'; import { appendForwardSlash } from '../../core/path.js'; function getRootPath(site?: string): string { diff --git a/packages/astro/src/core/render/util.ts b/packages/astro/src/core/render/util.ts new file mode 100644 index 000000000..9acf2d9d3 --- /dev/null +++ b/packages/astro/src/core/render/util.ts @@ -0,0 +1,29 @@ +import npath from 'path-browserify'; + +/** Normalize URL to its canonical form */ +export function createCanonicalURL(url: string, base?: string): URL { + let pathname = url.replace(/\/index.html$/, ''); // index.html is not canonical + pathname = pathname.replace(/\/1\/?$/, ''); // neither is a trailing /1/ (impl. detail of collections) + if (!npath.extname(pathname)) pathname = pathname.replace(/(\/+)?$/, '/'); // add trailing slash if there’s no extension + pathname = pathname.replace(/\/+/g, '/'); // remove duplicate slashes (URL() won’t) + return new URL(pathname, base); +} + +/** Check if a URL is already valid */ +export function isValidURL(url: string): boolean { + try { + new URL(url); + return true; + } catch (e) {} + return false; +} + +// https://vitejs.dev/guide/features.html#css-pre-processors +export const STYLE_EXTENSIONS = new Set(['.css', '.pcss', '.postcss', '.scss', '.sass', '.styl', '.stylus', '.less']); + +const cssRe = new RegExp( + `\\.(${Array.from(STYLE_EXTENSIONS) + .map((s) => s.slice(1)) + .join('|')})($|\\?)` +); +export const isCSSRequest = (request: string): boolean => cssRe.test(request); diff --git a/packages/astro/src/core/request.ts b/packages/astro/src/core/request.ts index e94f1372b..8b5c40748 100644 --- a/packages/astro/src/core/request.ts +++ b/packages/astro/src/core/request.ts @@ -1,6 +1,6 @@ import type { IncomingHttpHeaders } from 'http'; -import type { LogOptions } from './logger'; -import { warn } from './logger.js'; +import type { LogOptions } from './logger/core'; +import { warn } from './logger/core.js'; type HeaderType = Headers | Record | IncomingHttpHeaders; type RequestBody = ArrayBuffer | Blob | ReadableStream | URLSearchParams | FormData; diff --git a/packages/astro/src/core/routing/manifest/create.ts b/packages/astro/src/core/routing/manifest/create.ts index 1e0a6f3bc..6c7cb4829 100644 --- a/packages/astro/src/core/routing/manifest/create.ts +++ b/packages/astro/src/core/routing/manifest/create.ts @@ -1,12 +1,12 @@ import type { AstroConfig, ManifestData, RouteData } from '../../../@types/astro'; -import type { LogOptions } from '../../logger'; +import type { LogOptions } from '../../logger/core'; import fs from 'fs'; import path from 'path'; import { compile } from 'path-to-regexp'; import slash from 'slash'; import { fileURLToPath } from 'url'; -import { warn } from '../../logger.js'; +import { warn } from '../../logger/core.js'; interface Part { content: string; diff --git a/packages/astro/src/core/routing/validation.ts b/packages/astro/src/core/routing/validation.ts index d8fda32c4..80f50e19e 100644 --- a/packages/astro/src/core/routing/validation.ts +++ b/packages/astro/src/core/routing/validation.ts @@ -1,6 +1,6 @@ import type { ComponentInstance, GetStaticPathsResult } from '../../@types/astro'; -import type { LogOptions } from '../logger'; -import { warn } from '../logger.js'; +import type { LogOptions } from '../logger/core'; +import { warn } from '../logger/core.js'; interface ValidationOptions { ssr: boolean; diff --git a/packages/astro/src/core/util.ts b/packages/astro/src/core/util.ts index ca98419da..45885e9e8 100644 --- a/packages/astro/src/core/util.ts +++ b/packages/astro/src/core/util.ts @@ -8,24 +8,6 @@ import type { ErrorPayload } from 'vite'; import type { AstroConfig } from '../@types/astro'; import { removeEndingForwardSlash } from './path.js'; -/** Normalize URL to its canonical form */ -export function canonicalURL(url: string, base?: string): URL { - let pathname = url.replace(/\/index.html$/, ''); // index.html is not canonical - pathname = pathname.replace(/\/1\/?$/, ''); // neither is a trailing /1/ (impl. detail of collections) - if (!path.extname(pathname)) pathname = pathname.replace(/(\/+)?$/, '/'); // add trailing slash if there’s no extension - pathname = pathname.replace(/\/+/g, '/'); // remove duplicate slashes (URL() won’t) - return new URL(pathname, base); -} - -/** Check if a URL is already valid */ -export function isValidURL(url: string): boolean { - try { - new URL(url); - return true; - } catch (e) {} - return false; -} - /** Returns true if argument is an object of any prototype/class (but not null). */ export function isObject(value: unknown): value is Record { return typeof value === 'object' && value != null; @@ -181,106 +163,3 @@ export function getLocalAddress(serverAddress: string, config: AstroConfig): str return serverAddress; } } - -// Vendored from https://github.com/genmon/aboutfeeds/blob/main/tools/pretty-feed-v3.xsl -/** Basic stylesheet for RSS feeds */ -export const PRETTY_FEED_V3 = ` - - - - - - - <xsl:value-of select="/rss/channel/title"/> Web Feed - - - - - - -
-
-

- - - - - - - - - - - - - - - - - - - Web Feed Preview -

-

-

- - - - - Visit Website → - -
-

Recent Items

- -
-

- - - - - - -

- - Published: - -
-
-
- - -
-
`; diff --git a/packages/astro/src/integrations/index.ts b/packages/astro/src/integrations/index.ts index e1dc53a36..0746b05ca 100644 --- a/packages/astro/src/integrations/index.ts +++ b/packages/astro/src/integrations/index.ts @@ -3,6 +3,7 @@ import type { ViteDevServer } from 'vite'; import { AstroConfig, AstroRenderer, BuildConfig, RouteData } from '../@types/astro.js'; import { mergeConfig } from '../core/config.js'; import ssgAdapter from '../adapter-ssg/index.js'; +import type { ViteConfigWithSSR } from '../core/create-vite.js'; export async function runHookConfigSetup({ config: _config, command }: { config: AstroConfig; command: 'dev' | 'build' }): Promise { if (_config.adapter) { @@ -91,6 +92,14 @@ export async function runHookBuildStart({ config, buildConfig }: { config: Astro } } +export async function runHookBuildSetup({ config, vite, target }: { config: AstroConfig; vite: ViteConfigWithSSR; target: 'server' | 'client' }) { + for (const integration of config.integrations) { + if (integration.hooks['astro:build:setup']) { + await integration.hooks['astro:build:setup']({ vite, target }); + } + } +} + export async function runHookBuildDone({ config, pages, routes }: { config: AstroConfig; pages: string[]; routes: RouteData[] }) { for (const integration of config.integrations) { if (integration.hooks['astro:build:done']) { diff --git a/packages/astro/src/runtime/server/index.ts b/packages/astro/src/runtime/server/index.ts index 7f314f721..7cf56c35f 100644 --- a/packages/astro/src/runtime/server/index.ts +++ b/packages/astro/src/runtime/server/index.ts @@ -412,7 +412,8 @@ async function replaceHeadInjection(result: SSRResult, html: string): Promise { const Component = await componentFactory(result, props, children); if (!isAstroComponent(Component)) { - throw new Error('Cannot return a Response from a nested component.'); + const response: Response = Component; + throw response; } let template = await renderAstroComponent(Component); @@ -425,20 +426,31 @@ export async function renderPage( props: any, children: any ): Promise<{ type: 'html'; html: string } | { type: 'response'; response: Response }> { - const response = await componentFactory(result, props, children); + try { + const response = await componentFactory(result, props, children); - if (isAstroComponent(response)) { - let template = await renderAstroComponent(response); - const html = await replaceHeadInjection(result, template); - return { - type: 'html', - html, - }; - } else { - return { - type: 'response', - response, - }; + if (isAstroComponent(response)) { + let template = await renderAstroComponent(response); + const html = await replaceHeadInjection(result, template); + return { + type: 'html', + html, + }; + } else { + return { + type: 'response', + response, + }; + } + } catch (err) { + if (err instanceof Response) { + return { + type: 'response', + response: err, + }; + } else { + throw err; + } } } diff --git a/packages/astro/src/vite-plugin-astro-server/index.ts b/packages/astro/src/vite-plugin-astro-server/index.ts index 4a5b1a5fc..67df56aeb 100644 --- a/packages/astro/src/vite-plugin-astro-server/index.ts +++ b/packages/astro/src/vite-plugin-astro-server/index.ts @@ -2,7 +2,7 @@ import type * as vite from 'vite'; import type http from 'http'; import type { AstroConfig, ManifestData } from '../@types/astro'; import type { RenderResponse, SSROptions } from '../core/render/dev/index'; -import { debug, info, warn, error, LogOptions } from '../core/logger.js'; +import { debug, info, warn, error, LogOptions } from '../core/logger/core.js'; import { getParamsAndProps, GetParamsAndPropsError } from '../core/render/core.js'; import { createRouteManifest, matchRoute } from '../core/routing/index.js'; import stripAnsi from 'strip-ansi'; @@ -15,6 +15,7 @@ import serverErrorTemplate from '../template/5xx.js'; import { RouteCache } from '../core/render/route-cache.js'; import { fixViteErrorMessage } from '../core/errors.js'; import { createRequest } from '../core/request.js'; +import { Readable } from 'stream'; interface AstroPluginOptions { config: AstroConfig; @@ -44,12 +45,17 @@ async function writeWebResponse(res: http.ServerResponse, webResponse: Response) const { status, headers, body } = webResponse; res.writeHead(status, Object.fromEntries(headers.entries())); if (body) { - const reader = body.getReader(); - while (true) { - const { done, value } = await reader.read(); - if (done) break; - if (value) { - res.write(value); + if (body instanceof Readable) { + body.pipe(res); + return; + } else { + const reader = body.getReader(); + while (true) { + const { done, value } = await reader.read(); + if (done) break; + if (value) { + res.write(value); + } } } } @@ -134,7 +140,7 @@ async function handleRequest( await new Promise((resolve) => { req.setEncoding('utf-8'); req.on('data', (bts) => bytes.push(bts)); - req.on('close', resolve); + req.on('end', resolve); }); body = new TextEncoder().encode(bytes.join('')).buffer; } diff --git a/packages/astro/src/vite-plugin-astro/hmr.ts b/packages/astro/src/vite-plugin-astro/hmr.ts index 7afd956b0..fbc52262a 100644 --- a/packages/astro/src/vite-plugin-astro/hmr.ts +++ b/packages/astro/src/vite-plugin-astro/hmr.ts @@ -1,9 +1,9 @@ import type { AstroConfig } from '../@types/astro'; -import type { LogOptions } from '../core/logger.js'; +import type { LogOptions } from '../core/logger/core.js'; import type { ViteDevServer, ModuleNode, HmrContext } from 'vite'; import type { PluginContext as RollupPluginContext, ResolvedId } from 'rollup'; import { invalidateCompilation, isCached } from './compile.js'; -import { info } from '../core/logger.js'; +import { info } from '../core/logger/core.js'; import * as msg from '../core/messages.js'; interface TrackCSSDependenciesOptions { diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts index 8fcf7aafb..aded8f425 100644 --- a/packages/astro/src/vite-plugin-astro/index.ts +++ b/packages/astro/src/vite-plugin-astro/index.ts @@ -1,7 +1,7 @@ import type * as vite from 'vite'; import type { PluginContext } from 'rollup'; import type { AstroConfig } from '../@types/astro'; -import type { LogOptions } from '../core/logger.js'; +import type { LogOptions } from '../core/logger/core.js'; import esbuild from 'esbuild'; import { fileURLToPath } from 'url'; diff --git a/packages/astro/src/vite-plugin-astro/styles.ts b/packages/astro/src/vite-plugin-astro/styles.ts index d344298ea..85467506e 100644 --- a/packages/astro/src/vite-plugin-astro/styles.ts +++ b/packages/astro/src/vite-plugin-astro/styles.ts @@ -1,6 +1,6 @@ import type * as vite from 'vite'; -import { STYLE_EXTENSIONS } from '../core/render/dev/css.js'; +import { STYLE_EXTENSIONS } from '../core/render/util.js'; export type TransformHook = (code: string, id: string, ssr?: boolean) => Promise; diff --git a/packages/astro/src/vite-plugin-build-css/index.ts b/packages/astro/src/vite-plugin-build-css/index.ts index 24101516e..11eb6b096 100644 --- a/packages/astro/src/vite-plugin-build-css/index.ts +++ b/packages/astro/src/vite-plugin-build-css/index.ts @@ -4,9 +4,8 @@ import type { ModuleInfo, PluginContext } from 'rollup'; import * as path from 'path'; import esbuild from 'esbuild'; import { Plugin as VitePlugin } from 'vite'; -import { isCSSRequest } from '../core/render/dev/css.js'; +import { isCSSRequest } from '../core/render/util.js'; import { getPageDatasByChunk, getPageDataByViteID, hasPageDataByViteID } from '../core/build/internal.js'; -import { resolvedVirtualModuleId as virtualPagesModuleId } from '../core/build/vite-plugin-pages.js'; const PLUGIN_NAME = '@astrojs/rollup-plugin-build-css'; diff --git a/packages/astro/src/vite-plugin-build-html/index.ts b/packages/astro/src/vite-plugin-build-html/index.ts index ad5b8ba14..a3af1f97e 100644 --- a/packages/astro/src/vite-plugin-build-html/index.ts +++ b/packages/astro/src/vite-plugin-build-html/index.ts @@ -8,7 +8,7 @@ import type { Plugin as VitePlugin, ViteDevServer } from 'vite'; import type { AstroConfig } from '../@types/astro'; import type { BuildInternals } from '../core/build/internal'; import type { AllPagesData } from '../core/build/types'; -import type { LogOptions } from '../core/logger.js'; +import type { LogOptions } from '../core/logger/core.js'; import { prependDotSlash } from '../core/path.js'; import { render as ssrRender } from '../core/render/dev/index.js'; import { RouteCache } from '../core/render/route-cache.js'; diff --git a/packages/astro/src/vite-plugin-jsx/index.ts b/packages/astro/src/vite-plugin-jsx/index.ts index 65418815d..b04152a9a 100644 --- a/packages/astro/src/vite-plugin-jsx/index.ts +++ b/packages/astro/src/vite-plugin-jsx/index.ts @@ -1,14 +1,14 @@ import type { TransformResult } from 'rollup'; import type { Plugin, ResolvedConfig } from 'vite'; import type { AstroConfig, AstroRenderer } from '../@types/astro'; -import type { LogOptions } from '../core/logger.js'; +import type { LogOptions } from '../core/logger/core.js'; import babel from '@babel/core'; import esbuild from 'esbuild'; import * as colors from 'kleur/colors'; import * as eslexer from 'es-module-lexer'; import path from 'path'; -import { error } from '../core/logger.js'; +import { error } from '../core/logger/core.js'; import { parseNpmName } from '../core/util.js'; const JSX_RENDERER_CACHE = new WeakMap>(); diff --git a/packages/astro/test/astro-response.test.js b/packages/astro/test/astro-response.test.js new file mode 100644 index 000000000..719d14d30 --- /dev/null +++ b/packages/astro/test/astro-response.test.js @@ -0,0 +1,32 @@ +import { expect } from 'chai'; +import { load as cheerioLoad } from 'cheerio'; +import { loadFixture } from './test-utils.js'; + +// Asset bundling +describe('Returning responses', () => { + let fixture; + /** @type {import('./test-utils').DevServer} */ + let devServer; + + before(async () => { + fixture = await loadFixture({ + projectRoot: './fixtures/astro-response/', + }); + + devServer = await fixture.startDevServer(); + }); + + after(async () => { + await devServer.stop(); + }); + + it('Works from a page', async () => { + let response = await fixture.fetch('/not-found'); + expect(response.status).to.equal(404); + }); + + it('Works from a component', async () => { + let response = await fixture.fetch('/not-found-component'); + expect(response.status).to.equal(404); + }); +}); diff --git a/packages/astro/test/fixtures/astro-response/src/components/not-found.astro b/packages/astro/test/fixtures/astro-response/src/components/not-found.astro new file mode 100644 index 000000000..dd339e72b --- /dev/null +++ b/packages/astro/test/fixtures/astro-response/src/components/not-found.astro @@ -0,0 +1,6 @@ +--- +return new Response(null, { + status: 404, + statusText: `Not found` +}); +--- diff --git a/packages/astro/test/fixtures/astro-response/src/pages/not-found-component.astro b/packages/astro/test/fixtures/astro-response/src/pages/not-found-component.astro new file mode 100644 index 000000000..e1077e9c9 --- /dev/null +++ b/packages/astro/test/fixtures/astro-response/src/pages/not-found-component.astro @@ -0,0 +1,4 @@ +--- +import NotFound from '../components/not-found.astro'; +--- + diff --git a/packages/astro/test/fixtures/astro-response/src/pages/not-found.astro b/packages/astro/test/fixtures/astro-response/src/pages/not-found.astro new file mode 100644 index 000000000..dd339e72b --- /dev/null +++ b/packages/astro/test/fixtures/astro-response/src/pages/not-found.astro @@ -0,0 +1,6 @@ +--- +return new Response(null, { + status: 404, + statusText: `Not found` +}); +--- diff --git a/packages/astro/test/fixtures/ssr-api-route/src/pages/food.json.js b/packages/astro/test/fixtures/ssr-api-route/src/pages/food.json.js index 0003f2ad4..fecf83051 100644 --- a/packages/astro/test/fixtures/ssr-api-route/src/pages/food.json.js +++ b/packages/astro/test/fixtures/ssr-api-route/src/pages/food.json.js @@ -8,3 +8,13 @@ export function get() { ]) }; } + +export async function post(params, request) { + const body = await request.text(); + return new Response(body === `some data` ? `ok` : `not ok`, { + status: 200, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + } + }); +} diff --git a/packages/astro/test/ssr-api-route.test.js b/packages/astro/test/ssr-api-route.test.js index 2131c2b6f..54991e80a 100644 --- a/packages/astro/test/ssr-api-route.test.js +++ b/packages/astro/test/ssr-api-route.test.js @@ -36,4 +36,25 @@ describe('API routes in SSR', () => { const body = await response.json(); expect(body.length).to.equal(3); }); + + describe('Dev', () => { + let devServer; + before(async () => { + devServer = await fixture.startDevServer(); + }); + + after(async () => { + await devServer.stop(); + }); + + it('Can POST to API routes', async () => { + const response = await fixture.fetch('/food.json', { + method: 'POST', + body: `some data`, + }); + expect(response.status).to.equal(200); + const text = await response.text(); + expect(text).to.equal(`ok`); + }); + }); }); diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js index d978c4d65..56c18580b 100644 --- a/packages/astro/test/test-utils.js +++ b/packages/astro/test/test-utils.js @@ -6,7 +6,7 @@ import { resolveConfig, loadConfig } from '../dist/core/config.js'; import dev from '../dist/core/dev/index.js'; import build from '../dist/core/build/index.js'; import preview from '../dist/core/preview/index.js'; -import { loadApp } from '../dist/core/app/node.js'; +import { nodeLogDestination } from '../dist/core/logger/node.js'; import os from 'os'; import stripAnsi from 'strip-ansi'; @@ -71,17 +71,23 @@ export async function loadFixture(inlineConfig) { let config = await loadConfig({ cwd: fileURLToPath(cwd) }); config = merge(config, { ...inlineConfig, projectRoot: cwd }); + /** @type {import('../src/core/logger/core').LogOptions} */ + const logging = { + dest: nodeLogDestination, + level: 'error', + }; + return { - build: (opts = {}) => build(config, { mode: 'development', logging: 'error', ...opts }), + build: (opts = {}) => build(config, { mode: 'development', logging, ...opts }), startDevServer: async (opts = {}) => { - const devResult = await dev(config, { logging: 'error', ...opts }); + const devResult = await dev(config, { logging, ...opts }); config.devOptions.port = devResult.address.port; // update port return devResult; }, config, fetch: (url, init) => fetch(`http://${'127.0.0.1'}:${config.devOptions.port}${url.replace(/^\/?/, '/')}`, init), preview: async (opts = {}) => { - const previewServer = await preview(config, { logging: 'error', ...opts }); + const previewServer = await preview(config, { logging, ...opts }); return previewServer; }, readFile: (filePath) => fs.promises.readFile(new URL(filePath.replace(/^\//, ''), config.dist), 'utf8'), diff --git a/packages/integrations/deno/CHANGELOG.md b/packages/integrations/deno/CHANGELOG.md new file mode 100644 index 000000000..31593ed2b --- /dev/null +++ b/packages/integrations/deno/CHANGELOG.md @@ -0,0 +1,7 @@ +# @astrojs/node + +## 0.0.2-next.0 + +### Patch Changes + +- [#2873](https://github.com/withastro/astro/pull/2873) [`e4025d1f`](https://github.com/withastro/astro/commit/e4025d1f530310d6ab951109f4f53878a307471a) Thanks [@matthewp](https://github.com/matthewp)! - Improves the build by building to a single file for rendering diff --git a/packages/integrations/deno/package.json b/packages/integrations/deno/package.json new file mode 100644 index 000000000..6fd86ab71 --- /dev/null +++ b/packages/integrations/deno/package.json @@ -0,0 +1,30 @@ +{ + "name": "@astrojs/deno", + "description": "Deploy your site to a Deno server", + "version": "0.0.2-next.0", + "type": "module", + "types": "./dist/index.d.ts", + "author": "withastro", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/withastro/astro.git", + "directory": "packages/integrations/deno" + }, + "bugs": "https://github.com/withastro/astro/issues", + "homepage": "https://astro.build", + "exports": { + ".": "./dist/index.js", + "./server.js": "./dist/server.js", + "./package.json": "./package.json" + }, + "scripts": { + "build": "astro-scripts build \"src/**/*.ts\" && tsc", + "dev": "astro-scripts dev \"src/**/*.ts\"", + "test": "deno test --allow-run --allow-read --allow-net ./test/" + }, + "devDependencies": { + "astro": "workspace:*", + "astro-scripts": "workspace:*" + } +} diff --git a/packages/integrations/deno/readme.md b/packages/integrations/deno/readme.md new file mode 100644 index 000000000..4b3dc959f --- /dev/null +++ b/packages/integrations/deno/readme.md @@ -0,0 +1,66 @@ +# @astrojs/deno + +A server-side rendering adapter for use with Deno targets. Write your code in Astro/Node and deploy to Deno servers. + +In your astro.config.mjs use: + +```js +import { defineConfig } from 'astro/config'; +import deno from '@astrojs/deno'; + +export default defineConfig({ + adapter: deno() +}); +``` + +After performing a build there will be a `dist/server/entry.mjs` module. You can start a server simply by importing this module: + +```js +import './dist/entry.mjs'; +``` + +## API + +### Adapter options + +This adapter automatically starts a server when it is imported. You can configure this through options: + +```js +import { defineConfig } from 'astro/config'; +import deno from '@astrojs/deno'; + +export default defineConfig({ + adapter: deno({ + start: false + }) +}); +``` + +If disabling start you need to write your own web server and use `handle` to render requests: + +```ts +import { serve } from "https://deno.land/std@0.132.0/http/server.ts"; +import { handle } from './dist/entry.mjs'; + +serve((req: Request) => { + // Check the request, maybe do static file handling here. + + return handle(req); +}); +``` + +---- + +You an also pass in a port/hostname to use: + +```js +import { defineConfig } from 'astro/config'; +import deno from '@astrojs/deno'; + +export default defineConfig({ + adapter: deno({ + port: 8081, + hostname: 'myhost' + }) +}); +``` diff --git a/packages/integrations/deno/src/index.ts b/packages/integrations/deno/src/index.ts new file mode 100644 index 000000000..ad5462112 --- /dev/null +++ b/packages/integrations/deno/src/index.ts @@ -0,0 +1,33 @@ +import type { AstroAdapter, AstroIntegration } from 'astro'; + +interface Options { + port?: number; + hostname?: string; +} + +export function getAdapter(args?: Options): AstroAdapter { + return { + name: '@astrojs/deno', + serverEntrypoint: '@astrojs/deno/server.js', + args: args ?? {}, + exports: ['stop', 'handle'], + }; +} + +export default function createIntegration(args?: Options): AstroIntegration { + return { + name: '@astrojs/deno', + hooks: { + 'astro:config:done': ({ setAdapter }) => { + setAdapter(getAdapter(args)); + }, + 'astro:build:setup': ({ vite, target }) => { + if (target === 'server') { + vite.ssr = { + noExternal: true, + }; + } + }, + }, + }; +} diff --git a/packages/integrations/deno/src/server.ts b/packages/integrations/deno/src/server.ts new file mode 100644 index 000000000..e1659a843 --- /dev/null +++ b/packages/integrations/deno/src/server.ts @@ -0,0 +1,51 @@ +import './shim.js'; +import type { SSRManifest } from 'astro'; +import { App } from 'astro/app'; +// @ts-ignore +import { Server } from 'https://deno.land/std@0.132.0/http/server.ts'; + +interface Options { + port?: number; + hostname?: string; + start?: boolean; +} + +let _server: Server | undefined = undefined; +let _startPromise: Promise | undefined = undefined; + +export function start(manifest: SSRManifest, options: Options) { + if (options.start === false) { + return; + } + + const app = new App(manifest); + + const handler = async (request: Request) => { + const response = await app.render(request); + return response; + }; + + _server = new Server({ + port: options.port ?? 8085, + hostname: options.hostname ?? '0.0.0.0', + handler, + //onError: options.onError, + }); + + _startPromise = _server.listenAndServe(); +} + +export function createExports(manifest: SSRManifest) { + const app = new App(manifest); + return { + async stop() { + if (_server) { + _server.close(); + } + await Promise.resolve(_startPromise); + }, + async handle(request: Request) { + return app.render(request); + }, + }; +} diff --git a/packages/integrations/deno/src/shim.ts b/packages/integrations/deno/src/shim.ts new file mode 100644 index 000000000..1a4a6ee9b --- /dev/null +++ b/packages/integrations/deno/src/shim.ts @@ -0,0 +1,4 @@ +(globalThis as any).process = { + argv: [], + env: {}, +}; diff --git a/packages/integrations/deno/test/basics.test.js b/packages/integrations/deno/test/basics.test.js new file mode 100644 index 000000000..6f152d4f4 --- /dev/null +++ b/packages/integrations/deno/test/basics.test.js @@ -0,0 +1,14 @@ +import { runBuildAndStartApp } from './helpers.js'; +import { assertEquals, assert } from './deps.js'; + +Deno.test({ + name: 'Basics', + async fn() { + await runBuildAndStartApp('./fixtures/basics/', async () => { + const resp = await fetch('http://127.0.0.1:8085/'); + assertEquals(resp.status, 200); + const html = await resp.text(); + assert(html); + }); + }, +}); diff --git a/packages/integrations/deno/test/deps.js b/packages/integrations/deno/test/deps.js new file mode 100644 index 000000000..af5f89c13 --- /dev/null +++ b/packages/integrations/deno/test/deps.js @@ -0,0 +1,2 @@ +export * from 'https://deno.land/std@0.110.0/path/mod.ts'; +export * from 'https://deno.land/std@0.132.0/testing/asserts.ts'; diff --git a/packages/integrations/deno/test/fixtures/basics/astro.config.mjs b/packages/integrations/deno/test/fixtures/basics/astro.config.mjs new file mode 100644 index 000000000..dfdf9e182 --- /dev/null +++ b/packages/integrations/deno/test/fixtures/basics/astro.config.mjs @@ -0,0 +1,6 @@ +import { defineConfig } from 'astro/config'; +import deno from '@astrojs/deno'; + +export default defineConfig({ + adapter: deno() +}) diff --git a/packages/integrations/deno/test/fixtures/basics/package.json b/packages/integrations/deno/test/fixtures/basics/package.json new file mode 100644 index 000000000..a623de766 --- /dev/null +++ b/packages/integrations/deno/test/fixtures/basics/package.json @@ -0,0 +1,9 @@ +{ + "name": "@test/deno-astro-basic", + "version": "0.0.0", + "private": true, + "dependencies": { + "astro": "workspace:*", + "@astrojs/deno": "workspace:*" + } +} diff --git a/packages/integrations/deno/test/fixtures/basics/src/pages/index.astro b/packages/integrations/deno/test/fixtures/basics/src/pages/index.astro new file mode 100644 index 000000000..d8bc06a72 --- /dev/null +++ b/packages/integrations/deno/test/fixtures/basics/src/pages/index.astro @@ -0,0 +1,11 @@ +--- + +--- + + + Basic App on Deno + + +

Basic App on Deno

+ + diff --git a/packages/integrations/deno/test/helpers.js b/packages/integrations/deno/test/helpers.js new file mode 100644 index 000000000..659d24d5e --- /dev/null +++ b/packages/integrations/deno/test/helpers.js @@ -0,0 +1,20 @@ +import { fromFileUrl } from './deps.js'; +const dir = new URL('./', import.meta.url); + +export async function runBuild(fixturePath) { + let proc = Deno.run({ + cmd: ['node', '../../../../../astro/astro.js', 'build', '--silent'], + cwd: fromFileUrl(new URL(fixturePath, dir)), + }); + await proc.status(); + return async () => await proc.close(); +} + +export async function runBuildAndStartApp(fixturePath, cb) { + const url = new URL(fixturePath, dir); + const close = await runBuild(fixturePath); + const mod = await import(new URL('./dist/entry.mjs', url)); + await cb(); + await mod.stop(); + await close(); +} diff --git a/packages/integrations/deno/tsconfig.json b/packages/integrations/deno/tsconfig.json new file mode 100644 index 000000000..44baf375c --- /dev/null +++ b/packages/integrations/deno/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../tsconfig.base.json", + "include": ["src"], + "compilerOptions": { + "allowJs": true, + "module": "ES2020", + "outDir": "./dist", + "target": "ES2020" + } +} diff --git a/packages/integrations/node/readme.md b/packages/integrations/node/readme.md index 011485278..e9c8bf76e 100644 --- a/packages/integrations/node/readme.md +++ b/packages/integrations/node/readme.md @@ -1,6 +1,6 @@ # @astrojs/node -An experimental static-side rendering adapter for use with Node.js servers. +An experimental server-side rendering adapter for use with Node.js servers. In your astro.config.mjs use: diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 142780851..b973649e5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -467,6 +467,7 @@ importers: '@types/mime': ^2.0.3 '@types/mocha': ^9.1.0 '@types/parse5': ^6.0.3 + '@types/path-browserify': ^1.0.0 '@types/prettier': ^2.4.4 '@types/resolve': ^1.20.1 '@types/rimraf': ^3.0.2 @@ -499,6 +500,7 @@ importers: mocha: ^9.2.2 ora: ^6.1.0 parse5: ^6.0.1 + path-browserify: ^1.0.1 path-to-regexp: ^6.2.0 postcss: ^8.4.12 postcss-load-config: ^3.1.4 @@ -560,6 +562,7 @@ importers: mime: 3.0.0 ora: 6.1.0 parse5: 6.0.1 + path-browserify: 1.0.1 path-to-regexp: 6.2.0 postcss: 8.4.12 postcss-load-config: 3.1.4_postcss@8.4.12 @@ -600,6 +603,7 @@ importers: '@types/mime': 2.0.3 '@types/mocha': 9.1.0 '@types/parse5': 6.0.3 + '@types/path-browserify': 1.0.0 '@types/prettier': 2.4.4 '@types/resolve': 1.20.1 '@types/rimraf': 3.0.2 @@ -1190,6 +1194,22 @@ importers: astro-scripts: link:../../scripts uvu: 0.5.3 + packages/integrations/deno: + specifiers: + astro: workspace:* + astro-scripts: workspace:* + devDependencies: + astro: link:../../astro + astro-scripts: link:../../../scripts + + packages/integrations/deno/test/fixtures/basics: + specifiers: + '@astrojs/deno': workspace:* + astro: workspace:* + dependencies: + '@astrojs/deno': link:../../.. + astro: link:../../../../../astro + packages/integrations/lit: specifiers: '@lit-labs/ssr': ^2.0.4 @@ -3980,6 +4000,10 @@ packages: /@types/parse5/6.0.3: resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==} + /@types/path-browserify/1.0.0: + resolution: {integrity: sha512-XMCcyhSvxcch8b7rZAtFAaierBYdeHXVvg2iYnxOV0MCQHmPuRRmGZPFDRzPayxcGiiSL1Te9UIO+f3cuj0tfw==} + dev: true + /@types/prettier/2.4.4: resolution: {integrity: sha512-ReVR2rLTV1kvtlWFyuot+d1pkpG2Fw/XKE3PDAdj57rbM97ttSp9JZ2UsP+2EHTylra9cUf6JA7tGwW1INzUrA==} dev: true @@ -8472,6 +8496,10 @@ packages: tslib: 2.3.1 dev: false + /path-browserify/1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + dev: false + /path-exists/4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} diff --git a/scripts/stats/stats.csv b/scripts/stats/stats.csv index a582bd823..3a8ec9bd1 100644 --- a/scripts/stats/stats.csv +++ b/scripts/stats/stats.csv @@ -1,4 +1,5 @@ Date,Commits (24hr),Issues (24hr),Issues:BUG (24hr),Issues:RFC (24hr),Issues:DOC (24hr),PRs (24hr),Open PRs,Open Issues,Bugs: Needs Triage,Bugs: Accepted,RFC: In Progress,RFC: Accepted,Date (ISO) +"Wednesday, March 30, 2022",9,2,2,0,0,10,10,90,43,41,0,0,"2022-03-30T12:02:39.303Z" "Tuesday, March 29, 2022",19,8,8,0,0,9,5,88,41,41,0,0,"2022-03-29T12:06:39.897Z" "Monday, March 28, 2022",1,7,7,0,0,2,8,83,36,41,0,0,"2022-03-28T12:02:00.954Z" "Sunday, March 27, 2022",1,2,2,0,0,2,6,77,29,41,0,0,"2022-03-27T12:01:52.463Z"