Skip to content

Commit

Permalink
feat: deployment adapter api (#47)
Browse files Browse the repository at this point in the history
Prepares work for #30 
Adds `@lazarv/react-server-adapter-core` package to implement the
Deployment Adapter API
Updates Vercel adapter to use the Adapter API
  • Loading branch information
lazarv authored Sep 23, 2024
1 parent 640534a commit 94b8351
Show file tree
Hide file tree
Showing 15 changed files with 915 additions and 503 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/publish-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ jobs:
if: contains(needs.changed.outputs.all_changed_files, 'packages/react-server/')
run: pnpm dlx [email protected] publish --compact --pnpm ./packages/react-server --comment=update

- name: Publish @lazarv/react-server-adapter-core
if: contains(needs.changed.outputs.all_changed_files, 'packages/react-server-adapter-core/')
run: pnpm dlx [email protected] publish --compact --pnpm ./packages/react-server-adapter-core --comment=update

- name: Publish @lazarv/react-server-adapter-vercel
if: contains(needs.changed.outputs.all_changed_files, 'packages/react-server-adapter-vercel/')
run: pnpm dlx [email protected] publish --compact --pnpm ./packages/react-server-adapter-vercel --comment=update
20 changes: 18 additions & 2 deletions .github/workflows/release-experimental.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ jobs:
echo "VERSION=$VERSION" >> $GITHUB_ENV
- name: Prepare @lazarv/react-server
id: prepare-react-server
if: contains(needs.changed.outputs.all_changed_files, 'packages/react-server')
working-directory: ./packages/react-server
run: |
Expand All @@ -75,7 +76,7 @@ jobs:
- name: Publish @lazarv/react-server
id: publish-react-server
if: contains(needs.changed.outputs.all_changed_files, 'packages/react-server')
if: steps.prepare-react-server.outcome == 'success'
working-directory: ./packages/react-server
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
Expand All @@ -88,14 +89,29 @@ jobs:
run: |
gh release create "v${{ env.VERSION }}" --generate-notes
- name: Prepare @lazarv/react-server-adapter-core
id: prepare-react-server-adapter-core
if: contains(needs.changed.outputs.all_changed_files, 'packages/react-server-adapter-core')
working-directory: ./packages/react-server-adapter-core
run: |
jq --arg new_version "${{ env.VERSION }}" '.version = $new_version' package.json > tmp.json && mv tmp.json package.json
- name: Publish @lazarv/react-server-adapter-core
if: steps.prepare-react-server-adapter-core.outcome == 'success'
working-directory: ./packages/react-server-adapter-core
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
run: pnpm publish --access=public --no-git-checks

- name: Prepare @lazarv/react-server-adapter-vercel
id: prepare-react-server-adapter-vercel
if: contains(needs.changed.outputs.all_changed_files, 'packages/react-server-adapter-vercel')
working-directory: ./packages/react-server-adapter-vercel
run: |
jq --arg new_version "${{ env.VERSION }}" '.version = $new_version' package.json > tmp.json && mv tmp.json package.json
- name: Publish @lazarv/react-server-adapter-vercel
if: contains(needs.changed.outputs.all_changed_files, 'packages/react-server-adapter-vercel')
if: steps.prepare-react-server-adapter-vercel.outcome == 'success'
working-directory: ./packages/react-server-adapter-vercel
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
Expand Down
145 changes: 145 additions & 0 deletions docs/src/pages/en/(pages)/deploy/api.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
---
title: Adapter API
category: Deploy
order: 100
---

import Link from "../../../../components/Link"

# Adapter API

By using the Adapter API available in the `@lazarv/react-server-adapter-core` package, you can easily create a deployment adapter for any deployment target.

## Define the adapter handler

The adapter handler is a function that you need to implement to handle the deployment of your application. It will be called by the build process and it will receive the information needed to prepare the application for deployment.

An example of an adapter handler implementation can be found in the [Vercel adapter implementation](https://github.com/lazarv/react-server/blob/main/packages/react-server-adapter-vercel/index.mjs).

You can start by copying the Vercel adapter implementation and then modify it to fit your needs.

You need to export the adapter handler function from the file and then use the `createAdapter` function to create the adapter instance.

You also need to export default a function that will be used to create the adapter instance when adapter options are provided by the user in the `react-server.config.js` file.

```js
import { createAdapter } from "@lazarv/react-server-adapter-core";

export const adapter = createAdapter({
name: "Vercel",
outDir: ".vercel",
outStaticDir: "static",
handler: async ({ adapterOptions, files, copy, config, reactServerDir, reactServerOutDir, root, options }) => {
// Your adapter handler implementation
},
deploy: {
command: "vercel",
args: ["deploy", "--prebuilt"],
},
});

export default function defineConfig(adapterOptions) {
return async (_, root, options) => adapter(adapterOptions, root, options);
}
```

You need to pass adapter properties to the `createAdapter` function to configure the adapter. These properties are:

`name`: The name of the adapter.

`outDir`: The directory where the adapter will output the deployment configuration.

`outStaticDir`: The directory where the static files will be output. This is optional. When provided, the adapter will copy the static files to the output directory.

`handler`: The adapter handler function.

`deploy`: The deployment command and arguments. This is optional. When provided, the adapter will show what command the developer needs to run to deploy the application after it has been built. If the `--deploy` flag is provided during the build, the adapter will run this command.

## Adapter handler

The adapter handler function will receive the following properties:

- [ ] `adapterOptions`: The adapter options passed from the `react-server.config.js` file.
- [ ] `files`: The files object contains the static files, assets, client files, public files, server files and the dependencies.
- [ ] `copy`: The copy object contains the functions to copy the files to the output directory.
- [ ] `config`: The configuration object contains the configuration of the application.
- [ ] `reactServerDir`: The path to the directory where the build output is located.
- [ ] `reactServerOutDir`: The directory name where the build output is located.
- [ ] `root`: The entry point of the application.
- [ ] `options`: The options object contains the options passed from the CLI.

The `files` object contains the following functions:

- [ ] `static`: The function to get the static files.
- [ ] `assets`: The function to get the assets files.
- [ ] `client`: The function to get the client files.
- [ ] `public`: The function to get the public files.
- [ ] `server`: The function to get the server files.
- [ ] `dependencies`: The function to get the dependencies.
- [ ] `all`: The function to get all the static files (static + assets + client + public).

```js
const staticFiles = await files.static();
```

The `copy` object contains the following functions:

- [ ] `static`: The function to copy the static files.
- [ ] `assets`: The function to copy the assets files.
- [ ] `client`: The function to copy the client files.
- [ ] `public`: The function to copy the public files.
- [ ] `server`: The function to copy the server files.
- [ ] `dependencies`: The function to copy the dependencies.

```js
await copy.server(outServerDir);
```

## Helper functions

### banner

Shows a banner in the console.

```js
banner("building serverless functions");
```

### message

Shows a message in the console. Primary and secondary colors used to show the action and the message.

```js
message("creating", "index.func module");
```

### success

Shows a success message in the console.

```js
success("index.func serverless function initialized.");
```

### clearDirectory

Clears a directory.

```js
await clearDirectory(outServerDir);
```

### writeJSON

Writes a JSON file.

```js
await writeJSON(join(outServerDir, ".vc-config.json"), {
runtime: "nodejs20.x",
handler: "index.mjs",
launcherType: "Nodejs",
shouldAddHelpers: true,
supportsResponseStreaming: true,
...adapterOptions?.serverlessFunctions?.index,
});
```
4 changes: 3 additions & 1 deletion docs/src/pages/en/deploy.(index).mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ When you're finished with your app, you can deploy it to the web. The framework

You will learn how to use [adapters](/deploy/adapters) to configure your app for different deployment environments.

You can also learn how to deploy your app to different platforms using the available adapters. The framework provides adapters for [Vercel](/deploy/vercel) right now, but there are more coming soon to deploy your app to Netlify, Cloudflare Pages, Cloudflare Workers, or Serverless Stack.
You can also learn how to deploy your app to different platforms using the available adapters. The framework provides adapters for [Vercel](/deploy/vercel) right now, but there are more coming soon to deploy your app to Netlify, Cloudflare Pages, Cloudflare Workers, or Serverless Stack.

Find more information about how to implement deployment adapters in the [Adapter API](/deploy/api) section.
21 changes: 21 additions & 0 deletions packages/react-server-adapter-core/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2023 Viktor Lázár

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
5 changes: 5 additions & 0 deletions packages/react-server-adapter-core/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# @lazarv/react-server-adapter-core

Core library to develop deployment adapters for [@lazarv/react-server](https://npmjs.com/package/@lazarv/react-server).

See details at https://react-server.dev.
81 changes: 81 additions & 0 deletions packages/react-server-adapter-core/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
declare module "@lazarv/react-server-adapter-core" {
export interface Adapter<T = any> {
(adapterOptions: T, root: string, options: any): Promise<void>;
}

export function createAdapter<T = any>(options: {
name: string;
outDir: string;
outStaticDir?: string;
handler: (options: {
adapterOptions: T;
files: {
static: () => Promise<string[]>;
assets: () => Promise<string[]>;
client: () => Promise<string[]>;
public: () => Promise<string[]>;
server: () => Promise<string[]>;
dependencies: (
adapterFiles: string[]
) => Promise<{ src: string; dest: string }[]>;
all: () => Promise<string[]>;
};
copy: {
static: (out: string) => Promise<void>;
assets: (out: string) => Promise<void>;
client: (out: string) => Promise<void>;
public: (out: string) => Promise<void>;
server: (out: string) => Promise<void>;
dependencies: (out: string, adapterFiles: string[]) => Promise<void>;
};
config: Record<string, any>;
reactServerDir: string;
reactServerOutDir: string;
root: string;
options: any;
}) => Promise<void>;
}): Adapter<T>;

export function banner(message: string): void;
export function clearDirectory(dir: string): Promise<void>;
export function copy(src: string, dest: string): Promise<void>;
export function copyMessage(
file: string,
srcDir: string,
destDir: string,
reactServerOutDir: string
): void;
export function copyFiles(
message: string,
files: string[],
srcDir: string,
destDir: string,
reactServerOutDir: string
): Promise<void>;
export function message(primary: string, secondary?: string): void;
export function success(message: string): void;
export function writeJSON(
path: string,
data: Record<string, any>
): Promise<void>;
export function clearProgress(): void;
export function createProgress(
message: string,
total: number,
start?: number
): void;
export function progress(options: {
message: string;
files: string[];
onProgress: (file: string) => Promise<void>;
onFile: (file: string) => Promise<void>;
}): Promise<void>;
export function getConfig(): Record<string, any>;
export function getPublicDir(): string;
export function getFiles(pattern: string, srcDir?: string): Promise<string[]>;
export function getDependencies(
adapterFiles: string[],
reactServerDir: string
): Promise<string[]>;
export function spawnCommand(command: string, args: string[]): Promise<void>;
}
Loading

0 comments on commit 94b8351

Please sign in to comment.