diff --git a/.eslintignore b/.eslintignore index e06abff35d2df..d3507e8b96a9d 100644 --- a/.eslintignore +++ b/.eslintignore @@ -11,6 +11,7 @@ examples/with-flow/** examples/with-jest/** examples/with-mobx-state-tree/** examples/with-mobx/** +examples/with-tigris/db/models/todoItems.ts packages/next/src/bundles/webpack/packages/*.runtime.js packages/next/src/bundles/webpack/packages/lazy-compilation-*.js packages/next/src/compiled/**/* @@ -36,4 +37,4 @@ bench/nested-deps/pages/** bench/nested-deps/components/** packages/next-bundle-analyzer/index.d.ts examples/with-typescript-graphql/lib/gql/ -test/development/basic/hmr/components/parse-error.js \ No newline at end of file +test/development/basic/hmr/components/parse-error.js diff --git a/.eslintrc.json b/.eslintrc.json index 19d382aeb7941..b40e889bddedd 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -158,6 +158,9 @@ { "files": ["packages/**/*.tsx", "packages/**/*.ts"], "rules": { + // Note: you must disable the base rule as it can report incorrect errors + "no-shadow": "off", + "@typescript-eslint/no-shadow": ["warn", { "builtinGlobals": false }], "@typescript-eslint/no-unused-vars": [ "warn", { diff --git a/.github/ISSUE_TEMPLATE/1.bug_report.yml b/.github/ISSUE_TEMPLATE/1.bug_report.yml index 6b4863b3ce125..40de55adfb8a9 100644 --- a/.github/ISSUE_TEMPLATE/1.bug_report.yml +++ b/.github/ISSUE_TEMPLATE/1.bug_report.yml @@ -49,7 +49,7 @@ body: - type: input attributes: label: Link to the code that reproduces this issue - description: A link to a GitHub repository, a [StackBlitz](https://stackblitz.com/fork/github/vercel/next.js/tree/canary/examples/reproduction-template), or a [CodeSandbox](https://codesandbox.io/s/github/vercel/next.js/tree/canary/examples/reproduction-template) minimal reproduction. Minimal reproductions should be created from our [bug report template with `npx create-next-app -e reproduction-template`](https://github.com/vercel/next.js/tree/canary/examples/reproduction-template) and should include only changes that contribute to the issue. + description: A link to a GitHub repository, a [StackBlitz](https://stackblitz.com/fork/github/vercel/next.js/tree/canary/examples/reproduction-template), or a [CodeSandbox](https://codesandbox.io/p/sandbox/github/vercel/next.js/tree/canary/examples/reproduction-template) minimal reproduction. Minimal reproductions should be created from our [bug report template with `npx create-next-app -e reproduction-template`](https://github.com/vercel/next.js/tree/canary/examples/reproduction-template) and should include only changes that contribute to the issue. validations: required: true - type: textarea diff --git a/.github/actions/next-stats-action/package.json b/.github/actions/next-stats-action/package.json index 4d2e88dae3e3e..c4f4c4c1d1023 100644 --- a/.github/actions/next-stats-action/package.json +++ b/.github/actions/next-stats-action/package.json @@ -3,6 +3,7 @@ "main": "src/index.js", "dependencies": { "async-sema": "^3.1.0", + "execa": "2.0.3", "fs-extra": "^8.1.0", "get-port": "^5.0.0", "glob": "^7.1.4", diff --git a/.github/actions/next-stats-action/src/index.js b/.github/actions/next-stats-action/src/index.js index 8e2e9f4861087..23ea3882c5a46 100644 --- a/.github/actions/next-stats-action/src/index.js +++ b/.github/actions/next-stats-action/src/index.js @@ -78,9 +78,7 @@ if (!allowedActions.has(actionInfo.actionName) && !actionInfo.isRelease) { if (actionInfo.isRelease) { logger('Release detected, resetting mainRepo to last stable tag') const lastStableTag = await getLastStable(mainRepoDir, actionInfo.prRef) - mainNextSwcVersion = { - '@next/swc-linux-x64-gnu': lastStableTag, - } + mainNextSwcVersion = lastStableTag if (!lastStableTag) throw new Error('failed to get last stable tag') console.log('using latestStable', lastStableTag) await checkoutRef(lastStableTag, mainRepoDir) @@ -140,7 +138,7 @@ if (!allowedActions.has(actionInfo.actionName) && !actionInfo.isRelease) { const isMainRepo = dir === mainRepoDir const pkgPaths = await linkPackages({ repoDir: dir, - nextSwcPkg: isMainRepo ? mainNextSwcVersion : undefined, + nextSwcVersion: isMainRepo ? mainNextSwcVersion : undefined, }) if (isMainRepo) mainRepoPkgPaths = pkgPaths diff --git a/.github/actions/next-stats-action/src/prepare/repo-setup.js b/.github/actions/next-stats-action/src/prepare/repo-setup.js index f490a48ba3daa..0703dbdee13a0 100644 --- a/.github/actions/next-stats-action/src/prepare/repo-setup.js +++ b/.github/actions/next-stats-action/src/prepare/repo-setup.js @@ -4,11 +4,7 @@ const exec = require('../util/exec') const { remove } = require('fs-extra') const logger = require('../util/logger') const semver = require('semver') - -const mockTrace = () => ({ - traceAsyncFn: (fn) => fn(mockTrace()), - traceChild: () => mockTrace(), -}) +const execa = require('execa') module.exports = (actionInfo) => { return { @@ -58,12 +54,47 @@ module.exports = (actionInfo) => { } } }, - async linkPackages({ repoDir = '', nextSwcPkg, parentSpan }) { - const rootSpan = parentSpan - ? parentSpan.traceChild('linkPackages') - : mockTrace() + async linkPackages({ repoDir, nextSwcVersion }) { + let hasTestPack = false + + try { + hasTestPack = Boolean( + ((await fs.readJSON(path.join(repoDir, 'package.json'))).scripts || + {})['test-pack'] + ) + } catch (err) { + console.error(err) + } + + if (hasTestPack) { + execa.sync('pnpm', ['turbo', 'run', 'test-pack'], { + cwd: repoDir, + env: { NEXT_SWC_VERSION: nextSwcVersion }, + }) - return await rootSpan.traceAsyncFn(async () => { + const pkgPaths = new Map() + const pkgs = await fs.readdir(path.join(repoDir, 'packages')) + + pkgs.forEach((pkgDirname) => { + const { name } = require(path.join( + repoDir, + 'packages', + pkgDirname, + 'package.json' + )) + pkgPaths.set( + name, + path.join( + repoDir, + 'packages', + pkgDirname, + `packed-${pkgDirname}.tgz` + ) + ) + }) + return pkgPaths + } else { + // TODO: remove after next stable release (current v13.1.2) const pkgPaths = new Map() const pkgDatas = new Map() let pkgs @@ -78,97 +109,84 @@ module.exports = (actionInfo) => { throw err } - await rootSpan - .traceChild('prepare packages for packing') - .traceAsyncFn(async () => { - for (const pkg of pkgs) { - const pkgPath = path.join(repoDir, 'packages', pkg) - const packedPkgPath = path.join(pkgPath, `${pkg}-packed.tgz`) - - const pkgDataPath = path.join(pkgPath, 'package.json') - if (!fs.existsSync(pkgDataPath)) { - require('console').log(`Skipping ${pkgDataPath}`) - continue - } - const pkgData = require(pkgDataPath) - const { name } = pkgData - pkgDatas.set(name, { - pkgDataPath, - pkg, - pkgPath, - pkgData, - packedPkgPath, - }) - pkgPaths.set(name, packedPkgPath) - } + for (const pkg of pkgs) { + const pkgPath = path.join(repoDir, 'packages', pkg) + const packedPkgPath = path.join(pkgPath, `${pkg}-packed.tgz`) - for (const pkg of pkgDatas.keys()) { - const { pkgDataPath, pkgData } = pkgDatas.get(pkg) + const pkgDataPath = path.join(pkgPath, 'package.json') + if (!fs.existsSync(pkgDataPath)) { + require('console').log(`Skipping ${pkgDataPath}`) + continue + } + const pkgData = require(pkgDataPath) + const { name } = pkgData + pkgDatas.set(name, { + pkgDataPath, + pkg, + pkgPath, + pkgData, + packedPkgPath, + }) + pkgPaths.set(name, packedPkgPath) + } - for (const pkg of pkgDatas.keys()) { - const { packedPkgPath } = pkgDatas.get(pkg) - if (!pkgData.dependencies || !pkgData.dependencies[pkg]) - continue - pkgData.dependencies[pkg] = packedPkgPath - } + for (const pkg of pkgDatas.keys()) { + const { pkgDataPath, pkgData } = pkgDatas.get(pkg) - // make sure native binaries are included in local linking - if (pkg === '@next/swc') { - if (!pkgData.files) { - pkgData.files = [] - } - pkgData.files.push('native') - require('console').log( - 'using swc binaries: ', - await exec( - `ls ${path.join(path.dirname(pkgDataPath), 'native')}` - ) - ) - } + for (const pkg of pkgDatas.keys()) { + const { packedPkgPath } = pkgDatas.get(pkg) + if (!pkgData.dependencies || !pkgData.dependencies[pkg]) continue + pkgData.dependencies[pkg] = packedPkgPath + } - if (pkg === 'next') { - if (nextSwcPkg) { - Object.assign(pkgData.dependencies, nextSwcPkg) - } else { - if (pkgDatas.get('@next/swc')) { - pkgData.dependencies['@next/swc'] = - pkgDatas.get('@next/swc').packedPkgPath - } else { - pkgData.files.push('native') - } - } - } + // make sure native binaries are included in local linking + if (pkg === '@next/swc') { + if (!pkgData.files) { + pkgData.files = [] + } + pkgData.files.push('native') + require('console').log( + 'using swc binaries: ', + await exec(`ls ${path.join(path.dirname(pkgDataPath), 'native')}`) + ) + } - await fs.writeFile( - pkgDataPath, - JSON.stringify(pkgData, null, 2), - 'utf8' - ) + if (pkg === 'next') { + if (nextSwcVersion) { + Object.assign(pkgData.dependencies, { + '@next/swc-linux-x64-gnu': nextSwcVersion, + }) + } else { + if (pkgDatas.get('@next/swc')) { + pkgData.dependencies['@next/swc'] = + pkgDatas.get('@next/swc').packedPkgPath + } else { + pkgData.files.push('native') + } } - }) + } + + await fs.writeFile( + pkgDataPath, + JSON.stringify(pkgData, null, 2), + 'utf8' + ) + } // wait to pack packages until after dependency paths have been updated // to the correct versions - await rootSpan - .traceChild('packing packages') - .traceAsyncFn(async (packingSpan) => { - await Promise.all( - Array.from(pkgDatas.keys()).map(async (pkgName) => { - await packingSpan - .traceChild(`pack ${pkgName}`) - .traceAsyncFn(async () => { - const { pkg, pkgPath } = pkgDatas.get(pkgName) - await exec( - `cd ${pkgPath} && yarn pack -f '${pkg}-packed.tgz'`, - true - ) - }) - }) + await Promise.all( + Array.from(pkgDatas.keys()).map(async (pkgName) => { + const { pkg, pkgPath } = pkgDatas.get(pkgName) + await exec( + `cd ${pkgPath} && yarn pack -f '${pkg}-packed.tgz'`, + true ) }) + ) return pkgPaths - }) + } }, } } diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml index b58655a40f023..cd766044fe022 100644 --- a/.github/workflows/build_test_deploy.yml +++ b/.github/workflows/build_test_deploy.yml @@ -82,8 +82,11 @@ jobs: pnpm-store-${{ hashFiles('pnpm-lock.yaml') }} - run: pnpm install + + - run: TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} node run-tests.js --timings --write-timings -g 1/1 + if: ${{ steps.docs-change.outputs.DOCS_CHANGE == 'nope' }} + - run: pnpm run build - - run: node run-tests.js --timings --write-timings -g 1/1 - id: check-release run: | @@ -221,7 +224,6 @@ jobs: timeout-minutes: 10 env: NEXT_TELEMETRY_DISABLED: 1 - NEXT_TEST_JOB: 1 steps: - name: Setup node uses: actions/setup-node@v3 @@ -256,8 +258,7 @@ jobs: timeout-minutes: 35 env: NEXT_TELEMETRY_DISABLED: 1 - NEXT_TEST_JOB: 1 - TEST_TIMINGS_TOKEN: ${{ secrets.TEST_TIMINGS_TOKEN || secrets.GITHUB_TOKEN }} + TEST_TIMINGS_TOKEN: ${{ secrets.TEST_TIMINGS_TOKEN }} strategy: fail-fast: false matrix: @@ -283,7 +284,7 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v${{ matrix.node }} | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && xvfb-run node run-tests.js --type development --timings -g ${{ matrix.group }}/4 >> /proc/1/fd/1" + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v${{ matrix.node }} | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && NEXT_TEST_JOB=1 NEXT_TEST_MODE=dev TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} xvfb-run node run-tests.js --type development --timings -g ${{ matrix.group }}/4 >> /proc/1/fd/1" name: Run test/development if: ${{needs.build.outputs.docsChange == 'nope'}} # env: @@ -317,8 +318,7 @@ jobs: timeout-minutes: 35 env: NEXT_TELEMETRY_DISABLED: 1 - NEXT_TEST_JOB: 1 - TEST_TIMINGS_TOKEN: ${{ secrets.TEST_TIMINGS_TOKEN || secrets.GITHUB_TOKEN }} + TEST_TIMINGS_TOKEN: ${{ secrets.TEST_TIMINGS_TOKEN }} strategy: fail-fast: false matrix: @@ -344,11 +344,9 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v${{ matrix.node }} | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && xvfb-run node run-tests.js --type e2e --timings -g ${{ matrix.group }}/7 >> /proc/1/fd/1" + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v${{ matrix.node }} | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && NEXT_TEST_JOB=1 NEXT_TEST_MODE=dev TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} xvfb-run node run-tests.js --type e2e --timings -g ${{ matrix.group }}/7 >> /proc/1/fd/1" name: Run test/e2e (dev) if: ${{needs.build.outputs.docsChange == 'nope'}} - env: - NEXT_TEST_MODE: dev # RECORD_REPLAY_METADATA_TEST_RUN_TITLE: testDevE2E / Group ${{ matrix.group }} / Node ${{ matrix.node }} # RECORD_ALL_CONTENT: 1 # RECORD_REPLAY: 1 @@ -379,8 +377,7 @@ jobs: timeout-minutes: 15 env: NEXT_TELEMETRY_DISABLED: 1 - NEXT_TEST_JOB: 1 - TEST_TIMINGS_TOKEN: ${{ secrets.TEST_TIMINGS_TOKEN || secrets.GITHUB_TOKEN }} + TEST_TIMINGS_TOKEN: ${{ secrets.TEST_TIMINGS_TOKEN }} strategy: fail-fast: false matrix: @@ -406,7 +403,7 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v${{ matrix.node }} | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && xvfb-run node run-tests.js --type production --timings -g ${{ matrix.group }}/3 >> /proc/1/fd/1" + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v${{ matrix.node }} | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && NEXT_TEST_JOB=1 NEXT_TEST_MODE=start TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} xvfb-run node run-tests.js --type production --timings -g ${{ matrix.group }}/3 >> /proc/1/fd/1" name: Run test/production if: ${{needs.build.outputs.docsChange == 'nope'}} # env: @@ -430,8 +427,7 @@ jobs: timeout-minutes: 35 env: NEXT_TELEMETRY_DISABLED: 1 - NEXT_TEST_JOB: 1 - TEST_TIMINGS_TOKEN: ${{ secrets.TEST_TIMINGS_TOKEN || secrets.GITHUB_TOKEN }} + TEST_TIMINGS_TOKEN: ${{ secrets.TEST_TIMINGS_TOKEN }} strategy: fail-fast: false matrix: @@ -457,11 +453,9 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v${{ matrix.node }} | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && xvfb-run node run-tests.js --type e2e --timings -g ${{ matrix.group }}/7 >> /proc/1/fd/1" + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v${{ matrix.node }} | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && NEXT_TEST_JOB=1 NEXT_TEST_MODE=start TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} xvfb-run node run-tests.js --type e2e --timings -g ${{ matrix.group }}/7 >> /proc/1/fd/1" name: Run test/e2e (production) if: ${{needs.build.outputs.docsChange == 'nope'}} - env: - NEXT_TEST_MODE: start # RECORD_REPLAY_METADATA_TEST_RUN_TITLE: testProdE2E / Group ${{ matrix.group }} / Node ${{ matrix.node }} # RECORD_ALL_CONTENT: 1 # RECORD_REPLAY: 1 @@ -482,8 +476,7 @@ jobs: timeout-minutes: 35 env: NEXT_TELEMETRY_DISABLED: 1 - NEXT_TEST_JOB: 1 - TEST_TIMINGS_TOKEN: ${{ secrets.TEST_TIMINGS_TOKEN || secrets.GITHUB_TOKEN }} + TEST_TIMINGS_TOKEN: ${{ secrets.TEST_TIMINGS_TOKEN }} steps: - run: echo "${{needs.build.outputs.docsChange}}" @@ -508,7 +501,7 @@ jobs: - run: echo "CNA_CHANGE<> $GITHUB_OUTPUT; echo "$(node scripts/run-for-change.js --type cna --always-canary --exec echo 'yup')" >> $GITHUB_OUTPUT; echo "EOF" >> $GITHUB_OUTPUT id: cna-change - - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v16 | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && NEXT_TEST_CNA=1 xvfb-run node run-tests.js test/integration/create-next-app/index.test.ts test/integration/create-next-app/templates.test.ts >> /proc/1/fd/1" + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v16 | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && NEXT_TEST_JOB=1 NEXT_TEST_CNA=1 xvfb-run node run-tests.js test/integration/create-next-app/index.test.ts test/integration/create-next-app/templates.test.ts >> /proc/1/fd/1" if: ${{ needs.build.outputs.docsChange == 'nope' && steps.cna-change.outputs.CNA_CHANGE == 'yup' }} - name: Upload test trace @@ -528,8 +521,7 @@ jobs: timeout-minutes: 35 env: NEXT_TELEMETRY_DISABLED: 1 - NEXT_TEST_JOB: 1 - TEST_TIMINGS_TOKEN: ${{ secrets.TEST_TIMINGS_TOKEN || secrets.GITHUB_TOKEN }} + TEST_TIMINGS_TOKEN: ${{ secrets.TEST_TIMINGS_TOKEN }} strategy: fail-fast: false matrix: @@ -581,7 +573,7 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v16 | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && xvfb-run node run-tests.js --timings -g ${{ matrix.group }}/25 >> /proc/1/fd/1" + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v16 | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && NEXT_TEST_JOB=1 TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} xvfb-run node run-tests.js --timings -g ${{ matrix.group }}/25 >> /proc/1/fd/1" if: ${{needs.build.outputs.docsChange == 'nope'}} # env: # RECORD_REPLAY_METADATA_TEST_RUN_TITLE: testIntegration / Group ${{ matrix.group }} @@ -614,7 +606,6 @@ jobs: timeout-minutes: 10 env: NEXT_TELEMETRY_DISABLED: 1 - NEXT_TEST_JOB: 1 TEST_ELECTRON: 1 steps: - name: Setup node @@ -669,7 +660,6 @@ jobs: needs: [build, build-native-test] timeout-minutes: 10 env: - BROWSER_NAME: 'firefox' NEXT_TELEMETRY_DISABLED: 1 steps: - uses: actions/cache@v3 @@ -685,7 +675,7 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v16 | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && xvfb-run node run-tests.js test/integration/production/test/index.test.js >> /proc/1/fd/1" + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v16 | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && BROWSERNAME=firefox NEXT_TEST_JOB=1 TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} xvfb-run node run-tests.js test/integration/production/test/index.test.js >> /proc/1/fd/1" if: ${{needs.build.outputs.docsChange == 'nope'}} testSafari: @@ -694,51 +684,8 @@ jobs: needs: [build, build-native-test] timeout-minutes: 15 env: - BROWSER_NAME: 'safari' - NEXT_TEST_MODE: 'start' - NEXT_TELEMETRY_DISABLED: 1 - steps: - # https://github.com/actions/virtual-environments/issues/1187 - - name: tune linux network - run: sudo ethtool -K eth0 tx off rx off - - - uses: actions/cache@v3 - if: ${{needs.build.outputs.docsChange == 'nope'}} - id: restore-build - with: - path: ./* - key: ${{ github.sha }}-${{ github.run_number }} - - - uses: actions/download-artifact@v3 - if: ${{needs.build.outputs.docsChange == 'nope'}} - with: - name: next-swc-test-binary - path: packages/next-swc/native - - - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v16 | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && node run-tests.js -c 1 test/integration/production/test/index.test.js test/e2e/basepath.test.ts && DEVICE_NAME='iPhone XR' node run-tests.js -c 1 test/production/prerender-prefetch/index.test.ts >> /proc/1/fd/1" - if: ${{needs.build.outputs.docsChange == 'nope'}} - - testSafariOld: - name: Test Safari 10.1 (nav) - runs-on: ubuntu-latest - needs: [build, build-native-test] - timeout-minutes: 10 - env: - BROWSERSTACK: true - LEGACY_SAFARI: true - BROWSER_NAME: 'safari' NEXT_TELEMETRY_DISABLED: 1 - SKIP_LOCAL_SELENIUM_SERVER: true - BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} - BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} steps: - - name: Setup node - uses: actions/setup-node@v3 - if: ${{needs.build.outputs.docsChange == 'nope'}} - with: - node-version: 16 - check-latest: true - # https://github.com/actions/virtual-environments/issues/1187 - name: tune linux network run: sudo ethtool -K eth0 tx off rx off @@ -756,13 +703,7 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: npm i -g pnpm@${PNPM_VERSION} - if: ${{needs.build.outputs.docsChange == 'nope'}} - - - run: '[[ -z "$BROWSERSTACK_ACCESS_KEY" ]] && echo "Skipping for PR" || npm i -g browserstack-local@1.4.0' - if: ${{needs.build.outputs.docsChange == 'nope'}} - - - run: '[[ -z "$BROWSERSTACK_ACCESS_KEY" ]] && echo "Skipping for PR" || node run-tests.js test/integration/production-nav/test/index.test.js' + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v16 | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && NEXT_TEST_JOB=1 NEXT_TEST_MODE=start BROWSER_NAME=safari node run-tests.js -c 1 test/integration/production/test/index.test.js test/e2e/basepath.test.ts && DEVICE_NAME='iPhone XR' node run-tests.js -c 1 test/production/prerender-prefetch/index.test.ts >> /proc/1/fd/1" if: ${{needs.build.outputs.docsChange == 'nope'}} testFirefoxNode18: @@ -771,7 +712,6 @@ jobs: needs: [build, testFirefox, build-native-test] timeout-minutes: 10 env: - BROWSER_NAME: 'firefox' NEXT_TELEMETRY_DISABLED: 1 steps: - uses: actions/cache@v3 @@ -787,7 +727,7 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v18 | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && xvfb-run node run-tests.js test/integration/production/test/index.test.js >> /proc/1/fd/1" + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v18 | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && BROWSER_NAME=firefox NEXT_TEST_JOB=1 TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} xvfb-run node run-tests.js test/integration/production/test/index.test.js >> /proc/1/fd/1" if: ${{needs.build.outputs.docsChange == 'nope'}} publishRelease: @@ -840,10 +780,8 @@ jobs: needs: [publishRelease, build, build-native-test] env: NEXT_TELEMETRY_DISABLED: 1 - NEXT_TEST_JOB: 1 VERCEL_TEST_TOKEN: ${{ secrets.VERCEL_TEST_TOKEN }} - VERCEL_TEST_TEAM: 'vtest314-next-e2e-tests' - NEXT_TEST_MODE: deploy + VERCEL_TEST_TEAM: vtest314-next-e2e-tests steps: - uses: actions/cache@v3 id: restore-build @@ -860,7 +798,7 @@ jobs: - run: RESET_VC_PROJECT=true node scripts/reset-vercel-project.mjs name: Reset test project - - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v16 | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && xvfb-run node run-tests.js --type e2e >> /proc/1/fd/1" + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v16 | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && VERCEL_TEST_TOKEN=${{ secrets.VERCEL_TEST_TOKEN }} VERCEL_TEST_TEAM=vtest314-next-e2e-tests NEXT_TEST_JOB=1 NEXT_TEST_MODE=deploy TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} xvfb-run node run-tests.js --type e2e >> /proc/1/fd/1" name: Run test/e2e (deploy) - name: Upload test trace @@ -1264,6 +1202,7 @@ jobs: shell: bash - name: Upload artifact + if: ${{ needs.build.outputs.isRelease == 'true' }} uses: actions/upload-artifact@v3 with: name: next-swc-binaries @@ -1354,6 +1293,7 @@ jobs: run: pnpm turbo run --force cache-build-native -- --platform --release --target x86_64-unknown-freebsd - name: Upload artifact + if: ${{ needs.build.outputs.isRelease == 'true' }} uses: actions/upload-artifact@v3 with: name: next-swc-binaries @@ -1403,6 +1343,7 @@ jobs: run: '[[ -d "packages/next-swc/crates/wasm/pkg" ]] && mv packages/next-swc/crates/wasm/pkg packages/next-swc/crates/wasm/pkg-${{ matrix.target }} || ls packages/next-swc/crates/wasm' - name: Upload artifact + if: ${{ needs.build.outputs.isRelease == 'true' }} uses: actions/upload-artifact@v3 with: name: wasm-binaries diff --git a/.gitignore b/.gitignore index 6bbbba1173fd1..d5f0273c3fbc8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ dist .next target packages/next/wasm/@next +packages/*/packed-*.tgz # dependencies node_modules @@ -29,6 +30,7 @@ test/**/tsconfig.json /e2e-tests test/tmp/** test/.trace +test/traces # Editors **/.idea @@ -49,3 +51,4 @@ test-timings.json # Cache *.tsbuildinfo .swc/ +.turbo diff --git a/contributing/core/testing.md b/contributing/core/testing.md index 6025437f3cc4a..e0b250715b8d5 100644 --- a/contributing/core/testing.md +++ b/contributing/core/testing.md @@ -72,6 +72,7 @@ Some test-specific environment variables can be used to help debug isolated test - When investigating failures in isolated tests you can use `NEXT_TEST_SKIP_CLEANUP=1` to prevent deleting the temp folder created for the test, then you can run `pnpm next` while inside of the temp folder to debug the fully set-up test project. - You can also use `NEXT_SKIP_ISOLATE=1` if the test doesn't need to be installed to debug and it will run inside of the Next.js repo instead of the temp directory, this can also reduce test times locally but is not compatible with all tests. - The `NEXT_TEST_MODE` env variable allows toggling specific test modes for the `e2e` folder, it can be used when not using `pnpm test-dev` or `pnpm test-start` directly. Valid test modes can be seen here: https://github.com/vercel/next.js/blob/aa664868c102ddc5adc618415162d124503ad12e/test/lib/e2e-utils.ts#L46 +- You can use `NEXT_TEST_PREFER_OFFLINE=1` while testing to configure the package manager to include the [`--prefer-offline`](https://pnpm.io/cli/install#--prefer-offline) argument during test setup. This is helpful when running tests in internet restricted environments such as planes or public wifi. ### Debugging diff --git a/docs/advanced-features/customizing-babel-config.md b/docs/advanced-features/customizing-babel-config.md index aee52289f1589..ed6f6b4d7915e 100644 --- a/docs/advanced-features/customizing-babel-config.md +++ b/docs/advanced-features/customizing-babel-config.md @@ -24,7 +24,7 @@ Here's an example `.babelrc` file: } ``` -You can [take a look at this file](https://github.com/vercel/next.js/blob/canary/packages/next/build/babel/preset.ts) to learn about the presets included by `next/babel`. +You can [take a look at this file](https://github.com/vercel/next.js/blob/canary/packages/next/src/build/babel/preset.ts) to learn about the presets included by `next/babel`. To add presets/plugins **without configuring them**, you can do it this way: diff --git a/docs/api-reference/edge-runtime.md b/docs/api-reference/edge-runtime.md index 0a2704132b84e..51e199bffa0cd 100644 --- a/docs/api-reference/edge-runtime.md +++ b/docs/api-reference/edge-runtime.md @@ -111,6 +111,10 @@ The Next.js Edge Runtime is based on standard Web APIs, which is used by [Middle - [`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) - [`WebAssembly`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly) +## Next.js Specific Polyfills + +- [`AsyncLocalStorage`](https://nodejs.org/api/async_context.html#class-asynclocalstorage) + ## Environment Variables You can use `process.env` to access [Environment Variables](/docs/basic-features/environment-variables.md) for both `next dev` and `next build`. diff --git a/docs/api-reference/next.config.js/redirects.md b/docs/api-reference/next.config.js/redirects.md index ab5b48402dd40..68564f6de1088 100644 --- a/docs/api-reference/next.config.js/redirects.md +++ b/docs/api-reference/next.config.js/redirects.md @@ -251,7 +251,7 @@ module.exports = { { // does not add /docs since basePath: false is set source: '/without-basePath', - destination: '/another', + destination: 'https://example.com', basePath: false, permanent: false, }, diff --git a/docs/api-reference/next/link.md b/docs/api-reference/next/link.md index 14e0501ded9cd..f2d791db173cd 100644 --- a/docs/api-reference/next/link.md +++ b/docs/api-reference/next/link.md @@ -233,9 +233,9 @@ export function middleware(req) { const nextUrl = req.nextUrl if (nextUrl.pathname === '/dashboard') { if (req.cookies.authToken) { - return NextResponse.rewrite('/auth/dashboard') + return NextResponse.rewrite(new URL('/auth/dashboard', req.url)) } else { - return NextResponse.rewrite('/public/dashboard') + return NextResponse.rewrite(new URL('/public/dashboard', req.url)) } } } diff --git a/docs/api-reference/next/script.md b/docs/api-reference/next/script.md index b28a3903af2d1..2cf7c1d3d7e8b 100644 --- a/docs/api-reference/next/script.md +++ b/docs/api-reference/next/script.md @@ -71,22 +71,27 @@ Scripts that load with the `beforeInteractive` strategy are injected into the in Scripts denoted with this strategy are preloaded and fetched before any first-party code, but their execution does not block page hydration from occuring. -`beforeInteractive` scripts must be placed inside `pages/_app.js` and are designed to load scripts that are needed by the entire site (i.e. the script will load when any page in the application has been loaded server-side). +`beforeInteractive` scripts must be placed inside `pages/_document.js` and are designed to load scripts that are needed by the entire site (i.e. the script will load when any page in the application has been loaded server-side). **This strategy should only be used for critical scripts that need to be fetched before any part of the page becomes interactive.** ```jsx +import { Html, Head, Main, NextScript } from 'next/document' import Script from 'next/script' -export default function MyApp({ Component, pageProps }) { +export default function Document() { return ( - <> - ` + ), + } + } + + throw new WrappedBuildError( + new Error('missing required error components') + ) + } + try { return await this.renderToResponseWithComponents( { @@ -1928,7 +1965,7 @@ export default abstract class Server { err, }, }, - result! + result ) } catch (maybeFallbackError) { if (maybeFallbackError instanceof NoFallbackError) { diff --git a/packages/next/src/server/config-schema.ts b/packages/next/src/server/config-schema.ts index 304abb361839b..0daf8649bd72d 100644 --- a/packages/next/src/server/config-schema.ts +++ b/packages/next/src/server/config-schema.ts @@ -229,6 +229,9 @@ const configSchema = { adjustFontFallbacksWithSizeAdjust: { type: 'boolean', }, + allowedRevalidateHeaderKeys: { + type: 'array', + }, amp: { additionalProperties: false, properties: { diff --git a/packages/next/src/server/config-shared.ts b/packages/next/src/server/config-shared.ts index 5471fc29a49c4..4801d0933efbd 100644 --- a/packages/next/src/server/config-shared.ts +++ b/packages/next/src/server/config-shared.ts @@ -79,6 +79,7 @@ export interface NextJsWebpackConfig { } export interface ExperimentalConfig { + allowedRevalidateHeaderKeys?: string[] fetchCache?: boolean optimisticClientCache?: boolean middlewarePrefetch?: 'strict' | 'flexible' diff --git a/packages/next/src/server/dev/next-dev-server.ts b/packages/next/src/server/dev/next-dev-server.ts index 5bb59120df523..fee3933ff991d 100644 --- a/packages/next/src/server/dev/next-dev-server.ts +++ b/packages/next/src/server/dev/next-dev-server.ts @@ -1443,7 +1443,12 @@ export default class DevServer extends Server { // patched global in memory, creating a memory leak. this.restorePatchedGlobals() - return super.findPageComponents({ pathname, query, params, isAppPath }) + return await super.findPageComponents({ + pathname, + query, + params, + isAppPath, + }) } catch (err) { if ((err as any).code !== 'ENOENT') { throw err diff --git a/packages/next/src/server/image-optimizer.ts b/packages/next/src/server/image-optimizer.ts index 6dc952e90c4bd..8d35257ae0cda 100644 --- a/packages/next/src/server/image-optimizer.ts +++ b/packages/next/src/server/image-optimizer.ts @@ -408,18 +408,18 @@ export async function optimizeImage({ let optimizedBuffer = buffer if (sharp) { // Begin sharp transformation logic - const transformer = sharp(buffer) + const transformer = sharp(buffer, { + sequentialRead: true, + }) transformer.rotate() if (height) { transformer.resize(width, height) } else { - const { width: metaWidth } = await transformer.metadata() - - if (metaWidth && metaWidth > width) { - transformer.resize(width) - } + transformer.resize(width, undefined, { + withoutEnlargement: true, + }) } if (contentType === AVIF) { diff --git a/packages/next/src/server/next-server.ts b/packages/next/src/server/next-server.ts index 98ea6ffc67c7d..df71efb5662d7 100644 --- a/packages/next/src/server/next-server.ts +++ b/packages/next/src/server/next-server.ts @@ -810,7 +810,10 @@ export default class NextNodeServer extends BaseServer { new NodeNextResponse(newRes) ), // internal config so is not typed - trustHostHeader: (this.nextConfig.experimental as any).trustHostHeader, + trustHostHeader: (this.nextConfig.experimental as Record) + .trustHostHeader, + allowedRevalidateHeaderKeys: + this.nextConfig.experimental.allowedRevalidateHeaderKeys, }, this.minimalMode, this.renderOpts.dev, diff --git a/packages/next/src/server/next-typescript.ts b/packages/next/src/server/next-typescript.ts index 2835fd0b3c912..af774c1255bab 100644 --- a/packages/next/src/server/next-typescript.ts +++ b/packages/next/src/server/next-typescript.ts @@ -63,7 +63,7 @@ const API_DOCS: Record< '"error"': 'This errors if any dynamic Hooks or fetches are used. (This is equivalent to `getStaticProps`.)', '"force-static"': - 'This forces caching of all fetches and returns empty values from `useCookies`, `useHeaders` and `useSearchParams`.', + 'This forces caching of all fetches and returns empty values from `cookies`, `headers` and `useSearchParams`.', }, link: 'https://beta.nextjs.org/docs/api-reference/segment-config#dynamic', }, @@ -403,8 +403,12 @@ export function createTSPlugin(modules: { isDefaultFunctionExport(node) && isPositionInsideNode(position, node) ) { - const paramNode = (node as ts.FunctionDeclaration).parameters?.[0] - if (isPositionInsideNode(position, paramNode)) { + // Default export function might not accept parameters + const paramNode = (node as ts.FunctionDeclaration).parameters?.[0] as + | ts.ParameterDeclaration + | undefined + + if (paramNode && isPositionInsideNode(position, paramNode)) { const props = paramNode?.name if (props && ts.isObjectBindingPattern(props)) { let validProps = [] diff --git a/packages/next/src/telemetry/events/swc-load-failure.ts b/packages/next/src/telemetry/events/swc-load-failure.ts index 6414043b1ae4e..b6e617a58b246 100644 --- a/packages/next/src/telemetry/events/swc-load-failure.ts +++ b/packages/next/src/telemetry/events/swc-load-failure.ts @@ -20,7 +20,7 @@ export type EventSwcLoadFailure = { export async function eventSwcLoadFailure( event?: EventSwcLoadFailure['payload'] ): Promise { - const telemetry: Telemetry = traceGlobals.get('telemetry') + const telemetry: Telemetry | undefined = traceGlobals.get('telemetry') // can't continue if telemetry isn't set if (!telemetry) return diff --git a/packages/next/src/telemetry/project-id.ts b/packages/next/src/telemetry/project-id.ts index 5c307ef02b576..ac2b2713f336a 100644 --- a/packages/next/src/telemetry/project-id.ts +++ b/packages/next/src/telemetry/project-id.ts @@ -18,6 +18,7 @@ function _getProjectIdByGit() { { timeout: 1000, stdio: `pipe`, + windowsHide: true, } ) diff --git a/packages/next/src/telemetry/storage.ts b/packages/next/src/telemetry/storage.ts index 3911178315e77..e5281f1969b4d 100644 --- a/packages/next/src/telemetry/storage.ts +++ b/packages/next/src/telemetry/storage.ts @@ -10,7 +10,9 @@ import { _postPayload } from './post-payload' import { getRawProjectId } from './project-id' import { AbortController } from 'next/dist/compiled/@edge-runtime/primitives/abort-controller' import fs from 'fs' -import spawn from 'next/dist/compiled/cross-spawn' +// Note: cross-spawn is not used here as it causes +// a new command window to appear when we don't want it to +import { spawn } from 'child_process' // This is the key that stores whether or not telemetry is enabled or disabled. const TELEMETRY_KEY_ENABLED = 'telemetry.enabled' @@ -245,8 +247,10 @@ export class Telemetry { JSON.stringify(allEvents) ) - spawn('node', [require.resolve('./deteched-flush'), mode, dir], { + spawn(process.execPath, [require.resolve('./deteched-flush'), mode, dir], { detached: !this.NEXT_TELEMETRY_DEBUG, + windowsHide: true, + shell: false, ...(this.NEXT_TELEMETRY_DEBUG ? { stdio: 'inherit', diff --git a/packages/next/src/trace/report/to-telemetry.ts b/packages/next/src/trace/report/to-telemetry.ts index a308fc758badd..5eeb0d6e7b3a5 100644 --- a/packages/next/src/trace/report/to-telemetry.ts +++ b/packages/next/src/trace/report/to-telemetry.ts @@ -1,3 +1,4 @@ +import { Telemetry } from '../../telemetry/storage' import { traceGlobals } from '../shared' const TRACE_EVENT_ACCESSLIST = new Map( @@ -11,7 +12,7 @@ const reportToTelemetry = (spanName: string, duration: number) => { if (!eventName) { return } - const telemetry = traceGlobals.get('telemetry') + const telemetry: Telemetry | undefined = traceGlobals.get('telemetry') if (!telemetry) { return } diff --git a/packages/next/src/trace/shared.ts b/packages/next/src/trace/shared.ts index 1c04cc8ce442e..e4bbcbbe5c2ec 100644 --- a/packages/next/src/trace/shared.ts +++ b/packages/next/src/trace/shared.ts @@ -1,6 +1,13 @@ export type SpanId = number -export const traceGlobals: Map = new Map() +let _traceGlobals: Map = (global as any)._traceGlobals + +if (!_traceGlobals) { + _traceGlobals = new Map() +} +;(global as any)._traceGlobals = _traceGlobals + +export const traceGlobals: Map = _traceGlobals export const setGlobal = (key: any, val: any) => { traceGlobals.set(key, val) } diff --git a/packages/next/taskfile-swc.js b/packages/next/taskfile-swc.js index 43652e0b06763..dcf7ea6728419 100644 --- a/packages/next/taskfile-swc.js +++ b/packages/next/taskfile-swc.js @@ -22,7 +22,7 @@ module.exports = function (task) { } = {} ) { // Don't compile .d.ts - if (file.base.endsWith('.d.ts')) return + if (file.base.endsWith('.d.ts') || file.base.endsWith('.json')) return const isClient = serverOrClient === 'client' diff --git a/packages/next/taskfile.js b/packages/next/taskfile.js index 350b5e6d28bf5..4374d7c83aea5 100644 --- a/packages/next/taskfile.js +++ b/packages/next/taskfile.js @@ -2161,14 +2161,14 @@ export async function cli(task, opts) { export async function lib(task, opts) { await task - .source('src/lib/**/*.+(js|ts|tsx)') + .source('src/lib/**/*.+(js|ts|tsx|json)') .swc('server', { dev: opts.dev }) .target('dist/lib') } export async function lib_esm(task, opts) { await task - .source('src/lib/**/*.+(js|ts|tsx)') + .source('src/lib/**/*.+(js|ts|tsx|json)') .swc('server', { dev: opts.dev, esm: true }) .target('dist/esm/lib') } diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json index 8729b279f5d63..061b0a88de1f8 100644 --- a/packages/react-dev-overlay/package.json +++ b/packages/react-dev-overlay/package.json @@ -1,6 +1,6 @@ { "name": "@next/react-dev-overlay", - "version": "13.1.3-canary.0", + "version": "13.1.4-canary.0", "description": "A development-only overlay for developing React applications.", "repository": { "url": "vercel/next.js", @@ -15,7 +15,8 @@ "build": "rimraf dist && tsc -d -p tsconfig.json", "prepublishOnly": "cd ../../ && turbo run build", "dev": "tsc -d -w -p tsconfig.json", - "typescript": "tsec --noEmit -p tsconfig.json" + "typescript": "tsec --noEmit -p tsconfig.json", + "test-pack": "cd ../../ && pnpm test-pack react-dev-overlay" }, "dependencies": { "@babel/code-frame": "7.12.11", diff --git a/packages/react-dev-overlay/src/internal/container/Errors.tsx b/packages/react-dev-overlay/src/internal/container/Errors.tsx index 887d4f45f16b9..329436693bc83 100644 --- a/packages/react-dev-overlay/src/internal/container/Errors.tsx +++ b/packages/react-dev-overlay/src/internal/container/Errors.tsx @@ -57,7 +57,9 @@ const HotlinkedText: React.FC<{ if (linkRegex.test(word)) { return ( - {word} + + {word} + {index === array.length - 1 ? '' : ' '} ) diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json index f3b266b7bcf31..ce9e2604ce776 100644 --- a/packages/react-refresh-utils/package.json +++ b/packages/react-refresh-utils/package.json @@ -1,6 +1,6 @@ { "name": "@next/react-refresh-utils", - "version": "13.1.3-canary.0", + "version": "13.1.4-canary.0", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", @@ -14,7 +14,8 @@ "scripts": { "build": "rimraf dist && tsc -d -p tsconfig.json", "prepublishOnly": "cd ../../ && turbo run build", - "dev": "tsc -d -w -p tsconfig.json" + "dev": "tsc -d -w -p tsconfig.json", + "test-pack": "cd ../../ && pnpm test-pack react-refresh-utils" }, "peerDependencies": { "react-refresh": "0.12.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 48d3ebbb6c24f..7154c26b65c98 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -178,6 +178,7 @@ importers: tailwindcss: 1.1.3 taskr: 1.1.0 tree-kill: 1.2.2 + ts-node: 10.9.1 tsec: 0.2.1 turbo: 1.6.3 typescript: 4.8.2 @@ -299,7 +300,7 @@ importers: image-size: 0.9.3 is-animated: 2.0.2 isomorphic-unfetch: 3.0.0 - jest: 27.0.6 + jest: 27.0.6_ts-node@10.9.1 jest-extended: 1.2.1 json5: 2.2.3 ky: 0.19.1 @@ -354,6 +355,7 @@ importers: tailwindcss: 1.1.3 taskr: 1.1.0 tree-kill: 1.2.2 + ts-node: 10.9.1_cx2odcp7q42yre3tu7le55sjlu tsec: 0.2.1_sbe2uaqno6akssxfwbhgeg7v2q turbo: 1.6.3 typescript: 4.8.2 @@ -440,7 +442,7 @@ importers: packages/eslint-config-next: specifiers: - '@next/eslint-plugin-next': 13.1.3-canary.0 + '@next/eslint-plugin-next': 13.1.4-canary.0 '@rushstack/eslint-patch': ^1.1.3 '@typescript-eslint/parser': ^5.42.0 eslint: ^7.23.0 || ^8.0.0 @@ -512,12 +514,12 @@ importers: '@hapi/accept': 5.0.2 '@napi-rs/cli': 2.13.3 '@napi-rs/triples': 1.1.0 - '@next/env': 13.1.3-canary.0 - '@next/polyfill-module': 13.1.3-canary.0 - '@next/polyfill-nomodule': 13.1.3-canary.0 - '@next/react-dev-overlay': 13.1.3-canary.0 - '@next/react-refresh-utils': 13.1.3-canary.0 - '@next/swc': 13.1.3-canary.0 + '@next/env': 13.1.4-canary.0 + '@next/polyfill-module': 13.1.4-canary.0 + '@next/polyfill-nomodule': 13.1.4-canary.0 + '@next/react-dev-overlay': 13.1.4-canary.0 + '@next/react-refresh-utils': 13.1.4-canary.0 + '@next/swc': 13.1.4-canary.0 '@segment/ajv-human-errors': 2.1.2 '@swc/helpers': 0.4.14 '@taskr/clear': 1.1.0 @@ -4350,6 +4352,13 @@ packages: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true + /@cspotcode/source-map-support/0.8.1: + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + dev: true + /@csstools/postcss-color-function/1.1.0_postcss@8.4.14: resolution: {integrity: sha512-5D5ND/mZWcQoSfYnSPsXtuiFxhzmhxt6pcjrFLJyldj+p0ZN2vvRpYNX+lahFTtMhAYOa2WmkdGINr0yP0CvGA==} engines: {node: ^12 || ^14 || >=16} @@ -4931,7 +4940,7 @@ packages: slash: 3.0.0 dev: true - /@jest/core/27.0.6: + /@jest/core/27.0.6_ts-node@10.9.1: resolution: {integrity: sha512-SsYBm3yhqOn5ZLJCtccaBcvD/ccTLCeuDv8U41WJH/V1MW5eKUkeMHT9U+Pw/v1m1AIWlnIW/eM2XzQr0rEmow==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} peerDependencies: @@ -4952,7 +4961,7 @@ packages: exit: 0.1.2 graceful-fs: 4.2.10 jest-changed-files: 27.0.6 - jest-config: 27.0.6 + jest-config: 27.0.6_ts-node@10.9.1 jest-haste-map: 27.0.6 jest-message-util: 27.5.1 jest-regex-util: 27.0.6 @@ -5226,6 +5235,13 @@ packages: '@jridgewell/resolve-uri': 3.0.5 '@jridgewell/sourcemap-codec': 1.4.11 + /@jridgewell/trace-mapping/0.3.9: + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + dependencies: + '@jridgewell/resolve-uri': 3.0.5 + '@jridgewell/sourcemap-codec': 1.4.11 + dev: true + /@lerna/add/4.0.0: resolution: {integrity: sha512-cpmAH1iS3k8JBxNvnMqrGTTjbY/ZAiKa1ChJzFevMYY3eeqbvhsBKnBcxjRXtdrJ6bd3dCQM+ZtK+0i682Fhng==} engines: {node: '>= 10.18.0'} @@ -6956,6 +6972,22 @@ packages: resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} engines: {node: '>= 6'} + /@tsconfig/node10/1.0.9: + resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} + dev: true + + /@tsconfig/node12/1.0.11: + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + dev: true + + /@tsconfig/node14/1.0.3: + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + dev: true + + /@tsconfig/node16/1.0.3: + resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==} + dev: true + /@types/amphtml-validator/1.0.0: resolution: {integrity: sha512-CJOi00fReT1JehItkgTZDI47v9WJxUH/OLX0XzkDgyEed7dGdeUQfXk5CTRM7N9FkHdv3klSjsZxo5sH1oTIGg==} dependencies: @@ -7983,6 +8015,11 @@ packages: resolution: {integrity: sha512-oZRad/3SMOI/pxbbmqyurIx7jHw1wZDcR9G44L8pUVFEomX/0dH89SrM1KaDXuv1NpzAXz6Op/Xu/Qd5XXzdEA==} engines: {node: '>=0.4.0'} + /acorn-walk/8.2.0: + resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} + engines: {node: '>=0.4.0'} + dev: true + /acorn/6.4.2: resolution: {integrity: sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==} engines: {node: '>=0.4.0'} @@ -10335,6 +10372,10 @@ packages: sha.js: 2.4.11 dev: true + /create-require/1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + dev: true + /critters/0.0.6: resolution: {integrity: sha512-NUB3Om7tkf+XWi9+2kJ2A3l4/tHORDI1UT+nHxUqay2B/tJvMpiXcklDDLBH3fPn9Pe23uu0we/08Ukjy4cLCQ==} dependencies: @@ -11147,6 +11188,11 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dev: true + /diff/4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + dev: true + /diffie-hellman/5.0.3: resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==} dependencies: @@ -13953,7 +13999,7 @@ packages: dependencies: '@babel/code-frame': 7.18.6 '@sidvind/better-ajv-errors': 0.6.10_ajv@6.12.6 - acorn-walk: 8.0.0 + acorn-walk: 8.2.0 ajv: 6.12.6 chalk: 4.1.2 deepmerge: 4.2.2 @@ -15107,7 +15153,7 @@ packages: - supports-color dev: true - /jest-cli/27.0.6: + /jest-cli/27.0.6_ts-node@10.9.1: resolution: {integrity: sha512-qUUVlGb9fdKir3RDE+B10ULI+LQrz+MCflEH2UJyoUjoHHCbxDrMxSzjQAPUMsic4SncI62ofYCcAvW6+6rhhg==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} hasBin: true @@ -15117,14 +15163,14 @@ packages: node-notifier: optional: true dependencies: - '@jest/core': 27.0.6 + '@jest/core': 27.0.6_ts-node@10.9.1 '@jest/test-result': 27.0.6 '@jest/types': 27.5.1 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.10 import-local: 3.0.2 - jest-config: 27.0.6 + jest-config: 27.0.6_ts-node@10.9.1 jest-util: 27.5.1 jest-validate: 27.0.6 prompts: 2.3.0 @@ -15137,7 +15183,7 @@ packages: - utf-8-validate dev: true - /jest-config/27.0.6: + /jest-config/27.0.6_ts-node@10.9.1: resolution: {integrity: sha512-JZRR3I1Plr2YxPBhgqRspDE2S5zprbga3swYNrvY3HfQGu7p/GjyLOqwrYad97tX3U3mzT53TPHVmozacfP/3w==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} peerDependencies: @@ -15167,6 +15213,7 @@ packages: jest-validate: 27.5.1 micromatch: 4.0.4 pretty-format: 27.5.1 + ts-node: 10.9.1_cx2odcp7q42yre3tu7le55sjlu transitivePeerDependencies: - bufferutil - canvas @@ -15777,7 +15824,7 @@ packages: merge-stream: 2.0.0 supports-color: 8.1.1 - /jest/27.0.6: + /jest/27.0.6_ts-node@10.9.1: resolution: {integrity: sha512-EjV8aETrsD0wHl7CKMibKwQNQc3gIRBXlTikBmmHUeVMKaPFxdcUIBfoDqTSXDoGJIivAYGqCWVlzCSaVjPQsA==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} hasBin: true @@ -15787,9 +15834,9 @@ packages: node-notifier: optional: true dependencies: - '@jest/core': 27.0.6 + '@jest/core': 27.0.6_ts-node@10.9.1 import-local: 3.0.2 - jest-cli: 27.0.6 + jest-cli: 27.0.6_ts-node@10.9.1 transitivePeerDependencies: - bufferutil - canvas @@ -16757,6 +16804,10 @@ packages: semver: 6.3.0 dev: true + /make-error/1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + dev: true + /make-fetch-happen/8.0.13: resolution: {integrity: sha512-rQ5NijwwdU8tIaBrpTtSVrNCcAJfyDRcKBC76vOQlyJX588/88+TE+UpjWl4BgG7gCkp29wER7xcRqkeg+x64Q==} engines: {node: '>= 10'} @@ -22980,6 +23031,38 @@ packages: dependencies: glob: 7.2.0 + /ts-node/10.9.1_cx2odcp7q42yre3tu7le55sjlu: + resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@swc/core': 1.2.203 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.3 + '@types/node': 14.14.31 + acorn: 8.8.0 + acorn-walk: 8.2.0 + arg: 4.1.0 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 4.8.2 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + /tsconfig-paths/3.14.1: resolution: {integrity: sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==} dependencies: @@ -23651,6 +23734,10 @@ packages: hasBin: true dev: true + /v8-compile-cache-lib/3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + dev: true + /v8-compile-cache/2.1.0: resolution: {integrity: sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==} dev: true @@ -24319,6 +24406,11 @@ packages: buffer-crc32: 0.2.13 dev: true + /yn/3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + dev: true + /yocto-queue/0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} diff --git a/run-tests.js b/run-tests.js index 1899696474474..332aac0660437 100644 --- a/run-tests.js +++ b/run-tests.js @@ -292,6 +292,7 @@ async function main() { // run tests in headless mode by default HEADLESS: 'true', TRACE_PLAYWRIGHT: 'true', + NEXT_TELEMETRY_DISABLED: '1', ...(isFinalRun ? { // Events can be finicky in CI. This switches to a more diff --git a/scripts/check-pre-compiled.sh b/scripts/check-pre-compiled.sh index df6b2cf3b09e5..3e8a7042a1afb 100755 --- a/scripts/check-pre-compiled.sh +++ b/scripts/check-pre-compiled.sh @@ -16,7 +16,7 @@ cd ../../ # Make sure to exit with 1 if there are changes after running ncc-compiled # step to ensure we get any changes committed -if [[ ! -z $(git status -s) ]];then +if [[ ! -z $(git status -s) ]] && [[ -z $IS_PUBLISH ]];then echo "Detected changes" git diff -a --stat exit 1 diff --git a/scripts/run-for-change.js b/scripts/run-for-change.js index c16e05452fcf6..b21a034ba608e 100644 --- a/scripts/run-for-change.js +++ b/scripts/run-for-change.js @@ -17,6 +17,22 @@ const CHANGE_ITEM_GROUPS = { '.github/ISSUE_TEMPLATE', '.github/labeler.json', '.github/pull_request_template.md', + 'packages/next-plugin-storybook/readme.md', + 'packages/next/license.md', + 'packages/next/README.md', + 'packages/eslint-plugin-next/README.md', + 'packages/next-codemod/license.md', + 'packages/next-codemod/README.md', + 'packages/next-swc/crates/wasm/README.md', + 'packages/next-swc/README.md', + 'packages/next-bundle-analyzer/readme.md', + 'packages/next-mdx/license.md', + 'packages/next-mdx/readme.md', + 'packages/react-dev-overlay/README.md', + 'packages/react-refresh-utils/README.md', + 'packages/create-next-app/README.md', + 'packages/font/README.md', + 'packages/next-env/README.md', ], cna: ['packages/create-next-app'], 'next-swc': ['packages/next-swc', 'scripts/normalize-version-bump.js'], diff --git a/scripts/test-pack-package.mts b/scripts/test-pack-package.mts new file mode 100755 index 0000000000000..36399fe001c25 --- /dev/null +++ b/scripts/test-pack-package.mts @@ -0,0 +1,105 @@ +import path from 'path' +import fs from 'fs-extra' +import os from 'os' +import execa from 'execa' +import { randomBytes } from 'crypto' +import { fileURLToPath } from 'url' + +const main = async () => { + const __dirname = fileURLToPath(new URL('.', import.meta.url)) + const repoRoot = path.dirname(__dirname) + const pkgsDir = path.join(repoRoot, 'packages') + const currentPkgDirname = process.argv[2] + + const getPackedPkgPath = (pkgDirname: string) => + path.join(pkgsDir, pkgDirname, `packed-${pkgDirname}.tgz`) + const getPackageJsonPath = (pkgDirname: string) => + path.join(pkgsDir, pkgDirname, `package.json`) + + const allPkgDirnames = await fs.readdir(pkgsDir) + if (!allPkgDirnames.includes(currentPkgDirname)) { + throw new Error(`Unknown package '${currentPkgDirname}'`) + } + + const currentPkgDir = path.join(pkgsDir, currentPkgDirname) + + const tmpPkgPath = path.join( + os.tmpdir(), + `${currentPkgDirname}-${randomBytes(32).toString('hex')}` + ) + console.log(`Packing '${currentPkgDirname}' in '${tmpPkgPath}'.`) + + const packageJsonPath = getPackageJsonPath(currentPkgDirname) + const packageJson = await fs.readJson(packageJsonPath) + const dependencies = packageJson.dependencies + + if (packageJson?.scripts?.prepublishOnly) { + // There's a bug in `pnpm pack` where it will run + // the prepublishOnly script and that will fail. + // See https://github.com/pnpm/pnpm/issues/2941 + delete packageJson.scripts.prepublishOnly + } + + // @next/swc is devDependency in next, but we want to include it anyway + if (currentPkgDirname === 'next') { + dependencies['@next/swc'] = '*' + } + + // Modify dependencies to point to packed packages + if (dependencies) { + await Promise.all( + allPkgDirnames.map(async (depPkgDirname) => { + const { name: depPkgName } = await fs.readJson( + getPackageJsonPath(depPkgDirname) + ) + if (depPkgName in dependencies) { + dependencies[depPkgName] = getPackedPkgPath(depPkgDirname) + } + }) + ) + } + + // Ensure that we bundle binaries with swc + if (currentPkgDirname === 'next-swc') { + packageJson.files = packageJson.files ?? [] + packageJson.files.push('native') + + console.log('using swc binaries:') + await execa('ls', [ + path.join(path.dirname(packageJsonPath), 'native'), + ]).stdout?.pipe(process.stdout) + } + + // Allow overriding nateve swc version in next + if (currentPkgDirname === 'next' && process.env.NEXT_SWC_VERSION) { + dependencies['@next/swc-linux-x64-gnu'] = process.env.NEXT_SWC_VERSION + } + + try { + await fs.copy(currentPkgDir, tmpPkgPath, { + filter: (item) => + !item.includes('node_modules') && + !item.includes('.DS_Store') && + // Exclude Rust compilation files + (currentPkgDirname !== 'next' || + !/build[\\/]swc[\\/]target/.test(item)) && + (currentPkgDirname !== 'next-swc' || !/target/.test(item)), + }) + await fs.writeJson(path.join(tmpPkgPath, 'package.json'), packageJson) + // Copied from pnpm source: https://github.com/pnpm/pnpm/blob/5a5512f14c47f4778b8d2b6d957fb12c7ef40127/releasing/plugin-commands-publishing/src/pack.ts#L96 + const tmpTarball = path.join( + tmpPkgPath, + `${packageJson.name.replace('@', '').replace('/', '-')}-${ + packageJson.version + }.tgz` + ) + await execa('yarn', ['pack', '-f', tmpTarball], { + cwd: tmpPkgPath, + }) + await fs.copyFile(tmpTarball, getPackedPkgPath(currentPkgDirname)) + } finally { + await fs.remove(tmpPkgPath).catch() + } +} + +main() diff --git a/scripts/trace-next-server.js b/scripts/trace-next-server.js index e2d8659e9a072..ca1db9cdd574f 100644 --- a/scripts/trace-next-server.js +++ b/scripts/trace-next-server.js @@ -38,7 +38,7 @@ async function main() { console.log('using repodir', repoDir) await fs.ensureDir(workDir) - const pkgPaths = await linkPackages({ repoDir }) + const pkgPaths = await linkPackages({ repoDir: origRepoDir }) await fs.writeFile( path.join(workDir, 'package.json'), diff --git a/test/development/acceptance-app/ReactRefreshLogBox-scss.test.ts b/test/development/acceptance-app/ReactRefreshLogBox-scss.test.ts index 2d9328fe15e66..2524348db8e82 100644 --- a/test/development/acceptance-app/ReactRefreshLogBox-scss.test.ts +++ b/test/development/acceptance-app/ReactRefreshLogBox-scss.test.ts @@ -38,7 +38,7 @@ describe('ReactRefreshLogBox app', () => { ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) // Syntax error await session.patch('index.module.scss', `.button { font-size: :5px; }`) @@ -48,7 +48,7 @@ describe('ReactRefreshLogBox app', () => { // Fix syntax error await session.patch('index.module.scss', `.button { font-size: 5px; }`) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) // Not local error await session.patch('index.module.scss', `button { font-size: 5px; }`) diff --git a/test/development/acceptance-app/ReactRefreshLogBox.test.ts b/test/development/acceptance-app/ReactRefreshLogBox.test.ts index 20f7262256af9..b5bd55a261c8b 100644 --- a/test/development/acceptance-app/ReactRefreshLogBox.test.ts +++ b/test/development/acceptance-app/ReactRefreshLogBox.test.ts @@ -107,7 +107,7 @@ for (const variant of ['default', 'turbo']) { /Count: 1/ ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await cleanup() }) @@ -169,7 +169,7 @@ for (const variant of ['default', 'turbo']) { ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) expect(await session.hasErrorToast()).toBe(false) expect( @@ -180,7 +180,7 @@ for (const variant of ['default', 'turbo']) { await session.evaluate(() => document.querySelector('p').textContent) ).toBe('Count: 2') - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) expect(await session.hasErrorToast()).toBe(false) await cleanup() @@ -242,7 +242,7 @@ for (const variant of ['default', 'turbo']) { // TODO-APP: re-enable when error recovery doesn't reload the page. // expect(didNotReload).toBe(true) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) expect( await session.evaluate(() => document.querySelector('p').textContent) ).toBe('Hello') @@ -285,10 +285,7 @@ for (const variant of ['default', 'turbo']) { ` ) - expect(await browser.waitForElementByCss('p').text()).toBe( - 'Hello world 2' - ) - + await check(() => browser.elementByCss('p').text(), 'Hello world 2') await cleanup() }) @@ -529,7 +526,7 @@ for (const variant of ['default', 'turbo']) { ) // Expected: this fixes the problem - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await cleanup() }) @@ -704,7 +701,7 @@ for (const variant of ['default', 'turbo']) { ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await session.patch( 'index.js', @@ -755,7 +752,7 @@ for (const variant of ['default', 'turbo']) { ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) expect( await session.evaluate(() => document.querySelector('p').textContent) ).toBe('hello') @@ -787,7 +784,7 @@ for (const variant of ['default', 'turbo']) { ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) expect( await session.evaluate(() => document.querySelector('p').textContent) ).toBe('hello new') @@ -813,7 +810,7 @@ for (const variant of ['default', 'turbo']) { ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) // Syntax error await session.patch('index.module.css', `.button {`) @@ -1222,7 +1219,7 @@ for (const variant of ['default', 'turbo']) { () => browser.elementByCss('.nextjs-toast-errors').text(), /4 errors/ ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) // Add Component error await session.patch( @@ -1331,7 +1328,7 @@ for (const variant of ['default', 'turbo']) { expect(await browser.waitForElementByCss('#text').text()).toBe( 'Hello world' ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) // Re-add error await session.patch( @@ -1349,22 +1346,64 @@ for (const variant of ['default', 'turbo']) { await cleanup() }) - test('Hydration errors should get error link', async () => { - const { session, browser, cleanup } = await sandbox(next) + test('Import trace when module not found in layout', async () => { + const { session, cleanup } = await sandbox( + next, + + new Map([['app/module.js', `import "non-existing-module"`]]) + ) await session.patch( - 'app/page.js', + 'app/layout.js', ` - "use client" - export default function Page() { - return

