diff --git a/packages/next/build/analysis/extract-const-value.ts b/packages/next/build/analysis/extract-const-value.ts index 0cfb372c5b8f..edfb89ea5e29 100644 --- a/packages/next/build/analysis/extract-const-value.ts +++ b/packages/next/build/analysis/extract-const-value.ts @@ -11,6 +11,7 @@ import type { ObjectExpression, RegExpLiteral, StringLiteral, + TemplateLiteral, VariableDeclaration, } from '@swc/core' @@ -124,6 +125,10 @@ function isRegExpLiteral(node: Node): node is RegExpLiteral { return node.type === 'RegExpLiteral' } +function isTemplateLiteral(node: Node): node is TemplateLiteral { + return node.type === 'TemplateLiteral' +} + class UnsupportedValueError extends Error {} class NoSuchDeclarationError extends Error {} @@ -191,6 +196,33 @@ function extractValue(node: Node): any { } return obj + } else if (isTemplateLiteral(node)) { + // e.g. `abc` + if (node.expressions.length !== 0) { + // TODO: should we add support for `${'e'}d${'g'}'e'`? + throw new UnsupportedValueError() + } + + // When TemplateLiteral has 0 expressions, the length of quasis is always 1. + // Because when parsing TemplateLiteral, the parser yields the first quasi, + // then the first expression, then the next quasi, then the next expression, etc., + // until the last quasi. + // Thus if there is no expression, the parser ends at the frst and also last quasis + const firstQuasis = node.quasis[0] + + // A "cooked" interpretation where backslashes have special meaning, while a + // "raw" interpretation where backslashes do not have special meaning + // https://exploringjs.com/impatient-js/ch_template-literals.html#template-strings-cooked-vs-raw + const { cooked } = firstQuasis + + // FIXME: The type definition of "cooked" and "raw" (from swc) are outdated. + // Both of them should be string | null | undefined, not StringLiteral. + // It is a temporary type guard to make TypeScript happy. + // https://github.com/swc-project/swc/issues/4501 + if (cooked == null || typeof cooked === 'string') { + return cooked + } + return extractValue(cooked) } else { throw new UnsupportedValueError() } diff --git a/test/e2e/switchable-runtime/index.test.ts b/test/e2e/switchable-runtime/index.test.ts index 6c05d4ad30cf..2840750c0b4d 100644 --- a/test/e2e/switchable-runtime/index.test.ts +++ b/test/e2e/switchable-runtime/index.test.ts @@ -115,11 +115,15 @@ describe('Switchable runtime', () => { ) }) - it('should build /api/hello as an api route with edge runtime', async () => { - const response = await fetchViaHTTP(context.appPort, '/api/hello') - const text = await response.text() + it('should build /api/hello and /api/edge as an api route with edge runtime', async () => { + let response = await fetchViaHTTP(context.appPort, '/api/hello') + let text = await response.text() expect(text).toMatch(/Hello from .+\/api\/hello/) + response = await fetchViaHTTP(context.appPort, '/api/edge') + text = await response.text() + expect(text).toMatch(/Returned by Edge API Route .+\/api\/edge/) + if (!(global as any).isNextDeploy) { const manifest = await readJson( join(context.appDir, '.next/server/middleware-manifest.json') @@ -137,6 +141,17 @@ describe('Switchable runtime', () => { regexp: '^/api/hello$', wasm: [], }, + '/api/edge': { + env: [], + files: [ + 'server/edge-runtime-webpack.js', + 'server/pages/api/edge.js', + ], + name: 'pages/api/hello', + page: '/api/edge', + regexp: '^/api/edge$', + wasm: [], + }, }, }) } @@ -235,11 +250,15 @@ describe('Switchable runtime', () => { }) }) - it('should build /api/hello as an api route with edge runtime', async () => { - const response = await fetchViaHTTP(context.appPort, '/api/hello') - const text = await response.text() + it('should build /api/hello and /api/edge as an api route with edge runtime', async () => { + let response = await fetchViaHTTP(context.appPort, '/api/hello') + let text = await response.text() expect(text).toMatch(/Hello from .+\/api\/hello/) + response = await fetchViaHTTP(context.appPort, '/api/edge') + text = await response.text() + expect(text).toMatch(/Returned by Edge API Route .+\/api\/edge/) + if (!(global as any).isNextDeploy) { const manifest = await readJson( join(context.appDir, '.next/server/middleware-manifest.json') @@ -257,6 +276,17 @@ describe('Switchable runtime', () => { regexp: '^/api/hello$', wasm: [], }, + '/api/edge': { + env: [], + files: [ + 'server/edge-runtime-webpack.js', + 'server/pages/api/edge.js', + ], + name: 'pages/api/edge', + page: '/api/edge', + regexp: '^/api/edge$', + wasm: [], + }, }, }) } diff --git a/test/e2e/switchable-runtime/pages/api/edge.js b/test/e2e/switchable-runtime/pages/api/edge.js new file mode 100644 index 000000000000..d375a24aaab7 --- /dev/null +++ b/test/e2e/switchable-runtime/pages/api/edge.js @@ -0,0 +1,7 @@ +export default (req) => { + return new Response(`Returned by Edge API Route ${req.url}`) +} + +export const config = { + runtime: `experimental-edge`, +}