diff --git a/.github/.kodiak.toml b/.github/.kodiak.toml index a90b3113f6533..ee535afe225c9 100644 --- a/.github/.kodiak.toml +++ b/.github/.kodiak.toml @@ -13,6 +13,7 @@ notify_on_conflict = false [merge.message] title = "pull_request_title" body = "pull_request_body" +include_coauthors= true include_pr_number = true body_type = "markdown" strip_html_comments = true diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c78fb679b6e7a..76e9888bb1b2c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,6 +1,24 @@ # Learn how to add code owners here: # https://help.github.com/en/articles/about-code-owners -* @timneutkens @ijjk @shuding @styfle @huozhi @padmaia +* @timneutkens @ijjk @shuding @huozhi +/.github/ @timneutkens @ijjk @shuding @styfle @huozhi @padmaia /docs/ @timneutkens @ijjk @shuding @styfle @huozhi @padmaia @leerob @lfades @molebox /examples/ @timneutkens @ijjk @shuding @leerob @lfades + +# SWC Build (@padmaia) + +/packages/next/build/ @timneutkens @ijjk @shuding @padmaia @huozhi + +# Image Component (@styfle) + +/examples/image-component/ @timneutkens @ijjk @shuding @styfle +/packages/next/build/webpack/loaders/next-image-loader.js @timneutkens @ijjk @shuding @styfle +/packages/next/client/image.tsx @timneutkens @ijjk @shuding @styfle +/packages/next/image-types/ @timneutkens @ijjk @shuding @styfle +/packages/next/server/image-config.ts @timneutkens @ijjk @shuding @styfle +/packages/next/server/image-optimizer.ts @timneutkens @ijjk @shuding @styfle +/packages/next/server/serve-static.ts @timneutkens @ijjk @shuding @styfle +/packages/next/server/config.ts @timneutkens @ijjk @shuding @styfle +/test/integration/image-optimizer/ @timneutkens @ijjk @shuding @styfle +/test/integration/image-component/ @timneutkens @ijjk @shuding @styfle diff --git a/.github/actions/next-stats-action/src/index.js b/.github/actions/next-stats-action/src/index.js index 8277155f0a29e..770cf6f5cb0cb 100644 --- a/.github/actions/next-stats-action/src/index.js +++ b/.github/actions/next-stats-action/src/index.js @@ -118,6 +118,10 @@ if (!allowedActions.has(actionInfo.actionName) && !actionInfo.isRelease) { // in case of noisy environment slowing down initial repo build await exec(buildCommand, false, { timeout: 5 * 60 * 1000 }) } + await fs.copy( + path.join(__dirname, '../native'), + path.join(dir, 'packages/next/native') + ) logger(`Linking packages in ${dir}`) const pkgPaths = await linkPackages(dir) 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 c526e623f2bcd..aba3266fc7e5a 100644 --- a/.github/actions/next-stats-action/src/prepare/repo-setup.js +++ b/.github/actions/next-stats-action/src/prepare/repo-setup.js @@ -100,6 +100,10 @@ module.exports = (actionInfo) => { // make sure native binaries are included in local linking if (pkg === 'next') { pkgData.files.push('native') + console.log( + 'using swc binaries: ', + await exec(`ls ${path.join(path.dirname(pkgDataPath), 'native')}`) + ) } await fs.writeFile( pkgDataPath, diff --git a/.github/actions/next-stats-action/src/run/index.js b/.github/actions/next-stats-action/src/run/index.js index e2159e9a89bf2..f0dc07bd87a03 100644 --- a/.github/actions/next-stats-action/src/run/index.js +++ b/.github/actions/next-stats-action/src/run/index.js @@ -67,7 +67,7 @@ async function runConfigs( const results = await glob(rename.srcGlob, { cwd: statsAppDir }) for (const result of results) { let dest = rename.removeHash - ? result.replace(/(\.|-)[0-9a-f]{20}(\.|-)/g, '$1HASH$2') + ? result.replace(/(\.|-)[0-9a-f]{16}(\.|-)/g, '$1HASH$2') : rename.dest if (result === dest) continue await fs.move( diff --git a/.github/lock.yml b/.github/lock.yml index a836a1e1e94aa..ae1e70313fe43 100644 --- a/.github/lock.yml +++ b/.github/lock.yml @@ -1,6 +1,6 @@ # Configuration for lock-threads - https://github.com/dessant/lock-threads # Number of days of inactivity before a closed issue or pull request is locked -daysUntilLock: 365 +daysUntilLock: 45 # Comment to post before locking. Set to `false` to disable -lockComment: false +lockComment: 'This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please open a new issue with a reproduction. Thank you.' diff --git a/.github/workflows/build_native.yml b/.github/workflows/build_native.yml deleted file mode 100644 index 9f22bedf98cea..0000000000000 --- a/.github/workflows/build_native.yml +++ /dev/null @@ -1,105 +0,0 @@ -on: workflow_dispatch - -name: Build next-swc native binaries - -jobs: - build-native: - strategy: - matrix: - os: [ubuntu-18.04, macos-latest, windows-latest] - description: [default] - include: - - os: ubuntu-18.04 - target: x86_64-unknown-linux-gnu - name: linux-x64-gnu - - os: windows-latest - target: x86_64-pc-windows-msvc - name: win32-x64-msvc - - os: macos-latest - target: x86_64-apple-darwin - name: darwin-x64 - - os: macos-latest - target: aarch64-apple-darwin - name: darwin-arm64 - description: m1 - - name: next-swc - ${{ matrix.os }} - ${{ matrix.target }} - node@14 - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v2 - - name: Setup node - uses: actions/setup-node@v2 - with: - node-version: 14 - check-latest: true - - name: Install - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly-2021-08-12 - target: ${{ matrix.target }} - - name: Cache cargo registry - uses: actions/cache@v1 - with: - path: ~/.cargo/registry - key: stable-${{ matrix.os }}-node@14-cargo-registry-trimmed-${{ hashFiles('**/Cargo.lock') }} - - name: Cache cargo index - uses: actions/cache@v1 - with: - path: ~/.cargo/git - key: stable-${{ matrix.os }}-node@14-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }} - - name: Cache native binary - id: binary-cache - uses: actions/cache@v2 - with: - path: packages/next/native/** - key: next-swc-nightly-2021-08-12-${{ matrix.target }}-${{ hashFiles('.github/workflows/build_native.yml', 'packages/next/build/swc/**') }} - - name: Cross build aarch64 setup - if: ${{ matrix.target == 'aarch64-apple-darwin' && steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} - run: | - sudo rm -Rf /Library/Developer/CommandLineTools/SDKs/*; - export CC=$(xcrun -f clang); - export CXX=$(xcrun -f clang++); - SYSROOT=$(xcrun --sdk macosx --show-sdk-path); - export CFLAGS="-isysroot $SYSROOT -isystem $SYSROOT"; - # We use restore-key to pick latest cache. - # We will not get exact match, but doc says - # "If there are multiple partial matches for a restore key, the action returns the most recently created cache." - # So we get latest cache - - name: Cache built files - uses: actions/cache@v2 - with: - path: ./packages/next/build/swc/target - key: next-swc-cargo-cache-${{ matrix.os }}--${{ hashFiles('**/Cargo.lock') }} - restore-keys: | - next-swc-cargo-cache-${{ matrix.os }} - - name: 'Build' - if: steps.binary-cache.outputs.cache-hit != true - run: yarn build-native --target ${{ matrix.target }} - env: - MACOSX_DEPLOYMENT_TARGET: '10.13' - working-directory: packages/next - - name: Upload artifact - uses: actions/upload-artifact@v2.2.4 - with: - name: next-swc-binaries - path: packages/next/native/next-swc.${{ matrix.name }}.node - - name: Clear the cargo caches - run: | - cargo install cargo-cache --no-default-features --features ci-autoclean - cargo-cache - commit: - needs: build-native - runs-on: ubuntu-18.04 - - steps: - - uses: actions/checkout@v2 - - uses: actions/download-artifact@v2.0.10 - with: - name: next-swc-binaries - path: packages/next/native - - uses: EndBug/add-and-commit@v7 - with: - add: 'packages/next/native --force' - message: 'Build next-swc binaries' diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml index 4c6fef83ae388..0a3d7b02dc1fc 100644 --- a/.github/workflows/build_test_deploy.yml +++ b/.github/workflows/build_test_deploy.yml @@ -21,6 +21,9 @@ jobs: runs-on: ubuntu-latest env: NEXT_TELEMETRY_DISABLED: 1 + # we build a dev binary for use in CI so skip downloading + # canary next-swc binaries in the monorepo + NEXT_SKIP_NATIVE_POSTINSTALL: 1 outputs: docsChange: ${{ steps.docs-change.outputs.DOCS_CHANGE }} isRelease: ${{ steps.check-release.outputs.IS_RELEASE }} @@ -71,7 +74,7 @@ jobs: checkPrecompiled: name: Check Pre-compiled runs-on: ubuntu-latest - needs: [build, build-native] + needs: build env: NEXT_TELEMETRY_DISABLED: 1 steps: @@ -96,18 +99,6 @@ jobs: - run: rm -rf .git && mv .git-bak .git if: ${{needs.build.outputs.docsChange != 'docs only change'}} - - uses: actions/download-artifact@v2 - if: ${{needs.build.outputs.docsChange != 'docs only change'}} - with: - name: next-swc-binaries - path: packages/next/build/swc/dist - - # Only check linux build for now, mac builds can sometimes be different even with the same code - - run: | - mv ./packages/next/build/swc/dist/next-swc.linux-x64-gnu.node \ - ./packages/next/native/next-swc.linux-x64-gnu.node - if: ${{needs.build.outputs.docsChange != 'docs only change'}} - - run: ./scripts/check-pre-compiled.sh if: ${{needs.build.outputs.docsChange != 'docs only change'}} @@ -120,7 +111,7 @@ jobs: testUnit: name: Test Unit runs-on: ubuntu-latest - needs: build + needs: [build, build-native-dev] env: NEXT_TELEMETRY_DISABLED: 1 NEXT_TEST_JOB: 1 @@ -132,13 +123,19 @@ jobs: path: ./* key: ${{ github.sha }} + - uses: actions/download-artifact@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + name: next-swc-dev-binary + path: packages/next/native + - run: node run-tests.js --type unit if: ${{needs.build.outputs.docsChange != 'docs only change'}} testDev: name: Test Development runs-on: ubuntu-latest - needs: build + needs: [build, build-native-dev] env: NEXT_TELEMETRY_DISABLED: 1 NEXT_TEST_JOB: 1 @@ -156,6 +153,12 @@ jobs: path: ./* key: ${{ github.sha }} + - uses: actions/download-artifact@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + name: next-swc-dev-binary + path: packages/next/native + - run: npm i -g playwright-chromium@1.14.1 && npx playwright install-deps if: ${{needs.build.outputs.docsChange != 'docs only change'}} @@ -171,10 +174,20 @@ jobs: name: Run test/e2e (dev) if: ${{needs.build.outputs.docsChange != 'docs only change'}} + - name: Upload test trace + if: always() + uses: actions/upload-artifact@v2 + with: + name: test-trace + if-no-files-found: ignore + retention-days: 2 + path: | + test/traces + testProd: name: Test Production runs-on: ubuntu-latest - needs: build + needs: [build, build-native-dev] env: NEXT_TELEMETRY_DISABLED: 1 NEXT_TEST_JOB: 1 @@ -192,6 +205,12 @@ jobs: path: ./* key: ${{ github.sha }} + - uses: actions/download-artifact@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + name: next-swc-dev-binary + path: packages/next/native + - run: npm i -g playwright-chromium@1.14.1 && npx playwright install-deps if: ${{needs.build.outputs.docsChange != 'docs only change'}} @@ -210,7 +229,7 @@ jobs: testIntegration: name: Test Integration runs-on: ubuntu-latest - needs: build + needs: [build, build-native-dev] env: NEXT_TELEMETRY_DISABLED: 1 NEXT_TEST_JOB: 1 @@ -233,6 +252,12 @@ jobs: path: ./* key: ${{ github.sha }} + - uses: actions/download-artifact@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + name: next-swc-dev-binary + path: packages/next/native + - run: npm i -g playwright-chromium@1.14.1 && npx playwright install-deps if: ${{needs.build.outputs.docsChange != 'docs only change'}} @@ -243,10 +268,20 @@ jobs: - run: xvfb-run node run-tests.js --timings -g ${{ matrix.group }}/6 if: ${{needs.build.outputs.docsChange != 'docs only change'}} + - name: Upload test trace + if: always() + uses: actions/upload-artifact@v2 + with: + name: test-trace + if-no-files-found: ignore + retention-days: 2 + path: | + test/traces + testElectron: name: Test Electron runs-on: ubuntu-latest - needs: build + needs: [build, build-native-dev] env: NEXT_TELEMETRY_DISABLED: 1 NEXT_TEST_JOB: 1 @@ -259,6 +294,12 @@ jobs: path: ./* key: ${{ github.sha }} + - uses: actions/download-artifact@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + name: next-swc-dev-binary + path: packages/next/native + # TODO: remove after we fix watchpack watching too much - run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p if: ${{needs.build.outputs.docsChange != 'docs only change'}} @@ -271,7 +312,7 @@ jobs: testYarnPnP: runs-on: ubuntu-latest - needs: build + needs: [build, build-native-dev] env: NODE_OPTIONS: '--unhandled-rejections=strict' YARN_COMPRESSION_LEVEL: '0' @@ -283,6 +324,12 @@ jobs: path: ./* key: ${{ github.sha }} + - uses: actions/download-artifact@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + name: next-swc-dev-binary + path: packages/next/native + - run: bash ./scripts/test-pnp.sh if: ${{needs.build.outputs.docsChange != 'docs only change'}} @@ -307,7 +354,7 @@ jobs: testFirefox: name: Test Firefox (production) runs-on: ubuntu-latest - needs: build + needs: [build, build-native-dev] env: BROWSER_NAME: 'firefox' NEXT_TELEMETRY_DISABLED: 1 @@ -318,6 +365,11 @@ jobs: with: path: ./* key: ${{ github.sha }} + - uses: actions/download-artifact@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + name: next-swc-dev-binary + path: packages/next/native - run: npx playwright install-deps && npx playwright install firefox if: ${{needs.build.outputs.docsChange != 'docs only change'}} - run: node run-tests.js test/integration/production/test/index.test.js @@ -326,7 +378,7 @@ jobs: testSafari: name: Test Safari (production) runs-on: ubuntu-latest - needs: build + needs: [build, build-native-dev] env: BROWSERSTACK: true BROWSER_NAME: 'safari' @@ -346,6 +398,12 @@ jobs: path: ./* key: ${{ github.sha }} + - uses: actions/download-artifact@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + name: next-swc-dev-binary + path: packages/next/native + # TODO: use macos runner so that we can use playwright to test against # PRs instead of only running on canary? - run: '[[ -z "$BROWSERSTACK_ACCESS_KEY" ]] && echo "Skipping for PR" || npm i -g browserstack-local@1.4.0' @@ -357,7 +415,7 @@ jobs: testSafariOld: name: Test Safari 10.1 (nav) runs-on: ubuntu-latest - needs: [build, testSafari] + needs: [build, testSafari, build-native-dev] env: BROWSERSTACK: true LEGACY_SAFARI: true @@ -378,17 +436,62 @@ jobs: path: ./* key: ${{ github.sha }} + - uses: actions/download-artifact@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + name: next-swc-dev-binary + path: packages/next/native + - run: '[[ -z "$BROWSERSTACK_ACCESS_KEY" ]] && echo "Skipping for PR" || npm i -g browserstack-local@1.4.0' if: ${{needs.build.outputs.docsChange != 'docs only change'}} - run: '[[ -z "$BROWSERSTACK_ACCESS_KEY" ]] && echo "Skipping for PR" || node run-tests.js test/integration/production-nav/test/index.test.js' if: ${{needs.build.outputs.docsChange != 'docs only change'}} + testFirefoxNode17: + name: Test Firefox Node.js 17 + runs-on: ubuntu-latest + needs: [build, testFirefox, build-native-dev] + env: + BROWSER_NAME: 'firefox' + NEXT_TELEMETRY_DISABLED: 1 + steps: + - name: Setup node + uses: actions/setup-node@v2 + if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} + with: + node-version: 17 + check-latest: true + - uses: actions/cache@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + id: restore-build + with: + path: ./* + key: ${{ github.sha }} + - uses: actions/download-artifact@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + name: next-swc-dev-binary + path: packages/next/native + - run: npx playwright install-deps && npx playwright install firefox + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + - run: node run-tests.js test/integration/production/test/index.test.js + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + publishRelease: if: ${{ needs.build.outputs.isRelease == 'true' }} name: Potentially publish release runs-on: ubuntu-latest - needs: [build, build-native] + needs: + - build + - build-native + - build-windows-i686 + - build-windows-aarch64 + - build-linux-musl + - build-linux-arm7 + - build-linux-aarch64 + - build-android-aarch64 + - build-linux-aarch64-musl env: NPM_TOKEN: ${{ secrets.NPM_TOKEN_ELEVATED }} steps: @@ -412,19 +515,135 @@ jobs: releaseStats: name: Release Stats runs-on: ubuntu-latest - needs: [publishRelease] + needs: [publishRelease, build-native-dev] steps: - uses: actions/cache@v2 id: restore-build with: path: ./* key: ${{ github.sha }} + + - uses: actions/download-artifact@v2 + with: + name: next-swc-dev-binary + path: packages/next/native + + - run: cp -r packages/next/native .github/actions/next-stats-action/native + - run: ./scripts/release-stats.sh - uses: ./.github/actions/next-stats-action env: PR_STATS_COMMENT_TOKEN: ${{ secrets.PR_STATS_COMMENT_TOKEN }} + build-native-dev: + name: Build dev binary for tests + runs-on: ubuntu-18.04 + steps: + # https://github.com/actions/virtual-environments/issues/1187 + - name: tune linux network + run: sudo ethtool -K eth0 tx off rx off + + - uses: actions/checkout@v2 + with: + fetch-depth: 25 + + - run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'not-docs-only-change') + id: docs-change + + - name: Setup node + uses: actions/setup-node@v2 + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + with: + node-version: 14 + check-latest: true + + - name: Install + uses: actions-rs/toolchain@v1 + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + with: + profile: minimal + toolchain: nightly-2021-08-12 + + - name: Cache cargo registry + uses: actions/cache@v1 + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + with: + path: ~/.cargo/registry + key: stable-ubuntu-18.04-node@14-cargo-registry-trimmed-${{ hashFiles('**/Cargo.lock') }} + + - name: Cache cargo index + uses: actions/cache@v1 + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + with: + path: ~/.cargo/git + key: stable-ubuntu-18.04-node@14-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }} + + - name: Cache native binary + id: binary-cache + uses: actions/cache@v2 + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + with: + path: packages/next/native/next-swc.linux-x64-gnu.node + key: dev-next-swc-nightly-2021-08-12-linux-x64-gnu-${{ hashFiles('.github/workflows/build_test_deploy.yml', 'packages/next/build/swc/**') }} + + # We use restore-key to pick latest cache. + # We will not get exact match, but doc says + # "If there are multiple partial matches for a restore key, the action returns the most recently created cache." + # So we get latest cache + - name: Cache built files + uses: actions/cache@v2 + with: + path: ./packages/next/build/swc/target + key: next-swc-cargo-cache-ubuntu-18.04--${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + next-swc-cargo-cache-ubuntu-18.04 + + # since the repo's dependencies aren't installed we need + # to install napi globally + - run: npm i -g @napi-rs/cli@1.2.1 + + - name: Build + if: ${{ steps.binary-cache.outputs.cache-hit != 'true' && steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + run: yarn build-native + env: + MACOSX_DEPLOYMENT_TARGET: '10.13' + working-directory: packages/next + + - name: Upload artifact + uses: actions/upload-artifact@v2.2.4 + with: + name: next-swc-dev-binary + path: packages/next/native/next-swc.linux-x64-gnu.node + + - name: Clear the cargo caches + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + run: | + cargo install cargo-cache --no-default-features --features ci-autoclean + cargo-cache + + test-native: + name: Unit Test Native Code + runs-on: ubuntu-18.04 + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 25 + - run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'not-docs-only-change') + id: docs-change + - name: Install + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly-2021-08-12 + profile: minimal + - run: cd packages/next/build/swc && cargo test + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + + # Build binaries for publishing build-native: + needs: build + if: ${{ needs.build.outputs.isRelease == 'true' }} strategy: matrix: os: [ubuntu-18.04, macos-latest, windows-latest] @@ -459,45 +678,48 @@ jobs: run: sudo sysctl -w net.link.generic.system.hwcksum_tx=0 && sudo sysctl -w net.link.generic.system.hwcksum_rx=0 if: ${{ matrix.os == 'macos-latest' }} - - uses: actions/checkout@v2 - with: - fetch-depth: 25 - - run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'not-docs-only-change') - id: docs-change - name: Setup node uses: actions/setup-node@v2 - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} with: node-version: 14 check-latest: true + + # we use checkout here instead of the build cache since + # it can fail to restore in different OS' + - uses: actions/checkout@v2 + + # since the repo's dependencies aren't installed we need + # to install napi globally + - run: npm i -g @napi-rs/cli@1.2.1 + - name: Install uses: actions-rs/toolchain@v1 - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} with: profile: minimal toolchain: nightly-2021-08-12 target: ${{ matrix.target }} + - name: Cache cargo registry uses: actions/cache@v1 - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} with: path: ~/.cargo/registry key: stable-${{ matrix.os }}-node@14-cargo-registry-trimmed-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo index uses: actions/cache@v1 - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} with: path: ~/.cargo/git key: stable-${{ matrix.os }}-node@14-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }} + - name: Cache native binary id: binary-cache uses: actions/cache@v2 - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} with: - path: packages/next/native/next-swc.*.node + path: packages/next/native/next-swc.${{ matrix.name }}.node key: next-swc-nightly-2021-08-12-${{ matrix.target }}-${{ hashFiles('.github/workflows/build_test_deploy.yml', 'packages/next/build/swc/**') }} + - name: Cross build aarch64 setup - if: ${{ matrix.target == 'aarch64-apple-darwin' && steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + if: ${{ matrix.target == 'aarch64-apple-darwin' }} run: | sudo rm -Rf /Library/Developer/CommandLineTools/SDKs/*; export CC=$(xcrun -f clang); @@ -515,38 +737,395 @@ jobs: key: next-swc-cargo-cache-${{ matrix.os }}--${{ hashFiles('**/Cargo.lock') }} restore-keys: | next-swc-cargo-cache-${{ matrix.os }} + - name: 'Build' - if: ${{ steps.binary-cache.outputs.cache-hit != 'true' && steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} - run: yarn build-native --target ${{ matrix.target }} + if: ${{ steps.binary-cache.outputs.cache-hit != 'true' }} + run: yarn build-native --release --target ${{ matrix.target }} env: MACOSX_DEPLOYMENT_TARGET: '10.13' working-directory: packages/next + - name: Upload artifact uses: actions/upload-artifact@v2.2.4 with: name: next-swc-binaries path: packages/next/native/next-swc.${{ matrix.name }}.node + - name: Clear the cargo caches - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} run: | cargo install cargo-cache --no-default-features --features ci-autoclean cargo-cache - test-native: - name: Unit Test Native Code + build-windows-i686: + needs: build + if: ${{ needs.build.outputs.isRelease == 'true' }} + name: next-swc - windows-i686 - node@14 + runs-on: windows-latest + env: + CARGO_PROFILE_RELEASE_CODEGEN_UNITS: 32 + CARGO_PROFILE_RELEASE_LTO: 'false' + steps: + - name: Install node x86 + run: | + choco install nodejs-lts --x86 -y --force + refreshenv + + - name: Set 32bit Node.js path + run: | + echo "C:\\Program Files (x86)\\nodejs" >> $GITHUB_PATH + shell: bash + + - name: Node.js arch + run: node -e "console.log(process.arch)" + + # we use checkout here instead of the build cache since + # it can fail to restore in different OS' + - uses: actions/checkout@v2 + + # since the repo's dependencies aren't installed we need + # to install napi globally + - run: npm i -g @napi-rs/cli@1.2.1 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2021-08-12 + override: true + target: i686-pc-windows-msvc + + - name: Cache native binary + id: binary-cache + uses: actions/cache@v2 + with: + path: packages/next/native/next-swc.win32-ia32-msvc.node + key: next-swc-nightly-2021-08-12-win32-ia32-msvc-${{ hashFiles('.github/workflows/build_test_deploy.yml', 'packages/next/build/swc/**') }} + + - name: Build + if: ${{ steps.binary-cache.outputs.cache-hit != 'true' }} + shell: bash + run: yarn build-native --release --target i686-pc-windows-msvc + working-directory: packages/next + + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: next-swc-binaries + path: packages/next/native/next-swc.win32-ia32-msvc.node + + build-windows-aarch64: + needs: build + if: ${{ needs.build.outputs.isRelease == 'true' }} + name: next-swc - windows-aarch64 - node@14 + runs-on: windows-latest + steps: + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: 14 + + # we use checkout here instead of the build cache since + # it can fail to restore in different OS' + - uses: actions/checkout@v2 + + # since the repo's dependencies aren't installed we need + # to install napi globally + - run: npm i -g @napi-rs/cli@1.2.1 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2021-08-12 + override: true + target: aarch64-pc-windows-msvc + + - name: Cache native binary + id: binary-cache + uses: actions/cache@v2 + with: + path: packages/next/native/next-swc.win32-arm64-msvc.node + key: next-swc-nightly-2021-08-12-win32-arm64-msvc-${{ hashFiles('.github/workflows/build_test_deploy.yml', 'packages/next/build/swc/**') }} + + - name: Build + if: ${{ steps.binary-cache.outputs.cache-hit != 'true' }} + shell: bash + run: yarn build-native --release --target aarch64-pc-windows-msvc + working-directory: packages/next + + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: next-swc-binaries + path: packages/next/native/next-swc.win32-arm64-msvc.node + + build-linux-musl: + needs: build + if: ${{ needs.build.outputs.isRelease == 'true' }} + name: next-swc - linux-musl - node@lts + runs-on: ubuntu-latest + steps: + # we use checkout here instead of the build cache since + # it can fail to restore in different OS' + - uses: actions/checkout@v2 + + - name: Login to registry + run: | + docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD $DOCKER_REGISTRY_URL + env: + DOCKER_REGISTRY_URL: ghcr.io + DOCKER_USERNAME: ${{ github.actor }} + DOCKER_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + + - name: Cache + uses: actions/cache@v2 + with: + path: | + target/ + key: linux-musl-publish-integration + + - name: Pull docker image + run: | + docker pull ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine + docker tag ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine builder + + - name: Cache native binary + id: binary-cache + uses: actions/cache@v2 + with: + path: packages/next/native/next-swc.linux-x64-musl.node + key: next-swc-nightly-2021-08-12-linux-x64-musl-${{ hashFiles('.github/workflows/build_test_deploy.yml', 'packages/next/build/swc/**') }} + + - name: 'Build' + if: ${{ steps.binary-cache.outputs.cache-hit != 'true' }} + run: | + docker run --rm -v ~/.cargo/git:/root/.cargo/git -v ~/.cargo/registry:/root/.cargo/registry -v $(pwd)/packages/next:/build -w /build builder sh -c "npm i -g @napi-rs/cli@1.2.1 && yarn build-native --release --target x86_64-unknown-linux-musl" + + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: next-swc-binaries + path: packages/next/native/next-swc.linux-x64-musl.node + + build-linux-aarch64: + needs: build + if: ${{ needs.build.outputs.isRelease == 'true' }} + name: next-swc - aarch64-unknown-linux-gnu - node@14 runs-on: ubuntu-18.04 + steps: + - run: docker run --rm --privileged multiarch/qemu-user-static:register --reset + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: 14 + + # we use checkout here instead of the build cache since + # it can fail to restore in different OS' + - uses: actions/checkout@v2 + + # since the repo's dependencies aren't installed we need + # to install napi globally + - run: npm i -g @napi-rs/cli@1.2.1 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2021-08-12 + override: true + target: aarch64-unknown-linux-gnu + + - name: Cache + uses: actions/cache@v2 + with: + path: | + target/ + key: aarch64-linux-gnu-publish-integration + + - name: Install cross compile toolchain + run: | + sudo apt-get update + sudo apt-get install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu -y + + - name: Cache native binary + id: binary-cache + uses: actions/cache@v2 + with: + path: packages/next/native/next-swc.linux-arm64-gnu.node + key: next-swc-nightly-2021-08-12-linux-arm64-gnu-${{ hashFiles('.github/workflows/build_test_deploy.yml', 'packages/next/build/swc/**') }} + + - name: Cross build aarch64 + if: ${{ steps.binary-cache.outputs.cache-hit != 'true' }} + run: yarn build-native --release --target aarch64-unknown-linux-gnu + working-directory: packages/next + + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: next-swc-binaries + path: packages/next/native/next-swc.linux-arm64-gnu.node + + build-linux-aarch64-musl: + needs: build + if: ${{ needs.build.outputs.isRelease == 'true' }} + name: next-swc - aarch64-unknown-linux-musl - node@14 + runs-on: ubuntu-18.04 steps: + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: 14 + + # we use checkout here instead of the build cache since + # it can fail to restore in different OS' - uses: actions/checkout@v2 + + # since the repo's dependencies aren't installed we need + # to install napi globally + - run: npm i -g @napi-rs/cli@1.2.1 + + - name: Install Rust + uses: actions-rs/toolchain@v1 with: - fetch-depth: 25 - - run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'not-docs-only-change') - id: docs-change - - name: Install - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + profile: minimal + toolchain: nightly-2021-08-12 + override: true + target: aarch64-unknown-linux-musl + + - name: Cache + uses: actions/cache@v2 + with: + path: | + target/ + key: aarch64-linux-musl-publish-integration + + - name: Install cross compile toolchain + run: | + sudo apt-get update + sudo apt-get install gcc-aarch64-linux-gnu -y + + - name: Cache native binary + id: binary-cache + uses: actions/cache@v2 + with: + path: packages/next/native/next-swc.linux-arm64-musl.node + key: next-swc-nightly-2021-08-12-linux-arm64-musl-${{ hashFiles('.github/workflows/build_test_deploy.yml', 'packages/next/build/swc/**') }} + + - name: Cross build aarch64 + if: ${{ steps.binary-cache.outputs.cache-hit != 'true' }} + run: yarn build-native --release --target aarch64-unknown-linux-musl + working-directory: packages/next + + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: next-swc-binaries + path: packages/next/native/next-swc.linux-arm64-musl.node + + build-linux-arm7: + needs: build + if: ${{ needs.build.outputs.isRelease == 'true' }} + name: next-swc - arm7-unknown-linux-gnu - node@14 + runs-on: ubuntu-18.04 + steps: + - run: docker run --rm --privileged multiarch/qemu-user-static:register --reset + + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: 14 + + # we use checkout here instead of the build cache since + # it can fail to restore in different OS' + - uses: actions/checkout@v2 + + # since the repo's dependencies aren't installed we need + # to install napi globally + - run: npm i -g @napi-rs/cli@1.2.1 + + - name: Install Rust uses: actions-rs/toolchain@v1 with: + profile: minimal toolchain: nightly-2021-08-12 + override: true + target: armv7-unknown-linux-gnueabihf + + - name: Cache + uses: actions/cache@v2 + with: + path: | + target/ + key: arm7-linux-gnu-publish-integration + + - name: Install cross compile toolchain + run: | + sudo apt-get update + sudo apt-get install gcc-arm-linux-gnueabihf -y + + - name: Cache native binary + id: binary-cache + uses: actions/cache@v2 + with: + path: packages/next/native/next-swc.linux-arm-gnueabihf.node + key: next-swc-nightly-2021-08-12-linux-arm-gnueabihf-${{ hashFiles('.github/workflows/build_test_deploy.yml', 'packages/next/build/swc/**') }} + + - name: Cross build aarch64 + if: ${{ steps.binary-cache.outputs.cache-hit != 'true' }} + run: yarn build-native --release --target armv7-unknown-linux-gnueabihf + working-directory: packages/next + + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: next-swc-binaries + path: packages/next/native/next-swc.linux-arm-gnueabihf.node + + build-android-aarch64: + needs: build + if: ${{ needs.build.outputs.isRelease == 'true' }} + name: next-swc - Android - aarch64 + runs-on: macos-latest + steps: + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: 14 + + # we use checkout here instead of the build cache since + # it can fail to restore in different OS' + - uses: actions/checkout@v2 + + # since the repo's dependencies aren't installed we need + # to install napi globally + - run: npm i -g @napi-rs/cli@1.2.1 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: profile: minimal - - run: cd packages/next/build/swc && cargo test - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + toolchain: nightly-2021-08-12 + override: true + target: aarch64-linux-android + + - name: Cache native binary + id: binary-cache + uses: actions/cache@v2 + with: + path: packages/next/native/next-swc.android-arm64.node + key: next-swc-nightly-2021-08-12-android-arm64-${{ hashFiles('.github/workflows/build_test_deploy.yml', 'packages/next/build/swc/**') }} + + - name: Build + if: ${{ steps.binary-cache.outputs.cache-hit != 'true' }} + shell: bash + run: | + export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android24-clang" + yarn build-native --release --target aarch64-linux-android + working-directory: packages/next + + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: next-swc-binaries + path: packages/next/native/next-swc.android-arm64.node diff --git a/.github/workflows/cancel.yml b/.github/workflows/cancel.yml index ce58d11cab7c9..093016a7beaf7 100644 --- a/.github/workflows/cancel.yml +++ b/.github/workflows/cancel.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 2 steps: - - uses: styfle/cancel-workflow-action@0.5.0 + - uses: styfle/cancel-workflow-action@0.9.1 with: workflow_id: 444921, 444987 access_token: ${{ github.token }} diff --git a/.github/workflows/pull_request_stats.yml b/.github/workflows/pull_request_stats.yml index 4c4874cdb9f48..11d63ac5e5f8a 100644 --- a/.github/workflows/pull_request_stats.yml +++ b/.github/workflows/pull_request_stats.yml @@ -5,8 +5,95 @@ on: name: Generate Pull Request Stats jobs: + build-native-dev: + name: Build dev binary for tests + runs-on: ubuntu-18.04 + steps: + # https://github.com/actions/virtual-environments/issues/1187 + - name: tune linux network + run: sudo ethtool -K eth0 tx off rx off + + - uses: actions/checkout@v2 + with: + fetch-depth: 25 + + - run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'not-docs-only-change') + id: docs-change + + - name: Setup node + uses: actions/setup-node@v2 + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + with: + node-version: 14 + check-latest: true + + - name: Install + uses: actions-rs/toolchain@v1 + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + with: + profile: minimal + toolchain: nightly-2021-08-12 + + - name: Cache cargo registry + uses: actions/cache@v1 + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + with: + path: ~/.cargo/registry + key: stable-ubuntu-18.04-node@14-cargo-registry-trimmed-${{ hashFiles('**/Cargo.lock') }} + + - name: Cache cargo index + uses: actions/cache@v1 + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + with: + path: ~/.cargo/git + key: stable-ubuntu-18.04-node@14-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }} + + - name: Cache native binary + id: binary-cache + uses: actions/cache@v2 + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + with: + path: packages/next/native/next-swc.linux-x64-gnu.node + key: dev-next-swc-nightly-2021-08-12-linux-x64-gnu-${{ hashFiles('.github/workflows/build_test_deploy.yml', 'packages/next/build/swc/**') }} + + # We use restore-key to pick latest cache. + # We will not get exact match, but doc says + # "If there are multiple partial matches for a restore key, the action returns the most recently created cache." + # So we get latest cache + - name: Cache built files + uses: actions/cache@v2 + with: + path: ./packages/next/build/swc/target + key: next-swc-cargo-cache-ubuntu-18.04--${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + next-swc-cargo-cache-ubuntu-18.04 + + # since the repo's dependencies aren't installed we need + # to install napi globally + - run: npm i -g @napi-rs/cli@1.2.1 + + - name: Build + if: ${{ steps.binary-cache.outputs.cache-hit != 'true' && steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + run: yarn build-native + env: + MACOSX_DEPLOYMENT_TARGET: '10.13' + working-directory: packages/next + + - name: Upload artifact + uses: actions/upload-artifact@v2.2.4 + with: + name: next-swc-dev-binary + path: packages/next/native/next-swc.linux-x64-gnu.node + + - name: Clear the cargo caches + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + run: | + cargo install cargo-cache --no-default-features --features ci-autoclean + cargo-cache + stats: name: PR Stats + needs: build-native-dev runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -15,5 +102,15 @@ jobs: - run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'not-docs-only-change') id: docs-change + + - uses: actions/download-artifact@v2 + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + with: + name: next-swc-dev-binary + path: packages/next/native + + - run: cp -r packages/next/native .github/actions/next-stats-action/native + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + - uses: ./.github/actions/next-stats-action if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} diff --git a/.gitignore b/.gitignore index e883995fb2dc7..a610b8f09ae7c 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ test-timings.json # Vercel .vercel .now + +# Cache +*.tsbuildinfo \ No newline at end of file diff --git a/bench/nested-deps/next.config.js b/bench/nested-deps/next.config.js index d06b18cf4e29c..004e6c18198b6 100644 --- a/bench/nested-deps/next.config.js +++ b/bench/nested-deps/next.config.js @@ -1,9 +1,9 @@ +const idx = process.execArgv.indexOf('--cpu-prof') +if (idx >= 0) process.execArgv.splice(idx, 1) + module.exports = { eslint: { ignoreDuringBuilds: true, }, - experimental: { - swcLoader: true, - swcMinify: true, - }, + swcMinify: true, } diff --git a/bench/nested-deps/package.json b/bench/nested-deps/package.json index d025ab2b2ad3c..ad48206bed2d1 100644 --- a/bench/nested-deps/package.json +++ b/bench/nested-deps/package.json @@ -1,11 +1,11 @@ { "scripts": { - "prepare": "rm -rf components && mkdir components && node ./fuzzponent.js -d 2 -s 206 -o components", - "dev": "cross-env NEXT_PRIVATE_LOCAL_WEBPACK5=1 ../../node_modules/.bin/next dev", - "build": "cross-env NEXT_PRIVATE_LOCAL_WEBPACK5=1 ../../node_modules/.bin/next build", - "start": "cross-env NEXT_PRIVATE_LOCAL_WEBPACK5=1 ../../node_modules/.bin/next start", - "dev-nocache": "rm -rf .next && yarn dev", - "dev-cpuprofile-nocache": "rm -rf .next && cross-env NEXT_PRIVATE_LOCAL_WEBPACK5=1 node --cpu-prof ../../node_modules/.bin/next", - "build-nocache": "rm -rf .next && yarn build" + "prepare": "rimraf components && mkdir components && node ./fuzzponent.js -d 2 -s 206 -o components", + "dev": "cross-env NEXT_PRIVATE_LOCAL_WEBPACK5=1 node ../../node_modules/next/dist/bin/next dev", + "build": "cross-env NEXT_PRIVATE_LOCAL_WEBPACK5=1 node ../../node_modules/next/dist/bin/next build", + "start": "cross-env NEXT_PRIVATE_LOCAL_WEBPACK5=1 node ../../node_modules/next/dist/bin/next start", + "dev-nocache": "rimraf .next && yarn dev", + "dev-cpuprofile-nocache": "rimraf .next && cross-env NEXT_PRIVATE_LOCAL_WEBPACK5=1 node --cpu-prof ../../node_modules/next/dist/bin/next", + "build-nocache": "rimraf .next && yarn build" } } diff --git a/contributing.md b/contributing.md index 0028660ae5d32..1535ae5d4617a 100644 --- a/contributing.md +++ b/contributing.md @@ -15,8 +15,7 @@ To develop locally: 1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then - [clone](https://help.github.com/articles/cloning-a-repository/) it to your - local device. + [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device. 2. Create a new branch: ``` git checkout -b MY_BRANCH_NAME @@ -52,14 +51,14 @@ yarn build yarn prepublish ``` +By default the latest canary of the next-swc binaries will be installed and used. If you are actively working on Rust code or you need to test out the most recent Rust code that hasn't been published as a canary yet you can [install Rust](https://www.rust-lang.org/tools/install) and run `yarn --cwd packages/next build-native`. + If you need to clean the project for any reason, use `yarn clean`. ## Testing See the [testing readme](./test/readme.md) for information on writing tests. -You may have to [install Rust](https://www.rust-lang.org/tools/install) and build our native packages to see all tests pass locally. We check in binaries for the most common targets and those required for CI so that most people don't have to, but if you do not see a binary for your target in `packages/next/native`, you can build it by running `yarn --cwd packages/next build-native`. If you are working on the Rust code and you need to build the binaries for ci, you can manually trigger [the workflow](https://github.com/vercel/next.js/actions/workflows/build_native.yml) to build and commit with the "Run workflow" button. - ### Running tests ```sh @@ -127,7 +126,7 @@ EXAMPLE=./test/integration/basic There are two options to develop with your local version of the codebase: -### Set as local dependency in package.json +### Set as a local dependency in package.json 1. In your app's `package.json`, replace: @@ -165,7 +164,24 @@ There are two options to develop with your local version of the codebase: yarn install --force ``` -or +#### Troubleshooting + +- If you see the below error while running `yarn dev` with next: + +``` +Failed to load SWC binary, see more info here: https://nextjs.org/docs/messages/failed-loading-swc +``` + +Try to add the below section to your `package.json`, then run again + +```json +"optionalDependencies": { + "@next/swc-linux-x64-gnu": "canary", + "@next/swc-win32-x64-msvc": "canary", + "@next/swc-darwin-x64": "canary", + "@next/swc-darwin-arm64": "canary" +}, +``` ### Develop inside the monorepo @@ -175,7 +191,7 @@ or This will use the version of `next` built inside of the Next.js monorepo and the main `yarn dev` monorepo command can be running to make changes to the local -Next.js version at the same time (some changes might require re-running `yarn next-with-deps` to take affect). +Next.js version at the same time (some changes might require re-running `yarn next-with-deps` to take effect). ## Adding warning/error descriptions @@ -183,7 +199,7 @@ In Next.js we have a system to add helpful links to warnings and errors. This allows for the logged message to be short while giving a broader description and instructions on how to solve the warning/error. -In general all warnings and errors added should have these links attached. +In general, all warnings and errors added should have these links attached. Below are the steps to add a new link: diff --git a/docs/advanced-features/debugging.md b/docs/advanced-features/debugging.md index 407d0e98eb647..3c25d4a33fa6b 100644 --- a/docs/advanced-features/debugging.md +++ b/docs/advanced-features/debugging.md @@ -84,6 +84,16 @@ Once the server starts, open a new tab in Chrome and visit `chrome://inspect`, w Debugging server-side code here works much like debugging client-side code with Chrome DevTools, except that when you search for files here with Ctrl+P or +P, your source files will have paths starting with `webpack://{application-name}/./` (where `{application-name}` will be replaced with the name of your application according to your `package.json` file). +### Debugging on Windows + +Windows users may run into an issue when using `NODE_OPTIONS='--inspect'` as that syntax is not supported on Windows platforms. To get around this, install the [`cross-env`](https://www.npmjs.com/package/cross-env) package as a development dependency (`--dev` with Yarn or `-D` for NPM) and replace the `dev` script with the following. + +```json +"dev": "cross-env NODE_OPTIONS='--inspect' next dev", +``` + +`cross-env` will set the `NODE_OPTIONS` environment variable regardless of which platform you are on (including Mac, Linux, and Windows) and allow you to debug consistently across devices and operating systems. + ## More information To learn more about how to use a JavaScript debugger, take a look at the following documentation: diff --git a/docs/advanced-features/i18n-routing.md b/docs/advanced-features/i18n-routing.md index 383fe72d3fbde..8272276b47418 100644 --- a/docs/advanced-features/i18n-routing.md +++ b/docs/advanced-features/i18n-routing.md @@ -100,6 +100,8 @@ module.exports = { domains: [ { + // Note: subdomains must be included in the domain value to be matched + // e.g. www.example.com should be used if that is the expected hostname domain: 'example.com', defaultLocale: 'en-US', }, @@ -122,6 +124,7 @@ module.exports = { For example if you have `pages/blog.js` the following urls will be available: - `example.com/blog` +- `www.example.com/blog` - `example.fr/blog` - `example.nl/blog` - `example.nl/nl-BE/blog` @@ -139,6 +142,48 @@ When using Domain Routing, if a user with the `Accept-Language` header `fr;q=0.9 When using Sub-path Routing, the user would be redirected to `/fr`. +### Prefixing the Default Locale + +With Next.js 12 and [Middleware](/docs/middleware.md), we can add a prefix to the default locale with a [workaround](https://github.com/vercel/next.js/discussions/18419). + +For example, here's a `next.config.js` file with support for a few languages. Note the `"default"` locale has been added intentionally. + +```js +// next.config.js + +module.exports = { + i18n: { + locales: ['default', 'en', 'de', 'fr'], + defaultLocale: 'default', + localeDetection: false, + }, + trailingSlash: true, +} +``` + +Next, we can use [Middleware](/docs/middleware.md) to add custom routing rules: + +```js +// pages/_middleware.ts + +import { NextRequest, NextResponse } from 'next/server' + +const PUBLIC_FILE = /\.(.*)$/ + +export function middleware(request: NextRequest) { + const shouldHandleLocale = + !PUBLIC_FILE.test(request.nextUrl.pathname) && + !request.nextUrl.pathname.includes('/api/') && + request.nextUrl.locale === 'default' + + return shouldHandleLocale + ? NextResponse.redirect(`/en${request.nextUrl.href}`) + : undefined +} +``` + +This [Middleware](/docs/middleware.md) skips adding the default prefix to [API Routes](/docs/api-routes/introduction.md) and [public](/docs/basic-features/static-file-serving.md) files like fonts or images. If a request is made to the default locale, we redirect to our prefix `/en`. + ### Disabling Automatic Locale Detection The automatic locale detection can be disabled with: @@ -306,4 +351,4 @@ export async function getStaticProps({ locale }) { - `locales`: 100 total locales - `domains`: 100 total locale domain items -> **Note:** These limits have been added initially to prevent potential [performance issues at build time](#dynamic-routes-and-getStaticProps-pages). We are continuing to evaluate if these limits are sufficient. +> **Note:** These limits have been added initially to prevent potential [performance issues at build time](#dynamic-routes-and-getStaticProps-pages). You can workaround these limits with custom routing using [Middleware](/docs/middleware.md) in Next.js 12. diff --git a/docs/advanced-features/output-file-tracing.md b/docs/advanced-features/output-file-tracing.md new file mode 100644 index 0000000000000..b5ba359591af6 --- /dev/null +++ b/docs/advanced-features/output-file-tracing.md @@ -0,0 +1,24 @@ +--- +description: Next.js automatically traces which files are needed by each page to allow for easy deployment of your application. Learn how it works here. +--- + +# Output File Tracing + +During a build, Next.js will automatically trace each page and its dependencies to determine all of the files that are needed for deploying a production version of your application. + +This feature helps reduce the size of deployments drastically. Previously, when deploying with Docker you would need to have all files from your package's `dependencies` installed to run `next start`. Starting with Next.js 12, you can leverage Output File Tracing in the `.next/` directory to only include the necessary files. + +Furthermore, this removes the need for the deprecated `serverless` target which can cause various issues and also creates unnecessary duplication. + +## How It Works + +During `next build`, Next.js will use [`@vercel/nft`](https://github.com/vercel/nft) to statically analyze `import`, `require`, and `fs` usage to determine all files that a page might load. + +Next.js' production server is also traced for its needed files and output at `.next/next-server.js.nft.json` which can be leveraged in production. + +To leverage the `.nft.json` files emitted to the `.next` output directory, you can read the list of files in each trace which are relative to the `.nft.json` file and then copy them to your deployment location. + +## Caveats + +- There are some cases that Next.js might fail to include required files, or might incorrectly include unused files. In those cases, you can export page configs props `unstable_includeFiles` and `unstable_excludeFiles` respectively. Each prop accepts an array of [globs]() relative to the project's root to either include or exclude in the trace. +- Currently, Next.js does not do anything with the emitted `.nft.json` files. The files must be read by your deployment platform, for example [Vercel](https://vercel.com), to create a minimal deployment. In a future release, a new command is planned to utilize these `.nft.json` files. diff --git a/docs/advanced-features/react-18.md b/docs/advanced-features/react-18.md new file mode 100644 index 0000000000000..03d786dc2bbc2 --- /dev/null +++ b/docs/advanced-features/react-18.md @@ -0,0 +1,152 @@ +# React 18 + +[React 18](https://reactjs.org/blog/2021/06/08/the-plan-for-react-18.html) adds new features including, Suspense, automatic batching of updates, APIs like `startTransition`, and a new streaming API for server rendering with support for `React.lazy`. + +React 18 is still in alpha. Read more about React 18's [release plan](https://github.com/reactwg/react-18/discussions) and discussions from the [working group](https://github.com/reactwg/react-18/discussions). + +### React 18 Usage in Next.js + +Ensure you have the `alpha` version of React installed: + +```jsx +npm install next@latest react@alpha react-dom@alpha +``` + +### Enable SSR Streaming (Alpha) + +Concurrent features in React 18 include built-in support for server-side Suspense and SSR streaming support, allowing you to server-render pages using HTTP streaming. + +This is an experimental feature in Next.js 12, but once enabled, SSR will use the same [Edge Runtime](/docs/api-reference/edge-runtime.md) as [Middleware](/docs/middleware.md). + +To enable, use the experimental flag `concurrentFeatures: true`: + +```jsx +// next.config.js +module.exports = { + experimental: { + concurrentFeatures: true, + }, +} +``` + +Once enabled, you can use Suspense and SSR streaming for all pages. This also means that you can use Suspense-based data-fetching, `next/dynamic`, and React's built-in `React.lazy` with Suspense boundaries. + +```jsx +import dynamic from 'next/dynamic' +import { lazy } from 'react' + +import Content from '../components/content' + +// These two ways are identical: +const Profile = dynamic(() => import('./profile'), { suspense: true }) +const Footer = lazy(() => import('./footer')) + +export default function Home() { + return ( +
+ }> + {/* A component that uses Suspense-based */} + + + }> + + + }> +
+ +
+ ) +} +``` + +## React Server Components + +React Server Components allow us to render everything, including the components themselves, on the server. This is fundamentally different from server-side rendering where you're pre-generating HTML on the server. With Server Components, there's **zero client-side JavaScript needed,** making page rendering faster. This improves the user experience of your application, pairing the best parts of server-rendering with client-side interactivity. + +### Enable React Server Components (Alpha) + +To use React Server Components, ensure you have React 18 installed. Then, turn on the `concurrentFeatures` and `serverComponents` options in `next.config.js`: + +```jsx +// next.config.js +module.exports = { + experimental: { + concurrentFeatures: true, + serverComponents: true, + }, +} +``` + +Next, if you already have customized `pages/_document` component, you need to remove the `getInitialProps` static method and the `getServerSideProps` export if there’s any, otherwise it won't work with server components. If no custom Document component is provided, Next.js will fallback to a default one like below. + +```jsx +// pages/_document.js +import { Html, Head, Main, NextScript } from 'next/document' + +export default function Document() { + return ( + + + +
+ + + + ) +} +``` + +Then, you can start using React Server Components. [See our example](https://github.com/vercel/next-rsc-demo) for more information. + +### Server Components APIs (Alpha) + +To run a component on the server, append `.server.js` to the end of the filename. For example `./pages/home.server.js` is a Server Component. + +For client components, add `.client.js`. For example, `./components/avatar.client.js`. + +You can then import other server or client components from any server component. Note: a server component **can not** be imported by a client component. Components without "server/client" extensions will be treated as "universal component" and can be used and rendered by both sides, depending on where it is imported. For example: + +```jsx +// pages/home.server.js + +import React, { Suspense } from 'react' + +import Profile from '../components/profile.server' +import Content from '../components/content.client' + +export default function Home() { + return ( +
+

