From 9fe2fe64599be9d13e7914b2aff9699b026dc486 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Mon, 17 Oct 2022 15:51:17 +0100 Subject: [PATCH 1/4] fix: correctly handle matchers with lookaheads and i18n --- demos/middleware/middleware.ts | 2 +- package-lock.json | 71 ++++++++++-------------- packages/runtime/package.json | 1 + packages/runtime/src/helpers/edge.ts | 23 +++++--- packages/runtime/src/helpers/matchers.ts | 31 +++++++++++ 5 files changed, 77 insertions(+), 51 deletions(-) create mode 100644 packages/runtime/src/helpers/matchers.ts diff --git a/demos/middleware/middleware.ts b/demos/middleware/middleware.ts index f670c665ae..e7ee2acea5 100644 --- a/demos/middleware/middleware.ts +++ b/demos/middleware/middleware.ts @@ -94,7 +94,7 @@ export const config = { '/api/:all*', '/headers', { source: '/static' }, - { source: '/shows/:all*' }, + { source: '/shows/((?!99|88).*)' }, { source: '/conditional', has: [ diff --git a/package-lock.json b/package-lock.json index 51b8b5f3ee..97cc8625e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5557,13 +5557,13 @@ "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", - "devOptional": true + "dev": true }, "node_modules/@types/react": { "version": "17.0.50", "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.50.tgz", "integrity": "sha512-ZCBHzpDb5skMnc1zFXAXnL3l1FAdi+xZvwxK+PkglMmBrwjpp9nKaWuEvrGnSifCJmBFGxZOOFuwC6KH/s0NuA==", - "devOptional": true, + "dev": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -5589,7 +5589,7 @@ "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", - "devOptional": true + "dev": true }, "node_modules/@types/sinonjs__fake-timers": { "version": "8.1.1", @@ -9123,7 +9123,7 @@ "version": "3.0.11", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==", - "devOptional": true + "dev": true }, "node_modules/custom-routes": { "resolved": "demos/custom-routes", @@ -13155,7 +13155,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz", "integrity": "sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==", - "devOptional": true + "dev": true }, "node_modules/import-fresh": { "version": "3.3.0", @@ -19850,7 +19850,6 @@ "version": "0.1.24", "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.24.tgz", "integrity": "sha512-s2aEVuLhvnVJW6s/iPgEGK6R+/xngd2jNQ+xy4bXNDKxZKJH6jpPHY6kVeVv1IeLCHgswRj+Kl3ELaDjG6V1iw==", - "dev": true, "bin": { "regexp-tree": "bin/regexp-tree" } @@ -20326,7 +20325,7 @@ "version": "1.50.1", "resolved": "https://registry.npmjs.org/sass/-/sass-1.50.1.tgz", "integrity": "sha512-noTnY41KnlW2A9P8sdwESpDmo+KBNkukI1i8+hOK3footBUcohNHtdOJbckp46XO95nuvcHDDZ+4tmOnpK3hjw==", - "devOptional": true, + "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -23360,7 +23359,7 @@ }, "packages/runtime": { "name": "@netlify/plugin-nextjs", - "version": "4.25.0", + "version": "4.26.0", "license": "MIT", "dependencies": { "@netlify/esbuild": "0.14.39", @@ -23380,6 +23379,7 @@ "p-limit": "^3.1.0", "pathe": "^0.2.0", "pretty-bytes": "^5.6.0", + "regexp-tree": "^0.1.24", "semver": "^7.3.5", "slash": "^3.0.0", "tiny-glob": "^0.2.9" @@ -25874,8 +25874,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-3.0.0.tgz", "integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==", - "dev": true, - "requires": {} + "dev": true }, "chalk": { "version": "5.1.0", @@ -26226,8 +26225,7 @@ "version": "17.0.0", "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz", "integrity": "sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==", - "dev": true, - "requires": {} + "dev": true }, "eslint-import-resolver-typescript": { "version": "3.3.0", @@ -26363,6 +26361,7 @@ "p-limit": "^3.1.0", "pathe": "^0.2.0", "pretty-bytes": "^5.6.0", + "regexp-tree": "^0.1.24", "semver": "^7.3.5", "slash": "^3.0.0", "tiny-glob": "^0.2.9", @@ -27134,13 +27133,13 @@ "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", - "devOptional": true + "dev": true }, "@types/react": { "version": "17.0.50", "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.50.tgz", "integrity": "sha512-ZCBHzpDb5skMnc1zFXAXnL3l1FAdi+xZvwxK+PkglMmBrwjpp9nKaWuEvrGnSifCJmBFGxZOOFuwC6KH/s0NuA==", - "devOptional": true, + "dev": true, "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -27166,7 +27165,7 @@ "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", - "devOptional": true + "dev": true }, "@types/sinonjs__fake-timers": { "version": "8.1.1", @@ -27474,8 +27473,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} + "dev": true }, "acorn-walk": { "version": "7.2.0", @@ -29831,7 +29829,7 @@ "version": "3.0.11", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==", - "devOptional": true + "dev": true }, "custom-routes": { "version": "file:demos/custom-routes", @@ -30961,8 +30959,7 @@ "version": "8.5.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", - "dev": true, - "requires": {} + "dev": true }, "eslint-formatter-codeframe": { "version": "7.32.1", @@ -31407,8 +31404,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.0.1.tgz", "integrity": "sha512-uM4Tgo5u3UWQiroOyDEsYcVMOo7re3zmno0IZmB5auxoaQNIceAbXEkSt8RNrKtaYehARHG06pYK6K1JhtP0Zw==", - "dev": true, - "requires": {} + "dev": true }, "eslint-plugin-react": { "version": "7.29.4", @@ -31456,8 +31452,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.5.0.tgz", "integrity": "sha512-8k1gRt7D7h03kd+SAAlzXkQwWK22BnK6GKZG+FJA6BAGy22CFvl8kCIXKpVux0cCxMWDQUPqSok0LKaZ0aOcCw==", - "dev": true, - "requires": {} + "dev": true }, "eslint-plugin-unicorn": { "version": "43.0.2", @@ -32936,7 +32931,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz", "integrity": "sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==", - "devOptional": true + "dev": true }, "import-fresh": { "version": "3.3.0", @@ -33989,8 +33984,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "requires": {} + "dev": true }, "jest-regex-util": { "version": "27.5.1", @@ -38041,8 +38035,7 @@ "regexp-tree": { "version": "0.1.24", "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.24.tgz", - "integrity": "sha512-s2aEVuLhvnVJW6s/iPgEGK6R+/xngd2jNQ+xy4bXNDKxZKJH6jpPHY6kVeVv1IeLCHgswRj+Kl3ELaDjG6V1iw==", - "dev": true + "integrity": "sha512-s2aEVuLhvnVJW6s/iPgEGK6R+/xngd2jNQ+xy4bXNDKxZKJH6jpPHY6kVeVv1IeLCHgswRj+Kl3ELaDjG6V1iw==" }, "regexp.prototype.flags": { "version": "1.4.3", @@ -38410,7 +38403,7 @@ "version": "1.50.1", "resolved": "https://registry.npmjs.org/sass/-/sass-1.50.1.tgz", "integrity": "sha512-noTnY41KnlW2A9P8sdwESpDmo+KBNkukI1i8+hOK3footBUcohNHtdOJbckp46XO95nuvcHDDZ+4tmOnpK3hjw==", - "devOptional": true, + "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -38671,14 +38664,12 @@ "styled-jsx": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.0.2.tgz", - "integrity": "sha512-LqPQrbBh3egD57NBcHET4qcgshPks+yblyhPlH2GY8oaDgKs8SK4C3dBh3oSJjgzJ3G5t1SYEZGHkP+QEpX9EQ==", - "requires": {} + "integrity": "sha512-LqPQrbBh3egD57NBcHET4qcgshPks+yblyhPlH2GY8oaDgKs8SK4C3dBh3oSJjgzJ3G5t1SYEZGHkP+QEpX9EQ==" }, "use-sync-external-store": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.1.0.tgz", - "integrity": "sha512-SEnieB2FPKEVne66NpXPd1Np4R1lTNKfjuy3XdIoPQKYBAFdzbzSZlSn1KJZUiihQLQC5Znot4SBz1EOTBwQAQ==", - "requires": {} + "integrity": "sha512-SEnieB2FPKEVne66NpXPd1Np4R1lTNKfjuy3XdIoPQKYBAFdzbzSZlSn1KJZUiihQLQC5Znot4SBz1EOTBwQAQ==" } } }, @@ -39490,8 +39481,7 @@ "styled-jsx": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.0.6.tgz", - "integrity": "sha512-xOeROtkK5MGMDimBQ3J6iPId8q0t/BDoG5XN6oKkZClVz9ISF/hihN8OCn2LggMU6N32aXnrXBdn3auSqNS9fA==", - "requires": {} + "integrity": "sha512-xOeROtkK5MGMDimBQ3J6iPId8q0t/BDoG5XN6oKkZClVz9ISF/hihN8OCn2LggMU6N32aXnrXBdn3auSqNS9fA==" }, "supports-color": { "version": "9.2.2", @@ -40219,8 +40209,7 @@ "ws": { "version": "8.9.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.9.0.tgz", - "integrity": "sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==", - "requires": {} + "integrity": "sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==" } } }, @@ -40338,8 +40327,7 @@ "use-sync-external-store": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", - "requires": {} + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==" }, "util-deprecate": { "version": "1.0.2", @@ -40744,8 +40732,7 @@ "version": "7.5.7", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", - "dev": true, - "requires": {} + "dev": true }, "xdg-basedir": { "version": "4.0.0", diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 9dc30f8f24..b5e6f708bc 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -28,6 +28,7 @@ "p-limit": "^3.1.0", "pathe": "^0.2.0", "pretty-bytes": "^5.6.0", + "regexp-tree": "^0.1.24", "semver": "^7.3.5", "slash": "^3.0.0", "tiny-glob": "^0.2.9" diff --git a/packages/runtime/src/helpers/edge.ts b/packages/runtime/src/helpers/edge.ts index 949c493ec7..c18423d592 100644 --- a/packages/runtime/src/helpers/edge.ts +++ b/packages/runtime/src/helpers/edge.ts @@ -5,12 +5,13 @@ import { resolve, join } from 'path' import type { NetlifyConfig, NetlifyPluginConstants } from '@netlify/build' import { greenBright } from 'chalk' import destr from 'destr' -import { copy, copyFile, emptyDir, ensureDir, readJSON, readJson, writeJSON, writeJson } from 'fs-extra' +import { copy, copyFile, emptyDir, ensureDir, readJson, writeJSON, writeJson } from 'fs-extra' import type { MiddlewareManifest } from 'next/dist/build/webpack/plugins/middleware-plugin' import type { RouteHas } from 'next/dist/lib/load-custom-routes' import { outdent } from 'outdent' -import { getRequiredServerFiles } from './config' +import { getRequiredServerFiles, NextConfig } from './config' +import { makeLocaleOptional, stripLookahead } from './matchers' // This is the format as of next@12.2 interface EdgeFunctionDefinitionV1 { @@ -132,10 +133,12 @@ const writeEdgeFunction = async ({ edgeFunctionDefinition, edgeFunctionRoot, netlifyConfig, + nextConfig, }: { edgeFunctionDefinition: EdgeFunctionDefinition edgeFunctionRoot: string netlifyConfig: NetlifyConfig + nextConfig: NextConfig }): Promise< Array<{ function: string @@ -165,14 +168,21 @@ const writeEdgeFunction = async ({ // The v1 middleware manifest has a single regexp, but the v2 has an array of matchers if ('regexp' in edgeFunctionDefinition) { matchers.push({ regexp: edgeFunctionDefinition.regexp }) + } else if (nextConfig.i18n) { + matchers.push( + ...edgeFunctionDefinition.matchers.map((matcher) => ({ + regexp: makeLocaleOptional(matcher.regexp), + })), + ) } else { matchers.push(...edgeFunctionDefinition.matchers) } + await writeJson(join(edgeFunctionDir, 'matchers.json'), matchers) // We add a defintion for each matching path return matchers.map((matcher) => { - const pattern = matcher.regexp + const pattern = stripLookahead(matcher.regexp) return { function: name, pattern, name: edgeFunctionDefinition.name } }) } @@ -258,6 +268,7 @@ export const writeEdgeFunctions = async (netlifyConfig: NetlifyConfig) => { edgeFunctionDefinition, edgeFunctionRoot, netlifyConfig, + nextConfig, }) manifest.functions.push(...functionDefinitions) } @@ -270,6 +281,7 @@ export const writeEdgeFunctions = async (netlifyConfig: NetlifyConfig) => { edgeFunctionDefinition, edgeFunctionRoot, netlifyConfig, + nextConfig, }) manifest.functions.push(...functionDefinitions) } @@ -284,9 +296,4 @@ export const writeEdgeFunctions = async (netlifyConfig: NetlifyConfig) => { await writeJson(join(edgeFunctionRoot, 'manifest.json'), manifest) } -export const enableEdgeInNextConfig = async (publish: string) => { - const configFile = join(publish, 'required-server-files.json') - const config = await readJSON(configFile) - await writeJSON(configFile, config) -} /* eslint-enable max-lines */ diff --git a/packages/runtime/src/helpers/matchers.ts b/packages/runtime/src/helpers/matchers.ts new file mode 100644 index 0000000000..dc4799540c --- /dev/null +++ b/packages/runtime/src/helpers/matchers.ts @@ -0,0 +1,31 @@ +import { transform } from 'regexp-tree' + +// The Go regexp lib doesn't support lookaheads, so we need to remove them +export const stripLookahead = (regex: string) => { + // Early return if there's no lookahead + if (!regex?.includes('(?!')) { + return regex + } + try { + // Parse the regexp into an AST + const re = transform(`/${regex}/`, { + Assertion(path) { + // Remove the lookahead + if (path.node.kind === 'Lookahead') { + path.remove() + } + }, + }) + // Strip the leading and trailing slashes + return re.toString().slice(1, -1) + } catch { + // Failed to parse regex, so return unchanged + return regex + } +} + +const LOCALIZED_REGEX_PREFIX = '(?:\\/(_next\\/data\\/[^/]{1,}))?(?:\\/([^/.]{1,}))' +const OPTIONAL_REGEX_PREFIX = '(?:\\/(_next\\/data\\/[^/]{1,}))?(?:\\/([^/.]*))' + +// Make the locale section of the matcher regex optional +export const makeLocaleOptional = (regex: string) => regex.replace(LOCALIZED_REGEX_PREFIX, OPTIONAL_REGEX_PREFIX) From f6f12ab7bb6862eaf3edbb01d0a62abf0ad3ad4c Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Tue, 18 Oct 2022 19:24:04 +0100 Subject: [PATCH 2/4] chore: add tests --- demos/middleware/next.config.js | 4 ++ package-lock.json | 2 +- packages/runtime/src/helpers/matchers.ts | 2 +- test/matchers.spec.ts | 72 ++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 test/matchers.spec.ts diff --git a/demos/middleware/next.config.js b/demos/middleware/next.config.js index b47fec533d..b038685107 100644 --- a/demos/middleware/next.config.js +++ b/demos/middleware/next.config.js @@ -7,6 +7,10 @@ const nextConfig = { ignoreDuringBuilds: true, }, generateBuildId: () => 'build-id', + i18n: { + defaultLocale: 'en', + locales: ['en'], + }, } module.exports = nextConfig diff --git a/package-lock.json b/package-lock.json index 97cc8625e6..1af81833ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23359,7 +23359,7 @@ }, "packages/runtime": { "name": "@netlify/plugin-nextjs", - "version": "4.26.0", + "version": "4.27.1", "license": "MIT", "dependencies": { "@netlify/esbuild": "0.14.39", diff --git a/packages/runtime/src/helpers/matchers.ts b/packages/runtime/src/helpers/matchers.ts index dc4799540c..503f817b67 100644 --- a/packages/runtime/src/helpers/matchers.ts +++ b/packages/runtime/src/helpers/matchers.ts @@ -25,7 +25,7 @@ export const stripLookahead = (regex: string) => { } const LOCALIZED_REGEX_PREFIX = '(?:\\/(_next\\/data\\/[^/]{1,}))?(?:\\/([^/.]{1,}))' -const OPTIONAL_REGEX_PREFIX = '(?:\\/(_next\\/data\\/[^/]{1,}))?(?:\\/([^/.]*))' +const OPTIONAL_REGEX_PREFIX = '(?:\\/(_next\\/data\\/[^/]{1,}))?(?:\\/([^/.]{1,}))?' // Make the locale section of the matcher regex optional export const makeLocaleOptional = (regex: string) => regex.replace(LOCALIZED_REGEX_PREFIX, OPTIONAL_REGEX_PREFIX) diff --git a/test/matchers.spec.ts b/test/matchers.spec.ts new file mode 100644 index 0000000000..2504811504 --- /dev/null +++ b/test/matchers.spec.ts @@ -0,0 +1,72 @@ +import { makeLocaleOptional, stripLookahead } from '../packages/runtime/src/helpers/matchers' + +const makeDataPath = (path: string) => `/_next/data/build-id${path === '/' ? '/index' : path}.json` + +function checkPath(path: string, regex: string) { + const re = new RegExp(regex) + const dataPath = makeDataPath(path) + const testPath = re.test(path) + const testData = re.test(dataPath) + // For easier debugging + // console.log({ path, regex, dataPath, testPath, testData }) + return testPath && testData +} + +describe('the middleware path matcher', () => { + it('makes the locale slug optional in the regex for the root', () => { + // The regex generated by Next for the path "/" with i18n enabled + const regex = + '^(?:\\/(_next\\/data\\/[^/]{1,}))?(?:\\/([^/.]{1,}))(|\\.json|\\/?index|\\/?index\\.json)?[\\/#\\?]?$' + expect(checkPath('/', regex)).toBe(false) + expect(checkPath('/', makeLocaleOptional(regex))).toBe(true) + expect(checkPath('/en', makeLocaleOptional(regex))).toBe(true) + }) + + it('makes the locale slug optional in the regex for a subpath', () => { + // The regex generated by Next for the path "/static" with i18n enabled + const regex = '^(?:\\/(_next\\/data\\/[^/]{1,}))?(?:\\/([^/.]{1,}))\\/static(.json)?[\\/#\\?]?$' + expect(checkPath('/static', regex)).toBe(false) + expect(checkPath('/static', makeLocaleOptional(regex))).toBe(true) + expect(checkPath('/en/static', makeLocaleOptional(regex))).toBe(true) + }) + + it('does not change the regex when calling makeLocaleOptional with a regex that has no locale', () => { + const regexes = [ + '^(?:\\/(_next\\/data\\/[^/]{1,}))?(?:\\/(\\/?index|\\/?index\\.json))?[\\/#\\?]?$', + '^(?:\\/(_next\\/data\\/[^/]{1,}))?\\/api(?:\\/((?:[^\\/#\\?]+?)(?:\\/(?:[^\\/#\\?]+?))*))?(.json)?[\\/#\\?]?$', + '^(?:\\/(_next\\/data\\/[^/]{1,}))?\\/shows(?:\\/((?!99|88).*))(.json)?[\\/#\\?]?$', + ] + for (const regex of regexes) { + expect(makeLocaleOptional(regex)).toBe(regex) + } + }) + + it('removes lookaheads from the regex', () => { + const regexes = [ + '^(?:\\/(_next\\/data\\/[^/]{1,}))?(?:\\/([^/.]{1,}))\\/shows(?:\\/((?!99|88).*))(.json)?[\\/#\\?]?$', + '^(?:\\/(_next\\/data\\/[^/]{1,}))?\\/shows(?:\\/((?!99|88).*))(.json)?[\\/#\\?]?$', + ] + for (const regex of regexes) { + const stripped = stripLookahead(regex) + expect(regex).toMatch(/\(\?!/) + expect(stripped).not.toMatch(/\(\?!/) + } + }) + it('converts regexes with lookaheads to stripped ones that still match at least the same paths', () => { + const regex = '^(?:\\/(_next\\/data\\/[^/]{1,}))?\\/shows(?:\\/((?!99|88).*))(.json)?[\\/#\\?]?$' + expect(checkPath('/shows', regex)).toBe(false) + expect(checkPath('/shows/11', regex)).toBe(true) + expect(checkPath('/shows/99', regex)).toBe(false) + expect(checkPath('/shows/888', regex)).toBe(false) + + const stripped = stripLookahead(regex) + expect(checkPath('/shows', stripped)).toBe(false) + expect(checkPath('/shows/11', stripped)).toBe(true) + // These will be true because the regex is not as strict as the original one + // The strict test will be done in the JS entrypoint + expect(checkPath('/shows/99', stripped)).toBe(true) + expect(checkPath('/shows/888', stripped)).toBe(true) + }) +}) + +// "regexp": "^(?:\\/(_next\\/data\\/[^/]{1,}))?(?:\\/([^/.]{1,}))\\/shows(?:\\/((?!99|88).*))(.json)?[\\/#\\?]?$" From a7f6c85c84d13b2c065ee0081f452220e581260c Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Wed, 19 Oct 2022 10:37:48 +0100 Subject: [PATCH 3/4] fix: add back rest of matcher --- packages/runtime/src/helpers/edge.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/runtime/src/helpers/edge.ts b/packages/runtime/src/helpers/edge.ts index c18423d592..5e09746310 100644 --- a/packages/runtime/src/helpers/edge.ts +++ b/packages/runtime/src/helpers/edge.ts @@ -171,6 +171,7 @@ const writeEdgeFunction = async ({ } else if (nextConfig.i18n) { matchers.push( ...edgeFunctionDefinition.matchers.map((matcher) => ({ + ...matcher, regexp: makeLocaleOptional(matcher.regexp), })), ) From a265850bfe9018b8ea6bc885c534849ae5921dc2 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Wed, 19 Oct 2022 16:56:59 +0100 Subject: [PATCH 4/4] chore: remove comment --- test/matchers.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/matchers.spec.ts b/test/matchers.spec.ts index 2504811504..5cb1c75ee3 100644 --- a/test/matchers.spec.ts +++ b/test/matchers.spec.ts @@ -68,5 +68,3 @@ describe('the middleware path matcher', () => { expect(checkPath('/shows/888', stripped)).toBe(true) }) }) - -// "regexp": "^(?:\\/(_next\\/data\\/[^/]{1,}))?(?:\\/([^/.]{1,}))\\/shows(?:\\/((?!99|88).*))(.json)?[\\/#\\?]?$"