@astrojs/node
Advanced tools
Comparing version 0.0.0-imgcache-20220929145446 to 0.0.0-node-standalone-20221011210529
122
CHANGELOG.md
# @astrojs/node | ||
## 0.0.0-imgcache-20220929145446 | ||
## 0.0.0-node-standalone-20221011210529 | ||
### Major Changes | ||
- [#5056](https://github.com/withastro/astro/pull/5056) [`69e32cbba`](https://github.com/withastro/astro/commit/69e32cbba2ee8537a002755c34598dab834898c9) Thanks [@matthewp](https://github.com/matthewp)! - # 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'` which builds 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', | ||
}), | ||
}); | ||
``` | ||
### Minor Changes | ||
- [#5056](https://github.com/withastro/astro/pull/5056) [`69e32cbba`](https://github.com/withastro/astro/commit/69e32cbba2ee8537a002755c34598dab834898c9) Thanks [@matthewp](https://github.com/matthewp)! - # 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. | ||
- [#5056](https://github.com/withastro/astro/pull/5056) [`69e32cbba`](https://github.com/withastro/astro/commit/69e32cbba2ee8537a002755c34598dab834898c9) Thanks [@matthewp](https://github.com/matthewp)! - # New build configuration | ||
The ability to customize SSR build configuration more granular 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 if 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: '...', | ||
}, | ||
}); | ||
}, | ||
}, | ||
}; | ||
} | ||
``` | ||
## 1.1.0 | ||
### Minor Changes | ||
- [#4876](https://github.com/withastro/astro/pull/4876) [`d3091f89e`](https://github.com/withastro/astro/commit/d3091f89e92fcfe1ad48daca74055d54b1c853a3) Thanks [@matthewp](https://github.com/matthewp)! - Adds the Astro.cookies API | ||
@@ -8,0 +128,0 @@ |
import type { AstroAdapter, AstroIntegration } from 'astro'; | ||
export declare function getAdapter(): AstroAdapter; | ||
export default function createIntegration(): AstroIntegration; | ||
import type { Options, UserOptions } from './types'; | ||
export declare function getAdapter(options: Options): AstroAdapter; | ||
export default function createIntegration(userOptions: UserOptions): AstroIntegration; |
@@ -1,9 +0,16 @@ | ||
function getAdapter() { | ||
function getAdapter(options) { | ||
return { | ||
name: "@astrojs/node", | ||
serverEntrypoint: "@astrojs/node/server.js", | ||
exports: ["handler"] | ||
previewEntrypoint: "@astrojs/node/preview.js", | ||
exports: ["handler"], | ||
args: options | ||
}; | ||
} | ||
function createIntegration() { | ||
function createIntegration(userOptions) { | ||
if (!(userOptions == null ? void 0 : userOptions.mode)) { | ||
throw new Error(`[@astrojs/node] Setting the 'mode' option is required.`); | ||
} | ||
let needsBuildConfig = false; | ||
let _options; | ||
return { | ||
@@ -13,6 +20,21 @@ name: "@astrojs/node", | ||
"astro:config:done": ({ setAdapter, config }) => { | ||
setAdapter(getAdapter()); | ||
var _a, _b, _c; | ||
needsBuildConfig = !((_a = config.build) == null ? void 0 : _a.server); | ||
_options = { | ||
...userOptions, | ||
client: (_b = config.build.client) == null ? void 0 : _b.toString(), | ||
server: (_c = config.build.server) == null ? void 0 : _c.toString(), | ||
host: config.server.host, | ||
port: config.server.port | ||
}; | ||
setAdapter(getAdapter(_options)); | ||
if (config.output === "static") { | ||
console.warn(`[@astrojs/node] \`output: "server"\` is required to use this adapter.`); | ||
} | ||
}, | ||
"astro:build:start": ({ buildConfig }) => { | ||
if (needsBuildConfig) { | ||
_options.client = buildConfig.client.toString(); | ||
_options.server = buildConfig.server.toString(); | ||
} | ||
} | ||
@@ -19,0 +41,0 @@ } |
@@ -0,5 +1,7 @@ | ||
/// <reference types="node" /> | ||
import type { SSRManifest } from 'astro'; | ||
import type { IncomingMessage, ServerResponse } from 'http'; | ||
import type { Options } from './types'; | ||
export declare function createExports(manifest: SSRManifest): { | ||
handler(req: IncomingMessage, res: ServerResponse, next?: ((err?: unknown) => void) | undefined): Promise<void>; | ||
handler: (req: import("http").IncomingMessage, res: import("http").ServerResponse<import("http").IncomingMessage>, next?: ((err?: unknown) => void) | undefined) => Promise<void>; | ||
}; | ||
export declare function start(manifest: SSRManifest, options: Options): void; |
import { polyfill } from "@astrojs/webapi"; | ||
import { NodeApp } from "astro/app/node"; | ||
import middleware from "./middleware.js"; | ||
import startServer from "./standalone.js"; | ||
polyfill(globalThis, { | ||
@@ -9,46 +11,15 @@ exclude: "window document" | ||
return { | ||
async handler(req, res, next) { | ||
try { | ||
const route = app.match(req); | ||
if (route) { | ||
try { | ||
const response = await app.render(req); | ||
await writeWebResponse(app, res, response); | ||
} catch (err) { | ||
if (next) { | ||
next(err); | ||
} else { | ||
throw err; | ||
} | ||
} | ||
} else if (next) { | ||
return next(); | ||
} | ||
} catch (err) { | ||
if (!res.headersSent) { | ||
res.writeHead(500, `Server error`); | ||
res.end(); | ||
} | ||
} | ||
} | ||
handler: middleware(app) | ||
}; | ||
} | ||
async function writeWebResponse(app, res, webResponse) { | ||
const { status, headers, body } = webResponse; | ||
if (app.setCookieHeaders) { | ||
const setCookieHeaders = Array.from(app.setCookieHeaders(webResponse)); | ||
if (setCookieHeaders.length) { | ||
res.setHeader("Set-Cookie", setCookieHeaders); | ||
} | ||
function start(manifest, options) { | ||
if (options.mode !== "standalone" || process.env.ASTRO_NODE_AUTOSTART === "disabled") { | ||
return; | ||
} | ||
res.writeHead(status, Object.fromEntries(headers.entries())); | ||
if (body) { | ||
for await (const chunk of body) { | ||
res.write(chunk); | ||
} | ||
} | ||
res.end(); | ||
const app = new NodeApp(manifest); | ||
startServer(app, options); | ||
} | ||
export { | ||
createExports | ||
createExports, | ||
start | ||
}; |
{ | ||
"name": "@astrojs/node", | ||
"description": "Deploy your site to a Node.js server", | ||
"version": "0.0.0-imgcache-20220929145446", | ||
"version": "0.0.0-node-standalone-20221011210529", | ||
"type": "module", | ||
@@ -23,9 +23,12 @@ "types": "./dist/index.d.ts", | ||
"./server.js": "./dist/server.js", | ||
"./preview.js": "./dist/preview.js", | ||
"./package.json": "./package.json" | ||
}, | ||
"dependencies": { | ||
"@astrojs/webapi": "^1.1.0" | ||
"@astrojs/webapi": "^1.1.0", | ||
"send": "^0.18.0" | ||
}, | ||
"devDependencies": { | ||
"astro": "0.0.0-imgcache-20220929145446", | ||
"@types/send": "^0.17.1", | ||
"astro": "0.0.0-node-standalone-20221011210529", | ||
"astro-scripts": "0.0.8", | ||
@@ -32,0 +35,0 @@ "chai": "^4.3.6", |
@@ -1,2 +0,2 @@ | ||
# @astrojs/node 🔲 | ||
# @astrojs/node | ||
@@ -14,3 +14,3 @@ This adapter allows Astro to deploy your SSR site to Node targets. | ||
## Why Astro Node | ||
## Why @astrojs/node | ||
@@ -21,3 +21,3 @@ If you're using Astro as a static site builder—its behavior out of the box—you don't need an adapter. | ||
[Node](https://nodejs.org/en/) is a JavaScript runtime for server-side code. Frameworks like [Express](https://expressjs.com/) are built on top of it and make it easier to write server applications in Node. This adapter provides access to Node's API and creates a script to run your Astro project that can be utilized in Node applications. | ||
[Node.js](https://nodejs.org/en/) is a JavaScript runtime for server-side code. @astrojs/node can be used either in standalone mode or as middleware for other http servers, such as [Express](https://expressjs.com/). | ||
@@ -47,3 +47,3 @@ ## Installation | ||
```js title="astro.config.mjs" ins={2, 5-6} | ||
```js title="astro.config.mjs" ins={2, 5-8} | ||
import { defineConfig } from 'astro/config'; | ||
@@ -54,13 +54,39 @@ import node from '@astrojs/node'; | ||
output: 'server', | ||
adapter: node(), | ||
adapter: node({ | ||
mode: 'standalone' | ||
}), | ||
}); | ||
``` | ||
## Configuration | ||
This adapter does not expose any configuration options. | ||
@astrojs/node can be configured by passing options into the adapter function. The following options are available: | ||
### Mode | ||
Controls whether the adapter builds to `middleware` or `standalone` mode. | ||
- `middleware` mode allows the built output to be used as middleware for another Node.js server, like Express.js or Fastify. | ||
```js | ||
import { defineConfig } from 'astro/config'; | ||
import nodejs from '@astrojs/node'; | ||
export default defineConfig({ | ||
output: 'server', | ||
adapter: node({ | ||
mode: 'middleware' | ||
}), | ||
}); | ||
``` | ||
- `standalone` mode builds to server that automatically starts with the entry module is run. This allows you to more easily deploy your build to a host without any additional code. | ||
## Usage | ||
After [performing a build](https://docs.astro.build/en/guides/deploy/#building-your-site-locally) there will be a `dist/server/entry.mjs` module that exposes a `handler` function. This works like a [middleware](https://expressjs.com/en/guide/using-middleware.html) function: it can handle incoming requests and respond accordingly. | ||
First, [performing a build](https://docs.astro.build/en/guides/deploy/#building-your-site-locally). Depending on which `mode` selected (see above) follow the appropriate steps below: | ||
### Middleware | ||
### Using a middleware framework | ||
You can use this `handler` with any framework that supports the Node `request` and `response` objects. | ||
The server entrypoint is built to `./dist/server/entry.mjs` by default. This module exports a `handler` function that can be used with any framework that supports the Node `request` and `response` objects. | ||
@@ -80,41 +106,48 @@ For example, with Express: | ||
Note that middleware mode does not do file servering. You'll need to configure your HTTP framework to do that for you. By default the client assets are written to `./dist/client/`. | ||
### Using `http` | ||
### Standalone | ||
This output script does not require you use Express and can work with even the built-in `http` and `https` node modules. The handler does follow the convention calling an error function when either | ||
In standalone mode a server starts when the server entrypoint is run. By default it is built to `./dist/server/entry.mjs`. You can run it with: | ||
- A route is not found for the request. | ||
- There was an error rendering. | ||
```shell | ||
node ./dist/server/entry.mjs | ||
``` | ||
You can use these to implement your own 404 behavior like so: | ||
For standalone mode the server handles file servering in addition to the page and API routes. | ||
```js | ||
import http from 'http'; | ||
import { handler as ssrHandler } from './dist/server/entry.mjs'; | ||
#### HTTPS | ||
http.createServer(function(req, res) { | ||
ssrHandler(req, res, err => { | ||
if(err) { | ||
res.writeHead(500); | ||
res.end(err.toString()); | ||
} else { | ||
// Serve your static assets here maybe? | ||
// 404? | ||
res.writeHead(404); | ||
res.end(); | ||
} | ||
}); | ||
}).listen(8080); | ||
By default the standalone server uses HTTP. This works well if you have a proxy server in front of it that does HTTPS. If you need the standalone server to run HTTPS itself you need to provide your SSL key and certificate. | ||
You can pass the path to your key and certification via the environment variables `SERVER_CERT_PATH` and `SERVER_KEY_PATH`. This is how you might pass them in bash: | ||
```bash | ||
SERVER_KEY_PATH=./private/key.pem SERVER_CERT_PATH=./private/cert.pem node ./dist/server/entry.mjs | ||
``` | ||
## Troubleshooting | ||
### SyntaxError: Named export 'compile' not found | ||
## Configuration | ||
You may see this when running the entry script if it was built with npm or Yarn. This is a [known issue](https://github.com/withastro/astro/issues/4974) that will be fixed in a future release. As a workaround, add `"path-to-regexp"` to the `noExternal` array: | ||
This adapter does not expose any configuration options. | ||
```js title="astro.config.mjs" ins={8-12} | ||
import { defineConfig } from 'astro/config'; | ||
## Troubleshooting | ||
import node from "@astrojs/node"; | ||
For help, check out the `#support` channel on [Discord](https://astro.build/chat). Our friendly Support Squad members are here to help! | ||
export default defineConfig({ | ||
output: "server", | ||
adapter: node(), | ||
vite: { | ||
ssr: { | ||
noExternal: ["path-to-regexp"] | ||
} | ||
} | ||
}); | ||
``` | ||
For more help, check out the `#support` channel on [Discord](https://astro.build/chat). Our friendly Support Squad members are here to help! | ||
You can also check our [Astro Integration Documentation][astro-integration] for more on integrations. | ||
@@ -121,0 +154,0 @@ |
import type { AstroAdapter, AstroIntegration } from 'astro'; | ||
import type { Options, UserOptions } from './types'; | ||
export function getAdapter(): AstroAdapter { | ||
export function getAdapter(options: Options): AstroAdapter { | ||
return { | ||
name: '@astrojs/node', | ||
serverEntrypoint: '@astrojs/node/server.js', | ||
previewEntrypoint: '@astrojs/node/preview.js', | ||
exports: ['handler'], | ||
args: options | ||
}; | ||
} | ||
export default function createIntegration(): AstroIntegration { | ||
export default function createIntegration(userOptions: UserOptions): AstroIntegration { | ||
if(!userOptions?.mode) { | ||
throw new Error(`[@astrojs/node] Setting the 'mode' option is required.`) | ||
} | ||
let needsBuildConfig = false; | ||
let _options: Options; | ||
return { | ||
@@ -16,3 +25,11 @@ name: '@astrojs/node', | ||
'astro:config:done': ({ setAdapter, config }) => { | ||
setAdapter(getAdapter()); | ||
needsBuildConfig = !config.build?.server; | ||
_options = { | ||
...userOptions, | ||
client: config.build.client?.toString(), | ||
server: config.build.server?.toString(), | ||
host: config.server.host, | ||
port: config.server.port, | ||
}; | ||
setAdapter(getAdapter(_options)); | ||
@@ -23,4 +40,11 @@ if (config.output === 'static') { | ||
}, | ||
'astro:build:start': ({ buildConfig }) => { | ||
// Backwards compat | ||
if(needsBuildConfig) { | ||
_options.client = buildConfig.client.toString(); | ||
_options.server = buildConfig.server.toString(); | ||
} | ||
} | ||
}, | ||
}; | ||
} |
@@ -0,6 +1,7 @@ | ||
import type { SSRManifest } from 'astro'; | ||
import type { Options } from './types'; | ||
import { polyfill } from '@astrojs/webapi'; | ||
import type { SSRManifest } from 'astro'; | ||
import { NodeApp } from 'astro/app/node'; | ||
import type { IncomingMessage, ServerResponse } from 'http'; | ||
import type { Readable } from 'stream'; | ||
import middleware from './middleware.js'; | ||
import startServer from './standalone.js'; | ||
@@ -14,47 +15,13 @@ polyfill(globalThis, { | ||
return { | ||
async handler(req: IncomingMessage, res: ServerResponse, next?: (err?: unknown) => void) { | ||
try { | ||
const route = app.match(req); | ||
if (route) { | ||
try { | ||
const response = await app.render(req); | ||
await writeWebResponse(app, res, response); | ||
} catch (err: unknown) { | ||
if (next) { | ||
next(err); | ||
} else { | ||
throw err; | ||
} | ||
} | ||
} else if (next) { | ||
return next(); | ||
} | ||
} catch (err: unknown) { | ||
if (!res.headersSent) { | ||
res.writeHead(500, `Server error`); | ||
res.end(); | ||
} | ||
} | ||
}, | ||
handler: middleware(app) | ||
}; | ||
} | ||
async function writeWebResponse(app: NodeApp, res: ServerResponse, webResponse: Response) { | ||
const { status, headers, body } = webResponse; | ||
if (app.setCookieHeaders) { | ||
const setCookieHeaders: Array<string> = Array.from(app.setCookieHeaders(webResponse)); | ||
if (setCookieHeaders.length) { | ||
res.setHeader('Set-Cookie', setCookieHeaders); | ||
} | ||
export function start(manifest: SSRManifest, options: Options) { | ||
if(options.mode !== 'standalone' || process.env.ASTRO_NODE_AUTOSTART === 'disabled') { | ||
return; | ||
} | ||
res.writeHead(status, Object.fromEntries(headers.entries())); | ||
if (body) { | ||
for await (const chunk of body as unknown as Readable) { | ||
res.write(chunk); | ||
} | ||
} | ||
res.end(); | ||
const app = new NodeApp(manifest); | ||
startServer(app, options); | ||
} |
@@ -13,3 +13,3 @@ import nodejs from '../dist/index.js'; | ||
output: 'server', | ||
adapter: nodejs(), | ||
adapter: nodejs({ mode: 'middleware' }), | ||
}); | ||
@@ -16,0 +16,0 @@ await fixture.build(); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 5 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
45738
33
719
159
2
6
9
3
+ Addedsend@^0.18.0
+ Addeddebug@2.6.9(transitive)
+ Addeddepd@2.0.0(transitive)
+ Addeddestroy@1.2.0(transitive)
+ Addedee-first@1.1.1(transitive)
+ Addedencodeurl@1.0.2(transitive)
+ Addedescape-html@1.0.3(transitive)
+ Addedetag@1.8.1(transitive)
+ Addedfresh@0.5.2(transitive)
+ Addedhttp-errors@2.0.0(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedmime@1.6.0(transitive)
+ Addedms@2.0.02.1.3(transitive)
+ Addedon-finished@2.4.1(transitive)
+ Addedrange-parser@1.2.1(transitive)
+ Addedsend@0.18.0(transitive)
+ Addedsetprototypeof@1.2.0(transitive)
+ Addedstatuses@2.0.1(transitive)
+ Addedtoidentifier@1.0.1(transitive)