Skip to content

Commit aa36bc2

Browse files
authored
feat: enable API, SSR and DSG functions individually as required (#375)
* feat: enable API, SSR and DSG functions individually as required * chore: edit variable names for clarity * fix: run setupImageCdn independently of functions * chore: abstract file modification functions to reduce complexity in onBuild * chore: abstract config modification functions to reduce complexity in onBuild * chore: reduce complexity in onBuild handler * fix: missing function invocation in getNeededFunctions * feat: add env vars for disabling api/ssr/dsg individually * chore: abstract function skip env var checks
1 parent ea90a6f commit aa36bc2

File tree

4 files changed

+161
-81
lines changed

4 files changed

+161
-81
lines changed

plugin/src/helpers/config.ts

Lines changed: 104 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import { EOL } from 'os'
33
import path from 'path'
44
import process from 'process'
55

6-
import { stripIndent } from 'common-tags'
6+
import { NetlifyConfig } from '@netlify/build'
77
import fs, { existsSync } from 'fs-extra'
88
import type { GatsbyConfig, PluginRef } from 'gatsby'
99

1010
import { checkPackageVersion } from './files'
11+
import type { FunctionList } from './functions'
1112

1213
export async function spliceConfig({
1314
startMarker,
@@ -114,64 +115,126 @@ export async function checkConfig({ utils, netlifyConfig }): Promise<void> {
114115
}
115116
}
116117

118+
export async function modifyConfig({
119+
netlifyConfig,
120+
cacheDir,
121+
neededFunctions,
122+
}: {
123+
netlifyConfig: NetlifyConfig
124+
cacheDir: string
125+
neededFunctions: FunctionList
126+
}): Promise<void> {
127+
mutateConfig({ netlifyConfig, cacheDir, neededFunctions })
128+
129+
if (neededFunctions.includes('API')) {
130+
// Editing _redirects so it works with ntl dev
131+
await spliceConfig({
132+
startMarker: '# @netlify/plugin-gatsby redirects start',
133+
endMarker: '# @netlify/plugin-gatsby redirects end',
134+
contents: '/api/* /.netlify/functions/__api 200',
135+
fileName: path.join(netlifyConfig.build.publish, '_redirects'),
136+
})
137+
}
138+
}
139+
117140
export function mutateConfig({
118141
netlifyConfig,
119-
compiledFunctionsDir,
120142
cacheDir,
143+
neededFunctions,
144+
}: {
145+
netlifyConfig: NetlifyConfig
146+
cacheDir: string
147+
neededFunctions: FunctionList
121148
}): void {
122149
/* eslint-disable no-underscore-dangle, no-param-reassign */
123-
netlifyConfig.functions.__api = {
124-
included_files: [path.posix.join(compiledFunctionsDir, '**')],
125-
external_node_modules: ['msgpackr-extract'],
150+
if (neededFunctions.includes('API')) {
151+
netlifyConfig.functions.__api = {
152+
included_files: [path.posix.join(cacheDir, 'functions', '**')],
153+
external_node_modules: ['msgpackr-extract'],
154+
}
126155
}
127156

128-
netlifyConfig.functions.__dsg = {
129-
included_files: [
130-
'public/404.html',
131-
'public/500.html',
132-
path.posix.join(cacheDir, 'data', '**'),
133-
path.posix.join(cacheDir, 'query-engine', '**'),
134-
path.posix.join(cacheDir, 'page-ssr', '**'),
135-
'!**/*.js.map',
136-
],
137-
external_node_modules: ['msgpackr-extract'],
138-
node_bundler: 'esbuild',
157+
if (neededFunctions.includes('DSG')) {
158+
netlifyConfig.functions.__dsg = {
159+
included_files: [
160+
'public/404.html',
161+
'public/500.html',
162+
path.posix.join(cacheDir, 'data', '**'),
163+
path.posix.join(cacheDir, 'query-engine', '**'),
164+
path.posix.join(cacheDir, 'page-ssr', '**'),
165+
'!**/*.js.map',
166+
],
167+
external_node_modules: ['msgpackr-extract'],
168+
node_bundler: 'esbuild',
169+
}
139170
}
140171

141-
netlifyConfig.functions.__ssr = { ...netlifyConfig.functions.__dsg }
172+
if (neededFunctions.includes('SSR')) {
173+
netlifyConfig.functions.__ssr = {
174+
included_files: [
175+
'public/404.html',
176+
'public/500.html',
177+
path.posix.join(cacheDir, 'data', '**'),
178+
path.posix.join(cacheDir, 'query-engine', '**'),
179+
path.posix.join(cacheDir, 'page-ssr', '**'),
180+
'!**/*.js.map',
181+
],
182+
external_node_modules: ['msgpackr-extract'],
183+
node_bundler: 'esbuild',
184+
}
185+
}
142186
/* eslint-enable no-underscore-dangle, no-param-reassign */
143187
}
144188

145-
export function shouldSkipFunctions(cacheDir: string): boolean {
146-
if (
147-
process.env.NETLIFY_SKIP_GATSBY_FUNCTIONS === 'true' ||
148-
process.env.NETLIFY_SKIP_GATSBY_FUNCTIONS === '1'
149-
) {
150-
console.log(
151-
'Skipping Gatsby Functions and SSR/DSG support because the environment variable NETLIFY_SKIP_GATSBY_FUNCTIONS is set to true',
152-
)
153-
return true
154-
}
189+
export async function getNeededFunctions(
190+
cacheDir: string,
191+
): Promise<FunctionList> {
192+
if (!existsSync(path.join(cacheDir, 'functions'))) return []
155193

156-
if (!existsSync(path.join(cacheDir, 'functions'))) {
157-
console.log(
158-
`Skipping Gatsby Functions and SSR/DSG support because the site's Gatsby version does not support them`,
159-
)
160-
return true
194+
const neededFunctions = overrideNeededFunctions(
195+
await readFunctionSkipFile(cacheDir),
196+
)
197+
198+
const functionList = Object.keys(neededFunctions).filter(
199+
(name) => neededFunctions[name] === true,
200+
) as FunctionList
201+
202+
if (functionList.length === 0) {
203+
console.log('Skipping Gatsby Functions and SSR/DSG support')
204+
} else {
205+
console.log(`Enabling Gatsby ${functionList.join('/')} support`)
161206
}
162207

163-
const skipFile = path.join(cacheDir, '.nf-skip-gatsby-functions')
208+
return functionList
209+
}
210+
211+
async function readFunctionSkipFile(cacheDir: string) {
212+
try {
213+
// read skip file from gatsby-plugin-netlify
214+
return await fs.readJson(path.join(cacheDir, '.nf-skip-gatsby-functions'))
215+
} catch (error) {
216+
// missing skip file = all functions needed
217+
// empty or invalid skip file = no functions needed
218+
return error.code === 'ENOENT' ? { API: true, SSR: true, DSG: true } : {}
219+
}
220+
}
164221

165-
if (existsSync(skipFile)) {
166-
console.log(
167-
stripIndent`
168-
Skipping Gatsby Functions and SSR/DSG support because gatsby-plugin-netlify reported that this site does not use them.
169-
If this is incorrect, remove the file "${skipFile}" and try again.`,
170-
)
171-
return true
222+
// eslint-disable-next-line complexity
223+
function overrideNeededFunctions(neededFunctions) {
224+
const skipAll = isEnvSet('NETLIFY_SKIP_GATSBY_FUNCTIONS')
225+
const skipAPI = isEnvSet('NETLIFY_SKIP_API_FUNCTION')
226+
const skipSSR = isEnvSet('NETLIFY_SKIP_SSR_FUNCTION')
227+
const skipDSG = isEnvSet('NETLIFY_SKIP_DSG_FUNCTION')
228+
229+
return {
230+
API: skipAll || skipAPI ? false : neededFunctions.API,
231+
SSR: skipAll || skipSSR ? false : neededFunctions.SSR,
232+
DSG: skipAll || skipDSG ? false : neededFunctions.DSG,
172233
}
234+
}
173235

174-
return false
236+
function isEnvSet(envVar: string) {
237+
return process.env[envVar] === 'true' || process.env[envVar] === '1'
175238
}
176239

177240
export function getGatsbyRoot(publish: string): string {

plugin/src/helpers/files.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os from 'os'
22
import process from 'process'
33

4+
import { NetlifyConfig } from '@netlify/build'
45
import {
56
copyFile,
67
ensureDir,
@@ -12,6 +13,8 @@ import {
1213
import { dirname, join, resolve } from 'pathe'
1314
import semver from 'semver'
1415

16+
import type { FunctionList } from './functions'
17+
1518
const DEFAULT_LAMBDA_PLATFORM = 'linux'
1619
const DEFAULT_LAMBDA_ABI = '83'
1720
const DEFAULT_LAMBDA_ARCH = 'x64'
@@ -23,6 +26,20 @@ const RELOCATABLE_BINARIES = [
2326
`node.abi${DEFAULT_LAMBDA_ABI}.glibc.node`,
2427
]
2528

29+
export const modifyFiles = async ({
30+
netlifyConfig,
31+
neededFunctions,
32+
}: {
33+
netlifyConfig: NetlifyConfig
34+
neededFunctions: FunctionList
35+
}): Promise<void> => {
36+
if (neededFunctions.includes('SSR') || neededFunctions.includes('DSG')) {
37+
const root = dirname(netlifyConfig.build.publish)
38+
await patchFile(root)
39+
await relocateBinaries(root)
40+
}
41+
}
42+
2643
/**
2744
* Manually patching the bundle to work around various incompatibilities in some versions.
2845
*/

plugin/src/helpers/functions.ts

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { makeApiHandler, makeHandler } from '../templates/handlers'
66

77
import { getGatsbyRoot } from './config'
88

9+
export type FunctionList = Array<'API' | 'SSR' | 'DSG'>
10+
911
const writeFunction = async ({
1012
renderMode,
1113
handlerName,
@@ -34,30 +36,40 @@ const writeApiFunction = async ({ appDir, functionDir }) => {
3436
export const writeFunctions = async ({
3537
constants,
3638
netlifyConfig,
39+
neededFunctions,
3740
}: {
3841
constants: NetlifyPluginConstants
3942
netlifyConfig: NetlifyConfig
43+
neededFunctions: FunctionList
4044
}): Promise<void> => {
4145
const { PUBLISH_DIR, INTERNAL_FUNCTIONS_SRC } = constants
4246
const siteRoot = getGatsbyRoot(PUBLISH_DIR)
4347
const functionDir = resolve(INTERNAL_FUNCTIONS_SRC, '__api')
4448
const appDir = relative(functionDir, siteRoot)
4549

46-
await writeFunction({
47-
renderMode: 'SSR',
48-
handlerName: '__ssr',
49-
appDir,
50-
functionsSrc: INTERNAL_FUNCTIONS_SRC,
51-
})
50+
if (neededFunctions.includes('SSR')) {
51+
await writeFunction({
52+
renderMode: 'SSR',
53+
handlerName: '__ssr',
54+
appDir,
55+
functionsSrc: INTERNAL_FUNCTIONS_SRC,
56+
})
57+
}
58+
59+
if (neededFunctions.includes('DSG')) {
60+
await writeFunction({
61+
renderMode: 'DSG',
62+
handlerName: '__dsg',
63+
appDir,
64+
functionsSrc: INTERNAL_FUNCTIONS_SRC,
65+
})
66+
}
5267

53-
await writeFunction({
54-
renderMode: 'DSG',
55-
handlerName: '__dsg',
56-
appDir,
57-
functionsSrc: INTERNAL_FUNCTIONS_SRC,
58-
})
5968
await setupImageCdn({ constants, netlifyConfig })
60-
await writeApiFunction({ appDir, functionDir })
69+
70+
if (neededFunctions.includes('API')) {
71+
await writeApiFunction({ appDir, functionDir })
72+
}
6173
}
6274

6375
export const setupImageCdn = async ({

plugin/src/index.ts

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
1-
import path, { dirname, join } from 'path'
1+
import path from 'path'
22
import process from 'process'
33

44
import { NetlifyPluginOptions } from '@netlify/build'
55
import { stripIndent } from 'common-tags'
66
import { existsSync } from 'fs-extra'
77

88
import { normalizedCacheDir, restoreCache, saveCache } from './helpers/cache'
9-
import {
10-
checkConfig,
11-
mutateConfig,
12-
shouldSkipFunctions,
13-
spliceConfig,
14-
} from './helpers/config'
15-
import { patchFile, relocateBinaries } from './helpers/files'
9+
import { checkConfig, getNeededFunctions, modifyConfig } from './helpers/config'
10+
import { modifyFiles } from './helpers/files'
1611
import { deleteFunctions, writeFunctions } from './helpers/functions'
1712
import { checkZipSize } from './helpers/verification'
1813

@@ -58,35 +53,28 @@ export async function onBuild({
5853
The plugin no longer uses this and it should be deleted to avoid conflicts.\n`)
5954
}
6055

61-
if (shouldSkipFunctions(cacheDir)) {
62-
await deleteFunctions(constants)
63-
return
64-
}
65-
const compiledFunctionsDir = path.join(cacheDir, '/functions')
56+
const neededFunctions = await getNeededFunctions(cacheDir)
6657

67-
await writeFunctions({ constants, netlifyConfig })
58+
await deleteFunctions(constants)
6859

69-
mutateConfig({ netlifyConfig, cacheDir, compiledFunctionsDir })
60+
await writeFunctions({ constants, netlifyConfig, neededFunctions })
7061

71-
const root = dirname(netlifyConfig.build.publish)
72-
await patchFile(root)
73-
await relocateBinaries(root)
62+
await modifyConfig({ netlifyConfig, cacheDir, neededFunctions })
7463

75-
// Editing _redirects so it works with ntl dev
76-
spliceConfig({
77-
startMarker: '# @netlify/plugin-gatsby redirects start',
78-
endMarker: '# @netlify/plugin-gatsby redirects end',
79-
contents: '/api/* /.netlify/functions/__api 200',
80-
fileName: join(netlifyConfig.build.publish, '_redirects'),
81-
})
64+
await modifyFiles({ netlifyConfig, neededFunctions })
8265
}
8366

8467
export async function onPostBuild({
8568
constants: { PUBLISH_DIR, FUNCTIONS_DIST },
8669
utils,
8770
}): Promise<void> {
8871
await saveCache({ publish: PUBLISH_DIR, utils })
89-
for (const func of ['api', 'dsg', 'ssr']) {
90-
await checkZipSize(path.join(FUNCTIONS_DIST, `__${func}.zip`))
72+
73+
const cacheDir = normalizedCacheDir(PUBLISH_DIR)
74+
75+
const neededFunctions = await getNeededFunctions(cacheDir)
76+
77+
for (const func of neededFunctions) {
78+
await checkZipSize(path.join(FUNCTIONS_DIST, `__${func.toLowerCase()}.zip`))
9179
}
9280
}

0 commit comments

Comments
 (0)