Skip to content

Commit

Permalink
[middleware] support destructuring for env vars in static analysis (#…
Browse files Browse the repository at this point in the history
…37556)

This commit enables the following patterns in Middleware:

```ts
// with a dot notation
const { ENV_VAR, "ENV-VAR": myEnvVar } = process.env;

// or with an object access
const { ENV_VAR2, "ENV-VAR2": myEnvVar2 } = process["env"];
```

### Related

- @cramforce asked this fixed here: #37514 (comment)



## Feature

- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [x] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
  • Loading branch information
Schniz authored Jun 9, 2022
1 parent 7608321 commit 9155201
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 7 deletions.
62 changes: 56 additions & 6 deletions packages/next/build/webpack/plugins/middleware-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,18 +164,26 @@ function getCodeAnalizer(params: {
})
}

/**
* Declares an environment variable that is being used in this module
* through this static analysis.
*/
const addUsedEnvVar = (envVarName: string) => {
const buildInfo = getModuleBuildInfo(parser.state.module)
if (buildInfo.nextUsedEnvVars === undefined) {
buildInfo.nextUsedEnvVars = new Set()
}

buildInfo.nextUsedEnvVars.add(envVarName)
}

/**
* A handler for calls to `process.env` where we identify the name of the
* ENV variable being assigned and store it in the module info.
*/
const handleCallMemberChain = (_: unknown, members: string[]) => {
if (members.length >= 2 && members[0] === 'env') {
const buildInfo = getModuleBuildInfo(parser.state.module)
if (buildInfo.nextUsedEnvVars === undefined) {
buildInfo.nextUsedEnvVars = new Set()
}

buildInfo.nextUsedEnvVars.add(members[1])
addUsedEnvVar(members[1])
if (!isInMiddlewareLayer(parser)) {
return true
}
Expand Down Expand Up @@ -226,6 +234,37 @@ function getCodeAnalizer(params: {
hooks.new.for('NextResponse').tap(NAME, handleNewResponseExpression)
hooks.callMemberChain.for('process').tap(NAME, handleCallMemberChain)
hooks.expressionMemberChain.for('process').tap(NAME, handleCallMemberChain)

/**
* Support static analyzing environment variables through
* destructuring `process.env` or `process["env"]`:
*
* const { MY_ENV, "MY-ENV": myEnv } = process.env
* ^^^^^^ ^^^^^^
*/
hooks.declarator.tap(NAME, (declarator) => {
if (
declarator.init?.type === 'MemberExpression' &&
isProcessEnvMemberExpression(declarator.init) &&
declarator.id?.type === 'ObjectPattern'
) {
for (const property of declarator.id.properties) {
if (property.type === 'RestElement') continue
if (
property.key.type === 'Literal' &&
typeof property.key.value === 'string'
) {
addUsedEnvVar(property.key.value)
} else if (property.key.type === 'Identifier') {
addUsedEnvVar(property.key.name)
}
}

if (!isInMiddlewareLayer(parser)) {
return true
}
}
})
registerUnsupportedApiHooks(parser, compilation)
}
}
Expand Down Expand Up @@ -538,3 +577,14 @@ function isNullLiteral(expr: any) {
function isUndefinedIdentifier(expr: any) {
return expr.name === 'undefined'
}

function isProcessEnvMemberExpression(memberExpression: any): boolean {
return (
memberExpression.object?.type === 'Identifier' &&
memberExpression.object.name === 'process' &&
((memberExpression.property?.type === 'Literal' &&
memberExpression.property.value === 'env') ||
(memberExpression.property?.type === 'Identifier' &&
memberExpression.property.name === 'env'))
)
}
13 changes: 13 additions & 0 deletions test/integration/middleware-general/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,19 @@ export async function middleware(request) {
// The next line is required to allow to find the env variable
// eslint-disable-next-line no-unused-expressions
process.env.MIDDLEWARE_TEST

// The next line is required to allow to find the env variable
// eslint-disable-next-line no-unused-expressions
const { ANOTHER_MIDDLEWARE_TEST } = process.env
if (!ANOTHER_MIDDLEWARE_TEST) {
console.log('missing ANOTHER_MIDDLEWARE_TEST')
}

const { 'STRING-ENV-VAR': stringEnvVar } = process['env']
if (!stringEnvVar) {
console.log('missing STRING-ENV-VAR')
}

return serializeData(JSON.stringify({ process: { env: process.env } }))
}

Expand Down
8 changes: 7 additions & 1 deletion test/integration/middleware-general/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ describe('Middleware Runtime', () => {
context.buildId = 'development'
context.app = await launchApp(context.appDir, context.appPort, {
env: {
ANOTHER_MIDDLEWARE_TEST: 'asdf2',
'STRING-ENV-VAR': 'asdf3',
MIDDLEWARE_TEST: 'asdf',
NEXT_RUNTIME: 'edge',
},
Expand Down Expand Up @@ -100,6 +102,8 @@ describe('Middleware Runtime', () => {
context.appPort = await findPort()
context.app = await nextStart(context.appDir, context.appPort, {
env: {
ANOTHER_MIDDLEWARE_TEST: 'asdf2',
'STRING-ENV-VAR': 'asdf3',
MIDDLEWARE_TEST: 'asdf',
NEXT_RUNTIME: 'edge',
},
Expand All @@ -120,7 +124,7 @@ describe('Middleware Runtime', () => {
)
expect(manifest.middleware).toEqual({
'/': {
env: ['MIDDLEWARE_TEST'],
env: ['MIDDLEWARE_TEST', 'ANOTHER_MIDDLEWARE_TEST', 'STRING-ENV-VAR'],
files: ['server/edge-runtime-webpack.js', 'server/middleware.js'],
name: 'middleware',
page: '/',
Expand Down Expand Up @@ -215,6 +219,8 @@ function tests(context, locale = '') {
expect(readMiddlewareJSON(res)).toEqual({
process: {
env: {
ANOTHER_MIDDLEWARE_TEST: 'asdf2',
'STRING-ENV-VAR': 'asdf3',
MIDDLEWARE_TEST: 'asdf',
NEXT_RUNTIME: 'edge',
},
Expand Down

0 comments on commit 9155201

Please sign in to comment.