Skip to content
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

feat(gatsby-plugin-functions): Add the ability to run functions locally and on Gatsby Cloud #30192

Merged
merged 76 commits into from
Apr 16, 2021
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
098a209
Add new plugin
sidharthachatterjee Nov 16, 2020
7576e2a
Add functions plugin by default
sidharthachatterjee Nov 16, 2020
df0496f
Add stub
sidharthachatterjee Nov 16, 2020
5d0c3e8
Bump up package.json
sidharthachatterjee Nov 16, 2020
9febad5
wip
sidharthachatterjee Nov 24, 2020
0597a5f
Compile all functions found in build
sidharthachatterjee Nov 24, 2020
2536188
Add plugin by default only if GATSBY_EXPERIMENT_FUNCTIONS is set
sidharthachatterjee Nov 24, 2020
29159df
Move to public for now
sidharthachatterjee Nov 25, 2020
eec2a09
Move to functions dir
sidharthachatterjee Nov 25, 2020
6470ee4
Run a dev server for functions
sidharthachatterjee Nov 30, 2020
9635aa1
wip
sidharthachatterjee Nov 30, 2020
4a59a47
Move to onPreBootstrap
sidharthachatterjee Jan 5, 2021
85d7cfa
Add target node
abhiaiyer91 Jan 11, 2021
ef07abd
Bump up package.json
sidharthachatterjee Jan 7, 2021
72877bc
Bump up package.json
sidharthachatterjee Jan 12, 2021
daf3b04
Add support for env vars
sidharthachatterjee Feb 19, 2021
32bd3d6
Publish 0.1.0-4
sidharthachatterjee Feb 19, 2021
a2ae24e
Bump up peerDependencies
sidharthachatterjee Mar 2, 2021
c1a2a05
Update yarn lock
sidharthachatterjee Mar 11, 2021
3d9ecbc
Use a parameter in express middleware and ignore extension (#30222)
sidharthachatterjee Mar 12, 2021
6067919
Merge remote-tracking branch 'upstream/master' into feat/functions
Mar 16, 2021
2aacbcc
Publish 0.1.0-6
sidharthachatterjee Mar 16, 2021
1587dc2
Set a default functions directory path (#30277)
sidharthachatterjee Mar 17, 2021
f0ab110
feat(functions): Add body parser
julienp Mar 21, 2021
b5324f9
Require files from the .cache directory so user can run es/commonjs (…
abhiaiyer91 Mar 25, 2021
21e1841
Publish 0.1.0-7
sidharthachatterjee Mar 25, 2021
6ce15fa
Prefer directory from program in state
sidharthachatterjee Apr 2, 2021
b2c4aa8
Log invocations as verbose
sidharthachatterjee Apr 2, 2021
bd0acc5
Add timeout constant
sidharthachatterjee Apr 2, 2021
4cfde03
Add internal plugin
sidharthachatterjee Apr 2, 2021
cc98e76
Load functions internal plugin
sidharthachatterjee Apr 2, 2021
17730bd
Load functions during initialize and stick em in redux
sidharthachatterjee Apr 2, 2021
80a9882
Merge branch 'master' into feat/functions
sidharthachatterjee Apr 7, 2021
56176d3
Clean up comments
sidharthachatterjee Apr 7, 2021
eace7e8
Move initialization code back to plugin
sidharthachatterjee Apr 7, 2021
5c1f96e
Add experiment for Gatsby Functions
sidharthachatterjee Apr 7, 2021
6b8d414
Revert yarn changes
sidharthachatterjee Apr 7, 2021
b325d12
Remove plugin package
sidharthachatterjee Apr 7, 2021
88d76a1
Do not extract comments
sidharthachatterjee Apr 7, 2021
311c9ec
Update yarn.lock
sidharthachatterjee Apr 7, 2021
d554093
Update packages/gatsby/src/internal-plugins/functions/gatsby-node.ts
julienp Apr 13, 2021
63bc54c
chore(functions): Remove console.logs
julienp Apr 13, 2021
f08d0ee
chore(functions): Remove unused urlResolve import
julienp Apr 13, 2021
5d6d072
Merge remote-tracking branch 'upstream/master' into feat/functions
Apr 13, 2021
7fbac4a
get internal plugin functions working + some cleanups
KyleAMathews Apr 13, 2021
4af22f9
Fix regex per @jamo's suggestion + update yarn.lock
KyleAMathews Apr 13, 2021
324bd99
remove accidentally committed file
KyleAMathews Apr 13, 2021
92e05c9
globby isn't a dependency of gatsby
KyleAMathews Apr 13, 2021
2163f08
fix(functions): End request with status 500 on function error
julienp Apr 13, 2021
aad744d
Hot reload functions
KyleAMathews Apr 13, 2021
c5c61ec
Load env variables from .env.* files
KyleAMathews Apr 13, 2021
aaf53f9
Resolve result of function so we can catch errors from async functions
KyleAMathews Apr 13, 2021
d2d7af0
Log webpack warnings/errors when compiling to the terminal
KyleAMathews Apr 13, 2021
02fcfbe
Log how long function took to execute
KyleAMathews Apr 13, 2021
4bec919
reload watch when .env files change & use all process.env vars
KyleAMathews Apr 13, 2021
ac9e55d
Allow for arbitrarily deep functions
KyleAMathews Apr 13, 2021
a26ac14
Detect new functions and incorporate
KyleAMathews Apr 13, 2021
5cc3661
Log when functions rebuild or we restart the watcher
KyleAMathews Apr 13, 2021
ae8f46f
List functions on dev-404-page
KyleAMathews Apr 13, 2021
0336739
Only show new API instructions when trying to reach an API route + ad…
KyleAMathews Apr 13, 2021
7282d1f
Fix linting
KyleAMathews Apr 14, 2021
6caff93
put new code behind conditional
KyleAMathews Apr 14, 2021
4b5aa4e
Properly close webpack watcher + handle deleting functions
KyleAMathews Apr 14, 2021
10226e4
Ignore non-js extensions (so make typescript work)
KyleAMathews Apr 14, 2021
1ee2d24
Write out manifest file so can serve functions from 'gatsby serve'
KyleAMathews Apr 14, 2021
4a9fffd
Address @lekoart's comments
KyleAMathews Apr 15, 2021
3cba097
Add default type for SiteFunction & fix small bugs when site doesn't …
KyleAMathews Apr 15, 2021
9c164f7
Add initial suite of tests
KyleAMathews Apr 15, 2021
771fb62
Update snapshots
KyleAMathews Apr 15, 2021
b2f2af2
Fix more tests
KyleAMathews Apr 15, 2021
73dceb4
Add functions tests to circleci setup
KyleAMathews Apr 15, 2021
10cd117
Only parse once
KyleAMathews Apr 16, 2021
42de7d5
Small tweaks
KyleAMathews Apr 16, 2021
8405b31
add comment
KyleAMathews Apr 16, 2021
af36e09
Fix typescript support
KyleAMathews Apr 16, 2021
2b7c7cf
Add tests for apis with special characters h/t @lekoarts
KyleAMathews Apr 16, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147,272 changes: 0 additions & 147,272 deletions .yarn/releases/yarn-1.21.0.js

This file was deleted.

6 changes: 0 additions & 6 deletions .yarnrc

This file was deleted.

3 changes: 3 additions & 0 deletions packages/gatsby-plugin-functions/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"presets": [["babel-preset-gatsby-package", { "browser": true }]]
sidharthachatterjee marked this conversation as resolved.
Show resolved Hide resolved
}
33 changes: 33 additions & 0 deletions packages/gatsby-plugin-functions/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Logs
logs
*.log

# Runtime data
pids
*.pid
*.seed

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules

decls
dist

/*.js
!index.js
34 changes: 34 additions & 0 deletions packages/gatsby-plugin-functions/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Logs
logs
*.log

# Runtime data
pids
*.pid
*.seed

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
*.un~
yarn.lock
src
flow-typed
coverage
decls
examples
Empty file.
1 change: 1 addition & 0 deletions packages/gatsby-plugin-functions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"use strict";
sidharthachatterjee marked this conversation as resolved.
Show resolved Hide resolved
45 changes: 45 additions & 0 deletions packages/gatsby-plugin-functions/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"name": "gatsby-plugin-functions",
"version": "0.1.0-7",
"description": "Gatsby plugin that automatically creates serverless functions from files in specified directories",
"main": "index.js",
"scripts": {
"build": "babel src --out-dir . --ignore \"**/__tests__\" --extensions \".ts,.js\"",
"watch": "babel -w src --out-dir . --ignore \"**/__tests__\" --extensions \".ts,.js\"",
"prepare": "cross-env NODE_ENV=production npm run build"
},
"author": "Sid Chatterjee <me@sidharthachatterjee.com>",
"repository": {
"type": "git",
"url": "https://github.com/gatsbyjs/gatsby.git",
"directory": "packages/gatsby-plugin-functions"
},
"dependencies": {
"@babel/core": "^7.12.8",
"@babel/preset-env": "^7.12.7",
"@sindresorhus/slugify": "^1.1.0",
"babel-loader": "^8.2.1",
"chokidar": "^3.4.2",
"fs-exists-cached": "^1.0.0",
"gatsby-core-utils": "^1.5.0",
"gatsby-page-utils": "^0.4.0-next.0",
"globby": "^11.0.1",
"lodash": "^4.17.20",
"webpack": "^5.4.0",
"multer": "^1.4.2"
},
"devDependencies": {
"@babel/cli": "^7.11.6",
"@babel/core": "^7.11.6",
sidharthachatterjee marked this conversation as resolved.
Show resolved Hide resolved
"babel-preset-gatsby-package": "^0.7.0-next.0",
"cross-env": "^7.0.2"
},
"peerDependencies": {
"gatsby": "^3.0.0"
},
"engines": {
"node": ">=10.13.0"
sidharthachatterjee marked this conversation as resolved.
Show resolved Hide resolved
},
"homepage": "https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-plugin-functions#readme",
"license": "MIT"
}
181 changes: 181 additions & 0 deletions packages/gatsby-plugin-functions/src/gatsby-node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import fs from "fs-extra"
import glob from "globby"
import path from "path"
import webpack from "webpack"
import multer from "multer"
import * as express from "express"

