From e55af8a23233b6335f45b7a04b9d026990fb616c Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Wed, 12 Oct 2022 17:25:51 -0400 Subject: [PATCH] Node.js standalone mode + support for astro preview (#5056) * wip * Deprecate buildConfig and move to config.build * Implement the standalone server * Stay backwards compat * Add changesets * correctly merge URLs * Get config earlier * update node tests * Return the preview server * update remaining tests * swap usage and config ordering * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger * Update .changeset/metal-pumas-walk.md Co-authored-by: Sarah Rainsberger * Update .changeset/metal-pumas-walk.md Co-authored-by: Sarah Rainsberger * Update .changeset/stupid-points-refuse.md Co-authored-by: Sarah Rainsberger * Update .changeset/stupid-points-refuse.md Co-authored-by: Sarah Rainsberger * Link to build.server config Co-authored-by: Fred K. Schott Co-authored-by: Sarah Rainsberger --- .changeset/cyan-paws-fry.md | 38 ++++ .changeset/metal-pumas-walk.md | 43 ++++ .changeset/stupid-points-refuse.md | 49 +++++ examples/ssr/astro.config.mjs | 4 +- examples/ssr/package.json | 2 +- examples/ssr/server/server.mjs | 44 ---- packages/astro/src/@types/astro.ts | 97 ++++++++- packages/astro/src/core/build/index.ts | 6 +- packages/astro/src/core/config/config.ts | 6 +- packages/astro/src/core/config/schema.ts | 53 ++++- packages/astro/src/core/preview/index.ts | 195 ++++-------------- .../src/core/preview/static-preview-server.ts | 164 +++++++++++++++ packages/astro/src/core/util.ts | 5 + packages/astro/src/integrations/index.ts | 32 ++- .../test/benchmark/simple/astro.config.mjs | 2 +- .../static-build-ssr/astro.config.mjs | 2 +- packages/integrations/cloudflare/src/index.ts | 34 ++- packages/integrations/deno/src/index.ts | 15 +- packages/integrations/image/src/index.ts | 21 +- .../netlify/src/integration-edge-functions.ts | 46 +++-- .../netlify/src/integration-functions.ts | 28 ++- packages/integrations/node/README.md | 81 ++++---- packages/integrations/node/package.json | 5 +- packages/integrations/node/src/http-server.ts | 77 +++++++ packages/integrations/node/src/index.ts | 30 ++- packages/integrations/node/src/middleware.ts | 53 +++++ packages/integrations/node/src/preview.ts | 54 +++++ packages/integrations/node/src/server.ts | 53 +---- packages/integrations/node/src/standalone.ts | 53 +++++ packages/integrations/node/src/types.ts | 17 ++ .../integrations/node/test/api-route.test.js | 2 +- .../integrations/vercel/src/edge/adapter.ts | 28 ++- .../vercel/src/serverless/adapter.ts | 28 ++- pnpm-lock.yaml | 88 +++++++- 34 files changed, 1094 insertions(+), 361 deletions(-) create mode 100644 .changeset/cyan-paws-fry.md create mode 100644 .changeset/metal-pumas-walk.md create mode 100644 .changeset/stupid-points-refuse.md delete mode 100644 examples/ssr/server/server.mjs create mode 100644 packages/astro/src/core/preview/static-preview-server.ts create mode 100644 packages/integrations/node/src/http-server.ts create mode 100644 packages/integrations/node/src/middleware.ts create mode 100644 packages/integrations/node/src/preview.ts create mode 100644 packages/integrations/node/src/standalone.ts create mode 100644 packages/integrations/node/src/types.ts diff --git a/.changeset/cyan-paws-fry.md b/.changeset/cyan-paws-fry.md new file mode 100644 index 000000000000..df2585ecb929 --- /dev/null +++ b/.changeset/cyan-paws-fry.md @@ -0,0 +1,38 @@ +--- +'astro': minor +'@astrojs/node': minor +--- + +# Adapter support for `astro preview` + +Adapters are now about to support the `astro preview` command via a new integration option. The Node.js adapter `@astrojs/node` is the first of the built-in adapters to gain support for this. What this means is that if you are using `@astrojs/node` you can new preview your SSR app by running: + +```shell +npm run preview +``` + +## Adapter API + +We will be updating the other first party Astro adapters to support preview over time. Adapters can opt-in to this feature by providing the `previewEntrypoint` via the `setAdapter` function in `astro:config:done` hook. The Node.js adapter's code looks like this: + +```diff +export default function() { + return { + name: '@astrojs/node', + hooks: { + 'astro:config:done': ({ setAdapter, config }) => { + setAdapter({ + name: '@astrojs/node', + serverEntrypoint: '@astrojs/node/server.js', ++ previewEntrypoint: '@astrojs/node/preview.js', + exports: ['handler'], + }); + + // more here + } + } + }; +} +``` + +The `previewEntrypoint` is a module in the adapter's package that is a Node.js script. This script is run when `astro preview` is run and is charged with starting up the built server. See the Node.js implementation in `@astrojs/node` to see how that is implemented. diff --git a/.changeset/metal-pumas-walk.md b/.changeset/metal-pumas-walk.md new file mode 100644 index 000000000000..a6b15a07f3ef --- /dev/null +++ b/.changeset/metal-pumas-walk.md @@ -0,0 +1,43 @@ +--- +'@astrojs/node': major +--- + +# Standalone mode for the Node.js adapter + +New in `@astrojs/node` is support for __standalone mode__. With standalone mode you can start your production server without needing to write any server JavaScript yourself. The server starts simply by running the script like so: + +```shell +node ./dist/server/entry.mjs +``` + +To enable standalone mode, set the new `mode` to `'standalone'` option in your Astro config: + +```js +import { defineConfig } from 'astro/config'; +import nodejs from '@astrojs/node'; + +export default defineConfig({ + output: 'server', + adapter: nodejs({ + mode: 'standalone' + }) +}); +``` + +See the @astrojs/node documentation to learn all of the options available in standalone mode. + +## Breaking change + +This is a semver major change because the new `mode` option is required. Existing @astrojs/node users who are using their own HTTP server framework such as Express can upgrade by setting the `mode` option to `'middleware'` in order to build to a middleware mode, which is the same behavior and API as before. + +```js +import { defineConfig } from 'astro/config'; +import nodejs from '@astrojs/node'; + +export default defineConfig({ + output: 'server', + adapter: nodejs({ + mode: 'middleware' + }) +}); +``` diff --git a/.changeset/stupid-points-refuse.md b/.changeset/stupid-points-refuse.md new file mode 100644 index 000000000000..e79106541cbf --- /dev/null +++ b/.changeset/stupid-points-refuse.md @@ -0,0 +1,49 @@ +--- +'astro': minor +'@astrojs/cloudflare': minor +'@astrojs/deno': minor +'@astrojs/image': minor +'@astrojs/netlify': minor +'@astrojs/node': minor +'@astrojs/vercel': minor +--- + +# New build configuration + +The ability to customize SSR build configuration more granularly is now available in Astro. You can now customize the output folder for `server` (the server code for SSR), `client` (your client-side JavaScript and assets), and `serverEntry` (the name of the entrypoint server module). Here are the defaults: + +```js +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + output: 'server', + build: { + server: './dist/server/', + client: './dist/client/', + serverEntry: 'entry.mjs', + } +}); +``` + +These new configuration options are only supported in SSR mode and are ignored when building to SSG (a static site). + +## Integration hook change + +The integration hook `astro:build:start` includes a param `buildConfig` which includes all of these same options. You can continue to use this param in Astro 1.x, but it is deprecated in favor of the new `build.config` options. All of the built-in adapters have been updated to the new format. If you have an integration that depends on this param we suggest upgrading to do this instead: + +```js +export default function myIntegration() { + return { + name: 'my-integration', + hooks: { + 'astro:config:setup': ({ updateConfig }) => { + updateConfig({ + build: { + server: '...' + } + }); + } + } + } +} +``` diff --git a/examples/ssr/astro.config.mjs b/examples/ssr/astro.config.mjs index b859914ac3ab..2ff8bbaf55b5 100644 --- a/examples/ssr/astro.config.mjs +++ b/examples/ssr/astro.config.mjs @@ -5,6 +5,8 @@ import node from '@astrojs/node'; // https://astro.build/config export default defineConfig({ output: 'server', - adapter: node(), + adapter: node({ + mode: 'standalone' + }), integrations: [svelte()], }); diff --git a/examples/ssr/package.json b/examples/ssr/package.json index 5d05fca697ac..bbc19e2bd2b6 100644 --- a/examples/ssr/package.json +++ b/examples/ssr/package.json @@ -9,7 +9,7 @@ "build": "astro build", "preview": "astro preview", "astro": "astro", - "server": "node server/server.mjs" + "server": "node dist/server/entry.mjs" }, "devDependencies": {}, "dependencies": { diff --git a/examples/ssr/server/server.mjs b/examples/ssr/server/server.mjs deleted file mode 100644 index d7a0a7a40f77..000000000000 --- a/examples/ssr/server/server.mjs +++ /dev/null @@ -1,44 +0,0 @@ -import { createServer } from 'http'; -import fs from 'fs'; -import mime from 'mime'; -import { handler as ssrHandler } from '../dist/server/entry.mjs'; - -const clientRoot = new URL('../dist/client/', import.meta.url); - -async function handle(req, res) { - ssrHandler(req, res, async (err) => { - if (err) { - res.writeHead(500); - res.end(err.stack); - return; - } - - let local = new URL('.' + req.url, clientRoot); - try { - const data = await fs.promises.readFile(local); - res.writeHead(200, { - 'Content-Type': mime.getType(req.url), - }); - res.end(data); - } catch { - res.writeHead(404); - res.end(); - } - }); -} - -const server = createServer((req, res) => { - handle(req, res).catch((err) => { - console.error(err); - res.writeHead(500, { - 'Content-Type': 'text/plain', - }); - res.end(err.toString()); - }); -}); - -server.listen(8085); -console.log('Serving at http://localhost:8085'); - -// Silence weird