{typeof window === 'undefined' ? "hello" : "world"}

- } + import "./module" + + export default function RootLayout({ children }) { + return ( + + + {children} + + ) + } + ` ) - await browser.refresh() - await session.waitForAndOpenRuntimeError() - expect(await session.getRedboxDescription()).toMatchSnapshot() + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxSource()).toMatchSnapshot() + + await cleanup() + }) + + test("Can't resolve @import in CSS file", async () => { + const { session, cleanup } = await sandbox( + next, + new Map([ + ['app/styles1.css', '@import "./styles2.css"'], + ['app/styles2.css', '@import "./boom.css"'], + ]) + ) + + await session.patch( + 'app/layout.js', + ` + import "./styles1.css" + + export default function RootLayout({ children }) { + return ( + + + {children} + + ) + } + + ` + ) + + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxSource()).toMatchSnapshot() await cleanup() }) diff --git a/test/development/acceptance-app/ReactRefreshLogBoxMisc.test.ts b/test/development/acceptance-app/ReactRefreshLogBoxMisc.test.ts index 83768772c3e9a..6e8b1deb48ff4 100644 --- a/test/development/acceptance-app/ReactRefreshLogBoxMisc.test.ts +++ b/test/development/acceptance-app/ReactRefreshLogBoxMisc.test.ts @@ -88,7 +88,7 @@ describe.skip('ReactRefreshLogBox app', () => { } ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await session.patch( 'index.js', @@ -112,7 +112,7 @@ describe.skip('ReactRefreshLogBox app', () => { } ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await session.patch( 'index.js', @@ -136,7 +136,7 @@ describe.skip('ReactRefreshLogBox app', () => { } ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await session.patch( 'index.js', @@ -160,7 +160,7 @@ describe.skip('ReactRefreshLogBox app', () => { } ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await session.patch( 'index.js', diff --git a/test/development/acceptance-app/ReactRefreshModule.test.ts b/test/development/acceptance-app/ReactRefreshModule.test.ts index 614148ace38e6..ba562fe1908ee 100644 --- a/test/development/acceptance-app/ReactRefreshModule.test.ts +++ b/test/development/acceptance-app/ReactRefreshModule.test.ts @@ -20,7 +20,7 @@ describe('ReactRefreshModule app', () => { it('should allow any variable names', async () => { const { session, cleanup } = await sandbox(next, new Map([])) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) const variables = [ '_a', @@ -39,7 +39,7 @@ describe('ReactRefreshModule app', () => { return null }` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) expect(next.cliOutput).not.toContain( `'${variable}' has already been declared` ) diff --git a/test/development/acceptance-app/ReactRefreshRegression.test.ts b/test/development/acceptance-app/ReactRefreshRegression.test.ts index 216047ee35162..56a2856407eb9 100644 --- a/test/development/acceptance-app/ReactRefreshRegression.test.ts +++ b/test/development/acceptance-app/ReactRefreshRegression.test.ts @@ -3,6 +3,7 @@ import { sandbox } from './helpers' import { createNext, FileRef } from 'e2e-utils' import { NextInstance } from 'test/lib/next-modes/base' import path from 'path' +import { check } from 'next-test-utils' describe('ReactRefreshRegression app', () => { let next: NextInstance @@ -80,7 +81,7 @@ describe('ReactRefreshRegression app', () => { ) // Verify no hydration mismatch: - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await cleanup() }) @@ -250,9 +251,11 @@ describe('ReactRefreshRegression app', () => { ` ) - expect( - await session.evaluate(() => document.querySelector('p').textContent) - ).toBe('0') + await check( + () => session.evaluate(() => document.querySelector('p').textContent), + '0' + ) + await session.evaluate(() => document.querySelector('button').click()) expect( await session.evaluate(() => document.querySelector('p').textContent) @@ -356,7 +359,7 @@ describe('ReactRefreshRegression app', () => { let didNotReload = await session.patch('app/content.mdx', `Hello Foo!`) expect(didNotReload).toBe(true) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) expect( await session.evaluate( () => document.querySelector('#content').textContent @@ -365,7 +368,7 @@ describe('ReactRefreshRegression app', () => { didNotReload = await session.patch('app/content.mdx', `Hello Bar!`) expect(didNotReload).toBe(true) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) expect( await session.evaluate( () => document.querySelector('#content').textContent diff --git a/test/development/acceptance-app/__snapshots__/ReactRefreshLogBox-scss.test.ts.snap b/test/development/acceptance-app/__snapshots__/ReactRefreshLogBox-scss.test.ts.snap index 847360206a9cd..d8ecc49fc84cd 100644 --- a/test/development/acceptance-app/__snapshots__/ReactRefreshLogBox-scss.test.ts.snap +++ b/test/development/acceptance-app/__snapshots__/ReactRefreshLogBox-scss.test.ts.snap @@ -20,5 +20,5 @@ exports[`ReactRefreshLogBox app scss syntax errors 2`] = ` Syntax error: Selector \\"button\\" is not pure (pure selectors must contain at least one local class or id) > 1 | button { font-size: 5px; } - | ^" + | ^" `; diff --git a/test/development/acceptance-app/__snapshots__/ReactRefreshLogBox.test.ts.snap b/test/development/acceptance-app/__snapshots__/ReactRefreshLogBox.test.ts.snap index 83a71a3365965..7d13163467b63 100644 --- a/test/development/acceptance-app/__snapshots__/ReactRefreshLogBox.test.ts.snap +++ b/test/development/acceptance-app/__snapshots__/ReactRefreshLogBox.test.ts.snap @@ -1,9 +1,24 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ReactRefreshLogBox app default Hydration errors should get error link 1`] = ` -"Error: Text content does not match server-rendered HTML. +exports[`ReactRefreshLogBox app default Can't resolve @import in CSS file 1`] = ` +"./app/styles2.css +Module not found: Can't resolve './boom.css' -See more info here: https://nextjs.org/docs/messages/react-hydration-error" +Import trace for requested module: +./app/styles1.css + +https://nextjs.org/docs/messages/module-not-found" +`; + +exports[`ReactRefreshLogBox app default Import trace when module not found in layout 1`] = ` +"./app/module.js:1:0 +Module not found: Can't resolve 'non-existing-module' +> 1 | import \\"non-existing-module\\" + +Import trace for requested module: +./app/layout.js + +https://nextjs.org/docs/messages/module-not-found" `; exports[`ReactRefreshLogBox app default Should not show __webpack_exports__ when exporting anonymous arrow function 1`] = ` diff --git a/test/development/acceptance-app/hydration-error.test.ts b/test/development/acceptance-app/hydration-error.test.ts new file mode 100644 index 0000000000000..b475255a12625 --- /dev/null +++ b/test/development/acceptance-app/hydration-error.test.ts @@ -0,0 +1,1269 @@ +/* eslint-env jest */ +import { sandbox } from './helpers' +import { createNextDescribe, FileRef } from 'e2e-utils' +import path from 'path' + +// https://github.com/facebook/react/blob/main/packages/react-dom/src/__tests__/ReactDOMHydrationDiff-test.js +createNextDescribe( + 'Error Overlay for server components', + { + files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')), + dependencies: { + react: 'latest', + 'react-dom': 'latest', + }, + skipStart: true, + }, + ({ next }) => { + it('should show correct hydration errror when client and server render different text', async () => { + const { cleanup, session } = await sandbox( + next, + new Map([ + [ + 'app/page.js', + ` + 'use client' + const isClient = typeof window !== 'undefined' + export default function Mismatch() { + return ( +
+
{isClient ? "client" : "server"}
+
+ ); + } +`, + ], + ]) + ) + + await session.waitForAndOpenRuntimeError() + + expect(await session.getRedboxDescription()).toMatchInlineSnapshot(` + "Error: Text content does not match server-rendered HTML. + + Warning: Text content did not match. Server: \\"server\\" Client: \\"client\\" + + See more info here: https://nextjs.org/docs/messages/react-hydration-error" + `) + + await cleanup() + }) + // TODO: does not throw error, throw to show warning in overlay or remove this test + it.skip('should show correct hydration errror when client and server render different html', async () => { + const { cleanup, session } = await sandbox( + next, + new Map([ + [ + 'app/page.js', + ` + 'use client' + const isClient = typeof window !== 'undefined' + export default function Mismatch() { + return ( +
+
client" + : "server", + }} + /> +
+ ); + } +`, + ], + ]) + ) + + await session.waitForAndOpenRuntimeError() + + expect(await session.getRedboxDescription()).toMatchInlineSnapshot() + + await cleanup() + }) + // TODO: does not throw error, throw to show warning in overlay or remove this test + it.skip('should show correct hydration errror when client and server render different attributes', async () => { + const { cleanup, session } = await sandbox( + next, + new Map([ + [ + 'app/page.js', + ` + 'use client' + const isClient = typeof window !== 'undefined' + export default function Mismatch() { + return ( +
+
+
+ ); + } +`, + ], + ]) + ) + + await session.waitForAndOpenRuntimeError() + + expect(await session.getRedboxDescription()).toMatchInlineSnapshot() + + await cleanup() + }) + // TODO: does not throw error, throw to show warning in overlay or remove this test + it.skip('should show correct hydration errror when client renders extra attributes', async () => { + const { cleanup, session } = await sandbox( + next, + new Map([ + [ + 'app/page.js', + ` + 'use client' + const isClient = typeof window !== 'undefined' + export default function Mismatch() { + return ( +
+
+
+ ); + } +`, + ], + ]) + ) + + await session.waitForAndOpenRuntimeError() + + expect(await session.getRedboxDescription()).toMatchInlineSnapshot() + + await cleanup() + }) + // TODO: does not throw error, throw to show warning in overlay or remove this test + it.skip('should show correct hydration errror when server renders extra attributes', async () => { + const { cleanup, session } = await sandbox( + next, + new Map([ + [ + 'app/page.js', + ` + 'use client' + const isClient = typeof window !== 'undefined' + export default function Mismatch() { + return ( +
+
+
+ ); + } +`, + ], + ]) + ) + + await session.waitForAndOpenRuntimeError() + + expect(await session.getRedboxDescription()).toMatchInlineSnapshot() + + await cleanup() + }) + // TODO: does not throw error, throw to show warning in overlay or remove this test + it.skip('should show correct hydration errror when both client and server render extra attributes', async () => { + const { cleanup, session } = await sandbox( + next, + new Map([ + [ + 'app/page.js', + ` + 'use client' + const isClient = typeof window !== 'undefined' + export default function Mismatch() { + return ( +
+
+
+ ); + } +`, + ], + ]) + ) + + await session.waitForAndOpenRuntimeError() + + expect(await session.getRedboxDescription()).toMatchInlineSnapshot() + + await cleanup() + }) + // TODO: does not throw error, throw to show warning in overlay or remove this test + it.skip('should show correct hydration errror when client and server render different styles', async () => { + const { cleanup, session } = await sandbox( + next, + new Map([ + [ + 'app/page.js', + ` + 'use client' + const isClient = typeof window !== 'undefined' + export default function Mismatch() { + return ( +
+
+
+ ); + } +`, + ], + ]) + ) + + await session.waitForAndOpenRuntimeError() + + expect(await session.getRedboxDescription()).toMatchInlineSnapshot() + + await cleanup() + }) + it('should show correct hydration errror when client renders an extra element as only child', async () => { + const { cleanup, session } = await sandbox( + next, + new Map([ + [ + 'app/page.js', + ` + 'use client' + const isClient = typeof window !== 'undefined' + export default function Mismatch() { + return ( +
+ {isClient &&
} +
+ ); + } +`, + ], + ]) + ) + + await session.waitForAndOpenRuntimeError() + + expect(await session.getRedboxDescription()).toMatchInlineSnapshot(` + "Error: Hydration failed because the initial UI does not match what was rendered on the server. + + Warning: Expected server HTML to contain a matching
in
. + + See more info here: https://nextjs.org/docs/messages/react-hydration-error" + `) + + await cleanup() + }) + it('should show correct hydration errror when client renders an extra element in the beginning', async () => { + const { cleanup, session } = await sandbox( + next, + new Map([ + [ + 'app/page.js', + ` + 'use client' + const isClient = typeof window !== 'undefined' + export default function Mismatch() { + return ( +
+ {isClient &&
} +
+
+
+ ); + } +`, + ], + ]) + ) + + await session.waitForAndOpenRuntimeError() + + expect(await session.getRedboxDescription()).toMatchInlineSnapshot(` + "Error: Hydration failed because the initial UI does not match what was rendered on the server. + + Warning: Expected server HTML to contain a matching
in
. + + See more info here: https://nextjs.org/docs/messages/react-hydration-error" + `) + + await cleanup() + }) + it('should show correct hydration errror when client renders an extra element in the middle', async () => { + const { cleanup, session } = await sandbox( + next, + new Map([ + [ + 'app/page.js', + ` + 'use client' + const isClient = typeof window !== 'undefined' + export default function Mismatch() { + return ( +
+
+ {isClient &&
} +
+
+ ); + } +`, + ], + ]) + ) + + await session.waitForAndOpenRuntimeError() + + expect(await session.getRedboxDescription()).toMatchInlineSnapshot(` + "Error: Hydration failed because the initial UI does not match what was rendered on the server. + + Warning: Expected server HTML to contain a matching
in
. + + See more info here: https://nextjs.org/docs/messages/react-hydration-error" + `) + + await cleanup() + }) + it('should show correct hydration errror when client renders an extra element in the end', async () => { + const { cleanup, session } = await sandbox( + next, + new Map([ + [ + 'app/page.js', + ` + 'use client' + const isClient = typeof window !== 'undefined' + export default function Mismatch() { + return ( +
+
+
+ {isClient &&
} +
+ ); + } +`, + ], + ]) + ) + + await session.waitForAndOpenRuntimeError() + + expect(await session.getRedboxDescription()).toMatchInlineSnapshot(` + "Error: Hydration failed because the initial UI does not match what was rendered on the server. + + Warning: Expected server HTML to contain a matching