import { urlResolve } from "gatsby-core-utils"

import {
ParentSpanPluginArgs,
PluginOptions,
CreateDevServerArgs,
} from "gatsby"

const DEFAULT_FUNCTIONS_DIRECTORY_PATH = path.join(process.cwd(), `src/api`)

export async function onPreBootstrap(
{ reporter }: ParentSpanPluginArgs,
{
path: functionsDirectoryPath = DEFAULT_FUNCTIONS_DIRECTORY_PATH,
}: PluginOptions
): Promise<void> {
const activity = reporter.activityTimer(`Compiling Gatsby Functions`)
activity.start()

const functionsDirectory = path.resolve(
process.cwd(),
sidharthachatterjee marked this conversation as resolved.
Show resolved Hide resolved
functionsDirectoryPath as string
)

const functionsGlob = `**/*.{js,ts}`
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could support program.extensions but we would need to add a loader as well for each new one (like Coffeescript for example) and we don't inherit the Gatsby web pack config (to reduce complexity) so we would need to add a new lifecycle for this

We can consider doing this later

Let's add a comment in code for this


// Get initial list of files
const files = await glob(functionsGlob, { cwd: functionsDirectory })
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We currently support deep paths like /world/bob.js but we don't know if that would work on Gatsby Cloud. Let's confirm that.

Also, since this will be the source of truth for functions in a project, shall we put this in state and add a getter action for it? This will enable gatsby-plugin-gatsby-cloud

To do this, we need to add gatsby-plugin-functions as an internal plugin under an experiment?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remember to ensure that Gatsby Cloud knows how to turn on this experiment and also that this experiment will only be available from a certain version of Gatsby onwards

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should deprecate the plugin on npm and print a deprecation message (like what we did for gatsby-image)


if (files?.length === 0) {
reporter.warn(
`No functions found in directory: ${path.relative(
process.cwd(),
functionsDirectory
)}`
)
return
}

await fs.ensureDir(path.join(process.cwd(), `.cache`, `functions`))

await fs.emptyDir(path.join(process.cwd(), `.cache`, `functions`))

const gatsbyVarObject = Object.keys(process.env).reduce((acc, key) => {
if (key.match(/^GATSBY_/)) {
acc[key] = JSON.stringify(process.env[key])
}
return acc
}, {})

const varObject = Object.keys(gatsbyVarObject).reduce(
(acc, key) => {
acc[`process.env.${key}`] = gatsbyVarObject[key]
return acc
},
{
"process.env": `({})`,
}
)

try {
await Promise.all(
files.map(file => {
const config = {
entry: path.join(functionsDirectory, file),
output: {
path: path.join(process.cwd(), `.cache`, `functions`),
filename: file.replace(`.ts`, `.js`),
libraryTarget: `commonjs2`,
},
target: `node`,
// library: "yourLibName",

mode: `production`,
module: {
rules: [
{
test: [/.js$/, /.ts$/],
exclude: /node_modules/,
loader: `babel-loader`,
},
],
},
plugins: [new webpack.DefinePlugin(varObject)],
// devtool: `source-map`,
}

return new Promise((resolve, reject) =>
// if (stage === `develop`) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add this back in and add in coordination for hot reload and check NODE_ENV instead

// webpack(config).watch({}, () => {})

// return resolve()
// }

webpack(config).run((err, stats) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is expensive because webpack is run once for each function. Instead let's use a single one with multiple entries. Remember to disable split chunking.

if (stats?.compilation?.warnings?.length > 0) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look at how we sanitize Webpack errors in core and port that over here so that warnings look sane

reporter.warn(stats.compilation.warnings)
}

if (err) return reject(err)
const errors = stats.compilation.errors || []
if (errors.length > 0) return reject(stats.compilation.errors)
return resolve()
})
)
})
)
} catch (e) {
activity.panic(`Failed to compile Gatsby Functions.`, e)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we add hot reload, we should switch this over

Suggested change
activity.panic(`Failed to compile Gatsby Functions.`, e)
activity.panicOnBuild(`Failed to compile Gatsby Functions.`, e)

}

