diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | packages/astro/package.json | 5 | ||||
-rw-r--r-- | packages/astro/src/@types/astro.ts | 2 | ||||
-rw-r--r-- | packages/astro/src/core/build/index.ts | 3 | ||||
-rw-r--r-- | packages/astro/src/core/config.ts | 2 | ||||
-rw-r--r-- | packages/astro/src/core/routing/manifest/create.ts | 6 | ||||
-rw-r--r-- | packages/astro/src/integrations/index.ts | 3 | ||||
-rw-r--r-- | packages/integrations/vercel/README.md | 44 | ||||
-rw-r--r-- | packages/integrations/vercel/package.json | 33 | ||||
-rw-r--r-- | packages/integrations/vercel/src/index.ts | 79 | ||||
-rw-r--r-- | packages/integrations/vercel/src/shims.ts | 5 | ||||
-rw-r--r-- | packages/integrations/vercel/tsconfig.json | 10 | ||||
-rw-r--r-- | pnpm-lock.yaml | 34 |
13 files changed, 221 insertions, 6 deletions
diff --git a/.gitignore b/.gitignore index 2185b7f84..6a837da1d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules/ dist/ +.output/ *.tsbuildinfo .DS_Store .vercel diff --git a/packages/astro/package.json b/packages/astro/package.json index 70d26a3d2..d475244bb 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -103,6 +103,7 @@ "htmlparser2": "^7.2.0", "kleur": "^4.1.4", "magic-string": "^0.25.9", + "micromatch": "^4.0.5", "micromorph": "^0.1.2", "mime": "^3.0.0", "ora": "^6.1.0", @@ -145,6 +146,7 @@ "@types/diff": "^5.0.2", "@types/estree": "^0.0.51", "@types/html-escaper": "^3.0.0", + "@types/micromatch": "^4.0.2", "@types/mime": "^2.0.3", "@types/mocha": "^9.1.0", "@types/parse5": "^6.0.3", @@ -157,7 +159,8 @@ "chai": "^4.3.6", "cheerio": "^1.0.0-rc.10", "mocha": "^9.2.2", - "sass": "^1.49.9" + "sass": "^1.49.9", + "type-fest": "^2.12.1" }, "engines": { "node": "^14.15.0 || >=16.0.0", diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index fca4aff80..47c783e0d 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -502,6 +502,7 @@ export interface AstroConfig extends z.output<typeof AstroConfigSchema> { adapter: AstroAdapter | undefined; renderers: AstroRenderer[]; scripts: { stage: InjectedScriptStage; content: string }[]; + ignoredPages: string[]; }; } @@ -671,6 +672,7 @@ export interface AstroIntegration { updateConfig: (newConfig: Record<string, any>) => void; addRenderer: (renderer: AstroRenderer) => void; injectScript: (stage: InjectedScriptStage, content: string) => void; + ignorePages: (glob: string) => void; // TODO: Add support for `injectElement()` for full HTML element injection, not just scripts. // This may require some refactoring of `scripts`, `styles`, and `links` into something // more generalized. Consider the SSR use-case as well. diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts index 4962ce1c7..1800c2636 100644 --- a/packages/astro/src/core/build/index.ts +++ b/packages/astro/src/core/build/index.ts @@ -26,7 +26,7 @@ export interface BuildOptions { /** `astro build` */ export default async function build(config: AstroConfig, options: BuildOptions = { logging: defaultLogOptions }): Promise<void> { - applyPolyfill(); + config = await runHookConfigSetup({ config, command: 'build' }); const builder = new AstroBuilder(config, options); await builder.run(); } @@ -62,7 +62,6 @@ class AstroBuilder { const { logging } = this; this.timer.init = performance.now(); this.timer.viteStart = performance.now(); - this.config = await runHookConfigSetup({ config: this.config, command: 'build' }); const viteConfig = await createVite( { mode: this.mode, diff --git a/packages/astro/src/core/config.ts b/packages/astro/src/core/config.ts index 394c1c42d..8e4547d0a 100644 --- a/packages/astro/src/core/config.ts +++ b/packages/astro/src/core/config.ts @@ -206,7 +206,7 @@ export async function validateConfig(userConfig: any, root: string): Promise<Ast // First-Pass Validation const result = { ...(await AstroConfigRelativeSchema.parseAsync(userConfig)), - _ctx: { scripts: [], renderers: [], adapter: undefined }, + _ctx: { scripts: [], renderers: [], adapter: undefined, ignoredPages: [] }, }; // Final-Pass Validation (perform checks that require the full config object) if (!result.experimentalIntegrations && !result.integrations.every((int) => int.name.startsWith('@astrojs/'))) { diff --git a/packages/astro/src/core/routing/manifest/create.ts b/packages/astro/src/core/routing/manifest/create.ts index 1e0a6f3bc..8e0bdb736 100644 --- a/packages/astro/src/core/routing/manifest/create.ts +++ b/packages/astro/src/core/routing/manifest/create.ts @@ -4,6 +4,7 @@ import type { LogOptions } from '../../logger'; import fs from 'fs'; import path from 'path'; import { compile } from 'path-to-regexp'; +import micromatch from 'micromatch'; import slash from 'slash'; import { fileURLToPath } from 'url'; import { warn } from '../../logger.js'; @@ -178,11 +179,16 @@ export function createRouteManifest({ config, cwd }: { config: AstroConfig; cwd? fs.readdirSync(dir).forEach((basename) => { const resolved = path.join(dir, basename); const file = slash(path.relative(cwd || fileURLToPath(config.projectRoot), resolved)); + const pagePath = slash(path.relative(fileURLToPath(config.pages), resolved)); const isDir = fs.statSync(resolved).isDirectory(); const ext = path.extname(basename); const name = ext ? basename.slice(0, -ext.length) : basename; + if ((config._ctx?.ignoredPages || []).length > 0 && micromatch.isMatch(pagePath, config._ctx.ignoredPages)) { + return; + } + if (name[0] === '_') { return; } diff --git a/packages/astro/src/integrations/index.ts b/packages/astro/src/integrations/index.ts index b04caeaf1..580552775 100644 --- a/packages/astro/src/integrations/index.ts +++ b/packages/astro/src/integrations/index.ts @@ -24,6 +24,9 @@ export async function runHookConfigSetup({ config: _config, command }: { config: updateConfig: (newConfig) => { updatedConfig = mergeConfig(updatedConfig, newConfig) as AstroConfig; }, + ignorePages: (glob: string) => { + updatedConfig._ctx.ignoredPages.push(glob); + }, }); } } diff --git a/packages/integrations/vercel/README.md b/packages/integrations/vercel/README.md new file mode 100644 index 000000000..24fdb5187 --- /dev/null +++ b/packages/integrations/vercel/README.md @@ -0,0 +1,44 @@ +# @astrojs/netlify + +Deploy your server-side rendered (SSR) Astro app to [Netlify](https://www.netlify.com/). + +Use this adapter in your Astro configuration file: + +```js +import { defineConfig } from 'astro/config'; +import netlify from '@astrojs/netlify/functions'; + +export default defineConfig({ + adapter: netlify() +}); +``` + +After you build your site the `netlify/` folder will contain [Netlify Functions](https://docs.netlify.com/functions/overview/) in the `netlify/functions/` folder. + +Now you can deploy! + +```shell +netlify deploy +``` + +## Configuration + +The output folder is configuration with the `dist` property when creating the adapter. + +```js +import { defineConfig } from 'astro/config'; +import netlify from '@astrojs/netlify/functions'; + +export default defineConfig({ + adapter: netlify({ + dist: new URL('./dist/', import.meta.url) + }) +}); +``` + +And then point to the dist in your `netlify.toml`: + +```toml +[functions] + directory = "dist/functions" +``` diff --git a/packages/integrations/vercel/package.json b/packages/integrations/vercel/package.json new file mode 100644 index 000000000..d7713183c --- /dev/null +++ b/packages/integrations/vercel/package.json @@ -0,0 +1,33 @@ +{ + "name": "@astrojs/vercel", + "description": "Deploy your site to Vercel", + "version": "0.0.1", + "type": "module", + "types": "./dist/index.d.ts", + "author": "withastro", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/withastro/astro.git", + "directory": "packages/integrations/vercel" + }, + "bugs": "https://github.com/withastro/astro/issues", + "homepage": "https://astro.build", + "exports": { + ".": "./dist/index.js", + "./package.json": "./package.json" + }, + "scripts": { + "build": "astro-scripts build \"src/**/*.ts\" && tsc", + "dev": "astro-scripts dev \"src/**/*.ts\"" + }, + "dependencies": { + "@astrojs/webapi": "^0.11.0", + "esbuild": "0.14.25", + "globby": "^12.2.0" + }, + "devDependencies": { + "astro": "workspace:*", + "astro-scripts": "workspace:*" + } +} diff --git a/packages/integrations/vercel/src/index.ts b/packages/integrations/vercel/src/index.ts new file mode 100644 index 000000000..5d6794206 --- /dev/null +++ b/packages/integrations/vercel/src/index.ts @@ -0,0 +1,79 @@ +import type { AstroIntegration, AstroConfig } from 'astro'; +import type { IncomingMessage, ServerResponse } from 'http'; +import type { PathLike } from 'fs'; + +import fs from 'fs/promises'; +import { fileURLToPath } from 'url'; +import { globby } from 'globby'; +import esbuild from 'esbuild'; + +export type VercelRequest = IncomingMessage; +export type VercelResponse = ServerResponse; +export type VercelHandler = (request: VercelRequest, response: VercelResponse) => void | Promise<void>; + +const writeJson = (path: PathLike, data: any) => fs.writeFile(path, JSON.stringify(data), { encoding: 'utf-8' }); + +const ENDPOINT_GLOB = 'api/**/*.{js,ts,tsx}'; + +function vercelFunctions(): AstroIntegration { + let _config: AstroConfig; + let output: URL; + + return { + name: '@astrojs/vercel', + hooks: { + 'astro:config:setup': ({ config, ignorePages }) => { + output = new URL('./.output/', config.projectRoot); + config.dist = new URL('./static/', output); + config.buildOptions.pageUrlFormat = 'directory'; + ignorePages(ENDPOINT_GLOB); + }, + 'astro:config:done': async ({ config }) => { + _config = config; + }, + 'astro:build:start': async () => { + await fs.rm(output, { recursive: true, force: true }); + }, + 'astro:build:done': async ({ pages }) => { + // Split pages from the rest of files + await Promise.all( + pages.map(async ({ pathname }) => { + const origin = new URL(`./static/${pathname}index.html`, output); + const finalDir = new URL(`./server/pages/${pathname}`, output); + + await fs.mkdir(finalDir, { recursive: true }); + await fs.copyFile(origin, new URL(`./index.html`, finalDir)); + await fs.rm(origin); + }) + ); + + // Routes Manifest + // https://vercel.com/docs/file-system-api#configuration/routes + await writeJson(new URL(`./routes-manifest.json`, output), { + version: 3, + basePath: '/', + pages404: false, + }); + + const endpoints = await globby([ENDPOINT_GLOB, '!_*'], { onlyFiles: true, cwd: _config.pages }); + + if (endpoints.length === 0) return; + + await esbuild.build({ + entryPoints: endpoints.map((endpoint) => new URL(endpoint, _config.pages)).map(fileURLToPath), + outdir: fileURLToPath(new URL('./server/pages/api/', output)), + outbase: fileURLToPath(new URL('./api/', _config.pages)), + inject: [fileURLToPath(new URL('./shims.js', import.meta.url))], + bundle: true, + target: 'node14', + platform: 'node', + format: 'cjs', + }); + + await writeJson(new URL(`./package.json`, output), { type: 'commonjs' }); + }, + }, + }; +} + +export default vercelFunctions; diff --git a/packages/integrations/vercel/src/shims.ts b/packages/integrations/vercel/src/shims.ts new file mode 100644 index 000000000..01f7b39bf --- /dev/null +++ b/packages/integrations/vercel/src/shims.ts @@ -0,0 +1,5 @@ +import { polyfill } from '@astrojs/webapi'; + +polyfill(globalThis, { + exclude: 'window document', +}); diff --git a/packages/integrations/vercel/tsconfig.json b/packages/integrations/vercel/tsconfig.json new file mode 100644 index 000000000..44baf375c --- /dev/null +++ b/packages/integrations/vercel/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../tsconfig.base.json", + "include": ["src"], + "compilerOptions": { + "allowJs": true, + "module": "ES2020", + "outDir": "./dist", + "target": "ES2020" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 503d62826..997848edc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -464,6 +464,7 @@ importers: '@types/diff': ^5.0.2 '@types/estree': ^0.0.51 '@types/html-escaper': ^3.0.0 + '@types/micromatch': ^4.0.2 '@types/mime': ^2.0.3 '@types/mocha': ^9.1.0 '@types/parse5': ^6.0.3 @@ -494,6 +495,7 @@ importers: htmlparser2: ^7.2.0 kleur: ^4.1.4 magic-string: ^0.25.9 + micromatch: ^4.0.5 micromorph: ^0.1.2 mime: ^3.0.0 mocha: ^9.2.2 @@ -522,6 +524,7 @@ importers: strip-ansi: ^7.0.1 supports-esm: ^1.0.0 tsconfig-resolver: ^3.0.1 + type-fest: ^2.12.1 vite: ^2.8.6 yargs-parser: ^21.0.1 zod: ^3.14.3 @@ -556,6 +559,7 @@ importers: htmlparser2: 7.2.0 kleur: 4.1.4 magic-string: 0.25.9 + micromatch: 4.0.5 micromorph: 0.1.2 mime: 3.0.0 ora: 6.1.0 @@ -597,6 +601,7 @@ importers: '@types/diff': 5.0.2 '@types/estree': 0.0.51 '@types/html-escaper': 3.0.0 + '@types/micromatch': 4.0.2 '@types/mime': 2.0.3 '@types/mocha': 9.1.0 '@types/parse5': 6.0.3 @@ -610,6 +615,7 @@ importers: cheerio: 1.0.0-rc.10 mocha: 9.2.2 sass: 1.49.9 + type-fest: 2.12.1 packages/astro-prism: specifiers: @@ -1339,6 +1345,21 @@ importers: astro: link:../../astro astro-scripts: link:../../../scripts + packages/integrations/vercel: + specifiers: + '@astrojs/webapi': ^0.11.0 + astro: workspace:* + astro-scripts: workspace:* + esbuild: 0.14.25 + globby: ^12.2.0 + dependencies: + '@astrojs/webapi': link:../../webapi + esbuild: 0.14.25 + globby: 12.2.0 + devDependencies: + astro: link:../../astro + astro-scripts: link:../../../scripts + packages/integrations/vue: specifiers: '@vitejs/plugin-vue': ^2.2.4 @@ -3827,6 +3848,10 @@ packages: '@babel/types': 7.17.0 dev: true + /@types/braces/3.0.1: + resolution: {integrity: sha512-+euflG6ygo4bn0JHtn4pYqcXwRtLvElQ7/nnjDu7iYG56H0+OhCd7d6Ug0IE3WcFpZozBKW2+80FUbv5QGk5AQ==} + dev: true + /@types/chai/4.3.0: resolution: {integrity: sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==} dev: true @@ -3915,6 +3940,12 @@ packages: resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==} dev: false + /@types/micromatch/4.0.2: + resolution: {integrity: sha512-oqXqVb0ci19GtH0vOA/U2TmHTcRY9kuZl4mqUxe0QmJAlIW13kzhuK5pi1i9+ngav8FjpSb9FVS/GE00GLX1VA==} + dependencies: + '@types/braces': 3.0.1 + dev: true + /@types/mime/1.3.2: resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} dev: true @@ -10176,7 +10207,6 @@ packages: /type-fest/2.12.1: resolution: {integrity: sha512-AiknQSEqKVGDDjtZqeKrUoTlcj7FKhupmnVUgz6KoOKtvMwRGE6hUNJ/nVear+h7fnUPO1q/htSkYKb1pyntkQ==} engines: {node: '>=12.20'} - dev: false /typescript/4.6.3: resolution: {integrity: sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==} @@ -10689,7 +10719,7 @@ packages: /wide-align/1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} dependencies: - string-width: 1.0.2 + string-width: 4.2.3 dev: true /widest-line/4.0.1: |