Skip to content

Commit

Permalink
feat: @presta/utils, @presta/adapter-node
Browse files Browse the repository at this point in the history
fixes #155
  • Loading branch information
estrattonbailey committed Jan 19, 2022
1 parent 2f3017b commit f6f9f1e
Show file tree
Hide file tree
Showing 60 changed files with 780 additions and 264 deletions.
9 changes: 9 additions & 0 deletions .changeset/rare-items-leave.md
@@ -0,0 +1,9 @@
---
'@presta-example/node': minor
'@presta/adapter-node': minor
'@presta/adapter-vercel': minor
'presta': minor
'@presta/utils': minor
---

Factors out `@presta/utils`, refactors packages to use utils, introduces `@presta/adapter-node`
4 changes: 4 additions & 0 deletions examples/node/.gitignore
@@ -0,0 +1,4 @@
node_modules
build
.output
vercel.json
7 changes: 7 additions & 0 deletions examples/node/README.md
@@ -0,0 +1,7 @@
# Example: Markdown

To run: `npm start`. To build: `npm run build`.

## License

MIT License © [Sure Thing](https://github.com/sure-thing)
17 changes: 17 additions & 0 deletions examples/node/components/Nav.ts
@@ -0,0 +1,17 @@
export function Nav({ currentPath }: { currentPath?: string } = {}) {
return `
<div class='pb6 f aic'>
${[
{ href: '/', title: 'Home' },
{ href: '/about', title: 'About' },
{ href: '/contact', title: 'Contact' },
{ href: '/some-page', title: 'Some Page' },
{ href: '/not/found', title: '404' },
]
.map(
(link) => `<a href='${link.href}' class='mr4 ${currentPath === link.href ? 'active' : ''}'>${link.title}</a>`
)
.join('')}
</div>
`
}
17 changes: 17 additions & 0 deletions examples/node/package.json
@@ -0,0 +1,17 @@
{
"private": true,
"name": "@presta-example/node",
"scripts": {
"start": "presta dev",
"build": "presta"
},
"dependencies": {
"@presta/html": "workspace:^0.2.1",
"presta": "workspace:^0.43.10"
},
"devDependencies": {
"@presta/adapter-node": "workspace:^0.0.0",
"micromark": "^3.0.5"
},
"version": null
}
23 changes: 23 additions & 0 deletions examples/node/pages/About.ts
@@ -0,0 +1,23 @@
import { Event } from 'presta'
import { html } from '@presta/html'

import { Nav } from '../components/Nav'
import { link } from '../utils/documentProperties'

export function getStaticPaths() {
return ['/about']
}

export function handler(event: Event) {
return html({
head: {
link,
},
body: `
<div class='p10'>
${Nav({ currentPath: event.path })}
<h1>About</h1>
</div>
`,
})
}
9 changes: 9 additions & 0 deletions examples/node/pages/Api.ts
@@ -0,0 +1,9 @@
import { Event } from 'presta'

export const route = '/api/*'

export function handler(event: Event) {
return {
json: event,
}
}
23 changes: 23 additions & 0 deletions examples/node/pages/Home.ts
@@ -0,0 +1,23 @@
import { Event } from 'presta'
import { html } from '@presta/html'

import { Nav } from '../components/Nav'
import { link } from '../utils/documentProperties'

export function getStaticPaths() {
return ['/']
}

export function handler(event: Event) {
return html({
head: {
link,
},
body: `
<div class='p10'>
${Nav({ currentPath: event.path })}
<h1>Home</h1>
</div>
`,
})
}
21 changes: 21 additions & 0 deletions examples/node/pages/Page.ts
@@ -0,0 +1,21 @@
import { Event } from 'presta'
import { html } from '@presta/html'

import { Nav } from '../components/Nav'
import { link } from '../utils/documentProperties'

export const route = '*'

export function handler(event: Event) {
return html({
head: {
link,
},
body: `
<div class='p10'>
${Nav({ currentPath: event.path })}
<h1>Page ${event.path}</h1>
</div>
`,
})
}
4 changes: 4 additions & 0 deletions examples/node/presta.config.js
@@ -0,0 +1,4 @@
import node from '@presta/adapter-node'

export const files = 'pages/*.ts'
export const plugins = [node()]
3 changes: 3 additions & 0 deletions examples/node/public/style.css
@@ -0,0 +1,3 @@
.active {
color: #ff4567;
}
4 changes: 4 additions & 0 deletions examples/node/utils/documentProperties.ts
@@ -0,0 +1,4 @@
export const link = [
{ rel: 'stylesheet', href: 'https://unpkg.com/svbstrate@5.1.0/svbstrate.css' },
{ rel: 'stylesheet', href: '/style.css' },
]
2 changes: 2 additions & 0 deletions packages/adapter-node/.gitignore
@@ -0,0 +1,2 @@
node_modules
dist
6 changes: 6 additions & 0 deletions packages/adapter-node/.npmignore
@@ -0,0 +1,6 @@
/__tests__
/scripts
.gitignore
test.ts
tsconfig.json
index.ts
24 changes: 24 additions & 0 deletions packages/adapter-node/README.md
@@ -0,0 +1,24 @@
# @presta/adapter-node

Node deployment adapter plugin for Presta.

## Usage

Simply add to your Presta config (defaults to `presta.config.js`):

```javascript
import node from '@presta/adapter-node'

export const plugins = [node({ port: 4000 })]
```

This will generate a `server.js` file in your output directory. You can run it
like any other node server:

```bash
node build/server.js
```

## License

MIT License © [Sure Thing](https://github.com/sure-thing)
63 changes: 63 additions & 0 deletions packages/adapter-node/lib/__tests__/adapter.ts
@@ -0,0 +1,63 @@
import path from 'path'
import { suite } from 'uvu'
import * as assert from 'uvu/assert'
import { afix } from 'afix'

const test = suite('@presta/adapter-node - adapter')

test('adapter', async () => {
let plan = 0

const route = '/url'
const fixture = afix({
fn: ['build/functions/fn.js', 'export function handler() {}'],
})

const { adapter } = require('proxyquire')('../adapter', {
sirv: (dir: string) => {
plan++
assert.equal(dir, path.join(fixture.root, 'build/static'))
},
polka() {
return {
use() {
return {
all(r: string, fn: any) {
plan++
assert.equal(r, route)
fn()
},
listen() {
plan++
},
}
},
}
},
'@presta/utils/requestToEvent': {
requestToEvent() {
plan++
return {}
},
},
'@presta/utils/sendServerlessResponse': {
sendServerlessResponse() {
plan++
},
},
})

adapter(
{
staticOutput: path.join(fixture.root, 'build/static'),
functionsManifest: {
[route]: fixture.files.fn.path,
},
},
{ port: 4000 }
)

assert.equal(plan, 4)
})

test.run()
49 changes: 49 additions & 0 deletions packages/adapter-node/lib/__tests__/index.ts
@@ -0,0 +1,49 @@
import fs from 'fs'
import path from 'path'
import { suite } from 'uvu'
import * as assert from 'uvu/assert'
import { afix } from 'afix'

const test = suite('@presta/adapter-node')

test('onPostBuild', async () => {})

test('createPlugin', async () => {
let plan = 0
const fixture = afix({})
const output = path.join(fixture.root, 'build')

const { default: createPlugin } = require('proxyquire')('../index', {
esbuild: {
build() {
plan++
},
},
})

new Promise(async (y, n) => {
createPlugin()(
// @ts-ignore
{},
{
async onPostBuild(fn: any) {
plan++
try {
await fn({ output }, { port: 4000 })
y(1)
} catch (e) {
n(e)
}
},
}
)
})

const server = fs.readFileSync(path.join(output, 'server.js'), 'utf8')

assert.equal(plan, 2)
assert.ok(server.includes(output))
assert.ok(server.includes('4000'))
})

test.run()
33 changes: 33 additions & 0 deletions packages/adapter-node/lib/adapter.ts
@@ -0,0 +1,33 @@
/**
* THIS IS PROD CODE, BE CAREFUL WHAT YOU ADD TO THIS FILE
*/

import path from 'path'
import polka from 'polka'
import sirv from 'sirv'
import { Handler } from 'lambda-types'
import { requestToEvent } from '@presta/utils/requestToEvent'
import { sendServerlessResponse } from '@presta/utils/sendServerlessResponse'
import { HookPostBuildPayload } from 'presta'

import { Options } from './types'

export function adapter(props: HookPostBuildPayload, options: Options) {
const assets = sirv(path.resolve(__dirname, props.staticOutput))
const app = polka().use(assets)

for (const route in props.functionsManifest) {
app.all(route, async (req, res) => {
const event = await requestToEvent(req)
const { handler } = require(props.functionsManifest[route]) as { handler: Handler }
// @ts-ignore
const response = await handler(event, {})
// @ts-ignore
sendServerlessResponse(res, response)
})
}

app.listen(options.port, () => {
console.log(`presta server running on http://localhost:${options.port}`)
})
}
56 changes: 56 additions & 0 deletions packages/adapter-node/lib/index.ts
@@ -0,0 +1,56 @@
import fs from 'fs'
import path from 'path'
import { mkdir } from 'mk-dirs/sync'
import { createPlugin, logger, HookPostBuildPayload } from 'presta'
import { build as esbuild } from 'esbuild'
import { timer } from '@presta/utils/timer'

import { Options } from './types'

export async function onPostBuild(props: HookPostBuildPayload, options: Options) {
const { output } = props
const filepath = path.join(output, 'server.js')

mkdir(output)

fs.writeFileSync(
filepath,
`import { adapter } from '@presta/adapter-node/dist/adapter';
export default adapter(${JSON.stringify(props)}, ${JSON.stringify(options)});`,
'utf8'
)

await esbuild({
entryPoints: [filepath],
outdir: output,
platform: 'node',
target: ['node12'],
minify: true,
allowOverwrite: true, // it will be overwritten
format: 'cjs',
})
}

export default createPlugin((options: Partial<Options> = {}) => {
const opts = Object.assign({ port: 4000 }, options)
const time = timer()

return async function plugin(config, hooks) {
logger.debug({
label: '@presta/adapter-node',
message: `init`,
})

hooks.onPostBuild(async (props) => {
/* c8 ignore start */
await onPostBuild(props, opts)

logger.info({
label: '@presta/adapter-node',
message: `complete`,
duration: time(),
})
/* c8 ignore stop */
})
}
})

0 comments on commit f6f9f1e

Please sign in to comment.