Welcome to React Server Components

+ + + + +
+ ) +} +``` + +The `` and `` components will always be server-side rendered and streamed to the client, and will not be included by the client runtime. However `` will still be hydrated on the client-side, like normal React components. + +To see a full example, check out [link to the demo and repository](https://github.com/vercel/next-rsc-demo). + +## **Supported Next.js APIs** + +- `next/link` / `next/image` +- `next/document` / `next/app` +- Dynamic routing + +## **Unsupported Next.js APIs** + +While RSC and SSR streaming is still in the alpha stage, not all Next.js APIs are supported. The following Next.js APIs have limited functionality inside Server Components: + +- React internals: Most of React hooks such as `useContext`, `useState`, `useReducer`, `useEffect` and `useLayoutEffect` are not supported as of today since Server Components are executed per requests and aren't stateful. +- `next/head` +- Partial: Note that Inside `.client.js` components `useRouter` is supported +- Styled JSX +- CSS Modules +- Next.js I18n +- `getInitialProps`, `getStaticProps` and `getStaticPaths` + +React 18 without SSR streaming isn't affected. diff --git a/docs/api-reference/edge-runtime.md b/docs/api-reference/edge-runtime.md new file mode 100644 index 0000000000000..95e3f914472a8 --- /dev/null +++ b/docs/api-reference/edge-runtime.md @@ -0,0 +1,103 @@ +--- +description: The Next.js Edge Runtime is based on standard Web APIs. Learn more about the supported APIs available. +--- + +# Edge Runtime + +The Next.js Edge Runtime is based on standard Web APIs, which is used by [Middleware](/docs/middleware.md). + +## Runtime APIs + +### Globals + +- [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) +- [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) +- [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) + +### Base64 + +- [`atob`](https://developer.mozilla.org/en-US/docs/Web/API/atob): Decodes a string of data which has been encoded using base-64 encoding +- [`btoa`](https://developer.mozilla.org/en-US/docs/Web/API/btoa): Creates a base-64 encoded ASCII string from a string of binary data + +### Encoding + +- [`TextEncoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder): Takes a stream of code points as input and emits a stream of bytes (UTF8) +- [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder): Takes a stream of bytes as input and emit a stream of code points + +### Environment + +- `process.env`: Holds an object with all environment variables for both production and development in the exact same way as any other page or API in Next.js + +### Fetch + +The [Web Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) can be used from the runtime, enabling you to use Middleware as a proxy, or connect to external storage APIs + +A potential caveat to using the Fetch API in a Middleware function is latency. For example, if you have a Middleware function running a fetch request to New York, and a user accesses your site from London, the request will be resolved from the nearest Edge to the user (in this case, London), to the origin of the request, New York. There is a risk this could happen on every request, making your site slow to respond. When using the Fetch API, you _must_ make sure it does not run on every single request made. + +### Streams + +- [`TransformStream`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream): Consists of a pair of streams: a writable stream known as its writable side, and a readable stream, known as its readable side. Writes to the writable side, result in new data being made available for reading from the readable side. Support for web streams is quite limited at the moment, although it is more extended in the development environment +- [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream): A readable stream of byte data +- [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream): A standard abstraction for writing streaming data to a destination, known as a sink + +### Timers + +- [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval): Schedules a function to execute every time a given number of milliseconds elapses +- [`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval): Cancels the repeated execution set using `setInterval()` +- [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout): Schedules a function to execute in a given amount of time +- [`clearTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/clearTimeout): Cancels the delayed execution set using `setTimeout()` + +### Web + +- [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers): A [WHATWG](https://whatwg.org/) implementation of the headers API +- [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL): A WHATWG implementation of the URL API. +- [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams): A WHATWG implementation of `URLSearchParams` + +### Crypto + +- [`Crypto`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto): The `Crypto` interface represents basic cryptography features available in the current context +- [`crypto.randomUUID`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID): Lets you generate a v4 UUID using a cryptographically secure random number generator +- [`crypto.getRandomValues`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues): Lets you get cryptographically strong random values +- [`crypto.subtle`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/subtle): A read-only property that returns a [SubtleCrypto](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) which can then be used to perform low-level cryptographic operations + +### Logging + +- [`console.debug`](https://developer.mozilla.org/en-US/docs/Web/API/console/debug): Outputs a message to the console with the log level debug +- [`console.info`](https://developer.mozilla.org/en-US/docs/Web/API/console/info): Informative logging of information. You may use string substitution and additional arguments with this method +- [`console.clear`](https://developer.mozilla.org/en-US/docs/Web/API/console/clear): Clears the console +- [`console.dir`](https://developer.mozilla.org/en-US/docs/Web/API/console/dir): Displays an interactive listing of the properties of a specified JavaScript object +- [`console.count`](https://developer.mozilla.org/en-US/docs/Web/API/console/count): Log the number of times this line has been called with the given label +- [`console.time`](https://developer.mozilla.org/en-US/docs/Web/API/console/time): Starts a timer with a name specified as an input parameter + +## Unsupported APIs + +The Edge Runtime has some restrictions including: + +- Native Node.js APIs **are not supported**. For example, you can't read or write to the filesystem +- Node Modules _can_ be used, as long as they implement ES Modules and do not use any native Node.js APIs +- Calling `require` directly is **not allowed**. Use ES Modules instead + +The following JavaScript language features are disabled, and **will not work:** + +- [`eval`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval): Evaluates JavaScript code represented as a string +- [`new Function(evalString)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function): Creates a new function with the code provided as an argument + +The following Web APIs are currently not supported, but will be in the future: + +- [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController): Abort one or more Web requests when desired + +## Related + + + + diff --git a/docs/api-reference/next.config.js/build-indicator.md b/docs/api-reference/next.config.js/build-indicator.md new file mode 100644 index 0000000000000..cccaa3d4bfad6 --- /dev/null +++ b/docs/api-reference/next.config.js/build-indicator.md @@ -0,0 +1,29 @@ +--- +description: In development mode, pages include an indicator to let you know if your new code it's being compiled. You can opt-out of it here. +--- + +# Build indicator + +When you edit your code, and Next.js is compiling the application, a compilation indicator appears in the bottom right corner of the page. + +> **Note:** This indicator is only present in development mode and will not appear when building and running the app in production mode. + +In some cases this indicator can be misplaced on your page, for example, when conflicting with a chat launcher. To change its position, open `next.config.js` and set the `buildActivityPosition` in the `devIndicators` object to `bottom-right` (default), `bottom-left`, `top-right` or `top-left`: + +```js +module.exports = { + devIndicators: { + buildActivityPosition: 'bottom-right', + }, +} +``` + +In some cases this indicator might not be useful for you. To remove it, open `next.config.js` and disable the `buildActivity` config in `devIndicators` object: + +```js +module.exports = { + devIndicators: { + buildActivity: false, + }, +} +``` diff --git a/docs/api-reference/next.config.js/headers.md b/docs/api-reference/next.config.js/headers.md index d64c01c676b79..9cc6183514580 100644 --- a/docs/api-reference/next.config.js/headers.md +++ b/docs/api-reference/next.config.js/headers.md @@ -83,7 +83,7 @@ module.exports = { }, ], }, - ], + ] }, } ``` @@ -109,7 +109,7 @@ module.exports = { }, ], }, - ], + ] }, } ``` @@ -135,7 +135,7 @@ module.exports = { }, ], }, - ], + ] }, } ``` @@ -157,7 +157,7 @@ module.exports = { }, ], }, - ], + ] }, } ``` diff --git a/docs/api-reference/next.config.js/rewrites.md b/docs/api-reference/next.config.js/rewrites.md index 46c58eb6035dd..3a54cf4288ef9 100644 --- a/docs/api-reference/next.config.js/rewrites.md +++ b/docs/api-reference/next.config.js/rewrites.md @@ -50,7 +50,7 @@ Rewrites are applied to client-side routing, a `` will have - `locale`: `false` or `undefined` - whether the locale should not be included when matching. - `has` is an array of [has objects](#header-cookie-and-query-matching) with the `type`, `key` and `value` properties. -Rewrites are applied after checking the filesystem (pages and `/public` files) and before dynamic routes by default. This behavior can be changed by instead returning an object instead of an array from the `rewrites` function since `v10.1` of Next.js: +Rewrites are applied after checking the filesystem (pages and `/public` files) and before dynamic routes by default. This behavior can be changed by returning an object instead of an array from the `rewrites` function since `v10.1` of Next.js: ```js module.exports = { @@ -148,6 +148,8 @@ module.exports = { } ``` +Note: for static pages from the [Automatic Static Optimization](/docs/advanced-features/automatic-static-optimization.md) or [prerendering](/docs/basic-features/data-fetching.md#getstaticprops-static-generation) params from rewrites will be parsed on the client after hydration and provided in the query. + ## Path Matching Path matches are allowed, for example `/blog/:slug` will match `/blog/hello-world` (no nested paths): diff --git a/docs/api-reference/next.config.js/url-imports.md b/docs/api-reference/next.config.js/url-imports.md index 2752704f2c070..bb50c6ffe6b10 100644 --- a/docs/api-reference/next.config.js/url-imports.md +++ b/docs/api-reference/next.config.js/url-imports.md @@ -1,10 +1,13 @@ --- -description: Configure Next.js to allow importing modules from external URLs. +description: Configure Next.js to allow importing modules from external URLs (experimental). --- -# URL imports +# URL Imports -URL Imports are an experimental feature that allows you to import modules directly from external servers (instead of from the local disk). +URL imports are an experimental feature that allows you to import modules directly from external servers (instead of from the local disk). + +> **Warning**: This feature is experimental. Only use domains that you trust to download and execute on your machine. Please exercise +> discretion, and caution until the feature is flagged as stable. To opt-in, add the allowed URL prefixes inside `next.config.js`: @@ -19,11 +22,15 @@ module.exports = { Then, you can import modules directly from URLs: ```js -import { a, b, c } from 'https://example.com/modules/' +import { a, b, c } from 'https://example.com/modules/some/module.js' ``` URL Imports can be used everywhere normal package imports can be used. +## Security Model + +This feature is being designed with **security as the top priority**. To start, we added an experimental flag forcing you to explicitly allow the domains you accept URL imports from. We're working to take this further by limiting URL imports to execute in the browser sandbox using the [Edge Runtime](/docs/api-reference/edge-runtime.md). This runtime is used by [Middleware](/docs/middleware.md) as well as [Next.js Live](https://vercel.com/live). + ## Lockfile When using URL imports, Next.js will create a lockfile in the `next.lock` directory. @@ -35,3 +42,53 @@ This directory is intended to be committed to Git and should **not be included** Typically, no network requests are needed and any outdated lockfile will cause the build to fail. One exception is resources that respond with `Cache-Control: no-cache`. These resources will have a `no-cache` entry in the lockfile and will always be fetched from the network on each build. + +## Examples + +### Skypack + +```js +import confetti from 'https://cdn.skypack.dev/canvas-confetti' +import { useEffect } from 'react' + +export default () => { + useEffect(() => { + confetti() + }) + return

Hello

