Skip to content

Commit

Permalink
add nodejs_compat runtime check (#236)
Browse files Browse the repository at this point in the history
  • Loading branch information
dario-piotrowicz authored May 15, 2023
1 parent 3277f72 commit e053756
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 26 deletions.
9 changes: 9 additions & 0 deletions .changeset/new-waves-refuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@cloudflare/next-on-pages': minor
---

add nodejs_compat runtime check

add a runtime check for the presence of the nodejs_compat flag at runtime so that if developers
forget to use such flag instead of receiving an internal server error they receive an error specifically
telling them that they have not specified the flag
41 changes: 22 additions & 19 deletions src/buildApplication/generateGlobalJs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,29 @@
*/
export function generateGlobalJs(): string {
return `
import { AsyncLocalStorage } from 'node:async_hooks';
globalThis.AsyncLocalStorage = AsyncLocalStorage;
const __ENV_ALS_PROMISE__ = import('node:async_hooks').then(({ AsyncLocalStorage }) => {
globalThis.AsyncLocalStorage = AsyncLocalStorage;
const __ENV_ALS__ = new AsyncLocalStorage();
const envAsyncLocalStorage = new AsyncLocalStorage();
globalThis.process = {
env: new Proxy(
{},
{
get: (_, property) => {
${/* TODO: remove try-catch ASAP (after runtime fix) @dario */ ''}
try {
const result = Reflect.get(__ENV_ALS__.getStore(), property);
return result;
} catch (e) {
return undefined;
}
},
set: (_, property, value) => Reflect.set(__ENV_ALS__.getStore(), property, value),
}),
};
globalThis.process = {
env: new Proxy(
{},
{
get: (_, property) => {
${/* TODO: remove try-catch ASAP (after runtime fix) @dario */ ''}
try {
const result = Reflect.get(envAsyncLocalStorage.getStore(), property);
return result;
} catch (e) {
return undefined;
}
},
set: (_, property, value) => Reflect.set(envAsyncLocalStorage.getStore(), property, value),
}),
};
return envAsyncLocalStorage;
})
.catch(() => null);
`;
}
14 changes: 10 additions & 4 deletions templates/_worker.js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@ declare const __CONFIG__: ProcessedVercelConfig;

declare const __BUILD_OUTPUT__: VercelBuildOutput;

declare const __ENV_ALS__: AsyncLocalStorage<unknown> & {
NODE_ENV: string;
};
declare const __ENV_ALS_PROMISE__: Promise<null | AsyncLocalStorage<unknown>>;

export default {
async fetch(request, env, ctx) {
return __ENV_ALS__.run({ ...env, NODE_ENV: __NODE_ENV__ }, () => {
const envAsyncLocalStorage = await __ENV_ALS_PROMISE__;
if (!envAsyncLocalStorage) {
return new Response(
'Error: Could not access built-in node modules, please make sure that your Cloudflare Pages' +
" project has the 'nodejs_compat' compatibility flag set.",
{ status: 503 }
);
}
return envAsyncLocalStorage.run({ ...env, NODE_ENV: __NODE_ENV__ }, () => {
const adjustedRequest = adjustRequestForVercel(request);

return handleRequest(
Expand Down
19 changes: 16 additions & 3 deletions tests/src/buildApplication/generateGlobalJs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ import { generateGlobalJs } from '../../../src/buildApplication/generateGlobalJs

describe('generateGlobalJs', async () => {
describe('AsyncLocalStorage', () => {
test('should generate a promise for the AsyncLocalStorage import', async () => {
const globalJs = generateGlobalJs();
expect(globalJs).toContain(
"const __ENV_ALS_PROMISE__ = import('node:async_hooks')"
);
});

test('should make the AsyncLocalStorage globally available', async () => {
const globalJs = generateGlobalJs();
expect(globalJs).toContain(
Expand All @@ -12,7 +19,9 @@ describe('generateGlobalJs', async () => {

test('create an AsyncLocalStorage and set it as a proxy to process.env', async () => {
const globalJs = generateGlobalJs();
expect(globalJs).toContain('const __ENV_ALS__ = new AsyncLocalStorage()');
expect(globalJs).toContain(
'const envAsyncLocalStorage = new AsyncLocalStorage()'
);

const proxyRegexMatch = globalJs.match(
/globalThis.process = {[\S\s]*Proxy\(([\s\S]+)\)[\s\S]+}/
Expand All @@ -21,8 +30,12 @@ describe('generateGlobalJs', async () => {
expect(proxyRegexMatch?.length).toBe(2);

const proxyBody = proxyRegexMatch?.[1];
expect(proxyBody).toContain('Reflect.get(__ENV_ALS__.getStore()');
expect(proxyBody).toContain('Reflect.set(__ENV_ALS__.getStore()');
expect(proxyBody).toContain(
'Reflect.get(envAsyncLocalStorage.getStore()'
);
expect(proxyBody).toContain(
'Reflect.set(envAsyncLocalStorage.getStore()'
);
});
});
});

0 comments on commit e053756

Please sign in to comment.