activity.end()
}

export async function onCreateDevServer(
{ reporter, app }: CreateDevServerArgs,
{
path: functionsDirectoryPath = DEFAULT_FUNCTIONS_DIRECTORY_PATH,
}: PluginOptions
): Promise<void> {
const functionsGlob = `**/*.{js,ts}`
const functionsDirectory = path.resolve(
process.cwd(),
functionsDirectoryPath as string
)
const files = await glob(functionsGlob, { cwd: functionsDirectory })
sidharthachatterjee marked this conversation as resolved.
Show resolved Hide resolved

reporter.verbose(`Attaching functions to development server`)

const knownFunctions = new Map(
files.map(file => [
urlResolve(path.parse(file).dir, path.parse(file).name),
file,
])
)

app.use(
`/api/:functionName`,
multer().none(),
express.urlencoded({ extended: true }),
express.text(),
express.json(),
express.raw(),
sidharthachatterjee marked this conversation as resolved.
Show resolved Hide resolved
(req, res, next) => {
const { functionName } = req.params

if (knownFunctions.has(functionName)) {
const activity = reporter.activityTimer(
sidharthachatterjee marked this conversation as resolved.
Show resolved Hide resolved
`Executing function ${functionName}`
)
activity.start()
const compiledFunctionsDir = path.join(
process.cwd(),
sidharthachatterjee marked this conversation as resolved.
Show resolved Hide resolved
`.cache`,
`functions`
)
const funcNameToJs = knownFunctions.get(functionName) as string

try {
const fn = require(path.join(compiledFunctionsDir, funcNameToJs))
sidharthachatterjee marked this conversation as resolved.
Show resolved Hide resolved

const fnToExecute = (fn && fn.default) || fn

fnToExecute(req, res)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Promise.race with a timeout controlled by an env var that can be set and defaults to 30 seconds?

Should warn if race fails

} catch (e) {
reporter.error(e)
}
activity.end()
} else {
next()
}
}
)
}
Empty file.
3 changes: 3 additions & 0 deletions packages/gatsby-plugin-functions/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "../../tsconfig.json"
}
1 change: 1 addition & 0 deletions packages/gatsby/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"gatsby-react-router-scroll": "^4.2.0-next.0",
"gatsby-telemetry": "^2.2.0-next.0",
"glob": "^7.1.6",
"gatsby-plugin-functions": "^0.1.0-0",
KyleAMathews marked this conversation as resolved.
Show resolved Hide resolved
"got": "8.3.2",
"graphql": "^15.4.0",
"graphql-compose": "~7.25.0",
Expand Down
13 changes: 13 additions & 0 deletions packages/gatsby/src/bootstrap/load-plugins/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,19 @@ export function loadPlugins(
)
})

// Gatsby Functions

if (process.env.GATSBY_EXPERIMENT_FUNCTIONS) {
plugins.push(
processPlugin({
resolve: require.resolve(`gatsby-plugin-functions`),
options: {
path: slash(path.join(process.cwd(), `src/api`)),
},
})
)
}

// TypeScript support by default! use the user-provided one if it exists
const typescriptPlugin = (config.plugins || []).find(
plugin => plugin.resolve === `gatsby-plugin-typescript`
Expand Down
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"jsx": "preserve",
Expand Down
Loading