+} +``` + +### Static Image Imports + +```js +import Image from 'next/image' +import logo from 'https://github.com/vercel/next.js/raw/canary/test/integration/production/public/vercel.png' + +export default () => ( +
+ +
+) +``` + +### URLs in CSS + +```css +.className { + background: url('https://github.com/vercel/next.js/raw/canary/test/integration/production/public/vercel.png'); +} +``` + +### Asset Imports + +```js +import Image from 'next/image' + +const logo = new URL( + 'https://github.com/vercel/next.js/raw/canary/test/integration/production/public/vercel.png', + import.meta.url +) + +export default () =>
{logo.pathname}
+``` diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index cf7310e0e651f..957e53c43917d 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -119,9 +119,11 @@ const MyImage = (props) => { A string that provides information about how wide the image will be at different breakpoints. Defaults to `100vw` (the full width of the screen) when using `layout="responsive"` or `layout="fill"`. -`sizes` is important for performance when using `layout="responsive"` or `layout="fill"` with images that take up less than the full viewport width. +If you are using `layout="fill"` or `layout="responsive"`, it's important to assign `sizes` for any image that takes up less than the full viewport width. -If you are using `layout="fill"` or `layout="responsive"` and the image will always be less than half the viewport width, include `sizes="50vw"`. Without `sizes`, the image will be sent at twice the necessary resolution, decreasing performance. +For example, when the parent element will constrain the image to always be less than half the viewport width, use `sizes="50vw"`. Without `sizes`, the image will be sent at twice the necessary resolution, decreasing performance. + +If you are using `layout="intrinsic"` or `layout="fixed"`, then `sizes` is not needed because the upperbound width is constrained already. [Learn more](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-sizes). @@ -134,7 +136,7 @@ The quality of the optimized image, an integer between `1` and `100` where `100` When true, the image will be considered high priority and [preload](https://web.dev/preload-responsive-images/). Lazy loading is automatically disabled for images using `priority`. -You should use the `priority` attribute on any image which you suspect will be the [Largest Contentful Paint (LCP) element](https://nextjs.org/learn/seo/web-performance/lcp). It may be appropriate to have multiple priority images, as different images may be the LCP element for different viewport sizes. +You should use the `priority` property on any image detected as the [Largest Contentful Paint (LCP)](https://nextjs.org/learn/seo/web-performance/lcp) element. It may be appropriate to have multiple priority images, as different images may be the LCP element for different viewport sizes. Should only be used when the image is visible above the fold. Defaults to `false`. @@ -302,7 +304,7 @@ module.exports = { You can specify a list of image widths using the `images.imageSizes` property in your `next.config.js` file. These widths are concatenated with the array of [device sizes](#device-sizes) to form the full array of sizes used to generate image [srcset](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/srcset)s. -The reason there are two seperate lists is that imageSizes is only used for images which provide a [`sizes`](#sizes) prop, which indicates that the image is less than the full width of the screen. **Therefore, the sizes in imageSizes should all be smaller than the smallest size in deviceSizes.** +The reason there are two separate lists is that imageSizes is only used for images which provide a [`sizes`](#sizes) prop, which indicates that the image is less than the full width of the screen. **Therefore, the sizes in imageSizes should all be smaller than the smallest size in deviceSizes.** If no configuration is provided, the default below is used. diff --git a/docs/api-reference/next/script.md b/docs/api-reference/next/script.md new file mode 100644 index 0000000000000..49fe558c6b163 --- /dev/null +++ b/docs/api-reference/next/script.md @@ -0,0 +1,70 @@ +--- +description: Optimize loading of third-party scripts with the built-in Script component. +--- + +# next/script + +
+ Examples + +
+ +
+ Version History + +| Version | Changes | +| --------- | ------------------------- | +| `v11.0.0` | `next/script` introduced. | + +
+ +> **Note: This is API documentation for the Script Component. For a feature overview and usage information for scripts in Next.js, please see [Script Optimization](/docs/basic-features/script.md).** + +## Optional Props + +### src + +A path string specifying the URL of an external script. This can be either an absolute external URL or an internal path. + +### strategy + +The loading strategy of the script. + +| `strategy` | **Description** | +| ------------------- | ---------------------------------------------------------- | +| `beforeInteractive` | Load script before the page becomes interactive | +| `afterInteractive` | Load script immediately after the page becomes interactive | +| `lazyOnload` | Load script during browser idle time | + +### onLoad + +A method that returns additional JavaScript that should be executed after the script has finished loading. + +The following is an example of how to use the `onLoad` property: + +```jsx +import { useState } from 'react' +import Script from 'next/script' + +export default function Home() { + const [stripe, setStripe] = useState(null) + + return ( + <> + ``` +Or by using the `dangerouslySetInnerHTML` property: + +```jsx + - -// or - - + + diff --git a/test/integration/production-swcminify/static/data/item.txt b/test/integration/production-swcminify/static/data/item.txt new file mode 100644 index 0000000000000..a713074253483 --- /dev/null +++ b/test/integration/production-swcminify/static/data/item.txt @@ -0,0 +1 @@ +item \ No newline at end of file diff --git a/test/integration/production-swcminify/test/dynamic.js b/test/integration/production-swcminify/test/dynamic.js new file mode 100644 index 0000000000000..d22f147e51e04 --- /dev/null +++ b/test/integration/production-swcminify/test/dynamic.js @@ -0,0 +1,166 @@ +/* eslint-env jest */ +import webdriver from 'next-webdriver' +import cheerio from 'cheerio' +import { check } from 'next-test-utils' + +// These tests are similar to ../../basic/test/dynamic.js +export default (context, render) => { + async function get$(path, query) { + const html = await render(path, query) + return cheerio.load(html) + } + describe('Dynamic import', () => { + describe('default behavior', () => { + it('should render dynamic import components', async () => { + const $ = await get$('/dynamic/ssr') + expect($('body').text()).toMatch(/Hello World 1/) + }) + + it('should render one dynamically imported component and load its css files', async () => { + const $ = await get$('/dynamic/css') + const cssFiles = $('link[rel=stylesheet]') + expect(cssFiles.length).toBe(1) + }) + + it('should render three dynamically imported components and load their css files', async () => { + const $ = await get$('/dynamic/many-dynamic-css') + const cssFiles = $('link[rel=stylesheet]') + expect(cssFiles.length).toBe(3) + }) + + it('should bundle two css modules for one dynamically imported component into one css file', async () => { + const $ = await get$('/dynamic/many-css-modules') + const cssFiles = $('link[rel=stylesheet]') + expect(cssFiles.length).toBe(1) + }) + + it('should bundle two css modules for nested components into one css file', async () => { + const $ = await get$('/dynamic/nested-css') + const cssFiles = $('link[rel=stylesheet]') + expect(cssFiles.length).toBe(1) + }) + + it('should not remove css styles for same css file between page transitions', async () => { + let browser + try { + browser = await webdriver(context.appPort, '/dynamic/pagechange1') + await check(() => browser.elementByCss('body').text(), /PageChange1/) + const firstElement = await browser.elementById('with-css') + const css1 = await firstElement.getComputedCss('display') + expect(css1).toBe('flex') + await browser.eval(function () { + window.next.router.push('/dynamic/pagechange2') + }) + await check(() => browser.elementByCss('body').text(), /PageChange2/) + const secondElement = await browser.elementById('with-css') + const css2 = await secondElement.getComputedCss('display') + expect(css2).toBe(css1) + } finally { + if (browser) { + await browser.close() + } + } + }) + + // It seem to be abnormal, dynamic CSS modules are completely self-sufficient, so shared styles are copied across files + it('should output two css files even in case of three css module files while one is shared across files', async () => { + const $ = await get$('/dynamic/shared-css-module') + const cssFiles = $('link[rel=stylesheet]') + expect(cssFiles.length).toBe(2) + }) + + it('should render one dynamically imported component without any css files', async () => { + const $ = await get$('/dynamic/no-css') + const cssFiles = $('link[rel=stylesheet]') + expect(cssFiles.length).toBe(0) + }) + + it('should render even there are no physical chunk exists', async () => { + let browser + try { + browser = await webdriver(context.appPort, '/dynamic/no-chunk') + await check( + () => browser.elementByCss('body').text(), + /Welcome, normal/ + ) + await check( + () => browser.elementByCss('body').text(), + /Welcome, dynamic/ + ) + } finally { + if (browser) { + await browser.close() + } + } + }) + }) + describe('ssr:false option', () => { + it('should not render loading on the server side', async () => { + const $ = await get$('/dynamic/no-ssr') + expect($('body').text()).not.toMatch('loading...') + }) + + it('should render the component on client side', async () => { + let browser + try { + browser = await webdriver(context.appPort, '/dynamic/no-ssr') + await check( + () => browser.elementByCss('body').text(), + /Hello World 1/ + ) + } finally { + if (browser) { + await browser.close() + } + } + }) + }) + + describe('ssr:true option', () => { + it('should render the component on the server side', async () => { + const $ = await get$('/dynamic/ssr-true') + expect($('p').text()).toBe('Hello World 1') + }) + + it('should render the component on client side', async () => { + let browser + try { + browser = await webdriver(context.appPort, '/dynamic/ssr-true') + await check( + () => browser.elementByCss('body').text(), + /Hello World 1/ + ) + } finally { + if (browser) { + await browser.close() + } + } + }) + }) + + describe('custom loading', () => { + it('should render custom loading on the server side when `ssr:false` and `loading` is provided', async () => { + const $ = await get$('/dynamic/no-ssr-custom-loading') + expect($('p').text()).toBe('LOADING') + }) + + it('should render the component on client side', async () => { + let browser + try { + browser = await webdriver( + context.appPort, + '/dynamic/no-ssr-custom-loading' + ) + await check( + () => browser.elementByCss('body').text(), + /Hello World 1/ + ) + } finally { + if (browser) { + await browser.close() + } + } + }) + }) + }) +} diff --git a/test/integration/production-swcminify/test/index.test.js b/test/integration/production-swcminify/test/index.test.js new file mode 100644 index 0000000000000..8b8cf302e3e5c --- /dev/null +++ b/test/integration/production-swcminify/test/index.test.js @@ -0,0 +1,1129 @@ +/* eslint-env jest */ +/* global browserName */ +import cheerio from 'cheerio' +import fs, { existsSync } from 'fs-extra' +import globOriginal from 'glob' +import { + renderViaHTTP, + waitFor, + getPageFileFromPagesManifest, + check, + nextBuild, + nextStart, + findPort, + killApp, +} from 'next-test-utils' +import webdriver from 'next-webdriver' +import { + BUILD_MANIFEST, + PAGES_MANIFEST, + REACT_LOADABLE_MANIFEST, +} from 'next/constants' +import { recursiveReadDir } from 'next/dist/lib/recursive-readdir' +import fetch from 'node-fetch' +import { join, sep } from 'path' +import dynamicImportTests from './dynamic' +import processEnv from './process-env' +import security from './security' +import { promisify } from 'util' + +const glob = promisify(globOriginal) + +const appDir = join(__dirname, '../') +let appPort +let app + +const context = {} + +describe.skip('Production Usage with swcMinify', () => { + let output = '' + beforeAll(async () => { + const result = await nextBuild(appDir, undefined, { + stderr: true, + stdout: true, + }) + + appPort = await findPort() + context.appPort = appPort + app = await nextStart(appDir, appPort) + output = (result.stderr || '') + (result.stdout || '') + console.log(output) + + if (result.code !== 0) { + throw new Error(`Failed to build, exited with code ${result.code}`) + } + }) + afterAll(async () => { + await killApp(app) + }) + + it('should contain generated page count in output', async () => { + const pageCount = 40 + expect(output).toContain(`Generating static pages (0/${pageCount})`) + expect(output).toContain( + `Generating static pages (${pageCount}/${pageCount})` + ) + // we should only have 4 segments and the initial message logged out + expect(output.match(/Generating static pages/g).length).toBe(5) + }) + + it('should output traces', async () => { + const serverTrace = await fs.readJSON( + join(appDir, '.next/next-server.js.nft.json') + ) + + expect(serverTrace.version).toBe(1) + expect( + serverTrace.files.some((file) => + file.includes('next/dist/server/send-payload.js') + ) + ).toBe(true) + expect( + serverTrace.files.some((file) => + file.includes('next/dist/server/normalize-page-path.js') + ) + ).toBe(true) + expect( + serverTrace.files.some((file) => + file.includes('next/dist/server/render.js') + ) + ).toBe(true) + expect( + serverTrace.files.some((file) => + file.includes('next/dist/server/load-components.js') + ) + ).toBe(true) + + if (process.platform !== 'win32') { + expect( + serverTrace.files.some((file) => + file.includes('next/dist/compiled/webpack/bundle5.js') + ) + ).toBe(false) + expect( + serverTrace.files.some((file) => file.includes('node_modules/sharp')) + ).toBe(false) + expect( + serverTrace.files.some((file) => file.includes('react.development.js')) + ).toBe(false) + } + + const checks = [ + { + page: '/_app', + tests: [ + /webpack-runtime\.js/, + /node_modules\/react\/index\.js/, + /node_modules\/react\/package\.json/, + /node_modules\/react\/cjs\/react\.production\.min\.js/, + ], + notTests: [], + }, + { + page: '/client-error', + tests: [ + /webpack-runtime\.js/, + /chunks\/.*?\.js/, + /node_modules\/react\/index\.js/, + /node_modules\/react\/package\.json/, + /node_modules\/react\/cjs\/react\.production\.min\.js/, + /next\/link\.js/, + /next\/dist\/client\/link\.js/, + /next\/dist\/shared\/lib\/router\/utils\/resolve-rewrites\.js/, + /next\/dist\/pages\/_error\.js/, + /next\/error\.js/, + ], + notTests: [], + }, + { + page: '/dynamic', + tests: [ + /webpack-runtime\.js/, + /chunks\/.*?\.js/, + /node_modules\/react\/index\.js/, + /node_modules\/react\/package\.json/, + /node_modules\/react\/cjs\/react\.production\.min\.js/, + /next\/link\.js/, + /next\/dist\/client\/link\.js/, + /next\/dist\/shared\/lib\/router\/utils\/resolve-rewrites\.js/, + ], + notTests: [], + }, + { + page: '/index', + tests: [ + /webpack-runtime\.js/, + /chunks\/.*?\.js/, + /node_modules\/react\/index\.js/, + /node_modules\/react\/package\.json/, + /node_modules\/react\/cjs\/react\.production\.min\.js/, + /next\/link\.js/, + /next\/dist\/client\/link\.js/, + /next\/dist\/shared\/lib\/router\/utils\/resolve-rewrites\.js/, + /node_modules\/nanoid\/index\.js/, + /node_modules\/nanoid\/url-alphabet\/index\.js/, + ], + notTests: [ + /node_modules\/nanoid\/index\.cjs/, + /next\/dist\/pages\/_error\.js/, + /next\/error\.js/, + ], + }, + { + page: '/counter', + tests: [ + /webpack-runtime\.js/, + /chunks\/.*?\.js/, + /node_modules\/react\/index\.js/, + /node_modules\/react\/package\.json/, + /node_modules\/react\/cjs\/react\.production\.min\.js/, + /next\/router\.js/, + /next\/dist\/client\/router\.js/, + /next\/dist\/shared\/lib\/router\/utils\/resolve-rewrites\.js/, + ], + notTests: [], + }, + { + page: '/next-import', + tests: [ + /webpack-runtime\.js/, + /chunks\/.*?\.js/, + /node_modules\/react\/index\.js/, + /node_modules\/react\/package\.json/, + /node_modules\/react\/cjs\/react\.production\.min\.js/, + /next\/link\.js/, + /next\/dist\/client\/link\.js/, + /next\/dist\/shared\/lib\/router\/utils\/resolve-rewrites\.js/, + ], + notTests: [/next\/dist\/server\/next\.js/, /next\/dist\/bin/], + }, + ] + + for (const check of checks) { + const contents = await fs.readFile( + join(appDir, '.next/server/pages/', check.page + '.js.nft.json'), + 'utf8' + ) + const { version, files } = JSON.parse(contents) + expect(version).toBe(1) + + expect( + check.tests.every((item) => files.some((file) => item.test(file))) + ).toBe(true) + + if (sep === '/') { + expect( + check.notTests.some((item) => files.some((file) => item.test(file))) + ).toBe(false) + } + } + }) + + it('should not contain currentScript usage for publicPath', async () => { + const globResult = await glob('webpack-*.js', { + cwd: join(appDir, '.next/static/chunks'), + }) + + if (!globResult || globResult.length !== 1) { + throw new Error('could not find webpack-hash.js chunk') + } + + const content = await fs.readFile( + join(appDir, '.next/static/chunks', globResult[0]), + 'utf8' + ) + + expect(content).not.toContain('.currentScript') + }) + + describe('With basic usage', () => { + it('should render the page', async () => { + const html = await renderViaHTTP(appPort, '/') + expect(html).toMatch(/Hello World/) + }) + + if (browserName === 'internet explorer') { + it('should handle bad Promise polyfill', async () => { + const browser = await webdriver(appPort, '/bad-promise') + expect(await browser.eval('window.didRender')).toBe(true) + }) + + it('should polyfill RegExp successfully', async () => { + const browser = await webdriver(appPort, '/regexp-polyfill') + expect(await browser.eval('window.didRender')).toBe(true) + // wait a second for the script to be loaded + await waitFor(1000) + + expect(await browser.eval('window.isSticky')).toBe(true) + expect(await browser.eval('window.isMatch1')).toBe(true) + expect(await browser.eval('window.isMatch2')).toBe(false) + }) + } + + it('should polyfill Node.js modules', async () => { + const browser = await webdriver(appPort, '/node-browser-polyfills') + await browser.waitForCondition('window.didRender') + + const data = await browser + .waitForElementByCss('#node-browser-polyfills') + .text() + const parsedData = JSON.parse(data) + + expect(parsedData.vm).toBe(105) + expect(parsedData.hash).toBe( + 'a07a23e124ae01399e287505c2f7b2e69006540de08562de5ddd01fc7bbef3a5' + ) + expect(parsedData.path).toBe('/hello/world/test.txt') + expect(parsedData.buffer).toBe('hello world') + expect(parsedData.stream).toBe(true) + }) + + it('should allow etag header support', async () => { + const url = `http://localhost:${appPort}/` + const etag = (await fetch(url)).headers.get('ETag') + + const headers = { 'If-None-Match': etag } + const res2 = await fetch(url, { headers }) + expect(res2.status).toBe(304) + }) + + it('should allow etag header support with getStaticProps', async () => { + const url = `http://localhost:${appPort}/fully-static` + const etag = (await fetch(url)).headers.get('ETag') + + const headers = { 'If-None-Match': etag } + const res2 = await fetch(url, { headers }) + expect(res2.status).toBe(304) + }) + + it('should allow etag header support with getServerSideProps', async () => { + const url = `http://localhost:${appPort}/fully-dynamic` + const etag = (await fetch(url)).headers.get('ETag') + + const headers = { 'If-None-Match': etag } + const res2 = await fetch(url, { headers }) + expect(res2.status).toBe(304) + }) + + it('should have X-Powered-By header support', async () => { + const url = `http://localhost:${appPort}/` + const header = (await fetch(url)).headers.get('X-Powered-By') + + expect(header).toBe('Next.js') + }) + + it('should render 404 for routes that do not exist', async () => { + const url = `http://localhost:${appPort}/abcdefghijklmno` + const res = await fetch(url) + const text = await res.text() + const $html = cheerio.load(text) + expect($html('html').text()).toMatch(/404/) + expect(text).toMatch(/"statusCode":404/) + expect(res.status).toBe(404) + }) + + it('should render 404 for /_next/static route', async () => { + const html = await renderViaHTTP(appPort, '/_next/static') + expect(html).toMatch(/This page could not be found/) + }) + + it('should render 200 for POST on page', async () => { + const res = await fetch(`http://localhost:${appPort}/about`, { + method: 'POST', + }) + expect(res.status).toBe(200) + }) + + it('should render 404 for POST on missing page', async () => { + const res = await fetch(`http://localhost:${appPort}/fake-page`, { + method: 'POST', + }) + expect(res.status).toBe(404) + }) + + it('should render 404 for _next routes that do not exist', async () => { + const url = `http://localhost:${appPort}/_next/abcdef` + const res = await fetch(url) + expect(res.status).toBe(404) + }) + + it('should render 404 even if the HTTP method is not GET or HEAD', async () => { + const url = `http://localhost:${appPort}/_next/abcdef` + const methods = ['POST', 'PUT', 'DELETE'] + for (const method of methods) { + const res = await fetch(url, { method }) + expect(res.status).toBe(404) + } + }) + + it('should render 404 for dotfiles in /static', async () => { + const url = `http://localhost:${appPort}/static/.env` + const res = await fetch(url) + expect(res.status).toBe(404) + }) + + it('should return 405 method on static then GET and HEAD', async () => { + const res = await fetch( + `http://localhost:${appPort}/static/data/item.txt`, + { + method: 'POST', + } + ) + expect(res.headers.get('allow').includes('GET')).toBe(true) + expect(res.status).toBe(405) + }) + + it('should return 412 on static file when If-Unmodified-Since is provided and file is modified', async () => { + const buildManifest = require(join( + __dirname, + '../.next/build-manifest.json' + )) + + const files = buildManifest.pages['/'] + + for (const file of files) { + const res = await fetch(`http://localhost:${appPort}/_next/${file}`, { + method: 'GET', + headers: { 'if-unmodified-since': 'Fri, 12 Jul 2019 20:00:13 GMT' }, + }) + expect(res.status).toBe(412) + } + }) + + it('should return 200 on static file if If-Unmodified-Since is invalid date', async () => { + const buildManifest = require(join( + __dirname, + '../.next/build-manifest.json' + )) + + const files = buildManifest.pages['/'] + + for (const file of files) { + const res = await fetch(`http://localhost:${appPort}/_next/${file}`, { + method: 'GET', + headers: { 'if-unmodified-since': 'nextjs' }, + }) + expect(res.status).toBe(200) + } + }) + + it('should set Content-Length header', async () => { + const url = `http://localhost:${appPort}` + const res = await fetch(url) + expect(res.headers.get('Content-Length')).toBeDefined() + }) + + it('should set Cache-Control header', async () => { + const buildManifest = require(join('../.next', BUILD_MANIFEST)) + const reactLoadableManifest = require(join( + '../.next', + REACT_LOADABLE_MANIFEST + )) + const url = `http://localhost:${appPort}/_next/` + + const resources = new Set() + + const manifestKey = Object.keys(reactLoadableManifest).find((item) => { + return item + .replace(/\\/g, '/') + .endsWith('dynamic/css.js -> ../../components/dynamic-css/with-css') + }) + + // test dynamic chunk + reactLoadableManifest[manifestKey].files.forEach((f) => { + resources.add(url + f) + }) + + // test main.js runtime etc + for (const item of buildManifest.pages['/']) { + resources.add(url + item) + } + + const cssStaticAssets = await recursiveReadDir( + join(__dirname, '..', '.next', 'static'), + /\.css$/ + ) + expect(cssStaticAssets.length).toBeGreaterThanOrEqual(1) + expect(cssStaticAssets[0]).toMatch(/[\\/]css[\\/]/) + const mediaStaticAssets = await recursiveReadDir( + join(__dirname, '..', '.next', 'static'), + /\.svg$/ + ) + expect(mediaStaticAssets.length).toBeGreaterThanOrEqual(1) + expect(mediaStaticAssets[0]).toMatch(/[\\/]media[\\/]/) + ;[...cssStaticAssets, ...mediaStaticAssets].forEach((asset) => { + resources.add(`${url}static${asset.replace(/\\+/g, '/')}`) + }) + + const responses = await Promise.all( + [...resources].map((resource) => fetch(resource)) + ) + + responses.forEach((res) => { + try { + expect(res.headers.get('Cache-Control')).toBe( + 'public, max-age=31536000, immutable' + ) + } catch (err) { + err.message = res.url + ' ' + err.message + throw err + } + }) + }) + + it('should set correct Cache-Control header for static 404s', async () => { + // this is to fix where 404 headers are set to 'public, max-age=31536000, immutable' + const res = await fetch( + `http://localhost:${appPort}/_next//static/common/bad-static.js` + ) + + expect(res.status).toBe(404) + expect(res.headers.get('Cache-Control')).toBe( + 'no-cache, no-store, max-age=0, must-revalidate' + ) + }) + + it('should block special pages', async () => { + const urls = ['/_document', '/_app'] + for (const url of urls) { + const html = await renderViaHTTP(appPort, url) + expect(html).toMatch(/404/) + } + }) + + it('should not contain customServer in NEXT_DATA', async () => { + const html = await renderViaHTTP(appPort, '/') + const $ = cheerio.load(html) + expect('customServer' in JSON.parse($('#__NEXT_DATA__').text())).toBe( + false + ) + }) + }) + + describe('API routes', () => { + it('should work with pages/api/index.js', async () => { + const url = `http://localhost:${appPort}/api` + const res = await fetch(url) + const body = await res.text() + expect(body).toEqual('API index works') + }) + + it('should work with pages/api/hello.js', async () => { + const url = `http://localhost:${appPort}/api/hello` + const res = await fetch(url) + const body = await res.text() + expect(body).toEqual('API hello works') + }) + + it('should work with dynamic params and search string', async () => { + const url = `http://localhost:${appPort}/api/post-1?val=1` + const res = await fetch(url) + const body = await res.json() + + expect(body).toEqual({ val: '1', post: 'post-1' }) + }) + }) + + describe('With navigation', () => { + it('should navigate via client side', async () => { + const browser = await webdriver(appPort, '/') + const text = await browser + .elementByCss('a') + .click() + .waitForElementByCss('.about-page') + .elementByCss('div') + .text() + + expect(text).toBe('About Page') + await browser.close() + }) + + it('should navigate to nested index via client side', async () => { + const browser = await webdriver(appPort, '/another') + const text = await browser + .elementByCss('a') + .click() + .waitForElementByCss('.index-page') + .elementByCss('p') + .text() + + expect(text).toBe('Hello World') + await browser.close() + }) + + it('should set title by routeChangeComplete event', async () => { + const browser = await webdriver(appPort, '/') + await browser.eval(function setup() { + window.next.router.events.on( + 'routeChangeComplete', + function handler(url) { + window.routeChangeTitle = document.title + window.routeChangeUrl = url + } + ) + window.next.router.push('/with-title') + }) + await browser.waitForElementByCss('#with-title') + + const title = await browser.eval(`window.routeChangeTitle`) + const url = await browser.eval(`window.routeChangeUrl`) + expect(title).toBe('hello from title') + expect(url).toBe('/with-title') + }) + + it('should reload page successfully (on bad link)', async () => { + const browser = await webdriver(appPort, '/to-nonexistent') + await browser.eval(function setup() { + window.__DATA_BE_GONE = 'true' + }) + await browser.waitForElementByCss('#to-nonexistent-page') + await browser.click('#to-nonexistent-page') + await browser.waitForElementByCss('.about-page') + + const oldData = await browser.eval(`window.__DATA_BE_GONE`) + expect(oldData).toBeFalsy() + }) + + it('should reload page successfully (on bad data fetch)', async () => { + const browser = await webdriver(appPort, '/to-shadowed-page') + await browser.eval(function setup() { + window.__DATA_BE_GONE = 'true' + }) + await browser.waitForElementByCss('#to-shadowed-page').click() + await browser.waitForElementByCss('.about-page') + + const oldData = await browser.eval(`window.__DATA_BE_GONE`) + expect(oldData).toBeFalsy() + }) + }) + + it('should navigate to external site and back', async () => { + const browser = await webdriver(appPort, '/external-and-back') + const initialText = await browser.elementByCss('p').text() + expect(initialText).toBe('server') + + await browser + .elementByCss('a') + .click() + .waitForElementByCss('input') + .back() + .waitForElementByCss('p') + + await waitFor(1000) + const newText = await browser.elementByCss('p').text() + expect(newText).toBe('server') + }) + + it('should navigate to page with CSS and back', async () => { + const browser = await webdriver(appPort, '/css-and-back') + const initialText = await browser.elementByCss('p').text() + expect(initialText).toBe('server') + + await browser + .elementByCss('a') + .click() + .waitForElementByCss('input') + .back() + .waitForElementByCss('p') + + await waitFor(1000) + const newText = await browser.elementByCss('p').text() + expect(newText).toBe('client') + }) + + it('should navigate to external site and back (with query)', async () => { + const browser = await webdriver(appPort, '/external-and-back?hello=world') + const initialText = await browser.elementByCss('p').text() + expect(initialText).toBe('server') + + await browser + .elementByCss('a') + .click() + .waitForElementByCss('input') + .back() + .waitForElementByCss('p') + + await waitFor(1000) + const newText = await browser.elementByCss('p').text() + expect(newText).toBe('server') + }) + + it('should change query correctly', async () => { + const browser = await webdriver(appPort, '/query?id=0') + let id = await browser.elementByCss('#q0').text() + expect(id).toBe('0') + + await browser.elementByCss('#first').click().waitForElementByCss('#q1') + + id = await browser.elementByCss('#q1').text() + expect(id).toBe('1') + + await browser.elementByCss('#second').click().waitForElementByCss('#q2') + + id = await browser.elementByCss('#q2').text() + expect(id).toBe('2') + }) + + describe('Runtime errors', () => { + it('should render a server side error on the client side', async () => { + const browser = await webdriver(appPort, '/error-in-ssr-render') + await waitFor(2000) + const text = await browser.elementByCss('body').text() + // this makes sure we don't leak the actual error to the client side in production + expect(text).toMatch(/Internal Server Error\./) + const headingText = await browser.elementByCss('h1').text() + // This makes sure we render statusCode on the client side correctly + expect(headingText).toBe('500') + await browser.close() + }) + + it('should render a client side component error', async () => { + const browser = await webdriver(appPort, '/error-in-browser-render') + await waitFor(2000) + const text = await browser.elementByCss('body').text() + expect(text).toMatch( + /Application error: a client-side exception has occurred/ + ) + await browser.close() + }) + + it('should call getInitialProps on _error page during a client side component error', async () => { + const browser = await webdriver( + appPort, + '/error-in-browser-render-status-code' + ) + await waitFor(2000) + const text = await browser.elementByCss('body').text() + expect(text).toMatch(/This page could not be found\./) + await browser.close() + }) + }) + + describe('Misc', () => { + it('should handle already finished responses', async () => { + const html = await renderViaHTTP(appPort, '/finish-response') + expect(html).toBe('hi') + }) + + it('should allow to access /static/ and /_next/', async () => { + // This is a test case which prevent the following issue happening again. + // See: https://github.com/vercel/next.js/issues/2617 + await renderViaHTTP(appPort, '/_next/') + await renderViaHTTP(appPort, '/static/') + const data = await renderViaHTTP(appPort, '/static/data/item.txt') + expect(data).toBe('item') + }) + + it('Should allow access to public files', async () => { + const data = await renderViaHTTP(appPort, '/data/data.txt') + const file = await renderViaHTTP(appPort, '/file') + const legacy = await renderViaHTTP(appPort, '/static/legacy.txt') + expect(data).toBe('data') + expect(file).toBe('test') + expect(legacy).toMatch(`new static folder`) + }) + + // TODO: do we want to normalize this for firefox? It seems in + // the latest version of firefox the window state is not reset + // when navigating back from a hard navigation. This might be + // a bug as other browsers do not behave this way. + if (browserName !== 'firefox') { + it('should reload the page on page script error', async () => { + const browser = await webdriver(appPort, '/counter') + const counter = await browser + .elementByCss('#increase') + .click() + .click() + .elementByCss('#counter') + .text() + expect(counter).toBe('Counter: 2') + + // When we go to the 404 page, it'll do a hard reload. + // So, it's possible for the front proxy to load a page from another zone. + // Since the page is reloaded, when we go back to the counter page again, + // previous counter value should be gone. + const counterAfter404Page = await browser + .elementByCss('#no-such-page') + .click() + .waitForElementByCss('h1') + .back() + .waitForElementByCss('#counter-page') + .elementByCss('#counter') + .text() + expect(counterAfter404Page).toBe('Counter: 0') + + await browser.close() + }) + } + + it('should have default runtime values when not defined', async () => { + const html = await renderViaHTTP(appPort, '/runtime-config') + expect(html).toMatch(/found public config/) + expect(html).toMatch(/found server config/) + }) + + it('should not have runtimeConfig in __NEXT_DATA__', async () => { + const html = await renderViaHTTP(appPort, '/runtime-config') + const $ = cheerio.load(html) + const script = $('#__NEXT_DATA__').html() + expect(script).not.toMatch(/runtimeConfig/) + }) + + it('should add autoExport for auto pre-rendered pages', async () => { + for (const page of ['/', '/about']) { + const html = await renderViaHTTP(appPort, page) + const $ = cheerio.load(html) + const data = JSON.parse($('#__NEXT_DATA__').html()) + expect(data.autoExport).toBe(true) + } + }) + + it('should not add autoExport for non pre-rendered pages', async () => { + for (const page of ['/query']) { + const html = await renderViaHTTP(appPort, page) + const $ = cheerio.load(html) + const data = JSON.parse($('#__NEXT_DATA__').html()) + expect(!!data.autoExport).toBe(false) + } + }) + + it('should add prefetch tags when Link prefetch prop is used', async () => { + const browser = await webdriver(appPort, '/prefetch') + + if (browserName === 'internet explorer') { + // IntersectionObserver isn't present so we need to trigger manually + await waitFor(1000) + await browser.eval(`(function() { + window.next.router.prefetch('/') + window.next.router.prefetch('/process-env') + window.next.router.prefetch('/counter') + window.next.router.prefetch('/about') + })()`) + } + + await waitFor(2000) + + if (browserName === 'safari') { + const elements = await browser.elementsByCss('link[rel=preload]') + // optimized preloading uses defer instead of preloading and prefetches + // aren't generated client-side since safari does not support prefetch + expect(elements.length).toBe(0) + } else { + const elements = await browser.elementsByCss('link[rel=prefetch]') + expect(elements.length).toBe(4) + + for (const element of elements) { + const rel = await element.getAttribute('rel') + const as = await element.getAttribute('as') + expect(rel).toBe('prefetch') + expect(as).toBe('script') + } + } + await browser.close() + }) + + // This is a workaround to fix https://github.com/vercel/next.js/issues/5860 + // TODO: remove this workaround when https://bugs.webkit.org/show_bug.cgi?id=187726 is fixed. + it('It does not add a timestamp to link tags with prefetch attribute', async () => { + const browser = await webdriver(appPort, '/prefetch') + const links = await browser.elementsByCss('link[rel=prefetch]') + + for (const element of links) { + const href = await element.getAttribute('href') + expect(href).not.toMatch(/\?ts=/) + } + const scripts = await browser.elementsByCss('script[src]') + + for (const element of scripts) { + const src = await element.getAttribute('src') + expect(src).not.toMatch(/\?ts=/) + } + await browser.close() + }) + + if (browserName === 'chrome') { + it('should reload the page on page script error with prefetch', async () => { + const browser = await webdriver(appPort, '/counter') + if (global.browserName !== 'chrome') return + const counter = await browser + .elementByCss('#increase') + .click() + .click() + .elementByCss('#counter') + .text() + expect(counter).toBe('Counter: 2') + + // Let the browser to prefetch the page and error it on the console. + await waitFor(3000) + + // When we go to the 404 page, it'll do a hard reload. + // So, it's possible for the front proxy to load a page from another zone. + // Since the page is reloaded, when we go back to the counter page again, + // previous counter value should be gone. + const counterAfter404Page = await browser + .elementByCss('#no-such-page-prefetch') + .click() + .waitForElementByCss('h1') + .back() + .waitForElementByCss('#counter-page') + .elementByCss('#counter') + .text() + expect(counterAfter404Page).toBe('Counter: 0') + + await browser.close() + }) + } + }) + + it('should not expose the compiled page file in development', async () => { + const url = `http://localhost:${appPort}` + await fetch(`${url}/stateless`) // make sure the stateless page is built + const clientSideJsRes = await fetch( + `${url}/_next/development/static/development/pages/stateless.js` + ) + expect(clientSideJsRes.status).toBe(404) + const clientSideJsBody = await clientSideJsRes.text() + expect(clientSideJsBody).toMatch(/404/) + + const serverSideJsRes = await fetch( + `${url}/_next/development/server/static/development/pages/stateless.js` + ) + expect(serverSideJsRes.status).toBe(404) + const serverSideJsBody = await serverSideJsRes.text() + expect(serverSideJsBody).toMatch(/404/) + }) + + it('should not put backslashes in pages-manifest.json', () => { + // Whatever platform you build on, pages-manifest.json should use forward slash (/) + // See: https://github.com/vercel/next.js/issues/4920 + const pagesManifest = require(join('..', '.next', 'server', PAGES_MANIFEST)) + + for (let key of Object.keys(pagesManifest)) { + expect(key).not.toMatch(/\\/) + expect(pagesManifest[key]).not.toMatch(/\\/) + } + }) + + it('should handle failed param decoding', async () => { + const html = await renderViaHTTP(appPort, '/invalid-param/%DE~%C7%1fY/') + expect(html).toMatch(/400/) + expect(html).toMatch(/Bad Request/) + }) + + it('should replace static pages with HTML files', async () => { + const pages = ['/about', '/another', '/counter', '/dynamic', '/prefetch'] + for (const page of pages) { + const file = getPageFileFromPagesManifest(appDir, page) + + expect(file.endsWith('.html')).toBe(true) + } + }) + + it('should not replace non-static pages with HTML files', async () => { + const pages = ['/api', '/external-and-back', '/finish-response'] + + for (const page of pages) { + const file = getPageFileFromPagesManifest(appDir, page) + + expect(file.endsWith('.js')).toBe(true) + } + }) + + it('should handle AMP correctly in IE', async () => { + const browser = await webdriver(appPort, '/some-amp') + const text = await browser.elementByCss('p').text() + expect(text).toBe('Not AMP') + }) + + it('should warn when prefetch is true', async () => { + if (global.browserName !== 'chrome') return + let browser + try { + browser = await webdriver(appPort, '/development-logs') + const browserLogs = await browser.log('browser') + let found = false + browserLogs.forEach((log) => { + if (log.message.includes('Next.js auto-prefetches automatically')) { + found = true + } + }) + expect(found).toBe(false) + } finally { + if (browser) { + await browser.close() + } + } + }) + + it('should not emit profiling events', async () => { + expect(existsSync(join(appDir, '.next', 'profile-events.json'))).toBe(false) + }) + + it('should not emit stats', async () => { + expect(existsSync(join(appDir, '.next', 'next-stats.json'))).toBe(false) + }) + + it('should contain the Next.js version in window export', async () => { + let browser + try { + browser = await webdriver(appPort, '/about') + const version = await browser.eval('window.next.version') + expect(version).toBeTruthy() + expect(version).toBe(require('next/package.json').version) + } finally { + if (browser) { + await browser.close() + } + } + }) + + it('should clear all core performance marks', async () => { + let browser + try { + browser = await webdriver(appPort, '/fully-dynamic') + + const currentPerfMarks = await browser.eval( + `window.performance.getEntriesByType('mark')` + ) + const allPerfMarks = [ + 'beforeRender', + 'afterHydrate', + 'afterRender', + 'routeChange', + ] + + allPerfMarks.forEach((name) => + expect(currentPerfMarks).not.toContainEqual( + expect.objectContaining({ name }) + ) + ) + } finally { + if (browser) { + await browser.close() + } + } + }) + + it('should not clear custom performance marks', async () => { + let browser + try { + browser = await webdriver(appPort, '/mark-in-head') + + const customMarkFound = await browser.eval( + `window.performance.getEntriesByType('mark').filter(function(e) { + return e.name === 'custom-mark' + }).length === 1` + ) + expect(customMarkFound).toBe(true) + } finally { + if (browser) { + await browser.close() + } + } + }) + + it('should have defer on all script tags', async () => { + const html = await renderViaHTTP(appPort, '/') + const $ = cheerio.load(html) + let missing = false + + for (const script of $('script').toArray()) { + // application/json doesn't need async + if ( + script.attribs.type === 'application/json' || + script.attribs.src.includes('polyfills') + ) { + continue + } + + if (script.attribs.defer !== '' || script.attribs.async === '') { + missing = true + } + } + expect(missing).toBe(false) + }) + + it('should only have one DOCTYPE', async () => { + const html = await renderViaHTTP(appPort, '/') + expect(html).toMatch(/^ { + const browser = await webdriver(appPort, '/') + await browser.eval(`(function() { + window.beforeNav = 1 + window.next.router.push({ + pathname: '/non-existent', + query: { hello: 'world' } + }) + })()`) + + await check( + () => browser.eval('document.documentElement.innerHTML'), + /page could not be found/ + ) + + expect(await browser.eval('window.beforeNav')).toBeFalsy() + expect(await browser.eval('window.location.hash')).toBe('') + expect(await browser.eval('window.location.search')).toBe('?hello=world') + expect(await browser.eval('window.location.pathname')).toBe( + '/non-existent' + ) + }) + } + + it('should remove placeholder for next/image correctly', async () => { + const browser = await webdriver(context.appPort, '/') + + await browser.eval(`(function() { + window.beforeNav = 1 + window.next.router.push('/static-image') + })()`) + await browser.waitForElementByCss('#static-image') + + expect(await browser.eval('window.beforeNav')).toBe(1) + + await check( + () => browser.elementByCss('img').getComputedCss('background-image'), + 'none' + ) + + await browser.eval(`(function() { + window.beforeNav = 1 + window.next.router.push('/') + })()`) + await browser.waitForElementByCss('.index-page') + await waitFor(1000) + + await browser.eval(`(function() { + window.beforeNav = 1 + window.next.router.push('/static-image') + })()`) + await browser.waitForElementByCss('#static-image') + + expect(await browser.eval('window.beforeNav')).toBe(1) + + await check( + () => + browser + .elementByCss('#static-image') + .getComputedCss('background-image'), + 'none' + ) + + for (let i = 0; i < 5; i++) { + expect( + await browser + .elementByCss('#static-image') + .getComputedCss('background-image') + ).toBe('none') + await waitFor(500) + } + }) + + dynamicImportTests(context, (p, q) => renderViaHTTP(context.appPort, p, q)) + + processEnv(context) + if (browserName !== 'safari') security(context) +}) diff --git a/test/integration/production-swcminify/test/process-env.js b/test/integration/production-swcminify/test/process-env.js new file mode 100644 index 0000000000000..00d4aaa20f866 --- /dev/null +++ b/test/integration/production-swcminify/test/process-env.js @@ -0,0 +1,48 @@ +/* eslint-env jest */ +import webdriver from 'next-webdriver' +import { join } from 'path' +import { + readNextBuildClientPageFile, + readNextBuildServerPageFile, +} from 'next-test-utils' + +const appDir = join(__dirname, '..') + +export default (context) => { + describe('process.env', () => { + it('should set process.env.NODE_ENV in production', async () => { + const browser = await webdriver(context.appPort, '/process-env') + const nodeEnv = await browser.elementByCss('#node-env').text() + expect(nodeEnv).toBe('production') + await browser.close() + }) + }) + + describe('process.browser', () => { + it('should eliminate server only code on the client', async () => { + const clientCode = await readNextBuildClientPageFile( + appDir, + '/process-env' + ) + expect(clientCode).toMatch( + /__THIS_SHOULD_ONLY_BE_DEFINED_IN_BROWSER_CONTEXT__/ + ) + expect(clientCode).not.toMatch( + /__THIS_SHOULD_ONLY_BE_DEFINED_IN_SERVER_CONTEXT__/ + ) + }) + + it('should eliminate client only code on the server', async () => { + const serverCode = await readNextBuildServerPageFile( + appDir, + '/process-env' + ) + expect(serverCode).not.toMatch( + /__THIS_SHOULD_ONLY_BE_DEFINED_IN_BROWSER_CONTEXT__/ + ) + expect(serverCode).toMatch( + /__THIS_SHOULD_ONLY_BE_DEFINED_IN_SERVER_CONTEXT__/ + ) + }) + }) +} diff --git a/test/integration/production-swcminify/test/security.js b/test/integration/production-swcminify/test/security.js new file mode 100644 index 0000000000000..fd674399249bf --- /dev/null +++ b/test/integration/production-swcminify/test/security.js @@ -0,0 +1,369 @@ +/* eslint-env jest */ +/* global browserName */ +import webdriver from 'next-webdriver' +import { readFileSync } from 'fs' +import url from 'url' +import { join } from 'path' +import { + renderViaHTTP, + getBrowserBodyText, + waitFor, + fetchViaHTTP, +} from 'next-test-utils' +import { recursiveReadDir } from 'next/dist/lib/recursive-readdir' +import { homedir } from 'os' + +// Does the same evaluation checking for INJECTED for 5 seconds after hydration, triggering every 500ms +async function checkInjected(browser) { + const start = Date.now() + while (Date.now() - start < 5000) { + const bodyText = await getBrowserBodyText(browser) + if (/INJECTED/.test(bodyText)) { + throw new Error('Vulnerable to XSS attacks') + } + await waitFor(500) + } +} + +module.exports = (context) => { + describe('With Security Related Issues', () => { + it('should only access files inside .next directory', async () => { + const buildId = readFileSync(join(__dirname, '../.next/BUILD_ID'), 'utf8') + + const pathsToCheck = [ + `/_next/${buildId}/page/../../../info`, + `/_next/${buildId}/page/../../../info.js`, + `/_next/${buildId}/page/../../../info.json`, + `/_next/:buildId/webpack/chunks/../../../info.json`, + `/_next/:buildId/webpack/../../../info.json`, + `/_next/../../../info.json`, + `/static/../../../info.json`, + `/static/../info.json`, + `/../../../info.json`, + `/../../info.json`, + `/../info.json`, + `/info.json`, + ] + + for (const path of pathsToCheck) { + const data = await renderViaHTTP(context.appPort, path) + expect(data.includes('cool-version')).toBeFalsy() + } + }) + + it('should not allow accessing files outside .next/static directory', async () => { + const pathsToCheck = [ + `/_next/static/../server/pages-manifest.json`, + `/_next/static/../server/build-manifest.json`, + `/_next/static/../BUILD_ID`, + `/_next/static/../routes-manifest.json`, + ] + for (const path of pathsToCheck) { + const res = await fetchViaHTTP(context.appPort, path) + const text = await res.text() + try { + expect(res.status).toBe(404) + expect(text).toMatch(/This page could not be found/) + } catch (err) { + throw new Error(`Path ${path} accessible from the browser`) + } + } + }) + + it("should not leak the user's home directory into the build", async () => { + const buildId = readFileSync(join(__dirname, '../.next/BUILD_ID'), 'utf8') + + const readPath = join(__dirname, `../.next/static/${buildId}`) + const buildFiles = await recursiveReadDir(readPath, /\.js$/) + + if (buildFiles.length < 1) { + throw new Error('Could not locate any build files') + } + + const homeDir = homedir() + buildFiles.forEach((buildFile) => { + const content = readFileSync(join(readPath, buildFile), 'utf8') + if (content.includes(homeDir)) { + throw new Error( + `Found the user's home directory in: ${buildFile}, ${homeDir}\n\n${content}` + ) + } + // TODO: this checks the monorepo's path currently, we should check + // the Next.js apps directory instead once using isolated next + const checkPathProject = join(__dirname, ...Array(4).fill('..')) + if ( + content.includes(checkPathProject) || + (process.platform.match(/win/) && + content.includes(checkPathProject.replace(/\\/g, '\\\\'))) + ) { + throw new Error( + `Found the project path in: ${buildFile}, ${checkPathProject}\n\n${content}` + ) + } + }) + }) + + it('should prevent URI based XSS attacks', async () => { + const browser = await webdriver( + context.appPort, + '/\',document.body.innerHTML="INJECTED",\'' + ) + await checkInjected(browser) + await browser.close() + }) + + it('should prevent URI based XSS attacks using single quotes', async () => { + const browser = await webdriver( + context.appPort, + `/'-(document.body.innerHTML='INJECTED')-'` + ) + await checkInjected(browser) + await browser.close() + }) + + it('should prevent URI based XSS attacks using double quotes', async () => { + const browser = await webdriver( + context.appPort, + `/"-(document.body.innerHTML='INJECTED')-"` + ) + await checkInjected(browser) + + await browser.close() + }) + + it('should prevent URI based XSS attacks using semicolons and double quotes', async () => { + const browser = await webdriver( + context.appPort, + `/;"-(document.body.innerHTML='INJECTED')-"` + ) + await checkInjected(browser) + + await browser.close() + }) + + it('should prevent URI based XSS attacks using semicolons and single quotes', async () => { + const browser = await webdriver( + context.appPort, + `/;'-(document.body.innerHTML='INJECTED')-'` + ) + await checkInjected(browser) + + await browser.close() + }) + + it('should prevent URI based XSS attacks using src', async () => { + const browser = await webdriver( + context.appPort, + `/javascript:(document.body.innerHTML='INJECTED')` + ) + await checkInjected(browser) + + await browser.close() + }) + + it('should prevent URI based XSS attacks using querystring', async () => { + const browser = await webdriver( + context.appPort, + `/?javascript=(document.body.innerHTML='INJECTED')` + ) + await checkInjected(browser) + + await browser.close() + }) + + it('should prevent URI based XSS attacks using querystring and quotes', async () => { + const browser = await webdriver( + context.appPort, + `/?javascript="(document.body.innerHTML='INJECTED')"` + ) + await checkInjected(browser) + await browser.close() + }) + + it('should handle encoded value in the pathname correctly \\', async () => { + const res = await fetchViaHTTP( + context.appPort, + '/redirect/me/to-about/' + encodeURI('\\google.com'), + undefined, + { + redirect: 'manual', + } + ) + + const { pathname, hostname } = url.parse( + res.headers.get('location') || '' + ) + expect(res.status).toBe(307) + expect(pathname).toBe(encodeURI('/\\google.com/about')) + expect(hostname).toBe('localhost') + }) + + it('should handle encoded value in the pathname correctly %', async () => { + const res = await fetchViaHTTP( + context.appPort, + '/redirect/me/to-about/%25google.com', + undefined, + { + redirect: 'manual', + } + ) + + const { pathname, hostname } = url.parse( + res.headers.get('location') || '' + ) + expect(res.status).toBe(307) + expect(pathname).toBe('/%25google.com/about') + expect(hostname).toBe('localhost') + }) + + it('should handle encoded value in the query correctly', async () => { + const res = await fetchViaHTTP( + context.appPort, + '/trailing-redirect/?url=https%3A%2F%2Fgoogle.com%2Fimage%3Fcrop%3Dfocalpoint%26w%3D24&w=1200&q=100', + undefined, + { + redirect: 'manual', + } + ) + + const { pathname, hostname, query } = url.parse( + res.headers.get('location') || '' + ) + expect(res.status).toBe(308) + expect(pathname).toBe('/trailing-redirect') + expect(hostname).toBe('localhost') + expect(query).toBe( + 'url=https%3A%2F%2Fgoogle.com%2Fimage%3Fcrop%3Dfocalpoint%26w%3D24&w=1200&q=100' + ) + }) + + it('should handle encoded value in the pathname correctly /', async () => { + const res = await fetchViaHTTP( + context.appPort, + '/redirect/me/to-about/%2fgoogle.com', + undefined, + { + redirect: 'manual', + } + ) + + const { pathname, hostname } = url.parse( + res.headers.get('location') || '' + ) + expect(res.status).toBe(307) + expect(pathname).toBe('/%2fgoogle.com/about') + expect(hostname).not.toBe('google.com') + }) + + it('should handle encoded value in the pathname to query correctly (/)', async () => { + const res = await fetchViaHTTP( + context.appPort, + '/redirect-query-test/%2Fgoogle.com', + undefined, + { + redirect: 'manual', + } + ) + + const { pathname, hostname, query } = url.parse( + res.headers.get('location') || '' + ) + expect(res.status).toBe(307) + expect(pathname).toBe('/about') + expect(query).toBe('foo=%2Fgoogle.com') + expect(hostname).not.toBe('google.com') + expect(hostname).not.toMatch(/google/) + }) + + it('should handle encoded / value for trailing slash correctly', async () => { + const res = await fetchViaHTTP( + context.appPort, + '/%2fexample.com/', + undefined, + { redirect: 'manual' } + ) + + const { pathname, hostname } = url.parse( + res.headers.get('location') || '' + ) + expect(res.status).toBe(308) + expect(pathname).toBe('/%2fexample.com') + expect(hostname).not.toBe('example.com') + }) + + it('should handle encoded value in the pathname correctly /', async () => { + const res = await fetchViaHTTP( + context.appPort, + '/redirect/me/to-about/%2fgoogle.com', + undefined, + { + redirect: 'manual', + } + ) + + const { pathname, hostname } = url.parse( + res.headers.get('location') || '' + ) + expect(res.status).toBe(307) + expect(pathname).toBe('/%2fgoogle.com/about') + expect(hostname).not.toBe('google.com') + }) + + it('should handle encoded value in the pathname to query correctly (/)', async () => { + const res = await fetchViaHTTP( + context.appPort, + '/redirect-query-test/%2Fgoogle.com', + undefined, + { + redirect: 'manual', + } + ) + + const { pathname, hostname, query } = url.parse( + res.headers.get('location') || '' + ) + expect(res.status).toBe(307) + expect(pathname).toBe('/about') + expect(query).toBe('foo=%2Fgoogle.com') + expect(hostname).not.toBe('google.com') + expect(hostname).not.toMatch(/google/) + }) + + it('should handle encoded / value for trailing slash correctly', async () => { + const res = await fetchViaHTTP( + context.appPort, + '/%2fexample.com/', + undefined, + { redirect: 'manual' } + ) + + const { pathname, hostname } = url.parse( + res.headers.get('location') || '' + ) + expect(res.status).toBe(308) + expect(pathname).toBe('/%2fexample.com') + expect(hostname).not.toBe('example.com') + }) + + if (browserName !== 'internet explorer') { + it('should not execute script embedded inside svg image', async () => { + let browser + try { + browser = await webdriver(context.appPort, '/svg-image') + await browser.eval(`document.getElementById("img").scrollIntoView()`) + expect( + await browser.elementById('img').getAttribute('src') + ).toContain('xss.svg') + expect(await browser.elementById('msg').text()).toBe('safe') + browser = await webdriver( + context.appPort, + '/_next/image?url=%2Fxss.svg&w=256&q=75' + ) + expect(await browser.elementById('msg').text()).toBe('safe') + } finally { + if (browser) await browser.close() + } + }) + } + }) +} diff --git a/test/integration/production/next.config.js b/test/integration/production/next.config.js index 7b95408bd2670..cc65ddf365179 100644 --- a/test/integration/production/next.config.js +++ b/test/integration/production/next.config.js @@ -2,9 +2,6 @@ setInterval(() => {}, 250) module.exports = { - experimental: { - outputFileTracing: true, - }, onDemandEntries: { // Make sure entries are not getting disposed. maxInactiveAge: 1000 * 60 * 60, diff --git a/test/integration/production/pages/another.js b/test/integration/production/pages/another.js index 550e5be8b2a2a..218c5bdae9bb3 100644 --- a/test/integration/production/pages/another.js +++ b/test/integration/production/pages/another.js @@ -1,5 +1,8 @@ +import url from 'url' import Link from 'next/link' +console.log(url.parse('https://example.com')) + export default () => (
diff --git a/test/integration/production/pages/index.js b/test/integration/production/pages/index.js index 2c0b0cbd97533..dbd0d222fe0d0 100644 --- a/test/integration/production/pages/index.js +++ b/test/integration/production/pages/index.js @@ -1,6 +1,10 @@ import Link from 'next/link' if (typeof window === 'undefined') { + try { + let file = 'clear.js' + require('es5-ext/array/#/' + file) + } catch (_) {} import('nanoid').then((mod) => console.log(mod.nanoid())) } diff --git a/test/integration/production/test/index.test.js b/test/integration/production/test/index.test.js index 51683cd6139fd..4c887a89ab975 100644 --- a/test/integration/production/test/index.test.js +++ b/test/integration/production/test/index.test.js @@ -12,6 +12,7 @@ import { nextStart, findPort, killApp, + fetchViaHTTP, } from 'next-test-utils' import webdriver from 'next-webdriver' import { @@ -20,7 +21,6 @@ import { REACT_LOADABLE_MANIFEST, } from 'next/constants' import { recursiveReadDir } from 'next/dist/lib/recursive-readdir' -import fetch from 'node-fetch' import { join, sep } from 'path' import dynamicImportTests from './dynamic' import processEnv from './process-env' @@ -57,6 +57,12 @@ describe('Production Usage', () => { await killApp(app) }) + it('should not show target deprecation warning', () => { + expect(output).not.toContain( + 'The `target` config is deprecated and will be removed in a future version' + ) + }) + it('should contain generated page count in output', async () => { const pageCount = 40 expect(output).toContain(`Generating static pages (0/${pageCount})`) @@ -103,9 +109,6 @@ describe('Production Usage', () => { expect( serverTrace.files.some((file) => file.includes('node_modules/sharp')) ).toBe(false) - expect( - serverTrace.files.some((file) => file.includes('react.development.js')) - ).toBe(false) } const checks = [ @@ -117,7 +120,7 @@ describe('Production Usage', () => { /node_modules\/react\/package\.json/, /node_modules\/react\/cjs\/react\.production\.min\.js/, ], - notTests: [/node_modules\/react\/cjs\/react\.development\.js/], + notTests: [/\0/], }, { page: '/client-error', @@ -127,13 +130,14 @@ describe('Production Usage', () => { /node_modules\/react\/index\.js/, /node_modules\/react\/package\.json/, /node_modules\/react\/cjs\/react\.production\.min\.js/, + /node_modules\/next/, /next\/link\.js/, /next\/dist\/client\/link\.js/, /next\/dist\/shared\/lib\/router\/utils\/resolve-rewrites\.js/, /next\/dist\/pages\/_error\.js/, /next\/error\.js/, ], - notTests: [/node_modules\/react\/cjs\/react\.development\.js/], + notTests: [/\0/], }, { page: '/dynamic', @@ -143,11 +147,12 @@ describe('Production Usage', () => { /node_modules\/react\/index\.js/, /node_modules\/react\/package\.json/, /node_modules\/react\/cjs\/react\.production\.min\.js/, + /node_modules\/next/, /next\/link\.js/, /next\/dist\/client\/link\.js/, /next\/dist\/shared\/lib\/router\/utils\/resolve-rewrites\.js/, ], - notTests: [/node_modules\/react\/cjs\/react\.development\.js/], + notTests: [/\0/], }, { page: '/index', @@ -157,18 +162,15 @@ describe('Production Usage', () => { /node_modules\/react\/index\.js/, /node_modules\/react\/package\.json/, /node_modules\/react\/cjs\/react\.production\.min\.js/, + /node_modules\/next/, /next\/link\.js/, /next\/dist\/client\/link\.js/, /next\/dist\/shared\/lib\/router\/utils\/resolve-rewrites\.js/, /node_modules\/nanoid\/index\.js/, /node_modules\/nanoid\/url-alphabet\/index\.js/, + /node_modules\/es5-ext\/array\/#\/clear\.js/, ], - notTests: [ - /node_modules\/react\/cjs\/react\.development\.js/, - /node_modules\/nanoid\/index\.cjs/, - /next\/dist\/pages\/_error\.js/, - /next\/error\.js/, - ], + notTests: [/next\/dist\/pages\/_error\.js/, /next\/error\.js/, /\0/], }, { page: '/counter', @@ -178,11 +180,13 @@ describe('Production Usage', () => { /node_modules\/react\/index\.js/, /node_modules\/react\/package\.json/, /node_modules\/react\/cjs\/react\.production\.min\.js/, + /node_modules\/react\/cjs\/react\.development\.js/, + /node_modules\/next/, /next\/router\.js/, /next\/dist\/client\/router\.js/, /next\/dist\/shared\/lib\/router\/utils\/resolve-rewrites\.js/, ], - notTests: [/node_modules\/react\/cjs\/react\.development\.js/], + notTests: [/\0/], }, { page: '/next-import', @@ -192,11 +196,12 @@ describe('Production Usage', () => { /node_modules\/react\/index\.js/, /node_modules\/react\/package\.json/, /node_modules\/react\/cjs\/react\.production\.min\.js/, + /node_modules\/next/, /next\/link\.js/, /next\/dist\/client\/link\.js/, /next\/dist\/shared\/lib\/router\/utils\/resolve-rewrites\.js/, ], - notTests: [/next\/dist\/server\/next\.js/, /next\/dist\/bin/], + notTests: [/next\/dist\/server\/next\.js/, /next\/dist\/bin/, /\0/], }, ] @@ -209,12 +214,24 @@ describe('Production Usage', () => { expect(version).toBe(1) expect( - check.tests.every((item) => files.some((file) => item.test(file))) + check.tests.every((item) => { + if (files.some((file) => item.test(file))) { + return true + } + console.error(`Failed to find ${item} in`, files) + return false + }) ).toBe(true) if (sep === '/') { expect( - check.notTests.some((item) => files.some((file) => item.test(file))) + check.notTests.some((item) => { + if (files.some((file) => item.test(file))) { + console.error(`Found unexpected ${item} in`, files) + return true + } + return false + }) ).toBe(false) } } @@ -280,42 +297,50 @@ describe('Production Usage', () => { }) it('should allow etag header support', async () => { - const url = `http://localhost:${appPort}/` - const etag = (await fetch(url)).headers.get('ETag') + const url = `http://localhost:${appPort}` + const etag = (await fetchViaHTTP(url, '/')).headers.get('ETag') const headers = { 'If-None-Match': etag } - const res2 = await fetch(url, { headers }) + const res2 = await fetchViaHTTP(url, '/', undefined, { headers }) expect(res2.status).toBe(304) }) it('should allow etag header support with getStaticProps', async () => { - const url = `http://localhost:${appPort}/fully-static` - const etag = (await fetch(url)).headers.get('ETag') + const url = `http://localhost:${appPort}` + const etag = (await fetchViaHTTP(url, '/fully-static')).headers.get( + 'ETag' + ) const headers = { 'If-None-Match': etag } - const res2 = await fetch(url, { headers }) + const res2 = await fetchViaHTTP(url, '/fully-static', undefined, { + headers, + }) expect(res2.status).toBe(304) }) it('should allow etag header support with getServerSideProps', async () => { - const url = `http://localhost:${appPort}/fully-dynamic` - const etag = (await fetch(url)).headers.get('ETag') + const url = `http://localhost:${appPort}` + const etag = (await fetchViaHTTP(url, '/fully-dynamic')).headers.get( + 'ETag' + ) const headers = { 'If-None-Match': etag } - const res2 = await fetch(url, { headers }) + const res2 = await fetchViaHTTP(url, '/fully-dynamic', undefined, { + headers, + }) expect(res2.status).toBe(304) }) it('should have X-Powered-By header support', async () => { - const url = `http://localhost:${appPort}/` - const header = (await fetch(url)).headers.get('X-Powered-By') + const url = `http://localhost:${appPort}` + const header = (await fetchViaHTTP(url, '/')).headers.get('X-Powered-By') expect(header).toBe('Next.js') }) it('should render 404 for routes that do not exist', async () => { - const url = `http://localhost:${appPort}/abcdefghijklmno` - const res = await fetch(url) + const url = `http://localhost:${appPort}` + const res = await fetchViaHTTP(url, '/abcdefghijklmno') const text = await res.text() const $html = cheerio.load(text) expect($html('html').text()).toMatch(/404/) @@ -329,43 +354,57 @@ describe('Production Usage', () => { }) it('should render 200 for POST on page', async () => { - const res = await fetch(`http://localhost:${appPort}/about`, { - method: 'POST', - }) + const res = await fetchViaHTTP( + `http://localhost:${appPort}`, + '/about', + undefined, + { + method: 'POST', + } + ) expect(res.status).toBe(200) }) it('should render 404 for POST on missing page', async () => { - const res = await fetch(`http://localhost:${appPort}/fake-page`, { - method: 'POST', - }) + const res = await fetchViaHTTP( + `http://localhost:${appPort}`, + '/fake-page', + undefined, + { + method: 'POST', + } + ) expect(res.status).toBe(404) }) it('should render 404 for _next routes that do not exist', async () => { - const url = `http://localhost:${appPort}/_next/abcdef` - const res = await fetch(url) + const url = `http://localhost:${appPort}` + const res = await fetchViaHTTP(url, '/_next/abcdef') expect(res.status).toBe(404) }) it('should render 404 even if the HTTP method is not GET or HEAD', async () => { - const url = `http://localhost:${appPort}/_next/abcdef` + const url = `http://localhost:${appPort}` const methods = ['POST', 'PUT', 'DELETE'] for (const method of methods) { - const res = await fetch(url, { method }) + const res = await fetchViaHTTP(url, '/_next/abcdef', undefined, { + method, + }) expect(res.status).toBe(404) } }) it('should render 404 for dotfiles in /static', async () => { - const url = `http://localhost:${appPort}/static/.env` - const res = await fetch(url) + const url = `http://localhost:${appPort}` + const res = await fetchViaHTTP(url, '/static/.env') expect(res.status).toBe(404) }) it('should return 405 method on static then GET and HEAD', async () => { - const res = await fetch( - `http://localhost:${appPort}/static/data/item.txt`, + const res = await fetchViaHTTP( + `http://localhost:${appPort}`, + '/static/data/item.txt', + undefined, { method: 'POST', } @@ -383,10 +422,15 @@ describe('Production Usage', () => { const files = buildManifest.pages['/'] for (const file of files) { - const res = await fetch(`http://localhost:${appPort}/_next/${file}`, { - method: 'GET', - headers: { 'if-unmodified-since': 'Fri, 12 Jul 2019 20:00:13 GMT' }, - }) + const res = await fetchViaHTTP( + `http://localhost:${appPort}`, + `/_next/${file}`, + undefined, + { + method: 'GET', + headers: { 'if-unmodified-since': 'Fri, 12 Jul 2019 20:00:13 GMT' }, + } + ) expect(res.status).toBe(412) } }) @@ -400,17 +444,22 @@ describe('Production Usage', () => { const files = buildManifest.pages['/'] for (const file of files) { - const res = await fetch(`http://localhost:${appPort}/_next/${file}`, { - method: 'GET', - headers: { 'if-unmodified-since': 'nextjs' }, - }) + const res = await fetchViaHTTP( + `http://localhost:${appPort}`, + `/_next/${file}`, + undefined, + { + method: 'GET', + headers: { 'if-unmodified-since': 'nextjs' }, + } + ) expect(res.status).toBe(200) } }) it('should set Content-Length header', async () => { const url = `http://localhost:${appPort}` - const res = await fetch(url) + const res = await fetchViaHTTP(url, '/') expect(res.headers.get('Content-Length')).toBeDefined() }) @@ -420,7 +469,7 @@ describe('Production Usage', () => { '../.next', REACT_LOADABLE_MANIFEST )) - const url = `http://localhost:${appPort}/_next/` + const url = `http://localhost:${appPort}` const resources = new Set() @@ -432,12 +481,12 @@ describe('Production Usage', () => { // test dynamic chunk reactLoadableManifest[manifestKey].files.forEach((f) => { - resources.add(url + f) + resources.add('/' + f) }) // test main.js runtime etc for (const item of buildManifest.pages['/']) { - resources.add(url + item) + resources.add('/' + item) } const cssStaticAssets = await recursiveReadDir( @@ -453,11 +502,13 @@ describe('Production Usage', () => { expect(mediaStaticAssets.length).toBeGreaterThanOrEqual(1) expect(mediaStaticAssets[0]).toMatch(/[\\/]media[\\/]/) ;[...cssStaticAssets, ...mediaStaticAssets].forEach((asset) => { - resources.add(`${url}static${asset.replace(/\\+/g, '/')}`) + resources.add(`/static${asset.replace(/\\+/g, '/')}`) }) const responses = await Promise.all( - [...resources].map((resource) => fetch(resource)) + [...resources].map((resource) => + fetchViaHTTP(url, join('/_next', resource)) + ) ) responses.forEach((res) => { @@ -474,8 +525,9 @@ describe('Production Usage', () => { it('should set correct Cache-Control header for static 404s', async () => { // this is to fix where 404 headers are set to 'public, max-age=31536000, immutable' - const res = await fetch( - `http://localhost:${appPort}/_next//static/common/bad-static.js` + const res = await fetchViaHTTP( + `http://localhost:${appPort}`, + `/_next//static/common/bad-static.js` ) expect(res.status).toBe(404) @@ -503,22 +555,22 @@ describe('Production Usage', () => { describe('API routes', () => { it('should work with pages/api/index.js', async () => { - const url = `http://localhost:${appPort}/api` - const res = await fetch(url) + const url = `http://localhost:${appPort}` + const res = await fetchViaHTTP(url, `/api`) const body = await res.text() expect(body).toEqual('API index works') }) it('should work with pages/api/hello.js', async () => { - const url = `http://localhost:${appPort}/api/hello` - const res = await fetch(url) + const url = `http://localhost:${appPort}` + const res = await fetchViaHTTP(url, `/api/hello`) const body = await res.text() expect(body).toEqual('API hello works') }) it('should work with dynamic params and search string', async () => { - const url = `http://localhost:${appPort}/api/post-1?val=1` - const res = await fetch(url) + const url = `http://localhost:${appPort}` + const res = await fetchViaHTTP(url, `/api/post-1?val=1`) const body = await res.json() expect(body).toEqual({ val: '1', post: 'post-1' }) @@ -541,6 +593,8 @@ describe('Production Usage', () => { it('should navigate to nested index via client side', async () => { const browser = await webdriver(appPort, '/another') + await browser.eval('window.beforeNav = 1') + const text = await browser .elementByCss('a') .click() @@ -549,6 +603,7 @@ describe('Production Usage', () => { .text() expect(text).toBe('Hello World') + expect(await browser.eval('window.beforeNav')).toBe(1) await browser.close() }) @@ -878,16 +933,18 @@ describe('Production Usage', () => { it('should not expose the compiled page file in development', async () => { const url = `http://localhost:${appPort}` - await fetch(`${url}/stateless`) // make sure the stateless page is built - const clientSideJsRes = await fetch( - `${url}/_next/development/static/development/pages/stateless.js` + await fetchViaHTTP(`${url}`, `/stateless`) // make sure the stateless page is built + const clientSideJsRes = await fetchViaHTTP( + `${url}`, + '/_next/development/static/development/pages/stateless.js' ) expect(clientSideJsRes.status).toBe(404) const clientSideJsBody = await clientSideJsRes.text() expect(clientSideJsBody).toMatch(/404/) - const serverSideJsRes = await fetch( - `${url}/_next/development/server/static/development/pages/stateless.js` + const serverSideJsRes = await fetchViaHTTP( + `${url}`, + '/_next/development/server/static/development/pages/stateless.js' ) expect(serverSideJsRes.status).toBe(404) const serverSideJsBody = await serverSideJsRes.text() diff --git a/test/integration/re-export-all-exports-from-page-disallowed/.babelrc b/test/integration/re-export-all-exports-from-page-disallowed/.babelrc new file mode 100644 index 0000000000000..1ff94f7ed28e1 --- /dev/null +++ b/test/integration/re-export-all-exports-from-page-disallowed/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["next/babel"] +} diff --git a/test/integration/re-export-all-exports-from-page-disallowed/next.config.js b/test/integration/re-export-all-exports-from-page-disallowed/next.config.js deleted file mode 100644 index d5db0200db49a..0000000000000 --- a/test/integration/re-export-all-exports-from-page-disallowed/next.config.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - experimental: { - swcLoader: false, - }, -} diff --git a/test/integration/re-export-all-exports-from-page-disallowed/test/index.test.js b/test/integration/re-export-all-exports-from-page-disallowed/test/index.test.js index 043788b7e269c..95d833da1e1cb 100644 --- a/test/integration/re-export-all-exports-from-page-disallowed/test/index.test.js +++ b/test/integration/re-export-all-exports-from-page-disallowed/test/index.test.js @@ -12,7 +12,7 @@ describe('Re-export all exports from page is disallowed', () => { expect(code).toBe(1) expect(stderr).toMatch(/\/export-all-in-page/) - expect(stderr.split('\n\n')[2]).toMatchInlineSnapshot(` + expect(stderr.split('\n\n')[1]).toMatchInlineSnapshot(` "./pages/contact.js:3:1 Syntax error: Using \`export * from '...'\` in a page is disallowed. Please use \`export { default } from '...'\` instead. Read more: https://nextjs.org/docs/messages/export-all-in-page" diff --git a/test/integration/react-18-invalid-config/.gitignore b/test/integration/react-18-invalid-config/.gitignore new file mode 100644 index 0000000000000..b512c09d47662 --- /dev/null +++ b/test/integration/react-18-invalid-config/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/test/integration/react-18-invalid-config/index.test.js b/test/integration/react-18-invalid-config/index.test.js new file mode 100644 index 0000000000000..fe7e1e20c1bd9 --- /dev/null +++ b/test/integration/react-18-invalid-config/index.test.js @@ -0,0 +1,58 @@ +/* eslint-env jest */ + +import fs from 'fs-extra' +import { join } from 'path' +import { File, nextBuild } from 'next-test-utils' + +const appDir = __dirname +const nextConfig = new File(join(appDir, 'next.config.js')) + +function writeNextConfig(config) { + const content = `module.exports = { experimental: ${JSON.stringify(config)} }` + nextConfig.write(content) +} + +describe('Invalid react 18 webpack config', () => { + it('should enable `experimental.reactRoot` when `experimental.concurrentFeatures` enables', async () => { + writeNextConfig({ + concurrentFeatures: true, + }) + const { stderr } = await nextBuild(appDir, [], { stderr: true }) + nextConfig.restore() + + expect(stderr).toContain( + '`experimental.concurrentFeatures` requires `experimental.reactRoot` to be enabled along with React 18.' + ) + }) + + it('should enable `experimental.concurrentFeatures` for server components', async () => { + writeNextConfig({ + reactRoot: true, + concurrentFeatures: false, + serverComponents: true, + }) + const { stderr } = await nextBuild(appDir, [], { stderr: true }) + nextConfig.restore() + + expect(stderr).toContain( + '`experimental.concurrentFeatures` is required to be enabled along with `experimental.serverComponents`.' + ) + }) + + it('should warn user when not using react 18 and `experimental.reactRoot` is enabled', async () => { + const reactDomPackagePah = join(appDir, 'node_modules/react-dom') + await fs.mkdirp(reactDomPackagePah) + await fs.writeFile( + join(reactDomPackagePah, 'package.json'), + JSON.stringify({ name: 'react-dom', version: '17.0.0' }) + ) + writeNextConfig({ reactRoot: true }) + const { stderr } = await nextBuild(appDir, [], { stderr: true }) + await fs.remove(reactDomPackagePah) + nextConfig.restore() + + expect(stderr).toContain( + 'You have to use React 18 to use `experimental.reactRoot`.' + ) + }) +}) diff --git a/test/integration/react-18-invalid-config/next.config.js b/test/integration/react-18-invalid-config/next.config.js new file mode 100644 index 0000000000000..07523fe9fd1e0 --- /dev/null +++ b/test/integration/react-18-invalid-config/next.config.js @@ -0,0 +1 @@ +module.exports = { experimental: { reactRoot: true } } diff --git a/test/integration/react-18-invalid-config/pages/index.js b/test/integration/react-18-invalid-config/pages/index.js new file mode 100644 index 0000000000000..f7dff6a079465 --- /dev/null +++ b/test/integration/react-18-invalid-config/pages/index.js @@ -0,0 +1,3 @@ +export default function Index() { + return 'index' +} diff --git a/test/integration/react-18/app/components/dynamic-hello.js b/test/integration/react-18/app/components/dynamic-hello.js index 61d0ecaea9b88..594812b5e216d 100644 --- a/test/integration/react-18/app/components/dynamic-hello.js +++ b/test/integration/react-18/app/components/dynamic-hello.js @@ -4,7 +4,7 @@ import dynamic from 'next/dynamic' let ssr const suspense = false -const Hello = dynamic(() => import('./hello'), { +const Hello = dynamic(() => import(/* webpackMode: "eager" */ './hello'), { ssr, suspense, }) diff --git a/test/integration/react-18/app/next.config.js b/test/integration/react-18/app/next.config.js index 3604bd2c1ac01..97da67555b2c2 100644 --- a/test/integration/react-18/app/next.config.js +++ b/test/integration/react-18/app/next.config.js @@ -6,13 +6,13 @@ module.exports = { webpack(config) { const { alias } = config.resolve // FIXME: resolving react/jsx-runtime https://github.com/facebook/react/issues/20235 - alias['react/jsx-dev-runtime'] = require.resolve('react/jsx-dev-runtime.js') - alias['react/jsx-runtime'] = require.resolve('react/jsx-runtime.js') + alias['react/jsx-dev-runtime'] = 'react/jsx-dev-runtime.js' + alias['react/jsx-runtime'] = 'react/jsx-runtime.js' // Use react 18 - alias['react'] = require.resolve('react-18') - alias['react-dom'] = require.resolve('react-dom-18') - alias['react-dom/server'] = require.resolve('react-dom-18/server') + alias['react'] = 'react-18' + alias['react-dom'] = 'react-dom-18' + alias['react-dom/server'] = 'react-dom-18/server' return config }, diff --git a/test/integration/react-18/app/pages/use-id.js b/test/integration/react-18/app/pages/use-id.js new file mode 100644 index 0000000000000..1b5f6fef28fca --- /dev/null +++ b/test/integration/react-18/app/pages/use-id.js @@ -0,0 +1,5 @@ +import { useId } from 'react' + +export default function Page() { + return
{useId()}
+} diff --git a/test/integration/react-18/app/tsconfig.json b/test/integration/react-18/app/tsconfig.json index 93a83a407c40c..b8d597880a1ae 100644 --- a/test/integration/react-18/app/tsconfig.json +++ b/test/integration/react-18/app/tsconfig.json @@ -12,7 +12,8 @@ "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "preserve" + "jsx": "preserve", + "incremental": true }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], "exclude": ["node_modules"] diff --git a/test/integration/react-18/test/basics.js b/test/integration/react-18/test/basics.js index bb3c4ad99ced4..d54a9b02e4dd4 100644 --- a/test/integration/react-18/test/basics.js +++ b/test/integration/react-18/test/basics.js @@ -28,4 +28,15 @@ export default (context) => { expect(content).toBe('rab') expect(nextData.dynamicIds).toBeUndefined() }) + + it('useId() values should match on hydration', async () => { + const html = await renderViaHTTP(context.appPort, '/use-id') + const $ = cheerio.load(html) + const ssrId = $('#id').text() + + const browser = await webdriver(context.appPort, '/use-id') + const csrId = await browser.eval('document.getElementById("id").innerText') + + expect(ssrId).toEqual(csrId) + }) } diff --git a/test/integration/react-streaming-and-server-components/app/.env b/test/integration/react-streaming-and-server-components/app/.env new file mode 100644 index 0000000000000..afb1f219ebcf7 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/.env @@ -0,0 +1 @@ +ENV_VAR_TEST="env_var_test" \ No newline at end of file diff --git a/test/integration/react-streaming-and-server-components/app/components/bar.server.js b/test/integration/react-streaming-and-server-components/app/components/bar.server.js new file mode 100644 index 0000000000000..e8b54be31993b --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/components/bar.server.js @@ -0,0 +1,9 @@ +import Foo from './foo.client' + +export default function Bar() { + return ( +
+ bar.server.js: +
+ ) +} diff --git a/test/integration/react-streaming-and-server-components/app/components/foo.client.js b/test/integration/react-streaming-and-server-components/app/components/foo.client.js new file mode 100644 index 0000000000000..0e28745e7d8f3 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/components/foo.client.js @@ -0,0 +1,3 @@ +export default function foo() { + return 'foo.client' +} diff --git a/test/integration/react-streaming-and-server-components/app/components/red/index.client.js b/test/integration/react-streaming-and-server-components/app/components/red/index.client.js new file mode 100644 index 0000000000000..4213f7858900e --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/components/red/index.client.js @@ -0,0 +1,5 @@ +import styles from './style.module.css' + +export default function RedText({ children }) { + return
{children}
+} diff --git a/test/integration/react-streaming-and-server-components/app/components/red/style.module.css b/test/integration/react-streaming-and-server-components/app/components/red/style.module.css new file mode 100644 index 0000000000000..3efa99e6fb171 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/components/red/style.module.css @@ -0,0 +1,3 @@ +.text { + color: red; +} diff --git a/test/integration/react-streaming-and-server-components/app/next.config.js b/test/integration/react-streaming-and-server-components/app/next.config.js new file mode 100644 index 0000000000000..4d3b46674f7b4 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/next.config.js @@ -0,0 +1,20 @@ +module.exports = { + pageExtensions: ['js', 'ts', 'jsx'], // .tsx won't be treat as page, + experimental: { + reactRoot: true, + concurrentFeatures: true, + serverComponents: true, + }, + webpack(config) { + const { alias } = config.resolve + alias['react/jsx-dev-runtime'] = 'react-18/jsx-dev-runtime.js' + alias['react/jsx-runtime'] = 'react-18/jsx-runtime.js' + + // Use react 18 + alias['react'] = 'react-18' + alias['react-dom'] = 'react-dom-18' + alias['react-dom/server'] = 'react-dom-18/server' + + return config + }, +} diff --git a/test/integration/react-streaming-and-server-components/app/package.json b/test/integration/react-streaming-and-server-components/app/package.json new file mode 100644 index 0000000000000..90af0ce830c99 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/package.json @@ -0,0 +1,9 @@ +{ + "private": true, + "scripts": { + "lnext": "node -r ../../react-18/test/require-hook.js ../../../../packages/next/dist/bin/next", + "dev": "yarn lnext dev", + "build": "yarn lnext build", + "start": "yarn lnext start" + } +} diff --git a/test/integration/react-streaming-and-server-components/app/pages/404.js b/test/integration/react-streaming-and-server-components/app/pages/404.js new file mode 100644 index 0000000000000..92471c97e449d --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/pages/404.js @@ -0,0 +1,3 @@ +export default function Page404() { + return 'custom-404-page' +} diff --git a/test/integration/react-streaming-and-server-components/app/pages/api/ping.js b/test/integration/react-streaming-and-server-components/app/pages/api/ping.js new file mode 100644 index 0000000000000..ea5f41f4fa503 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/pages/api/ping.js @@ -0,0 +1,3 @@ +export default (req, res) => { + res.end('pong') +} diff --git a/test/integration/react-streaming-and-server-components/app/pages/css-modules.server.js b/test/integration/react-streaming-and-server-components/app/pages/css-modules.server.js new file mode 100644 index 0000000000000..a4499da5d1e26 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/pages/css-modules.server.js @@ -0,0 +1,10 @@ +// CSS modules can only be imported inside client components for now. +import RedText from '../components/red/index.client' + +export default function CSSM() { + return ( + +

This should be in red

+
+ ) +} diff --git a/test/integration/react-streaming-and-server-components/app/pages/dynamic-imports.js b/test/integration/react-streaming-and-server-components/app/pages/dynamic-imports.js new file mode 100644 index 0000000000000..60e8bf7fe0c16 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/pages/dynamic-imports.js @@ -0,0 +1,13 @@ +import { lazy, Suspense } from 'react' + +const Foo = lazy(() => import('../components/foo.client')) + +export default function Page() { + return ( +
+ + + +
+ ) +} diff --git a/test/integration/react-streaming-and-server-components/app/pages/global-styles-rsc.server.js b/test/integration/react-streaming-and-server-components/app/pages/global-styles-rsc.server.js new file mode 100644 index 0000000000000..9db6f53f3eb83 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/pages/global-styles-rsc.server.js @@ -0,0 +1,7 @@ +export default function GlobalStyle() { + return ( +
+

This should be in red

+
+ ) +} diff --git a/test/integration/react-streaming-and-server-components/app/pages/global-styles.js b/test/integration/react-streaming-and-server-components/app/pages/global-styles.js new file mode 100644 index 0000000000000..9db6f53f3eb83 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/pages/global-styles.js @@ -0,0 +1,7 @@ +export default function GlobalStyle() { + return ( +
+

This should be in red

+
+ ) +} diff --git a/test/integration/react-streaming-and-server-components/app/pages/index.server.js b/test/integration/react-streaming-and-server-components/app/pages/index.server.js new file mode 100644 index 0000000000000..4b61e6c76bad9 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/pages/index.server.js @@ -0,0 +1,15 @@ +import Foo from '../components/foo.client' + +const envVar = process.env.ENV_VAR_TEST + +export default function Index() { + return ( +
+

{`thisistheindexpage.server`}

+
{envVar}
+
+ +
+
+ ) +} diff --git a/test/integration/react-streaming-and-server-components/app/pages/invalid-ext.server.tsx b/test/integration/react-streaming-and-server-components/app/pages/invalid-ext.server.tsx new file mode 100644 index 0000000000000..39cf04efcdab0 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/pages/invalid-ext.server.tsx @@ -0,0 +1,3 @@ +export default function InvalidExt() { + return 'invalid-ext' +} diff --git a/test/integration/react-streaming-and-server-components/app/pages/multi.server.js b/test/integration/react-streaming-and-server-components/app/pages/multi.server.js new file mode 100644 index 0000000000000..035795e828a71 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/pages/multi.server.js @@ -0,0 +1,5 @@ +import Bar from '../components/bar.server' + +export default function Multi() { + return +} diff --git a/test/integration/react-streaming-and-server-components/app/pages/next-api/image.server.js b/test/integration/react-streaming-and-server-components/app/pages/next-api/image.server.js new file mode 100644 index 0000000000000..aada9437f2c33 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/pages/next-api/image.server.js @@ -0,0 +1,6 @@ +import NextImage from 'next/image' +import src from '../../public/test.jpg' + +export default function Image() { + return +} diff --git a/test/integration/react-streaming-and-server-components/app/pages/next-api/link.server.js b/test/integration/react-streaming-and-server-components/app/pages/next-api/link.server.js new file mode 100644 index 0000000000000..6258cbf2059a5 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/pages/next-api/link.server.js @@ -0,0 +1,9 @@ +import NextLink from 'next/link' + +export default function Link() { + return ( + + go home + + ) +} diff --git a/test/integration/react-streaming-and-server-components/app/pages/routes/[dynamic].server.js b/test/integration/react-streaming-and-server-components/app/pages/routes/[dynamic].server.js new file mode 100644 index 0000000000000..eee586678967b --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/pages/routes/[dynamic].server.js @@ -0,0 +1,3 @@ +export default function Pid() { + return '[pid]' // TODO: display based on query +} diff --git a/test/integration/react-streaming-and-server-components/app/pages/streaming.js b/test/integration/react-streaming-and-server-components/app/pages/streaming.js new file mode 100644 index 0000000000000..ac58f5e9e2c38 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/pages/streaming.js @@ -0,0 +1,23 @@ +import { Suspense } from 'react' + +let result +let promise +function Data() { + if (result) return result + if (!promise) + promise = new Promise((res) => { + setTimeout(() => { + result = 'next_streaming_data' + res() + }, 500) + }) + throw promise +} + +export default function Page() { + return ( + + + + ) +} diff --git a/test/integration/react-streaming-and-server-components/app/public/test.jpg b/test/integration/react-streaming-and-server-components/app/public/test.jpg new file mode 100644 index 0000000000000..d536c882412ed Binary files /dev/null and b/test/integration/react-streaming-and-server-components/app/public/test.jpg differ diff --git a/test/integration/react-streaming-and-server-components/app/styles.css b/test/integration/react-streaming-and-server-components/app/styles.css new file mode 100644 index 0000000000000..33ebbce0db69a --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/styles.css @@ -0,0 +1,3 @@ +#red { + color: red; +} diff --git a/test/integration/react-streaming-and-server-components/app/tsconfig.json b/test/integration/react-streaming-and-server-components/app/tsconfig.json new file mode 100644 index 0000000000000..1563f3e878573 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "incremental": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve" + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/test/integration/react-streaming-and-server-components/test/css.js b/test/integration/react-streaming-and-server-components/test/css.js new file mode 100644 index 0000000000000..141e88a7a8328 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/test/css.js @@ -0,0 +1,27 @@ +/* eslint-env jest */ +import webdriver from 'next-webdriver' + +export default function (context) { + it('should include global styles under `concurrentFeatures: true`', async () => { + const browser = await webdriver(context.appPort, '/global-styles') + const currentColor = await browser.eval( + `window.getComputedStyle(document.querySelector('#red')).color` + ) + expect(currentColor).toMatchInlineSnapshot(`"rgb(255, 0, 0)"`) + }) + it('should include global styles with `serverComponents: true`', async () => { + const browser = await webdriver(context.appPort, '/global-styles-rsc') + const currentColor = await browser.eval( + `window.getComputedStyle(document.querySelector('#red')).color` + ) + expect(currentColor).toMatchInlineSnapshot(`"rgb(255, 0, 0)"`) + }) + // TODO: fix this test + // it.skip('should include css modules with `serverComponents: true`', async () => { + // const browser = await webdriver(context.appPort, '/css-modules') + // const currentColor = await browser.eval( + // `window.getComputedStyle(document.querySelector('h1')).color` + // ) + // expect(currentColor).toMatchInlineSnapshot(`"rgb(255, 0, 0)"`) + // }) +} diff --git a/test/integration/react-streaming-and-server-components/test/index.test.js b/test/integration/react-streaming-and-server-components/test/index.test.js new file mode 100644 index 0000000000000..05b7218ccc5d8 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/test/index.test.js @@ -0,0 +1,323 @@ +/* eslint-env jest */ + +import cheerio from 'cheerio' +import { join } from 'path' +import fs from 'fs-extra' +import webdriver from 'next-webdriver' + +import { + File, + fetchViaHTTP, + findPort, + killApp, + launchApp, + nextBuild as _nextBuild, + nextStart as _nextStart, + renderViaHTTP, +} from 'next-test-utils' + +import css from './css' + +const nodeArgs = ['-r', join(__dirname, '../../react-18/test/require-hook.js')] +const appDir = join(__dirname, '../app') +const nativeModuleTestAppDir = join(__dirname, '../unsupported-native-module') +const distDir = join(__dirname, '../app/.next') +const documentPage = new File(join(appDir, 'pages/_document.jsx')) +const appPage = new File(join(appDir, 'pages/_app.js')) + +const documentWithGip = ` +import { Html, Head, Main, NextScript } from 'next/document' + +export default function Document() { + return ( + + + +
+ + + + ) +} + +Document.getInitialProps = (ctx) => { + return ctx.defaultGetInitialProps(ctx) +} +` + +const appWithGlobalCss = ` +import '../styles.css' + +function App({ Component, pageProps }) { + return +} + +export default App +` + +async function nextBuild(dir) { + return await _nextBuild(dir, [], { + stdout: true, + stderr: true, + nodeArgs, + }) +} + +async function nextStart(dir, port) { + return await _nextStart(dir, port, { + stdout: true, + stderr: true, + nodeArgs, + }) +} + +async function nextDev(dir, port) { + return await launchApp(dir, port, { + stdout: true, + stderr: true, + nodeArgs, + }) +} + +describe('concurrentFeatures - basic', () => { + it('should warn user for experimental risk with server components', async () => { + const edgeRuntimeWarning = + 'You are using the experimental Edge Runtime with `concurrentFeatures`.' + const rscWarning = `You have experimental React Server Components enabled. Continue at your own risk.` + const { stderr } = await nextBuild(appDir) + expect(stderr).toContain(edgeRuntimeWarning) + expect(stderr).toContain(rscWarning) + }) + it('should warn user that native node APIs are not supported', async () => { + const fsImportedErrorMessage = + 'Native Node.js APIs are not supported in the Edge Runtime with `concurrentFeatures` enabled. Found `dns` imported.' + const { stderr } = await nextBuild(nativeModuleTestAppDir) + expect(stderr).toContain(fsImportedErrorMessage) + }) +}) + +describe('concurrentFeatures - prod', () => { + const context = { appDir } + + beforeAll(async () => { + context.appPort = await findPort() + await nextBuild(context.appDir) + context.server = await nextStart(context.appDir, context.appPort) + }) + afterAll(async () => { + await killApp(context.server) + }) + + it('should generate rsc middleware manifests', async () => { + const distServerDir = join(distDir, 'server') + const hasFile = (filename) => fs.existsSync(join(distServerDir, filename)) + + const files = [ + 'middleware-build-manifest.js', + 'middleware-flight-manifest.js', + 'middleware-ssr-runtime.js', + 'middleware-manifest.json', + ] + files.forEach((file) => { + expect(hasFile(file)).toBe(true) + }) + }) + + it('should have clientInfo in middleware manifest', async () => { + const middlewareManifestPath = join( + distDir, + 'server', + 'middleware-manifest.json' + ) + const content = JSON.parse( + await fs.readFile(middlewareManifestPath, 'utf8') + ) + for (const item of [ + ['/', true], + ['/next-api/image', true], + ['/next-api/link', true], + ['/routes/[dynamic]', true], + ]) { + expect(content.clientInfo).toContainEqual(item) + } + expect(content.clientInfo).not.toContainEqual([['/404', true]]) + }) + + it('should support React.lazy and dynamic imports', async () => { + const html = await renderViaHTTP(context.appPort, '/dynamic-imports') + expect(html).toContain('foo.client') + }) + + runBasicTests(context) +}) + +describe('concurrentFeatures - dev', () => { + const context = { appDir } + + beforeAll(async () => { + context.appPort = await findPort() + context.server = await nextDev(context.appDir, context.appPort) + }) + afterAll(async () => { + await killApp(context.server) + }) + + // TODO: re-enabled test when update webpack with chunkLoading support + it.skip('should support React.lazy and dynamic imports', async () => { + const html = await renderViaHTTP(context.appPort, '/dynamic-imports') + expect(html).toContain('loading...') + + const browser = await webdriver(context.appPort, '/dynamic-imports') + const content = await browser.eval(`window.document.body.innerText`) + expect(content).toMatchInlineSnapshot('"foo.client"') + }) + + runBasicTests(context) +}) + +const cssSuite = { + runTests: css, + before: () => appPage.write(appWithGlobalCss), + after: () => appPage.delete(), +} + +runSuite('CSS', 'dev', cssSuite) +runSuite('CSS', 'prod', cssSuite) + +const documentSuite = { + runTests: (context) => { + it('should error when custom _document has getInitialProps method', async () => { + const res = await fetchViaHTTP(context.appPort, '/') + const html = await res.text() + + expect(res.status).toBe(500) + expect(html).toContain( + 'Error: `getInitialProps` in Document component is not supported with `concurrentFeatures` enabled.' + ) + }) + }, + before: () => documentPage.write(documentWithGip), + after: () => documentPage.delete(), +} + +runSuite('document', 'dev', documentSuite) +runSuite('document', 'prod', documentSuite) + +async function runBasicTests(context) { + it('should render the correct html', async () => { + const homeHTML = await renderViaHTTP(context.appPort, '/') + + // dynamic routes + const dynamicRouteHTML1 = await renderViaHTTP( + context.appPort, + '/routes/dynamic1' + ) + const dynamicRouteHTML2 = await renderViaHTTP( + context.appPort, + '/routes/dynamic2' + ) + + const path404HTML = await renderViaHTTP(context.appPort, '/404') + const pathNotFoundHTML = await renderViaHTTP( + context.appPort, + '/this-is-not-found' + ) + + expect(homeHTML).toContain('thisistheindexpage.server') + expect(homeHTML).toContain('env_var_test') + expect(homeHTML).toContain('foo.client') + + expect(dynamicRouteHTML1).toContain('[pid]') + expect(dynamicRouteHTML2).toContain('[pid]') + + expect(path404HTML).toContain('custom-404-page') + expect(pathNotFoundHTML).toContain('custom-404-page') + }) + + it('should suspense next/link on server side', async () => { + const linkHTML = await renderViaHTTP(context.appPort, '/next-api/link') + const $ = cheerio.load(linkHTML) + const linkText = $('div[hidden] > a[href="/"]').text() + + expect(linkText).toContain('go home') + }) + + it('should suspense next/image on server side', async () => { + const imageHTML = await renderViaHTTP(context.appPort, '/next-api/image') + const $ = cheerio.load(imageHTML) + const imageTag = $('div[hidden] > span > span > img') + + expect(imageTag.attr('src')).toContain('data:image') + }) + + it('should support multi-level server component imports', async () => { + const html = await renderViaHTTP(context.appPort, '/multi') + expect(html).toContain('bar.server.js:') + expect(html).toContain('foo.client') + }) + + it('should support streaming', async () => { + await fetchViaHTTP(context.appPort, '/streaming', null, {}).then( + async (response) => { + let result = '' + let gotFallback = false + let gotData = false + + await new Promise((resolve) => { + response.body.on('data', (chunk) => { + result += chunk.toString() + + gotData = result.includes('next_streaming_data') + if (!gotFallback) { + gotFallback = result.includes('next_streaming_fallback') + if (gotFallback) { + expect(gotData).toBe(false) + } + } + }) + + response.body.on('end', () => resolve()) + }) + + expect(gotFallback).toBe(true) + expect(gotData).toBe(true) + } + ) + + // Should end up with "next_streaming_data". + const browser = await webdriver(context.appPort, '/streaming') + const content = await browser.eval(`window.document.body.innerText`) + expect(content).toMatchInlineSnapshot('"next_streaming_data"') + }) + + it('should support api routes', async () => { + const res = await renderViaHTTP(context.appPort, '/api/ping') + expect(res).toContain('pong') + }) +} + +function runSuite(suiteName, env, { runTests, before, after }) { + const context = { appDir } + describe(`${suiteName} ${env}`, () => { + if (env === 'prod') { + beforeAll(async () => { + before?.() + context.appPort = await findPort() + context.server = await nextDev(context.appDir, context.appPort) + }) + } + if (env === 'dev') { + beforeAll(async () => { + before?.() + context.appPort = await findPort() + context.server = await nextDev(context.appDir, context.appPort) + }) + } + afterAll(async () => { + after?.() + await killApp(context.server) + }) + + runTests(context) + }) +} diff --git a/test/integration/react-streaming-and-server-components/unsupported-native-module/next.config.js b/test/integration/react-streaming-and-server-components/unsupported-native-module/next.config.js new file mode 100644 index 0000000000000..1c9d1b2a6c1c9 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/unsupported-native-module/next.config.js @@ -0,0 +1,19 @@ +module.exports = { + experimental: { + reactRoot: true, + concurrentFeatures: true, + serverComponents: true, + }, + webpack(config) { + const { alias } = config.resolve + alias['react/jsx-dev-runtime'] = 'react-18/jsx-dev-runtime.js' + alias['react/jsx-runtime'] = 'react-18/jsx-runtime.js' + + // Use react 18 + alias['react'] = 'react-18' + alias['react-dom'] = 'react-dom-18' + alias['react-dom/server'] = 'react-dom-18/server' + + return config + }, +} diff --git a/test/integration/react-streaming-and-server-components/unsupported-native-module/package.json b/test/integration/react-streaming-and-server-components/unsupported-native-module/package.json new file mode 100644 index 0000000000000..90af0ce830c99 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/unsupported-native-module/package.json @@ -0,0 +1,9 @@ +{ + "private": true, + "scripts": { + "lnext": "node -r ../../react-18/test/require-hook.js ../../../../packages/next/dist/bin/next", + "dev": "yarn lnext dev", + "build": "yarn lnext build", + "start": "yarn lnext start" + } +} diff --git a/test/integration/react-streaming-and-server-components/unsupported-native-module/pages/index.js b/test/integration/react-streaming-and-server-components/unsupported-native-module/pages/index.js new file mode 100644 index 0000000000000..31f0f204a9d3b --- /dev/null +++ b/test/integration/react-streaming-and-server-components/unsupported-native-module/pages/index.js @@ -0,0 +1,10 @@ +let EOF + +if (typeof window === 'undefined') { + EOF = require('dns').EOF +} + +export default function Index() { + console.log(EOF) + return 'Access Node.js native module dns' +} diff --git a/test/integration/read-only-source-hmr/pages/hello.js b/test/integration/read-only-source-hmr/pages/hello.js new file mode 100644 index 0000000000000..a55c274a1b416 --- /dev/null +++ b/test/integration/read-only-source-hmr/pages/hello.js @@ -0,0 +1,3 @@ +const Hello = () =>

Hello World

+ +export default Hello diff --git a/test/integration/read-only-source-hmr/test/index.test.js b/test/integration/read-only-source-hmr/test/index.test.js new file mode 100644 index 0000000000000..26eef62eb382b --- /dev/null +++ b/test/integration/read-only-source-hmr/test/index.test.js @@ -0,0 +1,123 @@ +/* eslint-env jest */ + +import fs from 'fs-extra' +import { + check, + findPort, + getBrowserBodyText, + killApp, + launchApp, +} from 'next-test-utils' +import webdriver from 'next-webdriver' +import { join } from 'path' + +const READ_ONLY_PERMISSIONS = 0o444 +const READ_WRITE_PERMISSIONS = 0o644 + +const appDir = join(__dirname, '..') +const pagePath = join(appDir, 'pages/hello.js') + +let appPort +let app + +async function writeReadOnlyFile(path, content) { + const exists = await fs + .access(path) + .then(() => true) + .catch(() => false) + + if (exists) { + await fs.chmod(path, READ_WRITE_PERMISSIONS) + } + + await fs.writeFile(path, content, 'utf8') + await fs.chmod(path, READ_ONLY_PERMISSIONS) +} + +describe('Read-only source HMR', () => { + beforeAll(async () => { + await fs.chmod(pagePath, READ_ONLY_PERMISSIONS) + + appPort = await findPort() + app = await launchApp(appDir, appPort, { + env: { + __NEXT_TEST_WITH_DEVTOOL: 1, + // Events can be finicky in CI. This switches to a more reliable + // polling method. + CHOKIDAR_USEPOLLING: 'true', + CHOKIDAR_INTERVAL: 500, + }, + }) + }) + + afterAll(async () => { + await fs.chmod(pagePath, READ_WRITE_PERMISSIONS) + await killApp(app) + }) + + it('should detect changes to a page', async () => { + let browser + + try { + browser = await webdriver(appPort, '/hello') + await check(() => getBrowserBodyText(browser), /Hello World/) + + const originalContent = await fs.readFile(pagePath, 'utf8') + const editedContent = originalContent.replace('Hello World', 'COOL page') + + await writeReadOnlyFile(pagePath, editedContent) + await check(() => getBrowserBodyText(browser), /COOL page/) + + await writeReadOnlyFile(pagePath, originalContent) + await check(() => getBrowserBodyText(browser), /Hello World/) + } finally { + if (browser) { + await browser.close() + } + } + }) + + it('should handle page deletion and subsequent recreation', async () => { + let browser + + try { + browser = await webdriver(appPort, '/hello') + await check(() => getBrowserBodyText(browser), /Hello World/) + + const originalContent = await fs.readFile(pagePath, 'utf8') + + await fs.remove(pagePath) + + await writeReadOnlyFile(pagePath, originalContent) + await check(() => getBrowserBodyText(browser), /Hello World/) + } finally { + if (browser) { + await browser.close() + } + } + }) + + it('should detect a new page', async () => { + let browser + const newPagePath = join(appDir, 'pages/new.js') + + try { + await writeReadOnlyFile( + newPagePath, + ` + const New = () =>

New page

+ + export default New + ` + ) + + browser = await webdriver(appPort, '/new') + await check(() => getBrowserBodyText(browser), /New page/) + } finally { + if (browser) { + await browser.close() + } + await fs.remove(newPagePath) + } + }) +}) diff --git a/test/integration/scss-fixtures/data-url/pages/_app.js b/test/integration/scss-fixtures/data-url/pages/_app.js new file mode 100644 index 0000000000000..b89fe16feb731 --- /dev/null +++ b/test/integration/scss-fixtures/data-url/pages/_app.js @@ -0,0 +1,12 @@ +import React from 'react' +import App from 'next/app' +import '../styles/global.scss' + +class MyApp extends App { + render() { + const { Component, pageProps } = this.props + return + } +} + +export default MyApp diff --git a/test/integration/scss-fixtures/data-url/pages/index.js b/test/integration/scss-fixtures/data-url/pages/index.js new file mode 100644 index 0000000000000..5cbac8a153d77 --- /dev/null +++ b/test/integration/scss-fixtures/data-url/pages/index.js @@ -0,0 +1,3 @@ +export default function Home() { + return
This text should be red.
+} diff --git a/test/integration/scss-fixtures/data-url/styles/global.scss b/test/integration/scss-fixtures/data-url/styles/global.scss new file mode 100644 index 0000000000000..8516e260051a8 --- /dev/null +++ b/test/integration/scss-fixtures/data-url/styles/global.scss @@ -0,0 +1,5 @@ +$var: red; +.red-text { + color: $var; + background-image: url("data:image/svg+xml,%3c%3fxml version='1.0' encoding='UTF-8'%3f%3e%3csvg width='114px' height='100px' viewBox='0 0 114 100' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3e%3cdefs%3e%3clinearGradient x1='100.929941%25' y1='181.283245%25' x2='41.7687834%25' y2='100%25' id='linearGradient-1'%3e%3cstop stop-color='white' offset='0%25'%3e%3c/stop%3e%3cstop stop-color='black' offset='100%25'%3e%3c/stop%3e%3c/linearGradient%3e%3c/defs%3e%3cg id='Page-1' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3e%3cg id='Black-Triangle' transform='translate(-293.000000%2c -150.000000)' fill='url(%23linearGradient-1)'%3e%3cpolygon id='Logotype---Black' points='350 150 407 250 293 250'%3e%3c/polygon%3e%3c/g%3e%3c/g%3e%3c/svg%3e"); +} diff --git a/test/integration/scss-modules/test/index.test.js b/test/integration/scss-modules/test/index.test.js index d26648d2a7974..0cf1d41a19cc2 100644 --- a/test/integration/scss-modules/test/index.test.js +++ b/test/integration/scss-modules/test/index.test.js @@ -51,7 +51,7 @@ describe('Basic SCSS Module Support', () => { const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8') expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot( - `".index_redText__2VIiM{color:red}"` + `".index_redText__WduQk{color:red}"` ) }) @@ -68,7 +68,7 @@ describe('Basic SCSS Module Support', () => { expect(cssSheet.attr('href')).toMatch(/^\/_next\/static\/css\/.*\.css$/) expect($('#verify-red').attr('class')).toMatchInlineSnapshot( - `"index_redText__2VIiM"` + `"index_redText__WduQk"` ) }) }) @@ -107,7 +107,7 @@ describe('3rd Party CSS Module Support', () => { const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8') expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot( - `".index_foo__9_fxH{position:relative}.index_foo__9_fxH .bar,.index_foo__9_fxH .baz{height:100%;overflow:hidden}.index_foo__9_fxH .lol,.index_foo__9_fxH>.lel{width:80%}"` + `".index_foo__gL0ty{position:relative}.index_foo__gL0ty .bar,.index_foo__gL0ty .baz{height:100%;overflow:hidden}.index_foo__gL0ty .lol,.index_foo__gL0ty>.lel{width:80%}"` ) }) @@ -124,7 +124,7 @@ describe('3rd Party CSS Module Support', () => { expect(cssSheet.attr('href')).toMatch(/^\/_next\/static\/css\/.*\.css$/) expect($('#verify-div').attr('class')).toMatchInlineSnapshot( - `"index_foo__9_fxH"` + `"index_foo__gL0ty"` ) }) }) @@ -309,7 +309,7 @@ describe('Valid CSS Module Usage from within node_modules', () => { const cssPreload = $('#nm-div') expect(cssPreload.text()).toMatchInlineSnapshot( - `"{\\"message\\":\\"Why hello there\\"} {\\"redText\\":\\"example_redText__1hNNA\\"}"` + `"{\\"message\\":\\"Why hello there\\"} {\\"redText\\":\\"example_redText__8_1ap\\"}"` ) }) @@ -323,7 +323,7 @@ describe('Valid CSS Module Usage from within node_modules', () => { const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8') expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot( - `".example_redText__1hNNA{color:red}"` + `".example_redText__8_1ap{color:red}"` ) }) }) @@ -362,7 +362,7 @@ describe('Valid Nested CSS Module Usage from within node_modules', () => { const cssPreload = $('#nm-div') expect(cssPreload.text()).toMatchInlineSnapshot( - `"{\\"message\\":\\"Why hello there\\"} {\\"other2\\":\\"example_other2__1pnJV\\",\\"subClass\\":\\"example_subClass__2EbKX other_className__E6nd8\\"}"` + `"{\\"message\\":\\"Why hello there\\"} {\\"other2\\":\\"example_other2__pD1TP\\",\\"subClass\\":\\"example_subClass___Qywg other_className__jR2X2\\"}"` ) }) @@ -376,7 +376,7 @@ describe('Valid Nested CSS Module Usage from within node_modules', () => { const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8') expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot( - `".other_other3__ZPN-Y{color:violet}.other_className__E6nd8{background:red;color:#ff0}.example_other2__1pnJV{color:red}.example_subClass__2EbKX{background:blue}"` + `".other_other3__QRKUk{color:violet}.other_className__jR2X2{background:red;color:#ff0}.example_other2__pD1TP{color:red}.example_subClass___Qywg{background:blue}"` ) }) }) @@ -409,7 +409,7 @@ describe('CSS Module Composes Usage (Basic)', () => { const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8') expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot( - `".index_className__2O8Wt{background:red;color:#ff0}.index_subClass__3e6Re{background:blue}"` + `".index_className__phGSl{background:red;color:#ff0}.index_subClass__fPp6w{background:blue}"` ) }) }) @@ -442,7 +442,7 @@ describe('CSS Module Composes Usage (External)', () => { const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8') expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot( - `".other_className__2VTl4{background:red;color:#ff0}.index_subClass__3e6Re{background:blue}"` + `".other_className__e_JDW{background:red;color:#ff0}.index_subClass__fPp6w{background:blue}"` ) }) }) @@ -480,7 +480,7 @@ describe('Dynamic Route CSS Module Usage', () => { const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8') expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot( - `"._post__home__3F5yW{background:red}"` + `"._post__home__a9vTy{background:red}"` ) }) @@ -538,7 +538,7 @@ describe('Catch-all Route CSS Module Usage', () => { const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8') expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot( - `".___post__home__psZf9{background:red}"` + `".___post__home__w1yuY{background:red}"` ) }) }) diff --git a/test/integration/scss/test/index.test.js b/test/integration/scss/test/index.test.js index e89eebd42f55a..a98b96f224199 100644 --- a/test/integration/scss/test/index.test.js +++ b/test/integration/scss/test/index.test.js @@ -638,7 +638,7 @@ describe('SCSS Support', () => { expect(cssFiles.length).toBe(1) const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8') expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatch( - /^\.red-text\{color:red;background-image:url\(\/_next\/static\/media\/dark\.[a-z0-9]{32}\.svg\) url\(\/_next\/static\/media\/dark2\.[a-z0-9]{32}\.svg\)\}\.blue-text\{color:orange;font-weight:bolder;background-image:url\(\/_next\/static\/media\/light\.[a-z0-9]{32}\.svg\);color:blue\}$/ + /^\.red-text\{color:red;background-image:url\(\/_next\/static\/media\/dark\.[a-f0-9]{8}\.svg\) url\(\/_next\/static\/media\/dark2\.[a-f0-9]{8}\.svg\)\}\.blue-text\{color:orange;font-weight:bolder;background-image:url\(\/_next\/static\/media\/light\.[a-f0-9]{8}\.svg\);color:blue\}$/ ) const mediaFiles = await readdir(mediaFolder) @@ -646,7 +646,7 @@ describe('SCSS Support', () => { expect( mediaFiles .map((fileName) => - /^(.+?)\..{32}\.(.+?)$/.exec(fileName).slice(1).join('.') + /^(.+?)\..{8}\.(.+?)$/.exec(fileName).slice(1).join('.') ) .sort() ).toMatchInlineSnapshot(` @@ -684,7 +684,7 @@ describe('SCSS Support', () => { expect(cssFiles.length).toBe(1) const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8') expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatch( - /^\.red-text\{color:red;background-image:url\(\/foo\/_next\/static\/media\/dark\.[a-z0-9]{32}\.svg\) url\(\/foo\/_next\/static\/media\/dark2\.[a-z0-9]{32}\.svg\)\}\.blue-text\{color:orange;font-weight:bolder;background-image:url\(\/foo\/_next\/static\/media\/light\.[a-z0-9]{32}\.svg\);color:blue\}$/ + /^\.red-text\{color:red;background-image:url\(\/foo\/_next\/static\/media\/dark\.[a-f0-9]{8}\.svg\) url\(\/foo\/_next\/static\/media\/dark2\.[a-f0-9]{8}\.svg\)\}\.blue-text\{color:orange;font-weight:bolder;background-image:url\(\/foo\/_next\/static\/media\/light\.[a-f0-9]{8}\.svg\);color:blue\}$/ ) const mediaFiles = await readdir(mediaFolder) @@ -692,7 +692,7 @@ describe('SCSS Support', () => { expect( mediaFiles .map((fileName) => - /^(.+?)\..{32}\.(.+?)$/.exec(fileName).slice(1).join('.') + /^(.+?)\..{8}\.(.+?)$/.exec(fileName).slice(1).join('.') ) .sort() ).toMatchInlineSnapshot(` @@ -730,7 +730,7 @@ describe('SCSS Support', () => { expect(cssFiles.length).toBe(1) const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8') expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatch( - /^\.red-text\{color:red;background-image:url\(\/foo\/_next\/static\/media\/dark\.[a-z0-9]{32}\.svg\) url\(\/foo\/_next\/static\/media\/dark2\.[a-z0-9]{32}\.svg\)\}\.blue-text\{color:orange;font-weight:bolder;background-image:url\(\/foo\/_next\/static\/media\/light\.[a-z0-9]{32}\.svg\);color:blue\}$/ + /^\.red-text\{color:red;background-image:url\(\/foo\/_next\/static\/media\/dark\.[a-f0-9]{8}\.svg\) url\(\/foo\/_next\/static\/media\/dark2\.[a-f0-9]{8}\.svg\)\}\.blue-text\{color:orange;font-weight:bolder;background-image:url\(\/foo\/_next\/static\/media\/light\.[a-f0-9]{8}\.svg\);color:blue\}$/ ) const mediaFiles = await readdir(mediaFolder) @@ -738,7 +738,7 @@ describe('SCSS Support', () => { expect( mediaFiles .map((fileName) => - /^(.+?)\..{32}\.(.+?)$/.exec(fileName).slice(1).join('.') + /^(.+?)\..{8}\.(.+?)$/.exec(fileName).slice(1).join('.') ) .sort() ).toMatchInlineSnapshot(` @@ -751,6 +751,35 @@ describe('SCSS Support', () => { }) }) + describe('Data Urls', () => { + const appDir = join(fixturesDir, 'data-url') + + beforeAll(async () => { + await remove(join(appDir, '.next')) + }) + + it('should compile successfully', async () => { + const { code, stdout } = await nextBuild(appDir, [], { + stdout: true, + }) + expect(code).toBe(0) + expect(stdout).toMatch(/Compiled successfully/) + }) + + it(`should've emitted expected files`, async () => { + const cssFolder = join(appDir, '.next/static/css') + + const files = await readdir(cssFolder) + const cssFiles = files.filter((f) => /\.css$/.test(f)) + + expect(cssFiles.length).toBe(1) + const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8') + expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatch( + /^\.red-text\{color:red;background-image:url\("data:[^"]+"\)\}$/ + ) + }) + }) + describe('Good CSS Import from node_modules', () => { const appDir = join(fixturesDir, 'npm-import') diff --git a/test/integration/serverless/test/index.test.js b/test/integration/serverless/test/index.test.js index c2db7d1494694..60871c2e86022 100644 --- a/test/integration/serverless/test/index.test.js +++ b/test/integration/serverless/test/index.test.js @@ -26,8 +26,14 @@ let appPort let app describe('Serverless', () => { + let output + beforeAll(async () => { - await nextBuild(appDir) + const result = await nextBuild(appDir, undefined, { + stderr: true, + stdout: true, + }) + output = result.stdout + result.stderr appPort = await findPort() app = await nextStart(appDir, appPort, { onStderr: (msg) => { @@ -37,6 +43,12 @@ describe('Serverless', () => { }) afterAll(() => killApp(app)) + it('should show target config deprecation warning', () => { + expect(output).toContain( + 'The `target` config is deprecated and will be removed in a future version' + ) + }) + it('should render the page', async () => { const html = await renderViaHTTP(appPort, '/') expect(html).toMatch(/Hello World/) diff --git a/test/integration/styled-jsx-plugin/app/next.config.js b/test/integration/styled-jsx-plugin/app/next.config.js deleted file mode 100644 index d5db0200db49a..0000000000000 --- a/test/integration/styled-jsx-plugin/app/next.config.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - experimental: { - swcLoader: false, - }, -} diff --git a/test/integration/telemetry/test/index.test.js b/test/integration/telemetry/test/index.test.js index c0784d859e309..0a01e59be41ab 100644 --- a/test/integration/telemetry/test/index.test.js +++ b/test/integration/telemetry/test/index.test.js @@ -569,6 +569,12 @@ describe('Telemetry CLI', () => { }) const regex = /NEXT_BUILD_FEATURE_USAGE[\s\S]+?{([\s\S]+?)}/g regex.exec(stderr).pop() // optimizeCss + const swcLoader = regex.exec(stderr).pop() + expect(swcLoader).toContain(`"featureName": "swcLoader"`) + expect(swcLoader).toContain(`"invocationCount": 1`) + const swcMinify = regex.exec(stderr).pop() + expect(swcMinify).toContain(`"featureName": "swcMinify"`) + expect(swcMinify).toContain(`"invocationCount": 0`) const image = regex.exec(stderr).pop() expect(image).toContain(`"featureName": "next/image"`) expect(image).toContain(`"invocationCount": 1`) diff --git a/test/integration/tsconfig-verifier/test/index.test.js b/test/integration/tsconfig-verifier/test/index.test.js index e07806deddae9..799ca2403d294 100644 --- a/test/integration/tsconfig-verifier/test/index.test.js +++ b/test/integration/tsconfig-verifier/test/index.test.js @@ -315,4 +315,61 @@ describe('tsconfig.json verifier', () => { `"{ \\"extends\\": \\"./tsconfig.base.json\\" }"` ) }) + + it('creates compilerOptions when you extend another config', async () => { + expect(await exists(tsConfig)).toBe(false) + expect(await exists(tsConfigBase)).toBe(false) + + await writeFile( + tsConfigBase, + ` + { + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve" + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx" + ], + "exclude": [ + "node_modules" + ] + } + ` + ) + await new Promise((resolve) => setTimeout(resolve, 500)) + + await writeFile(tsConfig, `{ "extends": "./tsconfig.base.json" }`) + await new Promise((resolve) => setTimeout(resolve, 500)) + + const { code } = await nextBuild(appDir) + expect(code).toBe(0) + + expect(await readFile(tsConfig, 'utf8')).toMatchInlineSnapshot(` + "{ + \\"extends\\": \\"./tsconfig.base.json\\", + \\"compilerOptions\\": { + \\"incremental\\": true + } + } + " + `) + }) }) diff --git a/test/integration/typescript/components/angle-bracket-type-assertions.ts b/test/integration/typescript/components/angle-bracket-type-assertions.ts new file mode 100644 index 0000000000000..121d08f87eac8 --- /dev/null +++ b/test/integration/typescript/components/angle-bracket-type-assertions.ts @@ -0,0 +1,4 @@ +// eslint-disable-next-line +const myVar = 'test' + +export default myVar diff --git a/test/integration/typescript/components/generics.ts b/test/integration/typescript/components/generics.ts new file mode 100644 index 0000000000000..4d9adcd05449e --- /dev/null +++ b/test/integration/typescript/components/generics.ts @@ -0,0 +1,10 @@ +class MyClass { + value: T + constructor(value: T) { + this.value = value + } +} + +const instance = new MyClass('Hello World from Generic') + +export default instance.value diff --git a/test/integration/typescript/pages/angle-bracket-type-assertions.tsx b/test/integration/typescript/pages/angle-bracket-type-assertions.tsx new file mode 100644 index 0000000000000..1d3f9c66f0b50 --- /dev/null +++ b/test/integration/typescript/pages/angle-bracket-type-assertions.tsx @@ -0,0 +1,3 @@ +import value from '../components/angle-bracket-type-assertions' + +export default () =>
{value}
diff --git a/test/integration/typescript/pages/generics.tsx b/test/integration/typescript/pages/generics.tsx new file mode 100644 index 0000000000000..e098876b6daf5 --- /dev/null +++ b/test/integration/typescript/pages/generics.tsx @@ -0,0 +1,3 @@ +import value from '../components/generics' + +export default () =>
{value}
diff --git a/test/integration/typescript/test/index.test.js b/test/integration/typescript/test/index.test.js index bf007cd77d196..46abf9f64059f 100644 --- a/test/integration/typescript/test/index.test.js +++ b/test/integration/typescript/test/index.test.js @@ -49,6 +49,16 @@ describe('TypeScript Features', () => { expect($('#cookies').text()).toBe('{}') }) + it('should render the generics page', async () => { + const $ = await get$('/generics') + expect($('#value').text()).toBe('Hello World from Generic') + }) + + it('should render the angle bracket type assertions page', async () => { + const $ = await get$('/angle-bracket-type-assertions') + expect($('#value').text()).toBe('test') + }) + it('should resolve files in correct order', async () => { const $ = await get$('/hello') expect($('#imported-value').text()).toBe('OK') diff --git a/test/integration/url-imports/next.config.js b/test/integration/url-imports/next.config.js index 637e92836b89d..a76bc7b584f8f 100644 --- a/test/integration/url-imports/next.config.js +++ b/test/integration/url-imports/next.config.js @@ -1,5 +1,8 @@ module.exports = { experimental: { - urlImports: ['http://localhost:12345/'], + urlImports: [ + 'http://localhost:12345/', + 'https://github.com/vercel/next.js/raw/canary/', + ], }, } diff --git a/test/integration/url-imports/pages/css.js b/test/integration/url-imports/pages/css.js new file mode 100644 index 0000000000000..3861e26ce14b9 --- /dev/null +++ b/test/integration/url-imports/pages/css.js @@ -0,0 +1,3 @@ +import styles from './css.module.css' + +export default () =>
diff --git a/test/integration/url-imports/pages/css.module.css b/test/integration/url-imports/pages/css.module.css new file mode 100644 index 0000000000000..68792e4aa13bd --- /dev/null +++ b/test/integration/url-imports/pages/css.module.css @@ -0,0 +1,6 @@ +.main { + background: url('https://github.com/vercel/next.js/raw/canary/test/integration/url/public/vercel.png'); + background-size: contain; + width: 300px; + height: 300px; +} diff --git a/test/integration/url-imports/pages/image.js b/test/integration/url-imports/pages/image.js new file mode 100644 index 0000000000000..5e15ec18b2310 --- /dev/null +++ b/test/integration/url-imports/pages/image.js @@ -0,0 +1,8 @@ +import Image from 'next/image' +import logo from 'https://github.com/vercel/next.js/raw/canary/test/integration/production/public/vercel.png' + +export default () => ( +
+ +
+) diff --git a/test/integration/url-imports/pages/ssg.js b/test/integration/url-imports/pages/ssg.js index 87336dbd0bf74..c9847b62d058b 100644 --- a/test/integration/url-imports/pages/ssg.js +++ b/test/integration/url-imports/pages/ssg.js @@ -1,17 +1,23 @@ import value from 'http://localhost:12345/value1.js' +const url = new URL( + 'https://github.com/vercel/next.js/raw/canary/test/integration/production/public/vercel.png?_=ssg', + import.meta.url +) + export async function getStaticProps() { return { props: { value, + url: url.pathname, }, } } -export default function Index({ value: staticValue }) { +export default function Index({ value: staticValue, url: staticUrl }) { return (
- Hello {staticValue}+{value} + Hello {staticValue}+{value}+{staticUrl}+{url.pathname}
) } diff --git a/test/integration/url-imports/pages/ssr.js b/test/integration/url-imports/pages/ssr.js index ef59de9ef7c65..2daf1bcf682e0 100644 --- a/test/integration/url-imports/pages/ssr.js +++ b/test/integration/url-imports/pages/ssr.js @@ -1,17 +1,23 @@ import value from 'http://localhost:12345/value2.js' +const url = new URL( + 'https://github.com/vercel/next.js/raw/canary/test/integration/production/public/vercel.png?_=ssr', + import.meta.url +) + export function getServerSideProps() { return { props: { value, + url: url.pathname, }, } } -export default function Index({ value: serverValue }) { +export default function Index({ value: serverValue, url: serverUrl }) { return (
- Hello {serverValue}+{value} + Hello {serverValue}+{value}+{serverUrl}+{url.pathname}
) } diff --git a/test/integration/url-imports/pages/static.js b/test/integration/url-imports/pages/static.js index 59ed40c2f4f7d..9a61b6901f053 100644 --- a/test/integration/url-imports/pages/static.js +++ b/test/integration/url-imports/pages/static.js @@ -1,9 +1,14 @@ import value from 'http://localhost:12345/value3.js' +const url = new URL( + 'https://github.com/vercel/next.js/raw/canary/test/integration/production/public/vercel.png?_=static', + import.meta.url +) + export default function Index(props) { return (
- Hello {value}+{value} + Hello {value}+{value}+{url.pathname}+{url.pathname}
) } diff --git a/test/integration/url-imports/test/index.test.js b/test/integration/url-imports/test/index.test.js index 85da21240c0c1..1b59b0145eec2 100644 --- a/test/integration/url-imports/test/index.test.js +++ b/test/integration/url-imports/test/index.test.js @@ -55,7 +55,8 @@ describe(`Handle url imports`, () => { afterAll(async () => { await killApp(app) }) - const expectedServer = /Hello 42\+42/ + const expectedServer = + /Hello 42\+42\+\/_next\/static\/media\/vercel\.[0-9a-f]{8}\.png\+\/_next\/static\/media\/vercel\.[0-9a-f]{8}\.png/ const expectedClient = new RegExp( expectedServer.source.replace(//g, '') ) @@ -77,6 +78,37 @@ describe(`Handle url imports`, () => { }) } + it(`should render a static url image import`, async () => { + let browser + try { + browser = await webdriver(appPort, '/image') + await browser.waitForElementByCss('#static-image') + await check( + () => browser.elementByCss('#static-image').getAttribute('src'), + /^\/_next\/image\?url=%2F_next%2Fstatic%2Fmedia%2Fvercel\.[0-9a-f]{8}\.png&/ + ) + } finally { + await browser.close() + } + }) + + it(`should allow url import in css`, async () => { + let browser + try { + browser = await webdriver(appPort, '/css') + await browser.waitForElementByCss('#static-css') + await check( + () => + browser + .elementByCss('#static-css') + .getComputedCss('background-image'), + /^url\("http:\/\/localhost:\d+\/_next\/static\/media\/vercel\.[0-9a-f]{8}\.png"\)$/ + ) + } finally { + await browser.close() + } + }) + it('should respond on value api', async () => { const data = await fetchViaHTTP(appPort, '/api/value').then( (res) => res.ok && res.json() diff --git a/test/lib/browsers/playwright.ts b/test/lib/browsers/playwright.ts index e6bcb289ab550..b62f2deceaeef 100644 --- a/test/lib/browsers/playwright.ts +++ b/test/lib/browsers/playwright.ts @@ -1,4 +1,5 @@ import { BrowserInterface } from './base' +import fs from 'fs-extra' import { chromium, webkit, @@ -8,12 +9,15 @@ import { Page, ElementHandle, } from 'playwright-chromium' +import path from 'path' let page: Page let browser: Browser let context: BrowserContext let pageLogs: Array<{ source: string; message: string }> = [] +const tracePlaywright = process.env.TRACE_PLAYWRIGHT + export async function quit() { await context?.close() await browser?.close() @@ -22,11 +26,10 @@ export async function quit() { } class Playwright extends BrowserInterface { - private browserName: string + private activeTrace?: string async setup(browserName: string) { if (browser) return - this.browserName = browserName const headless = !!process.env.HEADLESS if (browserName === 'safari') { @@ -44,6 +47,24 @@ class Playwright extends BrowserInterface { } async loadPage(url: string) { + if (this.activeTrace) { + const traceDir = path.join(__dirname, '../../traces') + const traceOutputPath = path.join( + traceDir, + `${path + .relative(path.join(__dirname, '../../'), process.env.TEST_FILE_PATH) + .replace(/\//g, '-')}`, + `playwright-${this.activeTrace}-${Date.now()}.zip` + ) + + await fs.remove(traceOutputPath) + await context.tracing + .stop({ + path: traceOutputPath, + }) + .catch((err) => console.error('failed to write playwright trace', err)) + } + // clean-up existing pages for (const oldPage of context.pages()) { await oldPage.close() @@ -61,6 +82,34 @@ class Playwright extends BrowserInterface { page.on('pageerror', (error) => { console.error('page error', error) }) + + if (tracePlaywright) { + page.on('websocket', (ws) => { + page + .evaluate(`console.log('connected to ws at ${ws.url()}')`) + .catch(() => {}) + ws.on('close', () => + page + .evaluate(`console.log('closed websocket ${ws.url()}')`) + .catch(() => {}) + ) + ws.on('framereceived', (frame) => { + if (!frame.payload.includes('pong')) { + page + .evaluate(`console.log('received ws message ${frame.payload}')`) + .catch(() => {}) + } + }) + }) + } + + if (tracePlaywright) { + await context.tracing.start({ + screenshots: true, + snapshots: true, + }) + this.activeTrace = encodeURIComponent(url) + } await page.goto(url, { waitUntil: 'load' }) } diff --git a/test/lib/e2e-utils.ts b/test/lib/e2e-utils.ts index b5a887a22351f..736af74930d0d 100644 --- a/test/lib/e2e-utils.ts +++ b/test/lib/e2e-utils.ts @@ -8,6 +8,8 @@ import { NextStartInstance } from './next-modes/next-start' const testFile = module.parent.filename const testsFolder = path.join(__dirname, '..') +process.env.TEST_FILE_PATH = testFile + let testMode = process.env.NEXT_TEST_MODE if (!testFile.match(/\.test\.(js|tsx?)/)) { diff --git a/test/lib/next-modes/base.ts b/test/lib/next-modes/base.ts index 845d990ee27a5..cb1c1e9a0e389 100644 --- a/test/lib/next-modes/base.ts +++ b/test/lib/next-modes/base.ts @@ -160,6 +160,25 @@ export class NextInstance { this.emit('destroy', []) await this.stop() + if (process.env.TRACE_PLAYWRIGHT) { + await fs + .copy( + path.join(this.testDir, '.next/trace'), + path.join( + __dirname, + '../../traces', + `${path + .relative( + path.join(__dirname, '../../'), + process.env.TEST_FILE_PATH + ) + .replace(/\//g, '-')}`, + `next-trace` + ) + ) + .catch(() => {}) + } + if (!process.env.NEXT_TEST_SKIP_CLEANUP) { await fs.remove(this.testDir) } diff --git a/test/lib/next-test-utils.js b/test/lib/next-test-utils.js index bb4b8ebfd7fbc..99a5b66ce3826 100644 --- a/test/lib/next-test-utils.js +++ b/test/lib/next-test-utils.js @@ -10,6 +10,7 @@ import { import { writeFile } from 'fs-extra' import getPort from 'get-port' import http from 'http' +import https from 'https' import server from 'next/dist/server/next' import _pkg from 'next/package.json' import fetch from 'node-fetch' @@ -20,12 +21,6 @@ import treeKill from 'tree-kill' export const nextServer = server export const pkg = _pkg -// polyfill Object.fromEntries for the test/integration/relay-analytics tests -// on node 10, this can be removed after we no longer support node 10 -if (!Object.fromEntries) { - Object.fromEntries = require('core-js/features/object/from-entries') -} - export function initNextServerScript( scriptPath, successRegexp, @@ -108,7 +103,19 @@ export function fetchViaHTTP(appPort, pathname, query, opts) { const url = `${pathname}${ typeof query === 'string' ? query : query ? `?${qs.stringify(query)}` : '' }` - return fetch(getFullUrl(appPort, url), opts) + return fetch(getFullUrl(appPort, url), { + // in node.js v17 fetch favors IPv6 but Next.js is + // listening on IPv4 by default so force IPv4 DNS resolving + agent: (parsedUrl) => { + if (parsedUrl.protocol === 'https:') { + return new https.Agent({ family: 4 }) + } + if (parsedUrl.protocol === 'http:') { + return new http.Agent({ family: 4 }) + } + }, + ...opts, + }) } export function findPort() { @@ -125,6 +132,7 @@ export function runNextCommand(argv, options = {}) { ...options.env, NODE_ENV: '', __NEXT_TEST_MODE: 'true', + NEXT_PRIVATE_OUTPUT_TRACE_ROOT: path.join(__dirname, '../../'), } return new Promise((resolve, reject) => { @@ -144,26 +152,38 @@ export function runNextCommand(argv, options = {}) { options.instance(instance) } + let mergedStdio = '' + let stderrOutput = '' if (options.stderr) { instance.stderr.on('data', function (chunk) { + mergedStdio += chunk stderrOutput += chunk if (options.stderr === 'log') { console.log(chunk.toString()) } }) + } else { + instance.stderr.on('data', function (chunk) { + mergedStdio += chunk + }) } let stdoutOutput = '' if (options.stdout) { instance.stdout.on('data', function (chunk) { + mergedStdio += chunk stdoutOutput += chunk if (options.stdout === 'log') { console.log(chunk.toString()) } }) + } else { + instance.stdout.on('data', function (chunk) { + mergedStdio += chunk + }) } instance.on('close', (code, signal) => { @@ -173,7 +193,9 @@ export function runNextCommand(argv, options = {}) { !options.ignoreFail && code !== 0 ) { - return reject(new Error(`command failed with code ${code}`)) + return reject( + new Error(`command failed with code ${code}\n${mergedStdio}`) + ) } resolve({ @@ -325,10 +347,9 @@ export function buildTS(args = [], cwd, env = {}) { }) } -// Kill a launched app -export async function killApp(instance) { +export async function killProcess(pid) { await new Promise((resolve, reject) => { - treeKill(instance.pid, (err) => { + treeKill(pid, (err) => { if (err) { if ( process.platform === 'win32' && @@ -351,6 +372,11 @@ export async function killApp(instance) { }) } +// Kill a launched app +export async function killApp(instance) { + await killProcess(instance.pid) +} + export async function startApp(app) { await app.prepare() const handler = app.getRequestHandler() diff --git a/test/lib/next-webdriver.ts b/test/lib/next-webdriver.ts index 19c56d73a5ebd..73b056716de81 100644 --- a/test/lib/next-webdriver.ts +++ b/test/lib/next-webdriver.ts @@ -2,6 +2,10 @@ import { getFullUrl } from 'next-test-utils' import os from 'os' import { BrowserInterface } from './browsers/base' +if (!process.env.TEST_FILE_PATH) { + process.env.TEST_FILE_PATH = module.parent.filename +} + let deviceIP: string const isBrowserStack = !!process.env.BROWSERSTACK ;(global as any).browserName = process.env.BROWSER_NAME || 'chrome' diff --git a/test/production/required-server-files-i18n.test.ts b/test/production/required-server-files-i18n.test.ts new file mode 100644 index 0000000000000..6285ccfb69a75 --- /dev/null +++ b/test/production/required-server-files-i18n.test.ts @@ -0,0 +1,721 @@ +import glob from 'glob' +import fs from 'fs-extra' +import cheerio from 'cheerio' +import { join } from 'path' +import { createNext, FileRef } from 'e2e-utils' +import { NextInstance } from 'test/lib/next-modes/base' +import { + check, + fetchViaHTTP, + findPort, + initNextServerScript, + killApp, + renderViaHTTP, +} from 'next-test-utils' + +describe('should set-up next', () => { + let next: NextInstance + let server + let appPort + let errors = [] + let requiredFilesManifest + + beforeAll(async () => { + next = await createNext({ + files: { + pages: new FileRef(join(__dirname, 'required-server-files/pages')), + lib: new FileRef(join(__dirname, 'required-server-files/lib')), + 'data.txt': new FileRef( + join(__dirname, 'required-server-files/data.txt') + ), + }, + nextConfig: { + i18n: { + locales: ['en', 'fr'], + defaultLocale: 'en', + }, + eslint: { + ignoreDuringBuilds: true, + }, + experimental: { + outputStandalone: true, + }, + async rewrites() { + return [ + { + source: '/some-catch-all/:path*', + destination: '/', + }, + { + source: '/to-dynamic/:path', + destination: '/dynamic/:path', + }, + ] + }, + }, + }) + await next.stop() + + requiredFilesManifest = JSON.parse( + await next.readFile('.next/required-server-files.json') + ) + await fs.move( + join(next.testDir, '.next/standalone'), + join(next.testDir, 'standalone') + ) + for (const file of await fs.readdir(next.testDir)) { + if (file !== 'standalone') { + await fs.remove(join(next.testDir, file)) + console.log('removed', file) + } + } + const files = glob.sync('**/*', { + cwd: join(next.testDir, 'standalone/.next/server/pages'), + dot: true, + }) + + console.error({ files }) + + for (const file of files) { + if (file.endsWith('.json') || file.endsWith('.html')) { + await fs.remove(join(next.testDir, '.next/server', file)) + } + } + + const testServer = join(next.testDir, 'standalone/server.js') + await fs.writeFile( + testServer, + (await fs.readFile(testServer, 'utf8')) + .replace('console.error(err)', `console.error('top-level', err)`) + .replace('conf:', 'minimalMode: true,conf:') + ) + appPort = await findPort() + server = await initNextServerScript( + testServer, + /Listening on/, + { + ...process.env, + PORT: appPort, + }, + undefined, + { + cwd: next.testDir, + onStderr(msg) { + if (msg.includes('top-level')) { + errors.push(msg) + } + }, + } + ) + }) + afterAll(async () => { + await next.destroy() + if (server) await killApp(server) + }) + + it('should output required-server-files manifest correctly', async () => { + expect(requiredFilesManifest.version).toBe(1) + expect(Array.isArray(requiredFilesManifest.files)).toBe(true) + expect(Array.isArray(requiredFilesManifest.ignore)).toBe(true) + expect(requiredFilesManifest.files.length).toBeGreaterThan(0) + expect(requiredFilesManifest.ignore.length).toBeGreaterThan(0) + expect(typeof requiredFilesManifest.config.configFile).toBe('undefined') + expect(typeof requiredFilesManifest.config.trailingSlash).toBe('boolean') + expect(typeof requiredFilesManifest.appDir).toBe('string') + }) + + it('should set correct SWR headers with notFound gsp', async () => { + await next.patchFile('standalone/data.txt', 'show') + + const res = await fetchViaHTTP(appPort, '/gsp', undefined, { + redirect: 'manual ', + }) + expect(res.status).toBe(200) + expect(res.headers.get('cache-control')).toBe( + 's-maxage=1, stale-while-revalidate' + ) + + await next.patchFile('standalone/data.txt', 'hide') + + const res2 = await fetchViaHTTP(appPort, '/gsp', undefined, { + redirect: 'manual ', + }) + expect(res2.status).toBe(404) + expect(res2.headers.get('cache-control')).toBe( + 's-maxage=1, stale-while-revalidate' + ) + }) + + it('should set correct SWR headers with notFound gssp', async () => { + await next.patchFile('standalone/data.txt', 'show') + + const res = await fetchViaHTTP(appPort, '/gssp', undefined, { + redirect: 'manual ', + }) + expect(res.status).toBe(200) + expect(res.headers.get('cache-control')).toBe( + 's-maxage=1, stale-while-revalidate' + ) + + await next.patchFile('standalone/data.txt', 'hide') + + const res2 = await fetchViaHTTP(appPort, '/gssp', undefined, { + redirect: 'manual ', + }) + expect(res2.status).toBe(404) + expect(res2.headers.get('cache-control')).toBe( + 's-maxage=1, stale-while-revalidate' + ) + }) + + it('should render SSR page correctly', async () => { + const html = await renderViaHTTP(appPort, '/') + const $ = cheerio.load(html) + const data = JSON.parse($('#props').text()) + + expect($('#index').text()).toBe('index page') + expect(data.hello).toBe('world') + + const html2 = await renderViaHTTP(appPort, '/') + const $2 = cheerio.load(html2) + const data2 = JSON.parse($2('#props').text()) + + expect($2('#index').text()).toBe('index page') + expect(isNaN(data2.random)).toBe(false) + expect(data2.random).not.toBe(data.random) + }) + + it('should render dynamic SSR page correctly', async () => { + const html = await renderViaHTTP(appPort, '/dynamic/first') + const $ = cheerio.load(html) + const data = JSON.parse($('#props').text()) + + expect($('#dynamic').text()).toBe('dynamic page') + expect($('#slug').text()).toBe('first') + expect(data.hello).toBe('world') + + const html2 = await renderViaHTTP(appPort, '/dynamic/second') + const $2 = cheerio.load(html2) + const data2 = JSON.parse($2('#props').text()) + + expect($2('#dynamic').text()).toBe('dynamic page') + expect($2('#slug').text()).toBe('second') + expect(isNaN(data2.random)).toBe(false) + expect(data2.random).not.toBe(data.random) + }) + + it('should render fallback page correctly', async () => { + const html = await renderViaHTTP(appPort, '/fallback/first') + const $ = cheerio.load(html) + const data = JSON.parse($('#props').text()) + + expect($('#fallback').text()).toBe('fallback page') + expect($('#slug').text()).toBe('first') + expect(data.hello).toBe('world') + + const html2 = await renderViaHTTP(appPort, '/fallback/first') + const $2 = cheerio.load(html2) + const data2 = JSON.parse($2('#props').text()) + + expect($2('#fallback').text()).toBe('fallback page') + expect($2('#slug').text()).toBe('first') + expect(isNaN(data2.random)).toBe(false) + expect(data2.random).not.toBe(data.random) + + const html3 = await renderViaHTTP(appPort, '/fallback/second') + const $3 = cheerio.load(html3) + const data3 = JSON.parse($3('#props').text()) + + expect($3('#fallback').text()).toBe('fallback page') + expect($3('#slug').text()).toBe('second') + expect(isNaN(data3.random)).toBe(false) + + const { pageProps: data4 } = JSON.parse( + await renderViaHTTP( + appPort, + `/_next/data/${next.buildId}/en/fallback/third.json` + ) + ) + expect(data4.hello).toBe('world') + expect(data4.slug).toBe('third') + }) + + it('should render SSR page correctly with x-matched-path', async () => { + const html = await renderViaHTTP(appPort, '/some-other-path', undefined, { + headers: { + 'x-matched-path': '/', + }, + }) + const $ = cheerio.load(html) + const data = JSON.parse($('#props').text()) + + expect($('#index').text()).toBe('index page') + expect(data.hello).toBe('world') + + const html2 = await renderViaHTTP(appPort, '/some-other-path', undefined, { + headers: { + 'x-matched-path': '/', + }, + }) + const $2 = cheerio.load(html2) + const data2 = JSON.parse($2('#props').text()) + + expect($2('#index').text()).toBe('index page') + expect(isNaN(data2.random)).toBe(false) + expect(data2.random).not.toBe(data.random) + }) + + it('should render dynamic SSR page correctly with x-matched-path', async () => { + const html = await renderViaHTTP(appPort, '/some-other-path', undefined, { + headers: { + 'x-matched-path': '/dynamic/[slug]?slug=first', + }, + }) + const $ = cheerio.load(html) + const data = JSON.parse($('#props').text()) + + expect($('#dynamic').text()).toBe('dynamic page') + expect($('#slug').text()).toBe('first') + expect(data.hello).toBe('world') + + const html2 = await renderViaHTTP(appPort, '/some-other-path', undefined, { + headers: { + 'x-matched-path': '/dynamic/[slug]?slug=second', + }, + }) + const $2 = cheerio.load(html2) + const data2 = JSON.parse($2('#props').text()) + + expect($2('#dynamic').text()).toBe('dynamic page') + expect($2('#slug').text()).toBe('second') + expect(isNaN(data2.random)).toBe(false) + expect(data2.random).not.toBe(data.random) + + const html3 = await renderViaHTTP(appPort, '/some-other-path', undefined, { + headers: { + 'x-matched-path': '/dynamic/[slug]?slug=%5Bslug%5D.json', + 'x-now-route-matches': '1=second&slug=second', + }, + }) + const $3 = cheerio.load(html3) + const data3 = JSON.parse($3('#props').text()) + + expect($3('#dynamic').text()).toBe('dynamic page') + expect($3('#slug').text()).toBe('second') + expect(isNaN(data3.random)).toBe(false) + expect(data3.random).not.toBe(data.random) + }) + + it('should render fallback page correctly with x-matched-path and routes-matches', async () => { + const html = await renderViaHTTP(appPort, '/fallback/first', undefined, { + headers: { + 'x-matched-path': '/fallback/first', + 'x-now-route-matches': '1=first', + }, + }) + const $ = cheerio.load(html) + const data = JSON.parse($('#props').text()) + + expect($('#fallback').text()).toBe('fallback page') + expect($('#slug').text()).toBe('first') + expect(data.hello).toBe('world') + + const html2 = await renderViaHTTP(appPort, `/fallback/[slug]`, undefined, { + headers: { + 'x-matched-path': '/fallback/[slug]', + 'x-now-route-matches': '1=second', + }, + }) + const $2 = cheerio.load(html2) + const data2 = JSON.parse($2('#props').text()) + + expect($2('#fallback').text()).toBe('fallback page') + expect($2('#slug').text()).toBe('second') + expect(isNaN(data2.random)).toBe(false) + expect(data2.random).not.toBe(data.random) + }) + + it('should return data correctly with x-matched-path', async () => { + const res = await fetchViaHTTP( + appPort, + `/_next/data/${next.buildId}/en/dynamic/first.json`, + undefined, + { + headers: { + 'x-matched-path': '/dynamic/[slug]?slug=first', + }, + } + ) + + const { pageProps: data } = await res.json() + + expect(data.slug).toBe('first') + expect(data.hello).toBe('world') + + const res2 = await fetchViaHTTP( + appPort, + `/_next/data/${next.buildId}/en/fallback/[slug].json`, + undefined, + { + headers: { + 'x-matched-path': `/_next/data/${next.buildId}/en/fallback/[slug].json`, + 'x-now-route-matches': '1=second', + }, + } + ) + + const { pageProps: data2 } = await res2.json() + + expect(data2.slug).toBe('second') + expect(data2.hello).toBe('world') + }) + + it('should render fallback optional catch-all route correctly with x-matched-path and routes-matches', async () => { + const html = await renderViaHTTP( + appPort, + '/catch-all/[[...rest]]', + undefined, + { + headers: { + 'x-matched-path': '/catch-all/[[...rest]]', + 'x-now-route-matches': '', + }, + } + ) + const $ = cheerio.load(html) + const data = JSON.parse($('#props').text()) + + expect($('#catch-all').text()).toBe('optional catch-all page') + expect(data.params).toEqual({}) + expect(data.hello).toBe('world') + + const html2 = await renderViaHTTP( + appPort, + '/catch-all/[[...rest]]', + undefined, + { + headers: { + 'x-matched-path': '/catch-all/[[...rest]]', + 'x-now-route-matches': '1=hello&catchAll=hello', + }, + } + ) + const $2 = cheerio.load(html2) + const data2 = JSON.parse($2('#props').text()) + + expect($2('#catch-all').text()).toBe('optional catch-all page') + expect(data2.params).toEqual({ rest: ['hello'] }) + expect(isNaN(data2.random)).toBe(false) + expect(data2.random).not.toBe(data.random) + + const html3 = await renderViaHTTP( + appPort, + '/catch-all/[[..rest]]', + undefined, + { + headers: { + 'x-matched-path': '/catch-all/[[...rest]]', + 'x-now-route-matches': '1=hello/world&catchAll=hello/world', + }, + } + ) + const $3 = cheerio.load(html3) + const data3 = JSON.parse($3('#props').text()) + + expect($3('#catch-all').text()).toBe('optional catch-all page') + expect(data3.params).toEqual({ rest: ['hello', 'world'] }) + expect(isNaN(data3.random)).toBe(false) + expect(data3.random).not.toBe(data.random) + }) + + it('should return data correctly with x-matched-path for optional catch-all route', async () => { + const res = await fetchViaHTTP( + appPort, + `/_next/data/${next.buildId}/en/catch-all.json`, + undefined, + { + headers: { + 'x-matched-path': '/en/catch-all/[[...rest]]', + }, + } + ) + + const { pageProps: data } = await res.json() + + expect(data.params).toEqual({}) + expect(data.hello).toBe('world') + + const res2 = await fetchViaHTTP( + appPort, + `/_next/data/${next.buildId}/en/catch-all/[[...rest]].json`, + undefined, + { + headers: { + 'x-matched-path': `/_next/data/${next.buildId}/en/catch-all/[[...rest]].json`, + 'x-now-route-matches': '1=hello&rest=hello', + }, + } + ) + + const { pageProps: data2 } = await res2.json() + + expect(data2.params).toEqual({ rest: ['hello'] }) + expect(data2.hello).toBe('world') + + const res3 = await fetchViaHTTP( + appPort, + `/_next/data/${next.buildId}/en/catch-all/[[...rest]].json`, + undefined, + { + headers: { + 'x-matched-path': `/_next/data/${next.buildId}/en/catch-all/[[...rest]].json`, + 'x-now-route-matches': '1=hello/world&rest=hello/world', + }, + } + ) + + const { pageProps: data3 } = await res3.json() + + expect(data3.params).toEqual({ rest: ['hello', 'world'] }) + expect(data3.hello).toBe('world') + }) + + it('should not apply trailingSlash redirect', async () => { + for (const path of [ + '/', + '/dynamic/another/', + '/dynamic/another', + '/fallback/first/', + '/fallback/first', + '/fallback/another/', + '/fallback/another', + ]) { + const res = await fetchViaHTTP(appPort, path, undefined, { + redirect: 'manual', + }) + + expect(res.status).toBe(200) + } + }) + + it('should normalize catch-all rewrite query values correctly', async () => { + const html = await renderViaHTTP( + appPort, + '/some-catch-all/hello/world', + { + path: 'hello/world', + }, + { + headers: { + 'x-matched-path': '/', + }, + } + ) + const $ = cheerio.load(html) + expect(JSON.parse($('#router').text()).query.path).toEqual([ + 'hello', + 'world', + ]) + }) + + it('should handle bad request correctly with rewrite', async () => { + const res = await fetchViaHTTP( + appPort, + '/to-dynamic/%c0.%c0.', + '?path=%c0.%c0.', + { + headers: { + 'x-matched-path': '/dynamic/[slug]', + }, + } + ) + expect(res.status).toBe(400) + expect(await res.text()).toContain('Bad Request') + }) + + it('should bubble error correctly for gip page', async () => { + errors = [] + const res = await fetchViaHTTP(appPort, '/errors/gip', { crash: '1' }) + expect(res.status).toBe(500) + expect(await res.text()).toBe('internal server error') + + await check( + () => (errors[0].includes('gip hit an oops') ? 'success' : errors[0]), + 'success' + ) + }) + + it('should bubble error correctly for gssp page', async () => { + errors = [] + const res = await fetchViaHTTP(appPort, '/errors/gssp', { crash: '1' }) + expect(res.status).toBe(500) + expect(await res.text()).toBe('internal server error') + await check( + () => (errors[0].includes('gssp hit an oops') ? 'success' : errors[0]), + 'success' + ) + }) + + it('should bubble error correctly for gsp page', async () => { + errors = [] + const res = await fetchViaHTTP(appPort, '/errors/gsp/crash') + expect(res.status).toBe(500) + expect(await res.text()).toBe('internal server error') + await check( + () => (errors[0].includes('gsp hit an oops') ? 'success' : errors[0]), + 'success' + ) + }) + + it('should bubble error correctly for API page', async () => { + errors = [] + const res = await fetchViaHTTP(appPort, '/api/error') + expect(res.status).toBe(500) + expect(await res.text()).toBe('internal server error') + await check( + () => + errors[0].includes('some error from /api/error') + ? 'success' + : errors[0], + 'success' + ) + }) + + it('should normalize optional values correctly for SSP page', async () => { + const res = await fetchViaHTTP( + appPort, + '/optional-ssp', + { rest: '', another: 'value' }, + { + headers: { + 'x-matched-path': '/optional-ssp/[[...rest]]', + }, + } + ) + + const html = await res.text() + const $ = cheerio.load(html) + const props = JSON.parse($('#props').text()) + expect(props.params).toEqual({}) + expect(props.query).toEqual({ another: 'value' }) + }) + + it('should normalize optional values correctly for SSG page', async () => { + const res = await fetchViaHTTP( + appPort, + '/optional-ssg', + { rest: '', another: 'value' }, + { + headers: { + 'x-matched-path': '/optional-ssg/[[...rest]]', + }, + } + ) + + const html = await res.text() + const $ = cheerio.load(html) + const props = JSON.parse($('#props').text()) + expect(props.params).toEqual({}) + }) + + it('should normalize optional values correctly for SSG page with encoded slash', async () => { + const res = await fetchViaHTTP( + appPort, + '/optional-ssg/[[...rest]]', + undefined, + { + headers: { + 'x-matched-path': '/optional-ssg/[[...rest]]', + 'x-now-route-matches': + '1=en%2Fes%2Fhello%252Fworld&rest=en%2Fes%2Fhello%252Fworld', + }, + } + ) + + const html = await res.text() + const $ = cheerio.load(html) + const props = JSON.parse($('#props').text()) + expect(props.params).toEqual({ + rest: ['en', 'es', 'hello/world'], + }) + }) + + it('should normalize optional values correctly for API page', async () => { + const res = await fetchViaHTTP( + appPort, + '/api/optional', + { rest: '', another: 'value' }, + { + headers: { + 'x-matched-path': '/api/optional/[[...rest]]', + }, + } + ) + + const json = await res.json() + expect(json.query).toEqual({ another: 'value' }) + expect(json.url).toBe('/api/optional?another=value') + }) + + it('should match the index page correctly', async () => { + const res = await fetchViaHTTP(appPort, '/', undefined, { + headers: { + 'x-matched-path': '/index', + }, + redirect: 'manual', + }) + + const html = await res.text() + const $ = cheerio.load(html) + expect($('#index').text()).toBe('index page') + }) + + it('should match the root dyanmic page correctly', async () => { + const res = await fetchViaHTTP(appPort, '/index', undefined, { + headers: { + 'x-matched-path': '/[slug]', + }, + redirect: 'manual', + }) + + const html = await res.text() + const $ = cheerio.load(html) + expect($('#slug-page').text()).toBe('[slug] page') + }) + + it('should have the correct asPath for fallback page', async () => { + const res = await fetchViaHTTP(appPort, '/en/fallback/[slug]', undefined, { + headers: { + 'x-matched-path': '/en/fallback/[slug]', + 'x-now-route-matches': '2=another&slug=another&1=en&nextLocale=en', + }, + redirect: 'manual', + }) + + const html = await res.text() + const $ = cheerio.load(html) + expect($('#fallback').text()).toBe('fallback page') + expect($('#slug').text()).toBe('another') + expect(JSON.parse($('#router').text()).asPath).toBe('/fallback/another') + expect(JSON.parse($('#router').text()).query.slug).toBe('another') + expect(JSON.parse($('#router').text()).locale).toBe('en') + }) + + it('should have the correct asPath for fallback page', async () => { + const res = await fetchViaHTTP(appPort, '/fr/fallback/[slug]', undefined, { + headers: { + 'x-matched-path': '/fr/fallback/[slug]', + 'x-now-route-matches': '2=another&slug=another&1=fr&nextLocale=fr', + }, + redirect: 'manual', + }) + + const html = await res.text() + const $ = cheerio.load(html) + expect($('#fallback').text()).toBe('fallback page') + expect($('#slug').text()).toBe('another') + expect(JSON.parse($('#router').text()).asPath).toBe('/fallback/another') + expect(JSON.parse($('#router').text()).query.slug).toBe('another') + expect(JSON.parse($('#router').text()).locale).toBe('fr') + }) +}) diff --git a/test/production/required-server-files.test.ts b/test/production/required-server-files.test.ts index 3dc8e6287d410..f7b9fafc25e22 100644 --- a/test/production/required-server-files.test.ts +++ b/test/production/required-server-files.test.ts @@ -1,7 +1,7 @@ import glob from 'glob' -import _fs from 'fs-extra' +import fs from 'fs-extra' import cheerio from 'cheerio' -import { join, dirname } from 'path' +import { join } from 'path' import { createNext, FileRef } from 'e2e-utils' import { NextInstance } from 'test/lib/next-modes/base' import { @@ -30,111 +30,68 @@ describe('should set-up next', () => { ), }, nextConfig: { - experimental: { - outputFileTracing: true, - }, eslint: { ignoreDuringBuilds: true, }, + experimental: { + outputStandalone: true, + }, async rewrites() { return [ { source: '/some-catch-all/:path*', destination: '/', }, + { + source: '/to-dynamic/:path', + destination: '/dynamic/:path', + }, ] }, }, }) await next.stop() - const keptFiles = new Set() - const nextServerTrace = require(join( - next.testDir, - '.next/next-server.js.nft.json' - )) requiredFilesManifest = JSON.parse( await next.readFile('.next/required-server-files.json') ) - requiredFilesManifest.files.forEach((file) => keptFiles.add(file)) - - const pageTraceFiles = glob.sync('**/*.nft.json', { - cwd: join(next.testDir, '.next/server/pages'), - }) - for (const traceFile of pageTraceFiles) { - const pageDir = dirname(join('.next/server/pages', traceFile)) - const trace = await _fs.readJSON( - join(next.testDir, '.next/server/pages', traceFile) - ) - keptFiles.add( - join('.next/server/pages', traceFile.replace('.nft.json', '')) - ) - - for (const file of trace.files) { - keptFiles.add(join(pageDir, file)) + await fs.move( + join(next.testDir, '.next/standalone'), + join(next.testDir, 'standalone') + ) + for (const file of await fs.readdir(next.testDir)) { + if (file !== 'standalone') { + await fs.remove(join(next.testDir, file)) + console.log('removed', file) } } - - const allFiles = glob.sync('**/*', { - cwd: next.testDir, + const files = glob.sync('**/*', { + cwd: join(next.testDir, 'standalone/.next/server/pages'), dot: true, }) - const nextServerTraceFiles = nextServerTrace.files.map((file) => { - return join(next.testDir, '.next', file) - }) + console.error({ files }) - for (const file of allFiles) { - const filePath = join(next.testDir, file) - if ( - !keptFiles.has(file) && - !(await _fs.stat(filePath).catch(() => null))?.isDirectory() && - !nextServerTraceFiles.includes(filePath) && - !file.match(/node_modules\/(react|react-dom)\//) && - file !== 'node_modules/next/dist/server/next-server.js' - ) { - await _fs.remove(filePath) + for (const file of files) { + if (file.endsWith('.json') || file.endsWith('.html')) { + await fs.remove(join(next.testDir, '.next/server', file)) } } - appPort = await findPort() - const testServer = join(next.testDir, 'server.js') - await _fs.writeFile( + const testServer = join(next.testDir, 'standalone/server.js') + await fs.writeFile( testServer, - ` - const http = require('http') - const NextServer = require('next/dist/server/next-server').default - const appPort = ${appPort} - - const nextApp = new NextServer({ - conf: ${JSON.stringify(requiredFilesManifest.config)}, - dir: "${next.testDir}", - quiet: false, - minimalMode: true, - }) - - server = http.createServer(async (req, res) => { - try { - await nextApp.getRequestHandler()(req, res) - } catch (err) { - console.error('top-level', err) - res.statusCode = 500 - res.end('error') - } - }) - server.listen(appPort, (err) => { - if (err) throw err - console.log(\`Listening at ::${appPort}\`) - }) - ` + (await fs.readFile(testServer, 'utf8')) + .replace('console.error(err)', `console.error('top-level', err)`) + .replace('conf:', 'minimalMode: true,conf:') ) - + appPort = await findPort() server = await initNextServerScript( testServer, - /Listening at/, + /Listening on/, { ...process.env, - NODE_ENV: 'production', + PORT: appPort, }, undefined, { @@ -164,7 +121,7 @@ describe('should set-up next', () => { }) it('should set correct SWR headers with notFound gsp', async () => { - await next.patchFile('data.txt', 'show') + await next.patchFile('standalone/data.txt', 'show') const res = await fetchViaHTTP(appPort, '/gsp', undefined, { redirect: 'manual ', @@ -174,7 +131,7 @@ describe('should set-up next', () => { 's-maxage=1, stale-while-revalidate' ) - await next.patchFile('data.txt', 'hide') + await next.patchFile('standalone/data.txt', 'hide') const res2 = await fetchViaHTTP(appPort, '/gsp', undefined, { redirect: 'manual ', @@ -186,7 +143,7 @@ describe('should set-up next', () => { }) it('should set correct SWR headers with notFound gssp', async () => { - await next.patchFile('data.txt', 'show') + await next.patchFile('standalone/data.txt', 'show') const res = await fetchViaHTTP(appPort, '/gssp', undefined, { redirect: 'manual ', @@ -196,7 +153,7 @@ describe('should set-up next', () => { 's-maxage=1, stale-while-revalidate' ) - await next.patchFile('data.txt', 'hide') + await next.patchFile('standalone/data.txt', 'hide') const res2 = await fetchViaHTTP(appPort, '/gssp', undefined, { redirect: 'manual ', @@ -556,11 +513,26 @@ describe('should set-up next', () => { }) }) + it('should handle bad request correctly with rewrite', async () => { + const res = await fetchViaHTTP( + appPort, + '/to-dynamic/%c0.%c0.', + '?path=%c0.%c0.', + { + headers: { + 'x-matched-path': '/dynamic/[slug]', + }, + } + ) + expect(res.status).toBe(400) + expect(await res.text()).toContain('Bad Request') + }) + it('should bubble error correctly for gip page', async () => { errors = [] const res = await fetchViaHTTP(appPort, '/errors/gip', { crash: '1' }) expect(res.status).toBe(500) - expect(await res.text()).toBe('error') + expect(await res.text()).toBe('internal server error') await check( () => (errors[0].includes('gip hit an oops') ? 'success' : errors[0]), @@ -572,7 +544,7 @@ describe('should set-up next', () => { errors = [] const res = await fetchViaHTTP(appPort, '/errors/gssp', { crash: '1' }) expect(res.status).toBe(500) - expect(await res.text()).toBe('error') + expect(await res.text()).toBe('internal server error') await check( () => (errors[0].includes('gssp hit an oops') ? 'success' : errors[0]), 'success' @@ -583,7 +555,7 @@ describe('should set-up next', () => { errors = [] const res = await fetchViaHTTP(appPort, '/errors/gsp/crash') expect(res.status).toBe(500) - expect(await res.text()).toBe('error') + expect(await res.text()).toBe('internal server error') await check( () => (errors[0].includes('gsp hit an oops') ? 'success' : errors[0]), 'success' @@ -594,7 +566,7 @@ describe('should set-up next', () => { errors = [] const res = await fetchViaHTTP(appPort, '/api/error') expect(res.status).toBe(500) - expect(await res.text()).toBe('error') + expect(await res.text()).toBe('internal server error') await check( () => errors[0].includes('some error from /api/error') diff --git a/test/production/typescript-basic.test.ts b/test/production/typescript-basic.test.ts new file mode 100644 index 0000000000000..9360b9444c7ac --- /dev/null +++ b/test/production/typescript-basic.test.ts @@ -0,0 +1,40 @@ +import { createNext } from 'e2e-utils' +import { renderViaHTTP } from 'next-test-utils' +import { NextInstance } from 'test/lib/next-modes/base' + +describe('TypeScript basic', () => { + let next: NextInstance + + beforeAll(async () => { + next = await createNext({ + files: { + 'pages/index.tsx': ` + import { useRouter } from 'next/router' + import Link from 'next/link' + + export default function Page() { + const router = useRouter() + return ( + <> +

hello world

+ + to /another + + + ) + } + `, + }, + dependencies: { + typescript: '4.4.3', + '@types/react': '16.9.17', + }, + }) + }) + afterAll(() => next.destroy()) + + it('have built and started correctly', async () => { + const html = await renderViaHTTP(next.url, '/') + expect(html).toContain('hello world') + }) +}) diff --git a/test/unit/is-equal-node.unit.test.ts b/test/unit/is-equal-node.unit.test.ts new file mode 100644 index 0000000000000..557ba23368a40 --- /dev/null +++ b/test/unit/is-equal-node.unit.test.ts @@ -0,0 +1,48 @@ +/** + * @jest-environment jsdom + */ +/* eslint-env jest */ +import { isEqualNode } from 'next/dist/client/head-manager' + +const createScriptElement = (attrs = {}) => { + const el = document.createElement('script') + for (const k in attrs) el.setAttribute(k, attrs[k]) + return el +} + +describe('isEqualNode', () => { + it('should equal itself', () => { + const el = createScriptElement() + expect(isEqualNode(el, el)).toBe(true) + }) + + it('should equal equivalent node that has no nonce', () => { + const el1 = createScriptElement() + const el2 = createScriptElement() + expect(isEqualNode(el1, el2)).toBe(true) + }) + + it('should equal equivalent node that has same nonce property, even if the original node has no html nonce attribute value', () => { + const el1 = createScriptElement({ nonce: 'abc123' }) + // Simulate Chrome/FF browser behavior of stripping off nonce value when adding element to the document + el1.setAttribute('nonce', '') + el1.nonce = 'abc123' + const el2 = createScriptElement({ nonce: 'abc123' }) + expect(isEqualNode(el1, el2)).toBe(true) + }) + + it('should not equal node with different nonce value', () => { + const el1 = createScriptElement({ nonce: 'abc123' }) + // Simulate Chrome/FF browser behavior of stripping off nonce value when adding element to the document + el1.setAttribute('nonce', '') + el1.nonce = 'abc123' + const el2 = createScriptElement({ nonce: 'xyz' }) + expect(isEqualNode(el1, el2)).toBe(false) + }) + + it('should not equal node with different html attribute value', () => { + const el1 = createScriptElement({ src: '1.js' }) + const el2 = createScriptElement({ src: '2.js' }) + expect(isEqualNode(el1, el2)).toBe(false) + }) +}) diff --git a/test/unit/next-babel-loader-dev.test.ts b/test/unit/next-babel-loader-dev.test.ts index c4b3af08fc729..90dffe8fdf6d1 100644 --- a/test/unit/next-babel-loader-dev.test.ts +++ b/test/unit/next-babel-loader-dev.test.ts @@ -10,6 +10,18 @@ const babel = async (code: string, queryOpts = {} as any) => { const { isServer = false, resourcePath = `index.js` } = queryOpts let isAsync = false + + const options = { + // loader opts + cwd: dir, + isServer, + distDir: path.resolve(dir, '.next'), + pagesDir: + 'pagesDir' in queryOpts ? queryOpts.pagesDir : path.resolve(dir, 'pages'), + cache: false, + development: true, + hasReactRefresh: !isServer, + } return new Promise((resolve, reject) => { function callback(err, content) { if (err) { @@ -27,18 +39,10 @@ const babel = async (code: string, queryOpts = {} as any) => { }, callback, emitWarning() {}, - query: { - // loader opts - cwd: dir, - isServer, - distDir: path.resolve(dir, '.next'), - pagesDir: - 'pagesDir' in queryOpts - ? queryOpts.pagesDir - : path.resolve(dir, 'pages'), - cache: false, - development: true, - hasReactRefresh: !isServer, + query: options, + // @ts-ignore exists + getOptions: function () { + return options }, currentTraceSpan: new Span({ name: 'test' }), })(code, null) diff --git a/test/unit/next-babel-loader-prod.test.ts b/test/unit/next-babel-loader-prod.test.ts index 4392784743007..4551d03789704 100644 --- a/test/unit/next-babel-loader-prod.test.ts +++ b/test/unit/next-babel-loader-prod.test.ts @@ -19,6 +19,19 @@ const babel = async (code: string, queryOpts = {} as any) => { } } + const options = { + // loader opts + cwd: dir, + isServer, + distDir: path.resolve(dir, '.next'), + pagesDir: + 'pagesDir' in queryOpts + ? queryOpts.pagesDir + : path.resolve(dir, 'pages'), + cache: false, + hasReactRefresh: false, + } + const res = loader.bind({ resourcePath, async() { @@ -27,17 +40,10 @@ const babel = async (code: string, queryOpts = {} as any) => { }, callback, emitWarning() {}, - query: { - // loader opts - cwd: dir, - isServer, - distDir: path.resolve(dir, '.next'), - pagesDir: - 'pagesDir' in queryOpts - ? queryOpts.pagesDir - : path.resolve(dir, 'pages'), - cache: false, - hasReactRefresh: false, + query: options, + // @ts-ignore exists + getOptions: function () { + return options }, currentTraceSpan: new Span({ name: 'test' }), })(code, null) diff --git a/test/unit/recursive-delete.test.ts b/test/unit/recursive-delete.test.ts index a2c4f7f43da9b..e2b8c293f1019 100644 --- a/test/unit/recursive-delete.test.ts +++ b/test/unit/recursive-delete.test.ts @@ -1,4 +1,5 @@ /* eslint-env jest */ +import fs from 'fs-extra' import { recursiveDelete } from 'next/dist/lib/recursive-delete' import { recursiveReadDir } from 'next/dist/lib/recursive-readdir' import { recursiveCopy } from 'next/dist/lib/recursive-copy' @@ -13,6 +14,7 @@ describe('recursiveDelete', () => { expect.assertions(1) try { await recursiveCopy(resolveDataDir, testResolveDataDir) + await fs.symlink('./aa', join(testResolveDataDir, 'symlink')) await recursiveDelete(testResolveDataDir) const result = await recursiveReadDir(testResolveDataDir, /.*/) expect(result.length).toBe(0) diff --git a/test/unit/split-cookies-string.test.ts b/test/unit/split-cookies-string.test.ts new file mode 100644 index 0000000000000..b78368fdc9fba --- /dev/null +++ b/test/unit/split-cookies-string.test.ts @@ -0,0 +1,143 @@ +import { splitCookiesString } from 'next/dist/server/web/utils' +import cookie, { CookieSerializeOptions } from 'next/dist/compiled/cookie' + +function generateCookies( + ...cookieOptions: (CookieSerializeOptions & { name: string; value: string })[] +) { + const cookies = cookieOptions.map((opts) => + cookie.serialize(opts.name, opts.value, opts) + ) + return { + joined: cookies.join(', '), + expected: cookies, + } +} + +describe('splitCookiesString', () => { + describe('with a single cookie', () => { + it('should parse a plain value', () => { + const { joined, expected } = generateCookies({ + name: 'foo', + value: 'bar', + }) + const result = splitCookiesString(joined) + expect(result).toEqual(expected) + }) + + it('should parse expires', () => { + const { joined, expected } = generateCookies({ + name: 'foo', + value: 'bar', + expires: new Date(), + }) + const result = splitCookiesString(joined) + expect(result).toEqual(expected) + }) + + it('should parse max-age', () => { + const { joined, expected } = generateCookies({ + name: 'foo', + value: 'bar', + maxAge: 10, + }) + const result = splitCookiesString(joined) + expect(result).toEqual(expected) + }) + + it('should parse with all the options', () => { + const { joined, expected } = generateCookies({ + name: 'foo', + value: 'bar', + expires: new Date(Date.now() + 10 * 1000), + maxAge: 10, + domain: 'https://foo.bar', + httpOnly: true, + path: '/path', + sameSite: 'lax', + secure: true, + }) + const result = splitCookiesString(joined) + expect(result).toEqual(expected) + }) + }) + + describe('with a mutliple cookies', () => { + it('should parse a plain value', () => { + const { joined, expected } = generateCookies( + { + name: 'foo', + value: 'bar', + }, + { + name: 'x', + value: 'y', + } + ) + const result = splitCookiesString(joined) + expect(result).toEqual(expected) + }) + + it('should parse expires', () => { + const { joined, expected } = generateCookies( + { + name: 'foo', + value: 'bar', + expires: new Date(), + }, + { + name: 'x', + value: 'y', + expires: new Date(), + } + ) + const result = splitCookiesString(joined) + expect(result).toEqual(expected) + }) + + it('should parse max-age', () => { + const { joined, expected } = generateCookies( + { + name: 'foo', + value: 'bar', + maxAge: 10, + }, + { + name: 'x', + value: 'y', + maxAge: 10, + } + ) + const result = splitCookiesString(joined) + expect(result).toEqual(expected) + }) + + it('should parse with all the options', () => { + const { joined, expected } = generateCookies( + { + name: 'foo', + value: 'bar', + expires: new Date(Date.now() + 10 * 1000), + maxAge: 10, + domain: 'https://foo.bar', + httpOnly: true, + path: '/path', + sameSite: 'lax', + secure: true, + }, + { + name: 'x', + value: 'y', + expires: new Date(Date.now() + 20 * 1000), + maxAge: 20, + domain: 'https://x.y', + httpOnly: true, + path: '/path', + sameSite: 'strict', + secure: true, + } + ) + const result = splitCookiesString(joined) + expect(result).toEqual(expected) + }) + }) +}) diff --git a/yarn.lock b/yarn.lock index 380a4877bab4c..69fe5c815854e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -74,6 +74,13 @@ dependencies: "@babel/highlight" "^7.14.5" +"@babel/code-frame@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.0.tgz#0dfc80309beec8411e65e706461c408b0bb9b431" + integrity sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA== + dependencies: + "@babel/highlight" "^7.16.0" + "@babel/compat-data@^7.12.5", "@babel/compat-data@^7.12.7": version "7.12.7" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.12.7.tgz#9329b4782a7d6bbd7eef57e11addf91ee3ef1e41" @@ -206,6 +213,15 @@ jsesc "^2.5.1" source-map "^0.5.0" +"@babel/generator@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.16.0.tgz#d40f3d1d5075e62d3500bccb67f3daa8a95265b2" + integrity sha512-RR8hUCfRQn9j9RPKEVXo9LiwoxLPYn6hNZlvUOR8tSnaxlD0p0+la00ZP9/SnRt6HchKr+X0fO2r8vrETiJGew== + dependencies: + "@babel/types" "^7.16.0" + jsesc "^2.5.1" + source-map "^0.5.0" + "@babel/helper-annotate-as-pure@^7.10.4", "@babel/helper-annotate-as-pure@^7.12.10": version "7.12.10" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.10.tgz#54ab9b000e60a93644ce17b3f37d313aaf1d115d" @@ -220,6 +236,13 @@ dependencies: "@babel/types" "^7.14.5" +"@babel/helper-annotate-as-pure@^7.15.4": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz#9a1f0ebcda53d9a2d00108c4ceace6a5d5f1f08d" + integrity sha512-ItmYF9vR4zA8cByDocY05o0LGUkp1zhbTQOH1NFyl5xXEqlTJQCEJjieriw+aFpxo16swMxUnUiKS7a/r4vtHg== + dependencies: + "@babel/types" "^7.16.0" + "@babel/helper-builder-binary-assignment-operator-visitor@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz#bb0b75f31bf98cbf9ff143c1ae578b87274ae1a3" @@ -383,6 +406,15 @@ "@babel/template" "^7.14.5" "@babel/types" "^7.14.5" +"@babel/helper-function-name@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz#b7dd0797d00bbfee4f07e9c4ea5b0e30c8bb1481" + integrity sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog== + dependencies: + "@babel/helper-get-function-arity" "^7.16.0" + "@babel/template" "^7.16.0" + "@babel/types" "^7.16.0" + "@babel/helper-get-function-arity@^7.12.10": version "7.12.10" resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz#b158817a3165b5faa2047825dfa61970ddcc16cf" @@ -404,6 +436,13 @@ dependencies: "@babel/types" "^7.14.5" +"@babel/helper-get-function-arity@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz#0088c7486b29a9cb5d948b1a1de46db66e089cfa" + integrity sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ== + dependencies: + "@babel/types" "^7.16.0" + "@babel/helper-hoist-variables@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz#d49b001d1d5a68ca5e6604dda01a6297f7c9381e" @@ -418,6 +457,13 @@ dependencies: "@babel/types" "^7.14.5" +"@babel/helper-hoist-variables@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz#4c9023c2f1def7e28ff46fc1dbcd36a39beaa81a" + integrity sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg== + dependencies: + "@babel/types" "^7.16.0" + "@babel/helper-member-expression-to-functions@^7.12.1": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.1.tgz#fba0f2fcff3fba00e6ecb664bb5e6e26e2d6165c" @@ -439,6 +485,13 @@ dependencies: "@babel/types" "^7.15.0" +"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.15.4": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz#90538e60b672ecf1b448f5f4f5433d37e79a3ec3" + integrity sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg== + dependencies: + "@babel/types" "^7.16.0" + "@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.12.1", "@babel/helper-module-imports@^7.12.5": version "7.12.5" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz#1bfc0229f794988f76ed0a4d4e90860850b54dfb" @@ -636,6 +689,13 @@ dependencies: "@babel/types" "^7.14.5" +"@babel/helper-split-export-declaration@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz#29672f43663e936df370aaeb22beddb3baec7438" + integrity sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw== + dependencies: + "@babel/types" "^7.16.0" + "@babel/helper-validator-identifier@^7.10.4", "@babel/helper-validator-identifier@^7.12.11": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed" @@ -651,6 +711,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz#6654d171b2024f6d8ee151bf2509699919131d48" integrity sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g== +"@babel/helper-validator-identifier@^7.15.7": + version "7.15.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" + integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w== + "@babel/helper-validator-option@^7.12.1", "@babel/helper-validator-option@^7.12.11": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.12.11.tgz#d66cb8b7a3e7fe4c6962b32020a131ecf0847f4f" @@ -735,6 +800,15 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/highlight@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.0.tgz#6ceb32b2ca4b8f5f361fb7fd821e3fddf4a1725a" + integrity sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g== + dependencies: + "@babel/helper-validator-identifier" "^7.15.7" + chalk "^2.0.0" + js-tokens "^4.0.0" + "@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.12.10", "@babel/parser@^7.12.11", "@babel/parser@^7.12.7", "@babel/parser@^7.3.3": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.11.tgz#9ce3595bcd74bc5c466905e86c535b8b25011e79" @@ -755,6 +829,11 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.3.tgz#3416d9bea748052cfcb63dbcc27368105b1ed862" integrity sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA== +"@babel/parser@^7.16.0": + version "7.16.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.2.tgz#3723cd5c8d8773eef96ce57ea1d9b7faaccd12ac" + integrity sha512-RUVpT0G2h6rOZwqLDTrKk7ksNv7YpAilTnYe1/Q+eDjxEceRMKVWbCsX7t8h6C1qCFi/1Y8WZjcEPBAFG27GPw== + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.14.5.tgz#4b467302e1548ed3b1be43beae2cc9cf45e0bb7e" @@ -2229,6 +2308,15 @@ "@babel/parser" "^7.14.5" "@babel/types" "^7.14.5" +"@babel/template@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.0.tgz#d16a35ebf4cd74e202083356fab21dd89363ddd6" + integrity sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A== + dependencies: + "@babel/code-frame" "^7.16.0" + "@babel/parser" "^7.16.0" + "@babel/types" "^7.16.0" + "@babel/traverse@7.15.0", "@babel/traverse@^7.15.0": version "7.15.0" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.15.0.tgz#4cca838fd1b2a03283c1f38e141f639d60b3fc98" @@ -2274,6 +2362,21 @@ debug "^4.1.0" globals "^11.1.0" +"@babel/traverse@^7.4.5": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.16.0.tgz#965df6c6bfc0a958c1e739284d3c9fa4a6e3c45b" + integrity sha512-qQ84jIs1aRQxaGaxSysII9TuDaguZ5yVrEuC0BN2vcPlalwfLovVmCjbFDPECPXcYM/wLvNFfp8uDOliLxIoUQ== + dependencies: + "@babel/code-frame" "^7.16.0" + "@babel/generator" "^7.16.0" + "@babel/helper-function-name" "^7.16.0" + "@babel/helper-hoist-variables" "^7.16.0" + "@babel/helper-split-export-declaration" "^7.16.0" + "@babel/parser" "^7.16.0" + "@babel/types" "^7.16.0" + debug "^4.1.0" + globals "^11.1.0" + "@babel/traverse@^7.7.2": version "7.13.0" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.13.0.tgz#6d95752475f86ee7ded06536de309a65fc8966cc" @@ -2323,6 +2426,14 @@ "@babel/helper-validator-identifier" "^7.14.5" to-fast-properties "^2.0.0" +"@babel/types@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.16.0.tgz#db3b313804f96aadd0b776c4823e127ad67289ba" + integrity sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg== + dependencies: + "@babel/helper-validator-identifier" "^7.15.7" + to-fast-properties "^2.0.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -2332,6 +2443,28 @@ version "1.4.0" resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" +"@emotion/is-prop-valid@^0.8.8": + version "0.8.8" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a" + integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA== + dependencies: + "@emotion/memoize" "0.7.4" + +"@emotion/memoize@0.7.4": + version "0.7.4" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb" + integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw== + +"@emotion/stylis@^0.8.4": + version "0.8.5" + resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04" + integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ== + +"@emotion/unitless@^0.7.4": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" + integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== + "@eslint/eslintrc@^0.4.0": version "0.4.0" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.0.tgz#99cc0a0584d72f1df38b900fb062ba995f395547" @@ -3537,28 +3670,17 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" -"@napi-rs/cli@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@napi-rs/cli/-/cli-1.1.0.tgz#ca58a0ff18a0af133f009773282846fb4509f9d1" - integrity sha512-eGOOybqsuABoeocHron/R8cA4A1JJeLlt8dBsmyIbdXduEs0SPr+caLX5avdz8D4xG9tYfxj0k8fN/PAletLPA== - dependencies: - "@octokit/rest" "^18.5.6" - chalk "^4.1.1" - clipanion "^2.6.2" - debug "^4.3.1" - fdir "^5.1.0" - inquirer "^8.1.0" - lodash "^4.17.21" - putasset "^5.0.3" - toml "^3.0.0" - tslib "^2.2.0" +"@napi-rs/cli@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@napi-rs/cli/-/cli-1.2.1.tgz#eccdf9e0835aec3adcef30074bf69c110ea65960" + integrity sha512-7FoYn1JSK5rTIG9KcKfYnZL/O0UjUMMuzZCXd//bJdkLw0Xx9EqQJs1X/Mg4KqywJYb79LDfxRMiJRSukPGDNw== -"@napi-rs/triples@^1.0.3": +"@napi-rs/triples@1.0.3", "@napi-rs/triples@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@napi-rs/triples/-/triples-1.0.3.tgz#76d6d0c3f4d16013c61e45dfca5ff1e6c31ae53c" integrity sha512-jDJTpta+P4p1NZTFVLHJ/TLFVYVcOqv6l8xwOeBKNPMgY/zDYH/YH7SJbvrr/h1RcS9GzbPcLKGzpuK9cV56UA== -"@node-rs/helper@1.2.1", "@node-rs/helper@^1.0.0": +"@node-rs/helper@^1.0.0": version "1.2.1" resolved "https://registry.yarnpkg.com/@node-rs/helper/-/helper-1.2.1.tgz#e079b05f21ff4329d82c4e1f71c0290e4ecdc70c" integrity sha512-R5wEmm8nbuQU0YGGmYVjEc0OHtYsuXdpRG+Ut/3wZ9XAvQWyThN08bTh2cBJgoZxHQUPtvRfeQuxcAgLuiBISg== @@ -3649,25 +3771,13 @@ puka "^1.0.1" read-package-json-fast "^2.0.1" -"@octokit/auth-token@^2.4.0", "@octokit/auth-token@^2.4.4": +"@octokit/auth-token@^2.4.4": version "2.4.5" resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.5.tgz#568ccfb8cb46f36441fac094ce34f7a875b197f3" integrity sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA== dependencies: "@octokit/types" "^6.0.3" -"@octokit/core@^2.4.3": - version "2.5.4" - resolved "https://registry.yarnpkg.com/@octokit/core/-/core-2.5.4.tgz#f7fbf8e4f86c5cc2497a8887ba2561ec8d358054" - integrity sha512-HCp8yKQfTITYK+Nd09MHzAlP1v3Ii/oCohv0/TW9rhSLvzb98BOVs2QmVYuloE6a3l6LsfyGIwb6Pc4ycgWlIQ== - dependencies: - "@octokit/auth-token" "^2.4.0" - "@octokit/graphql" "^4.3.1" - "@octokit/request" "^5.4.0" - "@octokit/types" "^5.0.0" - before-after-hook "^2.1.0" - universal-user-agent "^5.0.0" - "@octokit/core@^3.2.3": version "3.2.5" resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.2.5.tgz#57becbd5fd789b0592b915840855f3a5f233d554" @@ -3680,19 +3790,6 @@ before-after-hook "^2.1.0" universal-user-agent "^6.0.0" -"@octokit/core@^3.5.0": - version "3.5.1" - resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.5.1.tgz#8601ceeb1ec0e1b1b8217b960a413ed8e947809b" - integrity sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw== - dependencies: - "@octokit/auth-token" "^2.4.4" - "@octokit/graphql" "^4.5.8" - "@octokit/request" "^5.6.0" - "@octokit/request-error" "^2.0.5" - "@octokit/types" "^6.0.3" - before-after-hook "^2.2.0" - universal-user-agent "^6.0.0" - "@octokit/endpoint@^6.0.1": version "6.0.11" resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.11.tgz#082adc2aebca6dcefa1fb383f5efb3ed081949d1" @@ -3702,15 +3799,6 @@ is-plain-object "^5.0.0" universal-user-agent "^6.0.0" -"@octokit/graphql@^4.3.1": - version "4.6.4" - resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.6.4.tgz#0c3f5bed440822182e972317122acb65d311a5ed" - integrity sha512-SWTdXsVheRmlotWNjKzPOb6Js6tjSqA2a8z9+glDJng0Aqjzti8MEWOtuT8ZSu6wHnci7LZNuarE87+WJBG4vg== - dependencies: - "@octokit/request" "^5.6.0" - "@octokit/types" "^6.0.3" - universal-user-agent "^6.0.0" - "@octokit/graphql@^4.5.8": version "4.6.0" resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.6.0.tgz#f9abca55f82183964a33439d5264674c701c3327" @@ -3725,23 +3813,11 @@ resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-4.0.2.tgz#4b2bb553a16ab9e0fdeb29bd453b1c88cf129929" integrity sha512-quqmeGTjcVks8YaatVGCpt7QpUTs2PK0D3mW5aEQqmFKOuIZ/CxwWrgnggPjqP3CNp6eALdQRgf0jUpcG8X1/Q== -"@octokit/openapi-types@^8.2.1": - version "8.2.1" - resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-8.2.1.tgz#102e752a7378ff8d21057c70fd16f1c83856d8c5" - integrity sha512-BJz6kWuL3n+y+qM8Pv+UGbSxH6wxKf/SBs5yzGufMHwDefsa+Iq7ZGy1BINMD2z9SkXlIzk1qiu988rMuGXEMg== - "@octokit/plugin-enterprise-rest@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz#e07896739618dab8da7d4077c658003775f95437" integrity sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw== -"@octokit/plugin-paginate-rest@^2.2.0": - version "2.14.0" - resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.14.0.tgz#f469cb4a908792fb44679c5973d8bba820c88b0f" - integrity sha512-S2uEu2uHeI7Vf+Lvj8tv3O5/5TCAa8GHS0dUQN7gdM7vKA6ZHAbR6HkAVm5yMb1mbedLEbxOuQ+Fa0SQ7tCDLA== - dependencies: - "@octokit/types" "^6.18.0" - "@octokit/plugin-paginate-rest@^2.6.2": version "2.9.1" resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.9.1.tgz#e9bb34a89b7ed5b801f1c976feeb9b0078ecd201" @@ -3749,24 +3825,11 @@ dependencies: "@octokit/types" "^6.8.0" -"@octokit/plugin-request-log@^1.0.0": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85" - integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA== - "@octokit/plugin-request-log@^1.0.2": version "1.0.3" resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.3.tgz#70a62be213e1edc04bb8897ee48c311482f9700d" integrity sha512-4RFU4li238jMJAzLgAwkBAw+4Loile5haQMQr+uhFq27BmyJXcXSKvoQKqh0agsZEiUlW6iSv3FAgvmGkur7OQ== -"@octokit/plugin-rest-endpoint-methods@3.17.0": - version "3.17.0" - resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-3.17.0.tgz#d8ba04eb883849dd98666c55bf49d8c9fe7be055" - integrity sha512-NFV3vq7GgoO2TrkyBRUOwflkfTYkFKS0tLAPym7RNpkwLCttqShaEGjthOsPEEL+7LFcYv3mU24+F2yVd3npmg== - dependencies: - "@octokit/types" "^4.1.6" - deprecation "^2.3.1" - "@octokit/plugin-rest-endpoint-methods@4.10.1": version "4.10.1" resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.10.1.tgz#b7a9181d1f52fef70a13945c5b49cffa51862da1" @@ -3775,14 +3838,6 @@ "@octokit/types" "^6.8.2" deprecation "^2.3.1" -"@octokit/plugin-rest-endpoint-methods@5.4.1": - version "5.4.1" - resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.4.1.tgz#540ec90bb753dcaa682ee9f2cd6efdde9132fa90" - integrity sha512-Nx0g7I5ayAYghsLJP4Q1Ch2W9jYYM0FlWWWZocUro8rNxVwuZXGfFd7Rcqi9XDWepSXjg1WByiNJnZza2hIOvQ== - dependencies: - "@octokit/types" "^6.18.1" - deprecation "^2.3.1" - "@octokit/request-error@^2.0.0": version "2.0.5" resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.0.5.tgz#72cc91edc870281ad583a42619256b380c600143" @@ -3792,15 +3847,6 @@ deprecation "^2.0.0" once "^1.4.0" -"@octokit/request-error@^2.0.5", "@octokit/request-error@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.1.0.tgz#9e150357831bfc788d13a4fd4b1913d60c74d677" - integrity sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg== - dependencies: - "@octokit/types" "^6.0.3" - deprecation "^2.0.0" - once "^1.4.0" - "@octokit/request@^5.3.0", "@octokit/request@^5.4.12": version "5.4.14" resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.14.tgz#ec5f96f78333bb2af390afa5ff66f114b063bc96" @@ -3815,18 +3861,6 @@ once "^1.4.0" universal-user-agent "^6.0.0" -"@octokit/request@^5.4.0", "@octokit/request@^5.6.0": - version "5.6.0" - resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.6.0.tgz#6084861b6e4fa21dc40c8e2a739ec5eff597e672" - integrity sha512-4cPp/N+NqmaGQwbh3vUsYqokQIzt7VjsgTYVXiwpUP2pxd5YiZB2XuTedbb0SPtv9XS7nzAKjAuQxmY8/aZkiA== - dependencies: - "@octokit/endpoint" "^6.0.1" - "@octokit/request-error" "^2.1.0" - "@octokit/types" "^6.16.1" - is-plain-object "^5.0.0" - node-fetch "^2.6.1" - universal-user-agent "^6.0.0" - "@octokit/rest@15.2.6": version "15.2.6" resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-15.2.6.tgz#16226f58fbf0ba88f631848fb622dfe0ad410c0c" @@ -3840,16 +3874,6 @@ node-fetch "^2.1.1" url-template "^2.0.8" -"@octokit/rest@^17.1.3": - version "17.11.2" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-17.11.2.tgz#f3dbd46f9f06361c646230fd0ef8598e59183ead" - integrity sha512-4jTmn8WossTUaLfNDfXk4fVJgbz5JgZE8eCs4BvIb52lvIH8rpVMD1fgRCrHbSd6LRPE5JFZSfAEtszrOq3ZFQ== - dependencies: - "@octokit/core" "^2.4.3" - "@octokit/plugin-paginate-rest" "^2.2.0" - "@octokit/plugin-request-log" "^1.0.0" - "@octokit/plugin-rest-endpoint-methods" "3.17.0" - "@octokit/rest@^18.1.0": version "18.1.0" resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.1.0.tgz#9bf72604911a3433165bcc924263c9a706d32804" @@ -3860,30 +3884,6 @@ "@octokit/plugin-request-log" "^1.0.2" "@octokit/plugin-rest-endpoint-methods" "4.10.1" -"@octokit/rest@^18.5.6": - version "18.6.7" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.6.7.tgz#89b8ecd13edd9603f00453640d1fb0b4175d4b31" - integrity sha512-Kn6WrI2ZvmAztdx+HEaf88RuJn+LK72S8g6OpciE4kbZddAN84fu4fiPGxcEu052WmqKVnA/cnQsbNlrYC6rqQ== - dependencies: - "@octokit/core" "^3.5.0" - "@octokit/plugin-paginate-rest" "^2.6.2" - "@octokit/plugin-request-log" "^1.0.2" - "@octokit/plugin-rest-endpoint-methods" "5.4.1" - -"@octokit/types@^4.1.6": - version "4.1.10" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-4.1.10.tgz#e4029c11e2cc1335051775bc1600e7e740e4aca4" - integrity sha512-/wbFy1cUIE5eICcg0wTKGXMlKSbaAxEr00qaBXzscLXpqhcwgXeS6P8O0pkysBhRfyjkKjJaYrvR1ExMO5eOXQ== - dependencies: - "@types/node" ">= 8" - -"@octokit/types@^5.0.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-5.5.0.tgz#e5f06e8db21246ca102aa28444cdb13ae17a139b" - integrity sha512-UZ1pErDue6bZNjYOotCNveTXArOMZQFG6hKJfOnGnulVCMcVVi7YIIuuR4WfBhjo7zgpmzn/BkPDnUXtNx+PcQ== - dependencies: - "@types/node" ">= 8" - "@octokit/types@^6.0.3", "@octokit/types@^6.7.1", "@octokit/types@^6.8.0", "@octokit/types@^6.8.2": version "6.8.3" resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.8.3.tgz#1960951103c836ab2e55fe47a8da2bf76402824f" @@ -3892,13 +3892,6 @@ "@octokit/openapi-types" "^4.0.2" "@types/node" ">= 8" -"@octokit/types@^6.16.1", "@octokit/types@^6.18.0", "@octokit/types@^6.18.1": - version "6.18.1" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.18.1.tgz#a6db178536e649fd5d67a7b747754bcc43940be4" - integrity sha512-5YsddjO1U+xC8ZYKV8yZYebW55PCc7qiEEeZ+wZRr6qyclynzfyD65KZ5FdtIeP0/cANyFaD7hV69qElf1nMsQ== - dependencies: - "@octokit/openapi-types" "^8.2.1" - "@peculiar/asn1-schema@^2.0.32", "@peculiar/asn1-schema@^2.0.38": version "2.0.38" resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.0.38.tgz#98b6f12daad275ecd6774dfe31fb62f362900412" @@ -4251,11 +4244,6 @@ "@swc/core-win32-ia32-msvc" "1.2.97" "@swc/core-win32-x64-msvc" "1.2.97" -"@swc/jest@0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@swc/jest/-/jest-0.2.3.tgz#5c32aaa6298267a955d35eb67094edabd5db598f" - integrity sha512-ARZIY5OkXdFRQLHc/1i+yKrl0H3B1sa7Bu9XE8yTvYZZ4G5Ewu6oyyJBM52TiROP6EpMcF7ZeQQsKMZvzuKkNw== - "@szmarczak/http-timer@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" @@ -4443,6 +4431,11 @@ dependencies: "@types/node" "*" +"@types/content-disposition@0.5.4": + version "0.5.4" + resolved "https://registry.yarnpkg.com/@types/content-disposition/-/content-disposition-0.5.4.tgz#de48cf01c79c9f1560bcfd8ae43217ab028657f8" + integrity sha512-0mPF08jn9zYI0n0Q/Pnz7C4kThdSt+6LD4amsrYDDpgBfrVWa3TcCOxKX1zkGgYniGagRv8heN2cbh+CAn+uuQ== + "@types/content-type@1.1.3": version "1.1.3" resolved "https://registry.yarnpkg.com/@types/content-type/-/content-type-1.1.3.tgz#3688bd77fc12f935548eef102a4e34c512b03a07" @@ -5491,6 +5484,16 @@ acorn-walk@^8.0.0: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.0.0.tgz#56ae4c0f434a45fff4a125e7ea95fa9c98f67a16" integrity sha512-oZRad/3SMOI/pxbbmqyurIx7jHw1wZDcR9G44L8pUVFEomX/0dH89SrM1KaDXuv1NpzAXz6Op/Xu/Qd5XXzdEA== +acorn@8.5.0, acorn@^8.3.0, acorn@^8.4.1: + version "8.5.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" + integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== + +acorn@^6.2.1: + version "6.4.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" + integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== + acorn@^6.4.1: version "6.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" @@ -5519,11 +5522,6 @@ acorn@^8.2.4: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.4.1.tgz#56c36251fc7cabc7096adc18f05afe814321a28c" integrity sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA== -acorn@^8.3.0, acorn@^8.4.1: - version "8.5.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" - integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== - add-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa" @@ -6157,6 +6155,21 @@ babel-plugin-polyfill-regenerator@^0.2.2: dependencies: "@babel/helper-define-polyfill-provider" "^0.2.2" +"babel-plugin-styled-components@>= 1.12.0": + version "1.13.3" + resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-1.13.3.tgz#1f1cb3927d4afa1e324695c78f690900e3d075bc" + integrity sha512-meGStRGv+VuKA/q0/jXxrPNWEm4LPfYIqxooDTdmh8kFsP/Ph7jJG5rUPwUPX3QHUvggwdbgdGpo88P/rRYsVw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.15.4" + "@babel/helper-module-imports" "^7.15.4" + babel-plugin-syntax-jsx "^6.18.0" + lodash "^4.17.11" + +babel-plugin-syntax-jsx@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" + integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY= + babel-plugin-transform-async-to-promises@^0.8.15: version "0.8.15" resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-promises/-/babel-plugin-transform-async-to-promises-0.8.15.tgz#13b6d8ef13676b4e3c576d3600b85344bb1ba346" @@ -6245,15 +6258,15 @@ before-after-hook@^2.1.0: resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.1.1.tgz#99ae36992b5cfab4a83f6bee74ab27835f28f405" integrity sha512-5ekuQOvO04MDj7kYZJaMab2S8SPjGJbotVNyv7QYFCOAwrGZs/YnoDNlh1U+m5hl7H2D/+n0taaAV/tfyd3KMA== -before-after-hook@^2.2.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e" - integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ== - big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" +big.js@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-6.1.1.tgz#63b35b19dc9775c94991ee5db7694880655d5537" + integrity sha512-1vObw81a8ylZO5ePrtMay0n018TcftpTA5HFKDaSuiUDBo8biRBtjIobw60OpwuvrGk+FsxKamqN4cnmj/eXdg== + binary-extensions@^1.0.0: version "1.13.1" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" @@ -6269,15 +6282,6 @@ bindings@^1.4.0, bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" -bl@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" - integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - bluebird@^3.5.0, bluebird@^3.5.5: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" @@ -6490,7 +6494,7 @@ buffer-xor@^1.0.3: resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= -buffer@5.6.0, buffer@^5.5.0: +buffer@5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== @@ -6703,6 +6707,11 @@ camelcase@^6.0.0, camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== +camelize@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b" + integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs= + caniuse-api@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" @@ -6786,14 +6795,6 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" - integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - change-case@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/change-case/-/change-case-3.1.0.tgz#0e611b7edc9952df2e8513b27b42de72647dd17e" @@ -6848,11 +6849,6 @@ chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" -checkup@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/checkup/-/checkup-1.3.0.tgz#d3800276fea5d0f247ffc951be78c8b02f8e0d76" - integrity sha1-04ACdv6l0PJH/8lRvnjIsC+ODXY= - cheerio-select@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-1.4.0.tgz#3a16f21e37a2ef0f211d6d1aa4eff054bb22cdc9" @@ -7057,11 +7053,6 @@ cli-spinners@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.3.0.tgz#0632239a4b5aa4c958610142c34bb7a651fc8df5" -cli-spinners@^2.5.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.0.tgz#36c7dc98fb6a9a76bd6238ec3f77e2425627e939" - integrity sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q== - cli-truncate@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574" @@ -7078,11 +7069,6 @@ cli-width@^3.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== -clipanion@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/clipanion/-/clipanion-2.6.2.tgz#820e7440812052442455b248f927b187ed732f71" - integrity sha512-0tOHJNMF9+4R3qcbBL+4IxLErpaYSYvzs10aXuECDbZdJOuJHdagJMAqvLdeaUQTI/o2uSCDRpet6ywDiKOAYw== - cliui@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" @@ -7423,6 +7409,7 @@ contains-path@^0.1.0: content-disposition@0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" + integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== dependencies: safe-buffer "5.1.2" @@ -7735,7 +7722,7 @@ cross-spawn-async@^2.1.1: lru-cache "^4.0.0" which "^1.2.8" -cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5: +cross-spawn@6.0.5, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" dependencies: @@ -7801,6 +7788,11 @@ css-blank-pseudo@^0.1.4: dependencies: postcss "^7.0.5" +css-color-keywords@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" + integrity sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU= + css-color-names@0.0.4, css-color-names@^0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" @@ -7858,6 +7850,15 @@ css-select@~1.2.0: domutils "1.5.1" nth-check "~1.0.1" +css-to-react-native@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.0.0.tgz#62dbe678072a824a689bcfee011fc96e02a7d756" + integrity sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ== + dependencies: + camelize "^1.0.0" + css-color-keywords "^1.0.0" + postcss-value-parser "^4.0.2" + css-tree@1.0.0-alpha.37: version "1.0.0-alpha.37" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" @@ -9136,15 +9137,15 @@ eventemitter3@^4.0.0, eventemitter3@^4.0.4: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -events@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" - -events@^3.2.0: +events@3.3.0, events@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== +events@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" + evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" @@ -9194,19 +9195,6 @@ execa@^0.4.0: path-key "^1.0.0" strip-eof "^1.0.0" -execa@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" - integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== - dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - execa@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/execa/-/execa-5.0.0.tgz#4029b0007998a841fbd1032e5f4de86a3c1e3376" @@ -9477,11 +9465,6 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" -fdir@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/fdir/-/fdir-5.1.0.tgz#973e4934e6a3666b59ebdfc56f60bb8e9b16acb8" - integrity sha512-IgTtZwL52tx2wqWeuGDzXYTnNsEjNLahZpJw30hCQDyVnoHXwY5acNDnjGImTTL1R0z1PCyLw20VAbE5qLic3Q== - figgy-pudding@^3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" @@ -9512,13 +9495,6 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" -file-loader@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.0.0.tgz#97bbfaab7a2460c07bcbd72d3a6922407f67649f" - dependencies: - loader-utils "^2.0.0" - schema-utils "^2.6.5" - file-name@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/file-name/-/file-name-0.1.0.tgz#12b122f120f9c34dbc176c1ab81a548aced6def7" @@ -9960,7 +9936,7 @@ get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" -get-stream@^4.0.0, get-stream@^4.1.0: +get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== @@ -10630,6 +10606,13 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +hoist-non-react-statics@^3.0.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + homedir-polyfill@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" @@ -11034,26 +11017,6 @@ inquirer@7.3.3, inquirer@^7.3.3: strip-ansi "^6.0.0" through "^2.3.6" -inquirer@^8.1.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.1.1.tgz#7c53d94c6d03011c7bb2a947f0dca3b98246c26a" - integrity sha512-hUDjc3vBkh/uk1gPfMAD/7Z188Q8cvTGl0nxwaCdwSbzFh6ZKkZh+s2ozVxbE5G9ZNRyeY0+lgbAIOUFsFf98w== - dependencies: - ansi-escapes "^4.2.1" - chalk "^4.1.1" - cli-cursor "^3.1.0" - cli-width "^3.0.0" - external-editor "^3.0.3" - figures "^3.0.0" - lodash "^4.17.21" - mute-stream "0.0.8" - ora "^5.3.0" - run-async "^2.4.0" - rxjs "^6.6.6" - string-width "^4.1.0" - strip-ansi "^6.0.0" - through "^2.3.6" - internal-slot@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" @@ -11557,11 +11520,6 @@ is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" -is-unicode-supported@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" - integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== - is-upper-case@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/is-upper-case/-/is-upper-case-1.1.2.tgz#8d0b1fa7e7933a1e58483600ec7d9661cbaf756f" @@ -11589,7 +11547,7 @@ is-word-character@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.3.tgz#264d15541cbad0ba833d3992c34e6b40873b08aa" -is-wsl@2.2.0: +is-wsl@2.2.0, is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" dependencies: @@ -12130,11 +12088,6 @@ jest@27.0.6: import-local "^3.0.2" jest-cli "^27.0.6" -jju@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a" - integrity sha1-o6vicYryQaKykE+EpiWXDzia4yo= - jpeg-js@^0.4.2: version "0.4.3" resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.3.tgz#6158e09f1983ad773813704be80680550eff977b" @@ -12662,6 +12615,22 @@ loader-runner@^4.2.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== +"loader-utils2@npm:loader-utils@2.0.0", loader-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" + integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + +"loader-utils3@npm:loader-utils@3.1.3": + version "3.1.3" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-3.1.3.tgz#bd56dd5f8cc7b94c4f3cb0512be58126219253e8" + integrity sha512-iQeN+4aRVLiJU1J2BNTRg2cjhuFXWUX9DmvTDDtuwAm+ye6cMpUTLaPZmCFlZOrcDg93C9a17e/Hr+nQ9lquYw== + dependencies: + big.js "^6.1.1" + loader-utils@1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" @@ -12670,14 +12639,6 @@ loader-utils@1.2.3: emojis-list "^2.0.0" json5 "^1.0.1" -loader-utils@2.0.0, loader-utils@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^2.1.2" - loader-utils@^1.1.0, loader-utils@^1.2.3: version "1.4.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" @@ -12884,14 +12845,6 @@ log-symbols@^3.0.0: dependencies: chalk "^2.4.2" -log-symbols@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" - integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== - dependencies: - chalk "^4.1.0" - is-unicode-supported "^0.1.0" - log-update@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/log-update/-/log-update-2.3.0.tgz#88328fd7d1ce7938b29283746f0b1bc126b24708" @@ -12974,11 +12927,6 @@ lz-string@^1.4.4: resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY= -macos-release@^2.2.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.5.0.tgz#067c2c88b5f3fb3c56a375b2ec93826220fa1ff2" - integrity sha512-EIgv+QZ9r+814gjJj0Bt5vSLJLzswGmSUbUpbi9AIr/fsN2IWFBl2NucV9PAiek+U1STK468tEkxmVYUtuAN3g== - magic-string@^0.25.7: version "0.25.7" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" @@ -13498,24 +13446,12 @@ mime-db@1.47.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.47.0.tgz#8cb313e59965d3c05cfbf898915a267af46a335c" integrity sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw== -mime-db@1.48.0: - version "1.48.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.48.0.tgz#e35b31045dd7eada3aaad537ed88a33afbef2d1d" - integrity sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ== - mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24: version "2.1.26" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" dependencies: mime-db "1.43.0" -mime-types@^2.1.21: - version "2.1.31" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.31.tgz#a00d76b74317c61f9c2db2218b8e9f8e9c5c9e6b" - integrity sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg== - dependencies: - mime-db "1.48.0" - mime-types@^2.1.27: version "2.1.30" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.30.tgz#6e7be8b4c479825f85ed6326695db73f9305d62d" @@ -13558,14 +13494,12 @@ min-indent@^1.0.0: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -mini-css-extract-plugin@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.5.0.tgz#69bee3b273d2d4ee8649a2eb409514b7df744a27" - integrity sha512-SIbuLMv6jsk1FnLIU5OUG/+VMGUprEjM1+o2trOAx8i5KOKMrhyezb1dJ4Ugsykb8Jgq8/w5NEopy6escV9G7g== +mini-css-extract-plugin@2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.4.3.tgz#be742943c192b028645d4389084ef187615fff82" + integrity sha512-zekavl9mZuGyk7COjsfFY/f655AX61EKE0AthXPrmDk+oZyjZ9WzO4WPjXnnO9xl8obK2kmM6rAQrBEmk+WK1g== dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" - webpack-sources "^1.1.0" + schema-utils "^3.1.0" minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" @@ -13865,6 +13799,7 @@ negotiator@0.6.2: neo-async@2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" + integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1, neo-async@^2.6.2: version "2.6.2" @@ -14034,15 +13969,17 @@ node-modules-regexp@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" -node-notifier@5.4.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.0.tgz#7b455fdce9f7de0c63538297354f3db468426e6a" +node-notifier@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.1.tgz#f86e89bbc925f2b068784b31f382afdc6ca56be1" + integrity sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA== dependencies: growly "^1.3.0" - is-wsl "^1.1.0" - semver "^5.5.0" + is-wsl "^2.2.0" + semver "^7.3.2" shellwords "^0.1.1" - which "^1.3.0" + uuid "^8.3.0" + which "^2.0.2" node-pre-gyp@^0.13.0: version "0.13.0" @@ -14269,13 +14206,6 @@ npm-run-path@^1.0.0: dependencies: path-key "^1.0.0" -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= - dependencies: - path-key "^2.0.0" - npm-run-path@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-3.1.0.tgz#7f91be317f6a466efed3c9f2980ad8a4ee8b0fa5" @@ -14542,21 +14472,6 @@ ora@4.0.4: strip-ansi "^6.0.0" wcwidth "^1.0.1" -ora@^5.3.0: - version "5.4.1" - resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" - integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== - dependencies: - bl "^4.1.0" - chalk "^4.1.0" - cli-cursor "^3.1.0" - cli-spinners "^2.5.0" - is-interactive "^1.0.0" - is-unicode-supported "^0.1.0" - log-symbols "^4.1.0" - strip-ansi "^6.0.0" - wcwidth "^1.0.1" - os-browserify@0.3.0, os-browserify@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" @@ -14566,14 +14481,6 @@ os-homedir@^1.0.0, os-homedir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" -os-name@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801" - integrity sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg== - dependencies: - macos-release "^2.2.0" - windows-release "^3.1.0" - os-shim@^0.1.2: version "0.1.3" resolved "https://registry.yarnpkg.com/os-shim/-/os-shim-0.1.3.tgz#6b62c3791cf7909ea35ed46e17658bb417cb3917" @@ -14988,7 +14895,7 @@ path-key@^1.0.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-1.0.0.tgz#5d53d578019646c0d68800db4e146e6bdc2ac7af" integrity sha1-XVPVeAGWRsDWiADbThRua9wqx68= -path-key@^2.0.0, path-key@^2.0.1: +path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" @@ -16211,19 +16118,6 @@ purgecss@^1.4.0: postcss-selector-parser "^6.0.0" yargs "^14.0.0" -putasset@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/putasset/-/putasset-5.0.3.tgz#2fa82a8fc5e2333869df8ffb0e1f8618b1c87b9b" - integrity sha512-LGRp0SLOC4PDP/BawMaG3/hw6iKgQPRXcBF7WIzx2XTYwHVk2sS3gpvZqz6bf9GhKMal2phs+DF7J6eIAXEL4w== - dependencies: - "@octokit/rest" "^17.1.3" - checkup "^1.3.0" - mime-types "^2.1.21" - readjson "^2.0.1" - try-catch "^3.0.0" - try-to-catch "^3.0.0" - yargs-parser "^18.1.1" - pvtsutils@^1.1.6, pvtsutils@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pvtsutils/-/pvtsutils-1.2.0.tgz#619e4767093d23cd600482600c16f4c36d3025bb" @@ -16351,22 +16245,22 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" -"react-18@npm:react@next": - version "18.0.0-alpha-996da67b2-20211018" - resolved "https://registry.yarnpkg.com/react/-/react-18.0.0-alpha-996da67b2-20211018.tgz#9b92e0f6e0dc69a7e7d85a591593bbd53e4d660f" - integrity sha512-N8FJrmv97+0bDyqCATzqBXwtdIleQccf950MeJEnQbufh/lA7bRphMXOC5H/99cQJ1gl4zFoXFCuaU+UGbS6HA== +"react-18@npm:react@18.0.0-alpha-13455d26d-20211104": + version "18.0.0-alpha-13455d26d-20211104" + resolved "https://registry.yarnpkg.com/react/-/react-18.0.0-alpha-13455d26d-20211104.tgz#e21dbcf65b72a8ca15c8b078853b4c3eca428d40" + integrity sha512-rECM1RFXERi6L1KyvOylhxIzBznD40YB3cT5sFno5hyIpMCfbuKgH9UFchz3eIXUZbSaWDa5ljSH71dvs4tZPg== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" -"react-dom-18@npm:react-dom@next": - version "18.0.0-alpha-996da67b2-20211018" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.0.0-alpha-996da67b2-20211018.tgz#536b75359832644bae5343d638951e878b3bf438" - integrity sha512-cFhSJEZKsZONn5zhmD8n9FiAuAkzf0OrLxAdqCAHF6Dllp/p6lecOwptR46V7Q+ECu1zFV1CjAgU962Au/0CGA== +"react-dom-18@npm:react-dom@18.0.0-alpha-13455d26d-20211104": + version "18.0.0-alpha-13455d26d-20211104" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.0.0-alpha-13455d26d-20211104.tgz#e56be16370bfe2c1c9f68876f251ab995c22b6a6" + integrity sha512-LwfWfMoVjc6GCmqvhYeC07Pdn643GYCT0BvIAJWHRIVwdAdaSvSlnVl0iTpWVu4D9KMMEAZf1xSS3m/2NuGJ9w== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" - scheduler "0.21.0-alpha-996da67b2-20211018" + scheduler "0.21.0-alpha-13455d26d-20211104" react-dom@17.0.2: version "17.0.2" @@ -16382,7 +16276,7 @@ react-is@17.0.2: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^16.12.0, react-is@^16.8.1: +react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -16396,6 +16290,16 @@ react-refresh@0.8.3: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg== +react-server-dom-webpack@0.0.0-experimental-13455d26d-20211104: + version "0.0.0-experimental-13455d26d-20211104" + resolved "https://registry.yarnpkg.com/react-server-dom-webpack/-/react-server-dom-webpack-0.0.0-experimental-13455d26d-20211104.tgz#ba4ca0c3adcc4dd0ec1ef93ce6a45c6fc043aa71" + integrity sha512-ry5xxuF1th6/seRCXnhD8i5r/8e3EvWxIHIwTjN+MIhTVwc8tscMMhq2vqsTRn7Znd0LbrhCPRd4v1V6L+oMig== + dependencies: + acorn "^6.2.1" + loose-envify "^1.1.0" + neo-async "^2.6.1" + object-assign "^4.1.1" + react-ssr-prepass@1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/react-ssr-prepass/-/react-ssr-prepass-1.0.8.tgz#036abffe541975b20213cf7b261c05ac2843480d" @@ -16533,7 +16437,7 @@ read@1, read@~1.0.1: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0: +readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.5.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -16587,14 +16491,6 @@ readdirp@~3.5.0: dependencies: picomatch "^2.2.1" -readjson@^2.0.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/readjson/-/readjson-2.2.2.tgz#ed940ebdd72b88b383e02db7117402f980158959" - integrity sha512-PdeC9tsmLWBiL8vMhJvocq+OezQ3HhsH2HrN7YkhfYcTjQSa/iraB15A7Qvt7Xpr0Yd2rDNt6GbFwVQDg3HcAw== - dependencies: - jju "^1.4.0" - try-catch "^3.0.0" - recast@^0.16.1: version "0.16.2" resolved "https://registry.yarnpkg.com/recast/-/recast-0.16.2.tgz#3796ebad5fe49ed85473b479cd6df554ad725dc2" @@ -17270,13 +17166,6 @@ rxjs@^6.3.3, rxjs@^6.6.0: dependencies: tslib "^1.9.0" -rxjs@^6.6.6: - version "6.6.7" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" - integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== - dependencies: - tslib "^1.9.0" - sade@^1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/sade/-/sade-1.7.4.tgz#ea681e0c65d248d2095c90578c03ca0bb1b54691" @@ -17338,10 +17227,10 @@ saxes@^5.0.1: dependencies: xmlchars "^2.2.0" -scheduler@0.21.0-alpha-996da67b2-20211018: - version "0.21.0-alpha-996da67b2-20211018" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.21.0-alpha-996da67b2-20211018.tgz#de96129e104370b0a6c937c3d723d118f83afd41" - integrity sha512-7N20qihDl+CD+XFj8wwcqjArQ2UIW1xvwCBixHCKUAtJZ//4YYITfIjNpeci2OeYFw9RMiVabgF+LtzDT+64fw== +scheduler@0.21.0-alpha-13455d26d-20211104: + version "0.21.0-alpha-13455d26d-20211104" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.21.0-alpha-13455d26d-20211104.tgz#e317f99cdc61ec047abb91153b198374f9ae0d20" + integrity sha512-unjpzhesSK4cHetlpEKmHolIUMSb6HdRzmYK2fTK8rHd4TfooyzYPgytPClL2dd4r7aItvs7389uWiYZWcTB4A== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" @@ -17354,8 +17243,7 @@ scheduler@^0.20.2: loose-envify "^1.1.0" object-assign "^4.1.1" -"schema-utils2@npm:schema-utils@2.7.1", schema-utils@^2.6.5: - name schema-utils2 +"schema-utils2@npm:schema-utils@2.7.1": version "2.7.1" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== @@ -17445,14 +17333,7 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.0.0, semver@^7.1.1, semver@^7.1.3, semver@^7.3.2, semver@^7.3.4: - version "7.3.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" - integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== - dependencies: - lru-cache "^6.0.0" - -semver@^7.2.1, semver@^7.3.5: +semver@^7.0.0, semver@^7.1.1, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== @@ -17557,6 +17438,11 @@ shallow-clone@^3.0.0: dependencies: kind-of "^6.0.2" +shallowequal@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -17577,7 +17463,12 @@ shebang-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" -shell-quote@1.7.2, shell-quote@^1.6.1: +shell-quote@1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123" + integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw== + +shell-quote@^1.6.1: version "1.7.2" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" @@ -18262,6 +18153,22 @@ style-inject@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/style-inject/-/style-inject-0.3.0.tgz#d21c477affec91811cc82355832a700d22bf8dd3" +styled-components@5.3.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.3.3.tgz#312a3d9a549f4708f0fb0edc829eb34bde032743" + integrity sha512-++4iHwBM7ZN+x6DtPPWkCI4vdtwumQ+inA/DdAsqYd4SVgUKJie5vXyzotA00ttcFdQkCng7zc6grwlfIfw+lw== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/traverse" "^7.4.5" + "@emotion/is-prop-valid" "^0.8.8" + "@emotion/stylis" "^0.8.4" + "@emotion/unitless" "^0.7.4" + babel-plugin-styled-components ">= 1.12.0" + css-to-react-native "^3.0.0" + hoist-non-react-statics "^3.0.0" + shallowequal "^1.1.0" + supports-color "^5.5.0" + styled-jsx-plugin-postcss@3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/styled-jsx-plugin-postcss/-/styled-jsx-plugin-postcss-3.0.2.tgz#ec374dfcac1b6c1257117a7d102efed1f61c0896" @@ -18325,7 +18232,7 @@ supports-color@^3.2.3: dependencies: has-flag "^1.0.0" -supports-color@^5.3.0, supports-color@^5.4.0: +supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" dependencies: @@ -18785,11 +18692,6 @@ toidentifier@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" -toml@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/toml/-/toml-3.0.0.tgz#342160f1af1904ec9d204d03a5d61222d762c5ee" - integrity sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w== - totalist@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" @@ -18875,16 +18777,6 @@ trough@^1.0.0: dependencies: glob "^7.1.2" -try-catch@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/try-catch/-/try-catch-3.0.0.tgz#7996d8b89895e2e8ae62cbdbeb4fe17470f8131b" - integrity sha512-3uAqUnoemzca1ENvZ72EVimR+E8lqBbzwZ9v4CEbLjkaV3Q+FtdmPUt7jRtoSoTiYjyIMxEkf6YgUpe/voJ1ng== - -try-to-catch@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/try-to-catch/-/try-to-catch-3.0.0.tgz#a1903b44d13d5124c54d14a461d22ec1f52ea14b" - integrity sha512-eIm6ZXwR35jVF8By/HdbbkcaCDTBI5PpCPkejRKrYp0jyf/DbCCcRhHD7/O9jtFI3ewsqo9WctFEiJTS6i+CQA== - tsconfig-paths@^3.9.0: version "3.9.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" @@ -19335,13 +19227,6 @@ unistore@3.4.1: version "3.4.1" resolved "https://registry.yarnpkg.com/unistore/-/unistore-3.4.1.tgz#effdee7d9f2e00fdc6cecad5ab598ccd54344d38" -universal-user-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-5.0.0.tgz#a3182aa758069bf0e79952570ca757de3579c1d9" - integrity sha512-B5TPtzZleXyPrUMKCpEHFmVhMN6EhmJYjG5PQna9s7mXeSqGTLap4OpqLl5FCEFUI3UBmllkETwKf/db66Y54Q== - dependencies: - os-name "^3.1.0" - universal-user-agent@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" @@ -19525,7 +19410,7 @@ utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" -uuid@8.3.2: +uuid@8.3.2, uuid@^8.3.0: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== @@ -19792,7 +19677,7 @@ webpack-bundle-analyzer@4.3.0: sirv "^1.0.7" ws "^7.3.1" -"webpack-sources1@npm:webpack-sources@1.4.3", webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1: +"webpack-sources1@npm:webpack-sources@1.4.3", webpack-sources@^1.4.0, webpack-sources@^1.4.1: name webpack-sources1 version "1.4.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" @@ -19801,12 +19686,7 @@ webpack-bundle-analyzer@4.3.0: source-list-map "^2.0.0" source-map "~0.6.1" -"webpack-sources3@npm:webpack-sources@3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.0.tgz#b16973bcf844ebcdb3afde32eda1c04d0b90f89d" - integrity sha512-fahN08Et7P9trej8xz/Z7eRu8ltyiygEo/hnRi9KqBUs80KeDcnf96ZJo++ewWd84fEf3xSX9bp4ZS9hbw0OBw== - -webpack-sources@^3.2.0: +"webpack-sources3@npm:webpack-sources@3.2.1", webpack-sources@^3.2.0: version "3.2.1" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.1.tgz#251a7d9720d75ada1469ca07dbb62f3641a05b6d" integrity sha512-t6BMVLQ0AkjBOoRTZgqrWm7xbXMBzD+XDq2EZ96+vMfn3qKgsvdXZhbPZ4ElUOpdv4u+iiGe+w3+J75iy/bYGA== @@ -19840,10 +19720,10 @@ webpack-sources@^3.2.0: watchpack "^1.7.4" webpack-sources "^1.4.1" -"webpack5@npm:webpack@5.59.1": - version "5.59.1" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.59.1.tgz#60c77e9aad796252153d4d7ab6b2d4c11f0e548c" - integrity sha512-I01IQV9K96FlpXX3V0L4nvd7gb0r7thfuu1IfT2P4uOHOA77nKARAKDYGe/tScSHKnffNIyQhLC8kRXzY4KEHQ== +"webpack5@npm:webpack@5.64.0": + version "5.64.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.64.0.tgz#db3e12546f755930ccc9e0e21ba660871940c615" + integrity sha512-UclnN24m054HaPC45nmDEosX6yXWD+UGC12YtUs5i356DleAUGMDC9LBAw37xRRfgPKYIdCYjGA7RZ1AA+ZnGg== dependencies: "@types/eslint-scope" "^3.7.0" "@types/estree" "^0.0.50" @@ -19870,6 +19750,10 @@ webpack-sources@^3.2.0: watchpack "^2.2.0" webpack-sources "^3.2.0" +"webpack@link:./node_modules/webpack5": + version "0.0.0" + uid "" + websocket-driver@>=0.5.1: version "0.7.3" resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.3.tgz#a2d4e0d4f4f116f1e6297eba58b05d430100e9f9" @@ -19968,7 +19852,7 @@ which@1.2.x: dependencies: isexe "^2.0.0" -which@^1.2.12, which@^1.2.8, which@^1.2.9, which@^1.3.0, which@^1.3.1: +which@^1.2.12, which@^1.2.8, which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" dependencies: @@ -19993,13 +19877,6 @@ widest-line@^3.1.0: dependencies: string-width "^4.0.0" -windows-release@^3.1.0: - version "3.3.3" - resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.3.3.tgz#1c10027c7225743eec6b89df160d64c2e0293999" - integrity sha512-OSOGH1QYiW5yVor9TtmXKQvt2vjQqbYS+DqmsZw+r7xDwLXEeT3JGW0ZppFmHx4diyXmxt238KFR3N9jzevBRg== - dependencies: - execa "^1.0.0" - word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" @@ -20216,7 +20093,7 @@ yargs-parser@^15.0.0: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^18.1.1, yargs-parser@^18.1.3: +yargs-parser@^18.1.3: version "18.1.3" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" dependencies: