-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feature/issue 1142 AWS Adapter #1419
Draft
thescientist13
wants to merge
1
commit into
master
Choose a base branch
from
feature/issue-1142-aws-adapter
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
# @greenwood/plugin-adapter-aws | ||
|
||
## Overview | ||
|
||
Enables usage of [AWS](https://aws.amazon.com/) hosting for API routes and SSR pages. For more information and complete docs on Greenwood, please visit [our website](https://www.greenwoodjs.dev). | ||
|
||
> This package assumes you already have `@greenwood/cli` installed. | ||
|
||
## Features | ||
|
||
This plugin "adapts" SSR pages and API routes to be compatible with AWS Lambda and ready to use with IaC (Infrastructure as Code) tools like [SST](https://sst.dev/) and [Architect](https://arc.codes/). | ||
|
||
> _**Note:** You can see a working example of this plugin [here](https://github.com/ProjectEvergreen/greenwood-demo-adapter-aws)_. | ||
|
||
|
||
## Installation | ||
|
||
You can use your favorite JavaScript package manager to install this package. | ||
|
||
```bash | ||
# npm | ||
$ npm i -D @greenwood/plugin-adapter-aws | ||
|
||
# yarn | ||
$ yarn add @greenwood/plugin-adapter-aws --dev | ||
|
||
# pnpm | ||
$ pnpm add -D @greenwood/plugin-adapter-aws | ||
``` | ||
|
||
## Usage | ||
|
||
Add this plugin to your _greenwood.config.js_: | ||
|
||
```javascript | ||
import { greenwoodPluginAdapterAws } from '@greenwood/plugin-adapter-aws'; | ||
|
||
export default { | ||
// ... | ||
|
||
plugins: [ | ||
greenwoodPluginAdapterAws() | ||
] | ||
} | ||
``` | ||
|
||
## Options | ||
|
||
TODO | ||
|
||
## Caveats | ||
|
||
TODO |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
{ | ||
"name": "@greenwood/plugin-adapter-aws", | ||
"version": "0.31.1", | ||
"description": "A Greenwood plugin for supporting AWS serverless and edge runtimes.", | ||
"repository": "https://github.com/ProjectEvergreen/greenwood", | ||
"homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-adapter-aws", | ||
"author": "Owen Buckley <owen@thegreenhouse.io>", | ||
"license": "MIT", | ||
"keywords": [ | ||
"Greenwood", | ||
"Static Site Generator", | ||
"Server Side Rendering", | ||
"Full Stack Web Development", | ||
"Serverless", | ||
"Web Components", | ||
"AWS", | ||
"Edge" | ||
], | ||
"main": "src/index.js", | ||
"type": "module", | ||
"files": [ | ||
"src/" | ||
], | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"peerDependencies": { | ||
"@greenwood/cli": "^0.31.0" | ||
}, | ||
"devDependencies": { | ||
"@greenwood/cli": "^0.31.1" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
import fs from "fs/promises"; | ||
import path from "path"; | ||
import { checkResourceExists } from "@greenwood/cli/src/lib/resource-utils.js"; | ||
|
||
// https://vercel.com/docs/functions/serverless-functions/runtimes/node-js#node.js-helpers | ||
function generateOutputFormat(id, type) { | ||
const handlerAlias = "$handler"; | ||
const path = type === "page" ? `${id}.route` : id; | ||
|
||
return ` | ||
import { handler as ${handlerAlias} } from './${path}.js'; | ||
export async function handler (event, context) { | ||
const { body, headers = {}, rawPath = '', rawQueryString = '', routeKey = '' } = event; | ||
const method = routeKey.split(' ')[0]; | ||
const queryParams = rawQueryString === '' ? '' : \`?\${rawQueryString}\`; | ||
const contentType = headers['content-type'] || ''; | ||
let format = body; | ||
|
||
if (['GET', 'HEAD'].includes(method.toUpperCase())) { | ||
format = null | ||
} else if (contentType.includes('application/x-www-form-urlencoded') && event.isBase64Encoded) { | ||
const formData = new FormData(); | ||
const formParams = new URLSearchParams(atob(body)); | ||
|
||
formParams.forEach((value, key) => { | ||
formData.append(key, value); | ||
}); | ||
|
||
// when using FormData, let Request set the correct headers | ||
// or else it will come out as multipart/form-data | ||
// https://stackoverflow.com/a/43521052/417806 | ||
format = formData; | ||
delete headers['content-type']; | ||
} else if(contentType.includes('application/json')) { | ||
format = JSON.stringify(body); | ||
} | ||
|
||
const req = new Request(new URL(\`\${rawPath}\${queryParams}\`, \`http://\${headers.host}\`), { | ||
body: format, | ||
headers: new Headers(headers), | ||
method | ||
}); | ||
|
||
// https://docs.aws.amazon.com/lambda/latest/dg/services-apigateway.html#apigateway-example-event | ||
const res = await $handler(req); | ||
return { | ||
"body": await res.text(), | ||
"statusCode": res.status, | ||
"headers": Object.fromEntries(res.headers) | ||
} | ||
} | ||
`; | ||
} | ||
|
||
async function setupFunctionBuildFolder(id, outputType, outputRoot) { | ||
const outputFormat = generateOutputFormat(id, outputType); | ||
|
||
await fs.mkdir(outputRoot, { recursive: true }); | ||
await fs.writeFile(new URL("./index.js", outputRoot), outputFormat); | ||
await fs.writeFile( | ||
new URL("./package.json", outputRoot), | ||
JSON.stringify({ | ||
type: "module", | ||
}), | ||
); | ||
} | ||
|
||
async function awsAdapter(compilation) { | ||
const { outputDir, projectDirectory } = compilation.context; | ||
const { basePath } = compilation.config; | ||
const adapterOutputUrl = new URL("./.aws-output/", projectDirectory); | ||
const ssrPages = compilation.graph.filter((page) => page.isSSR); | ||
const apiRoutes = compilation.manifest.apis; | ||
|
||
if (!(await checkResourceExists(adapterOutputUrl))) { | ||
await fs.mkdir(adapterOutputUrl, { recursive: true }); | ||
} | ||
|
||
for (const page of ssrPages) { | ||
const outputType = "page"; | ||
const { id, outputHref } = page; | ||
const outputRoot = new URL(`./routes/${basePath}/${id}/`, adapterOutputUrl); | ||
const chunks = (await fs.readdir(outputDir)).filter( | ||
(file) => file.startsWith(`${id}.route.chunk`) && file.endsWith(".js"), | ||
); | ||
|
||
await setupFunctionBuildFolder(id, outputType, outputRoot); | ||
|
||
// handle user's actual route entry file | ||
await fs.cp( | ||
new URL(outputHref), | ||
new URL(`./${outputHref.replace(outputDir.href, "")}`, outputRoot), | ||
{ recursive: true }, | ||
); | ||
|
||
// and any (URL) chunks for the page | ||
for (const chunk of chunks) { | ||
await fs.cp(new URL(`./${chunk}`, outputDir), new URL(`./${chunk}`, outputRoot), { | ||
recursive: true, | ||
}); | ||
} | ||
} | ||
|
||
for (const [key, value] of apiRoutes.entries()) { | ||
const outputType = "api"; | ||
const { id, outputHref } = apiRoutes.get(key); | ||
const outputRoot = new URL(`.${basePath}/api/${id}/`, adapterOutputUrl); | ||
const { assets = [] } = value; | ||
|
||
await setupFunctionBuildFolder(id, outputType, outputRoot); | ||
|
||
await fs.cp(new URL(outputHref), new URL(`./${id}.js`, outputRoot), { recursive: true }); | ||
|
||
for (const asset of assets) { | ||
const name = path.basename(asset); | ||
|
||
await fs.cp(new URL(asset), new URL(`./${name}`, outputRoot), { recursive: true }); | ||
} | ||
} | ||
} | ||
|
||
const greenwoodPluginAdapterAws = () => [ | ||
{ | ||
type: "adapter", | ||
name: "plugin-adapter-aws", | ||
provider: (compilation) => { | ||
return async () => { | ||
await awsAdapter(compilation); | ||
}; | ||
}, | ||
}, | ||
]; | ||
|
||
export { greenwoodPluginAdapterAws }; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
woops!