diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml
index 1b91c3fb11ab..59b4e71bce5b 100644
--- a/.github/workflows/build_test_deploy.yml
+++ b/.github/workflows/build_test_deploy.yml
@@ -6,12 +6,17 @@ on:
name: Build, test, and deploy
+env:
+ NAPI_CLI_VERSION: 2.7.0
+ TURBO_VERSION: 1.2.6
+ RUST_TOOLCHAIN: nightly-2022-02-23
+
jobs:
check-examples:
name: Check examples
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Install moreutils
run: sudo apt install moreutils
- name: Check examples
@@ -30,12 +35,13 @@ jobs:
weekNum: ${{ steps.get-week.outputs.WEEK }}
steps:
- name: Setup node
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }}
with:
- node-version: 14
+ node-version: 16
+ check-latest: true
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
fetch-depth: 25
@@ -64,7 +70,7 @@ jobs:
- id: get-week
run: echo ::set-output name=WEEK::$(date +%U)
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
id: cache-build
with:
path: ./*
@@ -75,12 +81,13 @@ jobs:
needs: build
steps:
- name: Setup node
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }}
with:
- node-version: 14
+ node-version: 16
+ check-latest: true
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
id: restore-build
with:
path: ./*
@@ -97,24 +104,24 @@ jobs:
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
profile: minimal
- toolchain: nightly-2022-02-23
+ toolchain: ${{ env.RUST_TOOLCHAIN }}
components: rustfmt, clippy
- name: Cache cargo registry
- uses: actions/cache@v2
+ uses: actions/cache@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
path: ~/.cargo/registry
key: stable-ubuntu-clippy-cargo-registry
- name: Cache cargo index
- uses: actions/cache@v2
+ uses: actions/cache@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
path: ~/.cargo/git
key: stable-ubuntu-clippy-cargo-index
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
id: restore-build
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
@@ -136,23 +143,24 @@ jobs:
NEXT_TELEMETRY_DISABLED: 1
steps:
- name: Setup node
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
- node-version: 14
+ node-version: 16
+ check-latest: true
# https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
run: sudo ethtool -K eth0 tx off rx off
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
- run: mv .git .git-bak
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
id: restore-build
with:
@@ -180,19 +188,20 @@ jobs:
NEXT_TEST_JOB: 1
steps:
- name: Setup node
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
- node-version: 14
+ node-version: 16
+ check-latest: true
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
id: restore-build
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- - uses: actions/download-artifact@v2
+ - uses: actions/download-artifact@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
name: next-swc-test-binary
@@ -211,13 +220,14 @@ jobs:
strategy:
fail-fast: false
matrix:
- node: [16, 17]
+ node: [16, 18]
steps:
- name: Setup node
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
node-version: ${{ matrix.node }}
+ check-latest: true
- run: echo ${{needs.build.outputs.docsChange}}
@@ -225,14 +235,14 @@ jobs:
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
id: restore-build
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- - uses: actions/download-artifact@v2
+ - uses: actions/download-artifact@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
name: next-swc-test-binary
@@ -247,7 +257,7 @@ jobs:
- name: Upload test trace
if: always()
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: test-trace
if-no-files-found: ignore
@@ -265,10 +275,11 @@ jobs:
NEXT_TEST_REACT_VERSION: ^17
steps:
- name: Setup node
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
node-version: 16
+ check-latest: true
- run: echo ${{needs.build.outputs.docsChange}}
@@ -276,14 +287,14 @@ jobs:
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
id: restore-build
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- - uses: actions/download-artifact@v2
+ - uses: actions/download-artifact@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
name: next-swc-test-binary
@@ -298,7 +309,7 @@ jobs:
- name: Upload test trace
if: always()
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: test-trace
if-no-files-found: ignore
@@ -316,13 +327,14 @@ jobs:
strategy:
fail-fast: false
matrix:
- node: [16, 17]
+ node: [16, 18]
steps:
- name: Setup node
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
node-version: ${{ matrix.node }}
+ check-latest: true
- run: echo ${{needs.build.outputs.docsChange}}
@@ -330,14 +342,14 @@ jobs:
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
id: restore-build
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- - uses: actions/download-artifact@v2
+ - uses: actions/download-artifact@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
name: next-swc-test-binary
@@ -352,7 +364,7 @@ jobs:
- name: Upload test trace
if: always()
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: test-trace
if-no-files-found: ignore
@@ -370,10 +382,11 @@ jobs:
NEXT_TEST_REACT_VERSION: ^17
steps:
- name: Setup node
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
node-version: 16
+ check-latest: true
- run: echo ${{needs.build.outputs.docsChange}}
@@ -381,14 +394,14 @@ jobs:
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
id: restore-build
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- - uses: actions/download-artifact@v2
+ - uses: actions/download-artifact@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
name: next-swc-test-binary
@@ -403,7 +416,7 @@ jobs:
- name: Upload test trace
if: always()
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: test-trace
if-no-files-found: ignore
@@ -421,13 +434,14 @@ jobs:
strategy:
fail-fast: false
matrix:
- node: [16, 17]
+ node: [16, 18]
steps:
- name: Setup node
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
node-version: ${{ matrix.node }}
+ check-latest: true
- run: echo ${{needs.build.outputs.docsChange}}
@@ -435,14 +449,14 @@ jobs:
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
id: restore-build
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- - uses: actions/download-artifact@v2
+ - uses: actions/download-artifact@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
name: next-swc-test-binary
@@ -465,10 +479,11 @@ jobs:
NEXT_TEST_REACT_VERSION: ^17
steps:
- name: Setup node
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
node-version: 16
+ check-latest: true
- run: echo ${{needs.build.outputs.docsChange}}
@@ -476,14 +491,14 @@ jobs:
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
id: restore-build
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- - uses: actions/download-artifact@v2
+ - uses: actions/download-artifact@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
name: next-swc-test-binary
@@ -506,13 +521,14 @@ jobs:
strategy:
fail-fast: false
matrix:
- node: [16, 17]
+ node: [16, 18]
steps:
- name: Setup node
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
node-version: ${{ matrix.node }}
+ check-latest: true
- run: echo ${{needs.build.outputs.docsChange}}
@@ -520,14 +536,14 @@ jobs:
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
id: restore-build
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- - uses: actions/download-artifact@v2
+ - uses: actions/download-artifact@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
name: next-swc-test-binary
@@ -548,12 +564,17 @@ jobs:
NEXT_TELEMETRY_DISABLED: 1
NEXT_TEST_JOB: 1
NEXT_TEST_REACT_VERSION: ^17
+ strategy:
+ fail-fast: false
+ matrix:
+ node: [16, 18]
steps:
- name: Setup node
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
node-version: ${{ matrix.node }}
+ check-latest: true
- run: echo ${{needs.build.outputs.docsChange}}
@@ -561,14 +582,14 @@ jobs:
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
id: restore-build
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- - uses: actions/download-artifact@v2
+ - uses: actions/download-artifact@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
name: next-swc-test-binary
@@ -595,10 +616,11 @@ jobs:
group: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]
steps:
- name: Setup node
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
- node-version: 14
+ node-version: 16
+ check-latest: true
- run: echo ${{needs.build.outputs.docsChange}}
@@ -606,7 +628,7 @@ jobs:
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
id: restore-build
with:
@@ -617,7 +639,7 @@ jobs:
with:
version: 6.32.2
- - uses: actions/download-artifact@v2
+ - uses: actions/download-artifact@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
name: next-swc-test-binary
@@ -631,7 +653,7 @@ jobs:
- name: Upload test trace
if: always()
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: test-trace
if-no-files-found: ignore
@@ -649,19 +671,20 @@ jobs:
TEST_ELECTRON: 1
steps:
- name: Setup node
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
- node-version: 14
+ node-version: 16
+ check-latest: true
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
id: restore-build
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- - uses: actions/download-artifact@v2
+ - uses: actions/download-artifact@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
name: next-swc-test-binary
@@ -699,18 +722,19 @@ jobs:
NEXT_TELEMETRY_DISABLED: 1
steps:
- name: Setup node
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
- node-version: 14
+ node-version: 16
+ check-latest: true
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
id: restore-build
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- - uses: actions/download-artifact@v2
+ - uses: actions/download-artifact@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
name: next-swc-test-binary
@@ -737,23 +761,24 @@ jobs:
BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
steps:
- name: Setup node
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
- node-version: 14
+ node-version: 16
+ check-latest: true
# https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
id: restore-build
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- - uses: actions/download-artifact@v2
+ - uses: actions/download-artifact@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
name: next-swc-test-binary
@@ -781,23 +806,24 @@ jobs:
BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
steps:
- name: Setup node
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
- node-version: 14
+ node-version: 16
+ check-latest: true
# https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
id: restore-build
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- - uses: actions/download-artifact@v2
+ - uses: actions/download-artifact@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
name: next-swc-test-binary
@@ -809,8 +835,8 @@ jobs:
- 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
+ testFirefoxNode18:
+ name: Test Firefox Node.js 18
runs-on: ubuntu-latest
needs: [build, testFirefox, build-native-test]
env:
@@ -818,18 +844,18 @@ jobs:
NEXT_TELEMETRY_DISABLED: 1
steps:
- name: Setup node
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
- node-version: 17
+ node-version: 18
check-latest: true
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
id: restore-build
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- - uses: actions/download-artifact@v2
+ - uses: actions/download-artifact@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
name: next-swc-test-binary
@@ -851,27 +877,28 @@ jobs:
NPM_TOKEN: ${{ secrets.NPM_TOKEN_ELEVATED }}
steps:
- name: Setup node
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
- node-version: 14
+ node-version: 16
+ check-latest: true
# https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
id: restore-build
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- - uses: actions/download-artifact@v2
+ - uses: actions/download-artifact@v3
with:
name: next-swc-binaries
path: packages/next-swc/native
- - uses: actions/download-artifact@v2
+ - uses: actions/download-artifact@v3
with:
name: wasm-binaries
path: packages/next-swc/crates/wasm
@@ -886,17 +913,18 @@ jobs:
needs: [publishRelease, build-native-test]
steps:
- name: Setup node
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
with:
- node-version: 14
+ node-version: 16
+ check-latest: true
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
id: restore-build
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- - uses: actions/download-artifact@v2
+ - uses: actions/download-artifact@v3
with:
name: next-swc-test-binary
path: packages/next-swc/native
@@ -916,7 +944,7 @@ jobs:
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
fetch-depth: 25
@@ -924,7 +952,7 @@ jobs:
id: docs-change
- name: Setup node
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }}
with:
node-version: 16
@@ -935,17 +963,17 @@ jobs:
if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }}
with:
profile: minimal
- toolchain: nightly-2022-02-23
+ toolchain: ${{ env.RUST_TOOLCHAIN }}
- name: Cache cargo registry
- uses: actions/cache@v2
+ uses: actions/cache@v3
if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }}
with:
path: ~/.cargo/registry
key: stable-ubuntu-18.04-cargo-registry
- name: Cache cargo index
- uses: actions/cache@v2
+ uses: actions/cache@v3
if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }}
with:
path: ~/.cargo/git
@@ -957,7 +985,7 @@ jobs:
- name: Turbo Cache
id: turbo-cache
- uses: actions/cache@v2
+ uses: actions/cache@v3
if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }}
with:
path: .turbo
@@ -972,7 +1000,7 @@ jobs:
# "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
+ uses: actions/cache@v3
with:
path: ./packages/next-swc/target
key: next-swc-cargo-cache-dev-ubuntu-18.04-${{ hashFiles('**/Cargo.lock') }}
@@ -981,7 +1009,7 @@ jobs:
# since the repo's dependencies aren't installed we need
# to install napi globally
- - run: npm i -g @napi-rs/cli@2.4.4 turbo@1.0.28
+ - run: npm i -g @napi-rs/cli@${{ env.NAPI_CLI_VERSION }} turbo@${{ env.TURBO_VERSION }}
- name: Build
if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }}
run: turbo run build-native --cache-dir=".turbo" -- --release
@@ -989,7 +1017,7 @@ jobs:
MACOSX_DEPLOYMENT_TARGET: '10.13'
- name: Upload artifact
- uses: actions/upload-artifact@v2.2.4
+ uses: actions/upload-artifact@v3
with:
name: next-swc-test-binary
path: packages/next-swc/native/next-swc.linux-x64-gnu.node
@@ -1005,7 +1033,7 @@ jobs:
runs-on: ubuntu-18.04
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
fetch-depth: 25
- run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'not-docs-only-change')
@@ -1014,7 +1042,7 @@ jobs:
if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }}
uses: actions-rs/toolchain@v1
with:
- toolchain: nightly-2022-02-23
+ toolchain: ${{ env.RUST_TOOLCHAIN }}
profile: minimal
- run: cd packages/next-swc && cargo test
if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }}
@@ -1025,14 +1053,14 @@ jobs:
needs: [build, build-native-test, build-wasm-dev]
steps:
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
id: restore-build
with:
path: ./*
key: ${{ github.sha }}
- - uses: actions/download-artifact@v2
+ - uses: actions/download-artifact@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
name: wasm-dev-binary
@@ -1041,7 +1069,7 @@ jobs:
- run: ls packages/next-swc/crates/wasm
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
- - uses: actions/download-artifact@v2
+ - uses: actions/download-artifact@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
name: next-swc-test-binary
@@ -1050,7 +1078,7 @@ jobs:
# node version needs to be 16+ to use --no-addons option
- name: Setup node
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
with:
node-version: 16
check-latest: true
@@ -1076,17 +1104,17 @@ jobs:
- host: macos-latest
target: 'x86_64-apple-darwin'
build: |
- npm i -g @napi-rs/cli@2.4.4 turbo@1.0.28
+ npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" "turbo@${TURBO_VERSION}"
turbo run build-native --cache-dir=".turbo" -- --release
strip -x packages/next-swc/native/next-swc.*.node
- host: windows-latest
build: |
- npm i -g @napi-rs/cli@2.4.4 turbo@1.0.28
+ npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" "turbo@${TURBO_VERSION}"
turbo run build-native --cache-dir=".turbo" -- --release
target: 'x86_64-pc-windows-msvc'
- host: windows-latest
build: |
- npm i -g @napi-rs/cli@2.4.4 turbo@1.0.28
+ npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" "turbo@${TURBO_VERSION}"
turbo run build-native --cache-dir=".turbo" -- --release --target i686-pc-windows-msvc
target: 'i686-pc-windows-msvc'
- host: ubuntu-latest
@@ -1095,10 +1123,10 @@ jobs:
# Node.js in Baidu need to compatible with `GLIBC_2.12`
build: >-
set -e &&
- rustup toolchain install nightly-2022-02-23 &&
- rustup default nightly-2022-02-23 &&
+ rustup toolchain install "${RUST_TOOLCHAIN}" &&
+ rustup default "${RUST_TOOLCHAIN}" &&
rustup target add x86_64-unknown-linux-gnu &&
- npm i -g @napi-rs/cli@2.4.4 turbo@1.0.28 &&
+ npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" "turbo@${TURBO_VERSION}" &&
turbo run build-native --cache-dir=".turbo" -- --release --target x86_64-unknown-linux-gnu --zig --zig-abi-suffix 2.12 &&
llvm-strip -x packages/next-swc/native/next-swc.*.node
- host: ubuntu-latest
@@ -1106,10 +1134,10 @@ jobs:
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
build: >-
set -e &&
- rustup toolchain install nightly-2022-02-23 &&
- rustup default nightly-2022-02-23 &&
+ rustup toolchain install "${RUST_TOOLCHAIN}" &&
+ rustup default "${RUST_TOOLCHAIN}" &&
rustup target add x86_64-unknown-linux-musl &&
- npm i -g @napi-rs/cli@2.4.4 turbo@1.0.28 &&
+ npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" "turbo@${TURBO_VERSION}" &&
turbo run build-native --cache-dir=".turbo" -- --release --target x86_64-unknown-linux-musl &&
strip packages/next-swc/native/next-swc.*.node
- host: macos-latest
@@ -1120,7 +1148,7 @@ jobs:
export CXX=$(xcrun -f clang++);
SYSROOT=$(xcrun --sdk macosx --show-sdk-path);
export CFLAGS="-isysroot $SYSROOT -isystem $SYSROOT";
- npm i -g @napi-rs/cli@2.4.4 turbo@1.0.28
+ npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" "turbo@${TURBO_VERSION}"
turbo run build-native --cache-dir=".turbo" -- --release --target aarch64-apple-darwin
strip -x packages/next-swc/native/next-swc.*.node
- host: ubuntu-latest
@@ -1128,10 +1156,10 @@ jobs:
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine-zig
build: >-
set -e &&
- rustup toolchain install nightly-2022-02-23 &&
- rustup default nightly-2022-02-23 &&
+ rustup toolchain install "${RUST_TOOLCHAIN}" &&
+ rustup default "${RUST_TOOLCHAIN}" &&
rustup target add aarch64-unknown-linux-gnu &&
- npm i -g @napi-rs/cli@2.4.4 turbo@1.0.28 &&
+ npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" "turbo@${TURBO_VERSION}" &&
turbo run build-native --cache-dir=".turbo" -- --release --target aarch64-unknown-linux-gnu --zig --zig-abi-suffix 2.12 &&
llvm-strip -x packages/next-swc/native/next-swc.*.node
- host: ubuntu-18.04
@@ -1140,7 +1168,7 @@ jobs:
sudo apt-get update
sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf -y
build: |
- npm i -g @napi-rs/cli@2.4.4 turbo@1.0.28
+ npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" "turbo@${TURBO_VERSION}"
turbo run build-native --cache-dir=".turbo" -- --release --target armv7-unknown-linux-gnueabihf
arm-linux-gnueabihf-strip packages/next-swc/native/next-swc.*.node
- host: ubuntu-latest
@@ -1150,7 +1178,7 @@ jobs:
export CC="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang"
export CXX="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang++"
export PATH="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin:${PATH}"
- npm i -g @napi-rs/cli@2.4.4 turbo@1.0.28
+ npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" "turbo@${TURBO_VERSION}"
turbo run build-native --cache-dir=".turbo" -- --release --target aarch64-linux-android
${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android-strip packages/next-swc/native/next-swc.*.node
- host: ubuntu-latest
@@ -1160,7 +1188,7 @@ jobs:
export CC="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi24-clang"
export CXX="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi24-clang++"
export PATH="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin:${PATH}"
- npm i -g @napi-rs/cli@2.4.4 turbo@1.0.28
+ npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" "turbo@${TURBO_VERSION}"
turbo run build-native --cache-dir=".turbo" -- --release --target armv7-linux-androideabi
${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/arm-linux-androideabi-strip packages/next-swc/native/next-swc.*.node
- host: ubuntu-latest
@@ -1168,16 +1196,16 @@ jobs:
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
build: >-
set -e &&
- npm i -g @napi-rs/cli@2.4.4 turbo@1.0.28 &&
- rustup toolchain install nightly-2022-02-23 &&
- rustup default nightly-2022-02-23 &&
+ npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" "turbo@${TURBO_VERSION}" &&
+ rustup toolchain install "${RUST_TOOLCHAIN}" &&
+ rustup default "${RUST_TOOLCHAIN}" &&
rustup target add aarch64-unknown-linux-musl &&
turbo run build-native --cache-dir=".turbo" -- --release --target aarch64-unknown-linux-musl &&
llvm-strip -x packages/next-swc/native/next-swc.*.node
- host: windows-latest
target: 'aarch64-pc-windows-msvc'
build: |
- npm i -g @napi-rs/cli@2.4.4 turbo@1.0.28
+ npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" "turbo@${TURBO_VERSION}"
turbo run build-native --cache-dir=".turbo" -- --release --target aarch64-pc-windows-msvc
if: ${{ needs.build.outputs.isRelease == 'true' }}
needs: build
@@ -1199,13 +1227,13 @@ jobs:
if: ${{ matrix.settings.host == 'macos-latest' }}
# we use checkout here instead of the build cache since
# it can fail to restore in different OS'
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
# 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
+ uses: actions/cache@v3
with:
path: ./packages/next-swc/target
key: next-swc-cargo-cache-${{ matrix.settings.target }}--${{ hashFiles('**/Cargo.lock') }}
@@ -1213,7 +1241,7 @@ jobs:
next-swc-cargo-cache-${{ matrix.settings.target }}
- name: Turbo Cache
id: turbo-cache
- uses: actions/cache@v2
+ uses: actions/cache@v3
with:
path: .turbo
key: turbo-${{ github.job }}-${{ github.ref_name }}-${{ matrix.settings.target }}-${{ needs.build.outputs.weekNum }}-${{ github.sha }}
@@ -1223,7 +1251,7 @@ jobs:
turbo-${{ github.job }}-canary-${{ matrix.settings.target }}-${{ needs.build.outputs.weekNum }}-
- name: Setup node
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
if: ${{ !matrix.settings.docker }}
with:
node-version: 16
@@ -1236,17 +1264,17 @@ jobs:
with:
profile: minimal
override: true
- toolchain: nightly-2022-02-23
+ toolchain: ${{ env.RUST_TOOLCHAIN }}
target: ${{ matrix.settings.target }}
- name: Cache cargo registry
- uses: actions/cache@v2
+ uses: actions/cache@v3
with:
path: ~/.cargo/registry
key: ${{ matrix.settings.target }}-cargo-registry
- name: Cache cargo index
- uses: actions/cache@v2
+ uses: actions/cache@v3
with:
path: ~/.cargo/git
key: ${{ matrix.settings.target }}-cargo-index
@@ -1261,7 +1289,7 @@ jobs:
if: ${{ matrix.settings.docker }}
with:
image: ${{ matrix.settings.docker }}
- options: -v ${{ env.HOME }}/.cargo/git:/root/.cargo/git -v ${{ env.HOME }}/.cargo/registry:/root/.cargo/registry -v ${{ github.workspace }}:/build -w /build
+ options: -e RUST_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }} -e NAPI_CLI_VERSION=${{ env.NAPI_CLI_VERSION }} -e TURBO_VERSION=${{ env.TURBO_VERSION }} -v ${{ env.HOME }}/.cargo/git:/root/.cargo/git -v ${{ env.HOME }}/.cargo/registry:/root/.cargo/registry -v ${{ github.workspace }}:/build -w /build
run: ${{ matrix.settings.build }}
- name: 'Build'
@@ -1270,7 +1298,7 @@ jobs:
shell: bash
- name: Upload artifact
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: next-swc-binaries
path: packages/next-swc/native/next-swc.*.node
@@ -1283,30 +1311,31 @@ jobs:
target: [web, nodejs]
runs-on: ubuntu-latest
steps:
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
id: restore-build
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- name: Setup node
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
with:
- node-version: 14
+ node-version: 16
+ check-latest: true
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
- toolchain: nightly-2022-02-23
+ toolchain: ${{ env.RUST_TOOLCHAIN }}
override: true
target: wasm32-unknown-unknown
- - run: npm i -g turbo@1.0.28
+ - run: npm i -g turbo@${{ env.TURBO_VERSION }}
- name: Turbo cache
id: turbo-cache
- uses: actions/cache@v2
+ uses: actions/cache@v3
with:
path: .turbo
key: turbo-${{ github.job }}-${{ matrix.target }}-${{ github.ref_name }}-${{ needs.build.outputs.weekNum }}-${{ github.sha }}
@@ -1325,7 +1354,7 @@ jobs:
run: '[[ -d "packages/next-swc/crates/wasm/pkg" ]] && mv packages/next-swc/crates/wasm/pkg packages/next-swc/crates/wasm/pkg-${{ matrix.target }} || ls packages/next-swc/crates/wasm'
- name: Upload artifact
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: wasm-binaries
path: packages/next-swc/crates/wasm/pkg-*
@@ -1334,7 +1363,7 @@ jobs:
needs: build
runs-on: ubuntu-latest
steps:
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
id: restore-build
with:
@@ -1343,24 +1372,25 @@ jobs:
- name: Setup node
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
with:
- node-version: 14
+ node-version: 16
+ check-latest: true
- - run: npm i -g turbo@1.0.28
+ - run: npm i -g turbo@${{ env.TURBO_VERSION }}
- name: Install Rust
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
uses: actions-rs/toolchain@v1
with:
profile: minimal
- toolchain: nightly-2022-02-23
+ toolchain: ${{ env.RUST_TOOLCHAIN }}
override: true
target: wasm32-unknown-unknown
- name: Turbo Cache
id: turbo-cache
- uses: actions/cache@v2
+ uses: actions/cache@v3
if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }}
with:
path: .turbo
@@ -1384,7 +1414,7 @@ jobs:
- name: Upload artifact
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: wasm-dev-binary
path: packages/next-swc/crates/wasm/pkg-nodejs
@@ -1406,12 +1436,13 @@ jobs:
if: needs.check-trace-secrests.outputs.trace-api-key == 'true'
steps:
- name: Setup node
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
- node-version: 14
+ node-version: 16
+ check-latest: true
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
id: restore-build
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
@@ -1426,7 +1457,7 @@ jobs:
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
run: echo ${GIT_SHORT_SHA}
- - uses: actions/download-artifact@v2
+ - uses: actions/download-artifact@v3
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
name: next-swc-test-binary
diff --git a/.github/workflows/pull_request_stats.yml b/.github/workflows/pull_request_stats.yml
index 0d1e32362409..26e43bfe2a7e 100644
--- a/.github/workflows/pull_request_stats.yml
+++ b/.github/workflows/pull_request_stats.yml
@@ -13,7 +13,7 @@ jobs:
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
fetch-depth: 25
@@ -21,10 +21,10 @@ jobs:
id: docs-change
- name: Setup node
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }}
with:
- node-version: 14
+ node-version: 16
check-latest: true
- name: Install
@@ -54,7 +54,7 @@ jobs:
- name: Turbo Cache
id: turbo-cache
- uses: actions/cache@v2
+ uses: actions/cache@v3
if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }}
with:
path: .turbo
@@ -68,7 +68,7 @@ jobs:
# "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
+ uses: actions/cache@v3
with:
path: ./packages/next-target
key: next-swc-cargo-cache-ubuntu-18.04--${{ hashFiles('**/Cargo.lock') }}
@@ -77,8 +77,8 @@ jobs:
# since the repo's dependencies aren't installed we need
# to install napi globally
- - run: npm i -g @napi-rs/cli@1.2.1
- - run: npm i -g turbo@1.0.28
+ - run: npm i -g @napi-rs/cli@2.7.0
+ - run: npm i -g turbo@1.2.6
- name: Build
if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }}
@@ -90,7 +90,7 @@ jobs:
TURBO_PROJECT: nextjs
- name: Upload artifact
- uses: actions/upload-artifact@v2.2.4
+ uses: actions/upload-artifact@v3
with:
name: next-swc-dev-binary
path: packages/next-swc/native/next-swc.linux-x64-gnu.node
@@ -106,14 +106,14 @@ jobs:
needs: build-native-dev
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
fetch-depth: 25
- 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
+ - uses: actions/download-artifact@v3
if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }}
with:
name: next-swc-dev-binary
diff --git a/.github/workflows/test_react_experimental.yml b/.github/workflows/test_react_experimental.yml
index 8b56f79f4d9b..79b3381bcdd0 100644
--- a/.github/workflows/test_react_experimental.yml
+++ b/.github/workflows/test_react_experimental.yml
@@ -9,7 +9,7 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- run: yarn install --frozen-lockfile --check-files
env:
@@ -19,7 +19,7 @@ jobs:
- run: node run-tests.js --timings --write-timings -g 1/1
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
id: cache-build
with:
path: ./*
@@ -38,7 +38,7 @@ jobs:
matrix:
group: [1, 2, 3, 4, 5, 6]
steps:
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
id: restore-build
with:
path: ./*
diff --git a/.github/workflows/test_react_next.yml b/.github/workflows/test_react_next.yml
index 4411c0a521a5..e44f28170e73 100644
--- a/.github/workflows/test_react_next.yml
+++ b/.github/workflows/test_react_next.yml
@@ -9,7 +9,7 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- run: yarn install --frozen-lockfile --check-files
env:
@@ -19,7 +19,7 @@ jobs:
- run: node run-tests.js --timings --write-timings -g 1/1
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
id: cache-build
with:
path: ./*
@@ -38,7 +38,7 @@ jobs:
matrix:
group: [1, 2, 3, 4, 5, 6]
steps:
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
id: restore-build
with:
path: ./*
diff --git a/docs/api-reference/data-fetching/get-server-side-props.md b/docs/api-reference/data-fetching/get-server-side-props.md
index d7e7c1ee6ef5..8eed5bffa2e3 100644
--- a/docs/api-reference/data-fetching/get-server-side-props.md
+++ b/docs/api-reference/data-fetching/get-server-side-props.md
@@ -31,7 +31,7 @@ You can import modules in top-level scope for use in `getServerSideProps`. Impor
The `context` parameter is an object containing the following keys:
- `params`: If this page uses a [dynamic route](/docs/routing/dynamic-routes.md), `params` contains the route parameters. If the page name is `[id].js` , then `params` will look like `{ id: ... }`.
-- `req`: [The `HTTP` IncomingMessage object](https://nodejs.org/api/http.html#http_class_http_incomingmessage).
+- `req`: [The `HTTP` IncomingMessage object](https://nodejs.org/api/http.html#http_class_http_incomingmessage), with an additional `cookies` prop, which is an object with string keys mapping to string values of cookies.
- `res`: [The `HTTP` response object](https://nodejs.org/api/http.html#http_class_http_serverresponse).
- `query`: An object representing the query string.
- `preview`: `preview` is `true` if the page is in the [Preview Mode](/docs/advanced-features/preview-mode.md) and `false` otherwise.
diff --git a/docs/api-reference/next.config.js/custom-webpack-config.md b/docs/api-reference/next.config.js/custom-webpack-config.md
index 6b4535c7e80f..b58065f3b46f 100644
--- a/docs/api-reference/next.config.js/custom-webpack-config.md
+++ b/docs/api-reference/next.config.js/custom-webpack-config.md
@@ -36,6 +36,7 @@ The second argument to the `webpack` function is an object with the following pr
- `buildId`: `String` - The build id, used as a unique identifier between builds
- `dev`: `Boolean` - Indicates if the compilation will be done in development
- `isServer`: `Boolean` - It's `true` for server-side compilation, and `false` for client-side compilation
+- `nextRuntime`: `String` - The target runtime for server-side compilation; either `"edge"` or `"nodejs"`
- `defaultLoaders`: `Object` - Default loaders used internally by Next.js:
- `babel`: `Object` - Default `babel-loader` configuration
diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md
index dedee45bfb57..043f2487963f 100644
--- a/docs/api-reference/next/image.md
+++ b/docs/api-reference/next/image.md
@@ -16,6 +16,7 @@ description: Enable Image Optimization with the built-in Image component.
| Version | Changes |
| --------- | ----------------------------------------------------------------------------------------------------- |
+| `v12.1.7` | Experimental `remotePatterns` configuration added. |
| `v12.1.1` | `style` prop added. Experimental[\*](#experimental-raw-layout-mode) support for `layout="raw"` added. |
| `v12.1.0` | `dangerouslyAllowSVG` and `contentSecurityPolicy` configuration added. |
| `v12.0.9` | `lazyRoot` prop added. |
@@ -313,9 +314,64 @@ Other properties on the ` ` component will be passed to the underlying
## Configuration Options
+### Remote Patterns
+
+> Note: The `remotePatterns` configuration is currently **experimental** and subject to change. Please use [`domains`](#domains) for production use cases.
+
+To protect your application from malicious users, configuration is required in order to use external images. This ensures that only external images from your account can be served from the Next.js Image Optimization API. These external images can be configured with the `remotePatterns` property in your `next.config.js` file, as shown below:
+
+```js
+module.exports = {
+ experimental: {
+ images: {
+ remotePatterns: [
+ {
+ protocol: 'https',
+ hostname: 'example.com',
+ port: '',
+ pathname: '/account123/**',
+ },
+ ],
+ },
+ },
+}
+```
+
+> Note: The example above will ensure the `src` property of `next/image` must start with `https://example.com/account123/`. Any other protocol, hostname, port, or unmatched path will respond with 400 Bad Request.
+
+Below is another example of the `remotePatterns` property in the `next.config.js` file:
+
+```js
+module.exports = {
+ experimental: {
+ images: {
+ remotePatterns: [
+ {
+ protocol: 'https',
+ hostname: '**.example.com',
+ },
+ ],
+ },
+ },
+}
+```
+
+> Note: The example above will ensure the `src` property of `next/image` must start with `https://img1.example.com` or `https://me.avatar.example.com` or any number of subdomains. Any other protocol or unmatched hostname will respond with 400 Bad Request.
+
+Wildcard patterns can be used for both `pathname` and `hostname` and have the following syntax:
+
+- `*` match a single path segment or subdomain
+- `**` match any number of path segments at the end or subdomains at the beginning
+
+The `**` syntax does not work in the middle of the pattern.
+
### Domains
-To protect your application from malicious users, you must define a list of image provider domains that you want to be served from the Next.js Image Optimization API. This is configured in with the `domains` property in your `next.config.js` file, as shown below:
+Similar to [`remotePatterns`](#remote-patterns), the `domains` configuration can be used to provide a list of allowed hostnames for external images.
+
+However, the `domains` configuration does not support wildcard pattern matching and it cannot restrict protocol, port, or pathname.
+
+Below is an example of the `domains` property in the `next.config.js` file:
```js
module.exports = {
diff --git a/docs/basic-features/image-optimization.md b/docs/basic-features/image-optimization.md
index 0c55837410ec..16c75ffe5533 100644
--- a/docs/basic-features/image-optimization.md
+++ b/docs/basic-features/image-optimization.md
@@ -66,7 +66,7 @@ function Home() {
### Remote Images
-To use a remote image, the `src` property should be a URL string, which can be [relative](#loaders) or [absolute](#domains). Because Next.js does not have access to remote files during the build process, you'll need to provide the [`width`](/docs/api-reference/next/image.md#width), [`height`](/docs/api-reference/next/image.md#height) and optional [`blurDataURL`](/docs/api-reference/next/image.md#blurdataurl) props manually:
+To use a remote image, the `src` property should be a URL string, which can be [relative](#loaders) or [absolute](/docs/api-reference/next/image.md#domains). Because Next.js does not have access to remote files during the build process, you'll need to provide the [`width`](/docs/api-reference/next/image.md#width), [`height`](/docs/api-reference/next/image.md#height) and optional [`blurDataURL`](/docs/api-reference/next/image.md#blurdataurl) props manually:
```jsx
import Image from 'next/image'
@@ -93,15 +93,9 @@ export default function Home() {
Sometimes you may want to access a remote image, but still use the built-in Next.js Image Optimization API. To do this, leave the `loader` at its default setting and enter an absolute URL for the Image `src`.
-To protect your application from malicious users, you must define a list of remote domains that you intend to access this way. This is configured in your `next.config.js` file, as shown below:
+To protect your application from malicious users, you must define a list of remote hostnames you intend to allow remote access.
-```js
-module.exports = {
- images: {
- domains: ['example.com', 'example2.com'],
- },
-}
-```
+> Learn more about [`domains`](/docs/api-reference/next/image.md#domains) configuration.
### Loaders
@@ -207,7 +201,7 @@ For examples of the Image component used with the various fill modes, see the [I
## Configuration
-The `next/image` component and Next.js Image Optimization API can be configured in the [`next.config.js` file](/docs/api-reference/next.config.js/introduction.md). These configurations allow you to [enable remote domains](/docs/api-reference/next/image.md#domains), [define custom image breakpoints](/docs/api-reference/next/image.md#device-sizes), [change caching behavior](/docs/api-reference/next/image.md#caching-behavior) and more.
+The `next/image` component and Next.js Image Optimization API can be configured in the [`next.config.js` file](/docs/api-reference/next.config.js/introduction.md). These configurations allow you to [enable remote images](/docs/api-reference/next/image.md#domains), [define custom image breakpoints](/docs/api-reference/next/image.md#device-sizes), [change caching behavior](/docs/api-reference/next/image.md#caching-behavior) and more.
[**Read the full image configuration documentation for more information.**](/docs/api-reference/next/image.md#configuration-options)
diff --git a/docs/basic-features/layouts.md b/docs/basic-features/layouts.md
index 1bc5c1aa9250..9d4894db0a94 100644
--- a/docs/basic-features/layouts.md
+++ b/docs/basic-features/layouts.md
@@ -102,11 +102,10 @@ When using TypeScript, you must first create a new type for your pages which inc
import type { ReactElement } from 'react'
import Layout from '../components/layout'
import NestedLayout from '../components/nested-layout'
+import type { NextPageWithLayout } from './_app'
-export default function Page() {
- return {
- /** Your content */
- }
+const Page: NextPageWithLayout = () => {
+ return
hello world
}
Page.getLayout = function getLayout(page: ReactElement) {
@@ -116,6 +115,8 @@ Page.getLayout = function getLayout(page: ReactElement) {
)
}
+
+export default Page
```
```tsx
@@ -125,7 +126,7 @@ import type { ReactElement, ReactNode } from 'react'
import type { NextPage } from 'next'
import type { AppProps } from 'next/app'
-type NextPageWithLayout = NextPage & {
+export type NextPageWithLayout = NextPage & {
getLayout?: (page: ReactElement) => ReactNode
}
diff --git a/docs/basic-features/script.md b/docs/basic-features/script.md
index cb3905ce4800..4491d251f3a4 100644
--- a/docs/basic-features/script.md
+++ b/docs/basic-features/script.md
@@ -247,7 +247,7 @@ The `id` property is required for **inline scripts** in order for Next.js to tra
### Executing Code After Loading (`onLoad`)
-> **Note: Both `onLoad` and `onError` can't be used with the `beforeInteractive` loading strategy.**
+> **Note: `onLoad` and `onError` cannot be used with the `beforeInteractive` loading strategy.**
Some third-party scripts require users to run JavaScript code after the script has finished loading in order to instantiate content or call a function. If you are loading a script with either `afterInteractive` or `lazyOnload` as a loading strategy, you can execute code after it has loaded using the `onLoad` property:
diff --git a/docs/testing.md b/docs/testing.md
index 551933ad4d7d..11163588bbc5 100644
--- a/docs/testing.md
+++ b/docs/testing.md
@@ -263,10 +263,10 @@ npx create-next-app@latest --example with-jest with-jest-app
Since the release of [Next.js 12](https://nextjs.org/blog/next-12), Next.js now has built-in configuration for Jest.
-To set up Jest, install `jest` , `@testing-library/react`, `@testing-library/jest-dom`:
+To set up Jest, install `jest`, `jest-environment-jsdom`, `@testing-library/react`, `@testing-library/jest-dom`:
```bash
-npm install --save-dev jest @testing-library/react @testing-library/jest-dom
+npm install --save-dev jest jest-environment-jsdom @testing-library/react @testing-library/jest-dom
```
Create a `jest.config.js` file in your project's root directory and add the following:
diff --git a/errors/invalid-images-config.md b/errors/invalid-images-config.md
index b409f6056830..8a03674a31df 100644
--- a/errors/invalid-images-config.md
+++ b/errors/invalid-images-config.md
@@ -17,6 +17,8 @@ module.exports = {
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
// limit of 50 domains values
domains: [],
+ // limit of 50 objects
+ remotePatterns: [],
// path prefix for Image Optimization API, useful with `loader`
path: '/_next/image',
// loader can be 'default', 'imgix', 'cloudinary', 'akamai', or 'custom'
diff --git a/errors/no-cache.md b/errors/no-cache.md
index b3b0214ca31f..3ef035538512 100644
--- a/errors/no-cache.md
+++ b/errors/no-cache.md
@@ -78,7 +78,7 @@ cache:
Using GitHub's [actions/cache](https://github.com/actions/cache), add the following step in your workflow file:
```yaml
-uses: actions/cache@v2
+uses: actions/cache@v3
with:
# See here for caching with `yarn` https://github.com/actions/cache/blob/main/examples.md#node---yarn or you can leverage caching with actions/setup-node https://github.com/actions/setup-node
path: |
diff --git a/examples/cms-buttercms/.env.local.example b/examples/cms-buttercms/.env.local.example
index ea6dea901406..07198f69da10 100644
--- a/examples/cms-buttercms/.env.local.example
+++ b/examples/cms-buttercms/.env.local.example
@@ -1,3 +1 @@
-# Copy this file as .env.local
-BUTTERCMS_API_KEY=
-BUTTERCMS_PREVIEW_SECRET=
\ No newline at end of file
+NEXT_PUBLIC_BUTTER_CMS_API_KEY=your_auth_token
\ No newline at end of file
diff --git a/examples/cms-buttercms/README.md b/examples/cms-buttercms/README.md
index bf57965882e9..8e1ed65bdd2d 100644
--- a/examples/cms-buttercms/README.md
+++ b/examples/cms-buttercms/README.md
@@ -1,16 +1,20 @@
-# A statically generated blog example using Next.js and ButterCMS
+# A fully-functional, drop-in proof-of-concept Next.js app using ButterCMS
+
+This Next.js starter project fully integrates with dynamic sample content from your ButterCMS account, including main menu, pages, blog posts, categories, and tags, all with a beautiful, custom theme with already-implemented search functionality. All of the included sample content is automatically created in your account dashboard when you sign up for a free trial of ButterCMS.
+
+A copy of this starter project can be easily and quickly deployed to Vercel with the click of a button.
This example showcases Next.js's [Static Generation](https://nextjs.org/docs/basic-features/pages) feature using [ButterCMS](https://buttercms.com/) as the data source.
## Demo
-[https://next-blog-buttercms.vercel.app/](https://next-blog-buttercms.vercel.app/)
+Check out our live demo: [https://nextjs-starter-buttercms.vercel.app/](https://nextjs-starter-buttercms.vercel.app/)
## Deploy your own
-Once you have access to [the environment variables you'll need](#step-2-set-up-environment-variables), deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
+Once you have access to your Butter API token, you can deploy your Butterized proof-of-concept app to Vercel, the creators of Next.js, and spread your love of Butter. By clicking the button below, you'll create a copy of our starter project in your Git provider account, instantly deploy it, and institute a full content workflow connected to your ButterCMS account. Smooth.
-[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/cms-buttercms&project-name=cms-buttercms&repository-name=cms-buttercms&env=BUTTERCMS_API_KEY,BUTTERCMS_PREVIEW_SECRET&envDescription=Required%20to%20connect%20the%20app%20with%20ButterCMS&envLink=https://vercel.link/buttercms-env)
+[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FButterCMS%2Fnextjs-starter-buttercms&env=NEXT_PUBLIC_BUTTER_CMS_API_KEY&envDescription=Your%20ButterCMS%20API%20Token&envLink=https%3A%2F%2Fbuttercms.com%2Fsettings%2F&project-name=nextjs-starter-buttercms&repo-name=nextjs-starter-buttercms&redirect-url=https%3A%2F%2Fbuttercms.com%2Fonboarding%2Fvercel-starter-deploy-callback%2F&production-deploy-hook=Deploy%20Triggered%20from%20ButterCMS&demo-title=ButterCMS%20Next.js%20Starter&demo-description=Fully%20integrated%20with%20your%20ButterCMS%20account&demo-url=https%3A%2F%2Fnextjs-starter-buttercms.vercel.app%2F&demo-image=https://cdn.buttercms.com/r0tGK8xFRti2iRKBJ0eY&repository-name=nextjs-starter-buttercms)
### Related examples
@@ -33,6 +37,18 @@ Once you have access to [the environment variables you'll need](#step-2-set-up-e
## How to use
+### Option 1. Install via Github and NPM or Yarn
+
+First, install the dependencies by cloning the repo and running one of the following commands, depending on your current setup:
+
+```bash
+git clone https://github.com/ButterCMS/nextjs-starter-buttercms.git
+cd nextjs-starter-buttercms
+npm install # or yarn install
+```
+
+### Option 2. Install via Create-Next-App
+
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
```bash
@@ -49,7 +65,7 @@ pnpm create next-app -- --example cms-buttercms cms-buttercms-app
First, [create an account on ButterCMS](https://buttercms.com/).
-After signing up, you’ll be presented with the API key. We’ll use this in the next step.
+After signing up, you’ll be presented with your free API token. We’ll use this in the next step.
### Step 2. Set up environment variables
@@ -61,12 +77,12 @@ cp .env.local.example .env.local
Then set each variable on `.env.local`:
-- `BUTTERCMS_API_KEY` should be set as the API key.
-- `BUTTERCMS_PREVIEW_SECRET` can be any random string (but avoid spaces), like `MY_SECRET` - this is used for [Preview Mode](https://nextjs.org/docs/advanced-features/preview-mode).
+- `NEXT_PUBLIC_BUTTER_CMS_API_KEY` should be set as the API key.
### Step 3. Run Next.js in development mode
-When you sign up to ButterCMS, it creates an example blog post automatically. You can run Next.js in development mode to view a blog containing this example post.
+When you sign up for ButterCMS, it creates all of the example content used by this starter project. You can run Next.js in development mode to view your fully-functional starter project, including landing page with
+API-based components, API-based menu, and a blog.
```bash
npm install
@@ -78,44 +94,39 @@ yarn install
yarn dev
```
-Your blog should be up and running on [http://localhost:3000](http://localhost:3000)! If it doesn't work, post on [GitHub discussions](https://github.com/vercel/next.js/discussions).
+Your starter project should be up and running on [http://localhost:3000](http://localhost:3000)! If it doesn't work, post on [GitHub discussions](https://github.com/vercel/next.js/discussions).
### Step 4. Try preview mode
+Your starter project is automatically configured to show draft changes saved in your Butter account when run locally or deploy to a hosting provider. To disable this behavior, set the following value in your `.env.local` file: `PREVIEW=false`.
+
To try preview mode, [create a blog post](https://buttercms.com/post/):
- Set the **Title** as `Draft Post Test`.
- Fill the content and summary with dummy text.
-- Set the **Featured Image** by downloading some image from [Unsplash](https://unsplash.com/).
Most importantly, **do not publish** the blog post. Instead, click **Save Draft**.
-Now, if you go to the post page on localhost, you won't see this post because it’s not published. However, if you use the **Preview Mode**, you'll be able to see the change ([Documentation](https://nextjs.org/docs/advanced-features/preview-mode)).
-
-To enable the Preview Mode, go to this URL:
-
-```
-http://localhost:3000/api/preview?secret=&slug=draft-post-test
-```
-
-- `` should be the string you entered for `BUTTERCMS_PREVIEW_SECRET`.
+If you have not already, set `PREVIEW=false` in your `.env.local` file and restart your local
+development server.
-You should now be able to see the draft post. To exit the preview mode, you can click **Click here to exit preview mode** at the top.
+Now, if you go to the your blog list view page on localhost: [http://localhost:3000/#blog](http://localhost:3000/#blog), you won't see this post, as its status has not yet been updated to `published`. However, if you use **Preview Mode** by deleting `PREVIEW=false` from your `.env.local` file, your new post will appear ([Documentation](https://nextjs.org/docs/advanced-features/preview-mode)).
-**Tip**: [You can set the preview URL on ButterCMS](https://buttercms.com/kb/preview-urls).
+**Tip**: [You can set a preview URL on ButterCMS](https://buttercms.com/kb/preview-urls) for pages
+deployed to Vercel, allowing you to live-preview changes on the web from within your Butter account! Sweet!
### Step 5. Deploy on Vercel
You can deploy this app to the cloud with [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
-#### Deploy Your Local Project
+#### Deploy from our template
-To deploy your local project to Vercel, push it to GitHub/GitLab/Bitbucket and [import to Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example).
+If you want to deploy a copy of our starter to Vercel without any changes, you can just click this button:
-**Important**: When you import your project on Vercel, make sure to click on **Environment Variables** and set them to match your `.env.local` file.
+[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FButterCMS%2Fnextjs-starter-buttercms&env=NEXT_PUBLIC_BUTTER_CMS_API_KEY&envDescription=Your%20ButterCMS%20API%20Token&envLink=https%3A%2F%2Fbuttercms.com%2Fsettings%2F&project-name=nextjs-starter-buttercms&repo-name=nextjs-starter-buttercms&redirect-url=https%3A%2F%2Fbuttercms.com%2Fonboarding%2Fvercel-starter-deploy-callback%2F&production-deploy-hook=Deploy%20Triggered%20from%20ButterCMS&demo-title=ButterCMS%20Next.js%20Starter&demo-description=Fully%20integrated%20with%20your%20ButterCMS%20account&demo-url=https%3A%2F%2Fnextjs-starter-buttercms.vercel.app%2F&demo-image=https://cdn.buttercms.com/r0tGK8xFRti2iRKBJ0eY&repository-name=nextjs-starter-buttercms)
-#### Deploy from Our Template
+#### Deploy your local project
-Alternatively, you can deploy using our template by clicking on the Deploy button below.
+To deploy your local project to Vercel (one in which you've made changes), push it to GitHub/GitLab/Bitbucket and [import to Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example).
-[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/cms-buttercms&project-name=cms-buttercms&repository-name=cms-buttercms&env=BUTTERCMS_API_KEY,BUTTERCMS_PREVIEW_SECRET&envDescription=Required%20to%20connect%20the%20app%20with%20ButterCMS&envLink=https://vercel.link/buttercms-env)
+**Important**: When you import your project on Vercel, make sure to click on **Environment Variables** and set them to match your `.env.local` file.
diff --git a/examples/cms-buttercms/app.json b/examples/cms-buttercms/app.json
new file mode 100644
index 000000000000..4d4c60d77be7
--- /dev/null
+++ b/examples/cms-buttercms/app.json
@@ -0,0 +1,18 @@
+{
+ "name": "ButterCMS NextJS Starter Project ",
+ "description": "Drop-in proof-of-concept NextJs app, fully integrated with your ButterCMS account.",
+ "repository": "https://github.com/ButterCMS/nextjs-starter-buttercms",
+ "logo": "https://cdn.buttercms.com/R3fbtvoRT2CqEQSmk8hb",
+ "keywords": ["Next.js", "buttercms", "cms", "blog"],
+ "buildpacks": [
+ {
+ "url": "heroku/nodejs"
+ }
+ ],
+ "env": {
+ "NEXT_PUBLIC_BUTTER_CMS_API_KEY": {
+ "description": "The API token of your ButterCMS account",
+ "value": ""
+ }
+ }
+}
diff --git a/examples/cms-buttercms/components/alert.js b/examples/cms-buttercms/components/alert.js
deleted file mode 100644
index 051f3319649e..000000000000
--- a/examples/cms-buttercms/components/alert.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import Container from './container'
-import cn from 'classnames'
-import { EXAMPLE_PATH } from '@/lib/constants'
-
-export default function Alert({ preview }) {
- return (
-
-
-
- {preview ? (
- <>
- This is page is a preview.{' '}
-
- Click here
- {' '}
- to exit preview mode.
- >
- ) : (
- <>
- The source code for this blog is{' '}
-
- available on GitHub
-
- .
- >
- )}
-
-
-
- )
-}
diff --git a/examples/cms-buttercms/components/author-card.js b/examples/cms-buttercms/components/author-card.js
new file mode 100644
index 000000000000..da25b5e222d6
--- /dev/null
+++ b/examples/cms-buttercms/components/author-card.js
@@ -0,0 +1,7 @@
+export default function AuthorCard({ author }) {
+ return (
+
+ {author.first_name} {author.last_name}
+
+ )
+}
diff --git a/examples/cms-buttercms/components/avatar.js b/examples/cms-buttercms/components/avatar.js
deleted file mode 100644
index 6777f5031359..000000000000
--- a/examples/cms-buttercms/components/avatar.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import Image from 'next/image'
-
-export default function Avatar({ name, picture }) {
- return (
-
- )
-}
diff --git a/examples/cms-buttercms/components/blog/blog.js b/examples/cms-buttercms/components/blog/blog.js
new file mode 100644
index 000000000000..3f2706685192
--- /dev/null
+++ b/examples/cms-buttercms/components/blog/blog.js
@@ -0,0 +1,42 @@
+import Link from 'next/link'
+
+import PostPreviewCondensed from './post-preview-condensed'
+
+export default function Blog({ posts }) {
+ return (
+
+
+
+
+
+
Latest Blog Posts
+
+ Butter also has a built in blog engine which makes it dead
+ simple to launch a new company blog.
+
+
+
+ View All Blog Posts
+
+
+
+
+
+
+ {posts.map((post) => (
+
+ ))}
+
+
+
+ )
+}
diff --git a/examples/cms-buttercms/components/blog/categories-widget.js b/examples/cms-buttercms/components/blog/categories-widget.js
new file mode 100644
index 000000000000..4b409eebc9b1
--- /dev/null
+++ b/examples/cms-buttercms/components/blog/categories-widget.js
@@ -0,0 +1,18 @@
+import Link from 'next/link'
+
+export default function CategoriesWidget({ categories }) {
+ return (
+
+ )
+}
diff --git a/examples/cms-buttercms/components/blog/post-preview-condensed.js b/examples/cms-buttercms/components/blog/post-preview-condensed.js
new file mode 100644
index 000000000000..a788c7df6f3b
--- /dev/null
+++ b/examples/cms-buttercms/components/blog/post-preview-condensed.js
@@ -0,0 +1,40 @@
+import Link from 'next/link'
+import Image from 'next/image'
+
+export default function PostPreviewCondensed({
+ title,
+ coverImage,
+ coverImageAlt,
+ excerpt,
+ slug,
+}) {
+ return (
+
+
+ {coverImage && (
+
+
+
+ )}
+
+
+
+
+ )
+}
diff --git a/examples/cms-buttercms/components/blog/post-preview.js b/examples/cms-buttercms/components/blog/post-preview.js
new file mode 100644
index 000000000000..24cd6e9d9143
--- /dev/null
+++ b/examples/cms-buttercms/components/blog/post-preview.js
@@ -0,0 +1,67 @@
+import Image from 'next/image'
+import Link from 'next/link'
+
+import HumanDate from '@/components/human-date'
+import AuthorCard from '@/components/author-card'
+
+export default function PostsPreview({
+ title,
+ coverImage,
+ coverImageAlt,
+ date,
+ author,
+ tags,
+ excerpt,
+ slug,
+}) {
+ return (
+
+
+
+
+
+
+
+
+
+ {' '}
+
+
+ {tags.map((tag) => (
+
+
+
+ {tag.name}
+
+
+
+ ))}
+
+
+ {coverImage && (
+
+
+
+ )}
+
+
+
+
+ )
+}
diff --git a/examples/cms-buttercms/components/blog/posts-list.js b/examples/cms-buttercms/components/blog/posts-list.js
new file mode 100644
index 000000000000..59a8245ba31e
--- /dev/null
+++ b/examples/cms-buttercms/components/blog/posts-list.js
@@ -0,0 +1,24 @@
+import PostsPreview from './post-preview'
+
+export default function PostsList({ posts }) {
+ return (
+
+
+ {posts.map((post) => (
+
+ ))}
+ {!posts.length &&
No blog posts found.
}
+
+
+ )
+}
diff --git a/examples/cms-buttercms/components/blog/search-widget.js b/examples/cms-buttercms/components/blog/search-widget.js
new file mode 100644
index 000000000000..da498214b278
--- /dev/null
+++ b/examples/cms-buttercms/components/blog/search-widget.js
@@ -0,0 +1,15 @@
+export default function SearchWidget() {
+ return (
+
+
+
Search This Site
+
+
+
+ )
+}
diff --git a/examples/cms-buttercms/components/container.js b/examples/cms-buttercms/components/container.js
deleted file mode 100644
index fc1c29dfb074..000000000000
--- a/examples/cms-buttercms/components/container.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function Container({ children }) {
- return {children}
-}
diff --git a/examples/cms-buttercms/components/cover-image.js b/examples/cms-buttercms/components/cover-image.js
deleted file mode 100644
index 0acaa9c23b99..000000000000
--- a/examples/cms-buttercms/components/cover-image.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import Image from 'next/image'
-import Link from 'next/link'
-import cn from 'classnames'
-
-export default function CoverImage({ title, url, slug }) {
- const image = (
-
- )
-
- return (
-
- {slug ? (
-
-
{image}
-
- ) : (
- image
- )}
-
- )
-}
diff --git a/examples/cms-buttercms/components/date.js b/examples/cms-buttercms/components/date.js
deleted file mode 100644
index eac5681378bf..000000000000
--- a/examples/cms-buttercms/components/date.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import { parseISO, format } from 'date-fns'
-
-export default function Date({ dateString }) {
- const date = parseISO(dateString)
- return {format(date, 'LLLL d, yyyy')}
-}
diff --git a/examples/cms-buttercms/components/footer-section.js b/examples/cms-buttercms/components/footer-section.js
new file mode 100644
index 000000000000..e421538f0b6a
--- /dev/null
+++ b/examples/cms-buttercms/components/footer-section.js
@@ -0,0 +1,78 @@
+import Image from 'next/image'
+
+export default function FooterSection({ mainMenu }) {
+ const links = mainMenu.map((link) => ({
+ ...link,
+ url: link.url[0] === '#' ? `/${link.url}` : link.url,
+ }))
+
+ return (
+
+
+
+
+
+
+
+ ButterCMS is your content backend. Build better with Butter.
+
+
+
+
+
+
+
+
+
Subscribe Newsletter
+
+
+
+
+
+
+ )
+}
diff --git a/examples/cms-buttercms/components/footer.js b/examples/cms-buttercms/components/footer.js
deleted file mode 100644
index b305c3eb40b0..000000000000
--- a/examples/cms-buttercms/components/footer.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import Container from './container'
-import { EXAMPLE_PATH } from '@/lib/constants'
-
-export default function Footer() {
- return (
-
-
-
-
- Statically Generated with Next.js.
-
-
-
-
-
- )
-}
diff --git a/examples/cms-buttercms/components/header-section.js b/examples/cms-buttercms/components/header-section.js
new file mode 100644
index 000000000000..1a88a8b4e142
--- /dev/null
+++ b/examples/cms-buttercms/components/header-section.js
@@ -0,0 +1,51 @@
+import { useEffect, useState, useRef } from 'react'
+
+import Image from 'next/image'
+
+import MainMenu from './main-menu/main-menu'
+
+export default function HeaderSection({ mainMenu }) {
+ const [isNavbarSticky, setIsNavbarSticky] = useState(false)
+ const navbarAreaEl = useRef(null)
+
+ function fixNavBar() {
+ if (navbarAreaEl.current) {
+ setIsNavbarSticky(window.pageYOffset > navbarAreaEl.current.offsetTop)
+ }
+ }
+
+ useEffect(() => {
+ window.addEventListener('scroll', fixNavBar)
+
+ return () => {
+ window.removeEventListener('scroll', fixNavBar)
+ }
+ }, [])
+
+ return (
+
+ )
+}
diff --git a/examples/cms-buttercms/components/header.js b/examples/cms-buttercms/components/header.js
deleted file mode 100644
index 562e7e3eebb6..000000000000
--- a/examples/cms-buttercms/components/header.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import Link from 'next/link'
-
-export default function Header() {
- return (
-
-
- Blog
-
- .
-
- )
-}
diff --git a/examples/cms-buttercms/components/hero-post.js b/examples/cms-buttercms/components/hero-post.js
deleted file mode 100644
index f45ed8853b12..000000000000
--- a/examples/cms-buttercms/components/hero-post.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import Avatar from './avatar'
-import Date from './date'
-import CoverImage from './cover-image'
-import Link from 'next/link'
-
-export default function HeroPost({
- title,
- coverImage,
- date,
- excerpt,
- author,
- slug,
-}) {
- return (
-
- )
-}
diff --git a/examples/cms-buttercms/components/human-date.js b/examples/cms-buttercms/components/human-date.js
new file mode 100644
index 000000000000..59a52989ad3d
--- /dev/null
+++ b/examples/cms-buttercms/components/human-date.js
@@ -0,0 +1,6 @@
+import { parseISO, format } from 'date-fns'
+
+export default function HumanDate({ dateString }) {
+ const date = parseISO(dateString)
+ return {format(date, 'MMM d, yyyy')}
+}
diff --git a/examples/cms-buttercms/components/intro.js b/examples/cms-buttercms/components/intro.js
deleted file mode 100644
index c3003c619664..000000000000
--- a/examples/cms-buttercms/components/intro.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import { CMS_NAME, CMS_URL } from '@/lib/constants'
-
-export default function Intro() {
- return (
-
- )
-}
diff --git a/examples/cms-buttercms/components/landing-page-sections/feature.js b/examples/cms-buttercms/components/landing-page-sections/feature.js
new file mode 100644
index 000000000000..68ce10e56d13
--- /dev/null
+++ b/examples/cms-buttercms/components/landing-page-sections/feature.js
@@ -0,0 +1,17 @@
+import Image from 'next/image'
+
+export default function Feature({ headline, description, icon }) {
+ return (
+
+
+
+ {icon && }
+
+
+
{headline}
+
{description}
+
+
+
+ )
+}
diff --git a/examples/cms-buttercms/components/landing-page-sections/features.js b/examples/cms-buttercms/components/landing-page-sections/features.js
new file mode 100644
index 000000000000..fa70e7f05273
--- /dev/null
+++ b/examples/cms-buttercms/components/landing-page-sections/features.js
@@ -0,0 +1,36 @@
+import Feature from './feature'
+
+export default function Features({
+ headline,
+ subheadline,
+ features,
+ scrollAnchorId,
+}) {
+ return (
+
+ )
+}
diff --git a/examples/cms-buttercms/components/landing-page-sections/hero.js b/examples/cms-buttercms/components/landing-page-sections/hero.js
new file mode 100644
index 000000000000..f9d267be2ee7
--- /dev/null
+++ b/examples/cms-buttercms/components/landing-page-sections/hero.js
@@ -0,0 +1,43 @@
+import Image from 'next/image'
+
+export default function Hero({
+ headline,
+ subheadline,
+ image,
+ buttonLabel,
+ buttonUrl,
+ scrollAnchorId,
+}) {
+ return (
+
+ )
+}
diff --git a/examples/cms-buttercms/components/landing-page-sections/landing-page-section.js b/examples/cms-buttercms/components/landing-page-sections/landing-page-section.js
new file mode 100644
index 000000000000..fa9ed572086b
--- /dev/null
+++ b/examples/cms-buttercms/components/landing-page-sections/landing-page-section.js
@@ -0,0 +1,50 @@
+import dynamic from 'next/dynamic'
+
+import camelcaseKeys from 'camelcase-keys'
+
+import Preloader from '@/components/preloader'
+import MissingSection from './missing-section'
+
+export default function LandingPageSection({ type, sectionData }) {
+ const sectionsComponentPaths = () => ({
+ hero: dynamic(
+ () =>
+ import('@/components/landing-page-sections/hero').catch(
+ () => () => MissingSection
+ ),
+ {
+ loading: Preloader,
+ }
+ ),
+ two_column_with_image: dynamic(
+ () =>
+ import(
+ '@/components/landing-page-sections/two-column-with-image'
+ ).catch(() => () => MissingSection),
+ {
+ loading: Preloader,
+ }
+ ),
+ features: dynamic(
+ () =>
+ import('@/components/landing-page-sections/features').catch(
+ () => () => MissingSection
+ ),
+ {
+ loading: Preloader,
+ }
+ ),
+ testimonials: dynamic(
+ () =>
+ import('@/components/landing-page-sections/testimonials').catch(
+ () => () => MissingSection
+ ),
+ {
+ loading: Preloader,
+ }
+ ),
+ })
+ const SectionComponent = sectionsComponentPaths()[type] || MissingSection
+
+ return
+}
diff --git a/examples/cms-buttercms/components/landing-page-sections/missing-section.js b/examples/cms-buttercms/components/landing-page-sections/missing-section.js
new file mode 100644
index 000000000000..0feed8dd83f1
--- /dev/null
+++ b/examples/cms-buttercms/components/landing-page-sections/missing-section.js
@@ -0,0 +1,10 @@
+export default function MissingSection({ type, ...sectionData }) {
+ console.log(`Missing section ${type} data ${sectionData}`)
+
+ return (
+
+
Missing a template for {type}
+
Check console for component details
+
+ )
+}
diff --git a/examples/cms-buttercms/components/landing-page-sections/testimonial.js b/examples/cms-buttercms/components/landing-page-sections/testimonial.js
new file mode 100644
index 000000000000..602551243f5c
--- /dev/null
+++ b/examples/cms-buttercms/components/landing-page-sections/testimonial.js
@@ -0,0 +1,16 @@
+export default function Testimonial({ quote, name, title }) {
+ return (
+
+ )
+}
diff --git a/examples/cms-buttercms/components/landing-page-sections/testimonials.js b/examples/cms-buttercms/components/landing-page-sections/testimonials.js
new file mode 100644
index 000000000000..fe163ae7cba4
--- /dev/null
+++ b/examples/cms-buttercms/components/landing-page-sections/testimonials.js
@@ -0,0 +1,57 @@
+import { useEffect } from 'react'
+
+import { tns } from 'tiny-slider'
+
+import Testimonial from './testimonial'
+
+export default function Testimonials({
+ headline,
+ testimonial: testimonials,
+ scrollAnchorId,
+}) {
+ useEffect(() => {
+ tns({
+ container: '.testimonial-active',
+ autoplay: true,
+ autoplayTimeout: 5000,
+ autoplayButtonOutput: false,
+ mouseDrag: true,
+ gutter: 0,
+ nav: false,
+ navPosition: 'bottom',
+ controls: true,
+ controlsText: [
+ ' ',
+ ' ',
+ ],
+ items: 1,
+ })
+ })
+
+ return (
+
+ )
+}
diff --git a/examples/cms-buttercms/components/landing-page-sections/two-column-with-image.js b/examples/cms-buttercms/components/landing-page-sections/two-column-with-image.js
new file mode 100644
index 000000000000..87e3a7943551
--- /dev/null
+++ b/examples/cms-buttercms/components/landing-page-sections/two-column-with-image.js
@@ -0,0 +1,60 @@
+import Image from 'next/image'
+
+export default function TwoColumnWithImage({
+ headline,
+ subheadline,
+ buttonLabel,
+ buttonUrl,
+ image,
+ imagePosition,
+ scrollAnchorId,
+}) {
+ return (
+
+ )
+}
diff --git a/examples/cms-buttercms/components/layout.js b/examples/cms-buttercms/components/layout.js
deleted file mode 100644
index 399802b095aa..000000000000
--- a/examples/cms-buttercms/components/layout.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import Alert from './alert'
-import Footer from './footer'
-import Meta from './meta'
-
-export default function Layout({ preview, children }) {
- return (
- <>
-
-
-
- >
- )
-}
diff --git a/examples/cms-buttercms/components/main-menu/main-menu-link.js b/examples/cms-buttercms/components/main-menu/main-menu-link.js
new file mode 100644
index 000000000000..a4e941864de2
--- /dev/null
+++ b/examples/cms-buttercms/components/main-menu/main-menu-link.js
@@ -0,0 +1,9 @@
+export default function MainMenuLink({ url, label, active, callbackOnClick }) {
+ return (
+
+
+ {label}
+
+
+ )
+}
diff --git a/examples/cms-buttercms/components/main-menu/main-menu.js b/examples/cms-buttercms/components/main-menu/main-menu.js
new file mode 100644
index 000000000000..67263cacd07d
--- /dev/null
+++ b/examples/cms-buttercms/components/main-menu/main-menu.js
@@ -0,0 +1,95 @@
+import { useEffect, useState, useRef } from 'react'
+
+import MainMenuLink from './main-menu-link'
+
+export default function ManiMenu({ mainMenuLinks }) {
+ const [activeMenuLink, setActiveMenuLink] = useState(
+ mainMenuLinks.length ? mainMenuLinks[0].url : ''
+ )
+
+ function highlightLinks() {
+ const sections = document.querySelectorAll('.page-scroll')
+ const scrollPos =
+ window.pageYOffset ||
+ document.documentElement.scrollTop ||
+ document.body.scrollTop
+
+ sections.forEach((currLink) => {
+ const val = currLink.getAttribute('href').slice(1)
+ if (val[0] !== '#') {
+ return
+ }
+ const refElement = document.querySelector(val)
+
+ if (!refElement) {
+ return
+ }
+
+ const scrollTopMinus = scrollPos + 73
+
+ if (
+ refElement.offsetTop <= scrollTopMinus &&
+ refElement.offsetTop + refElement.offsetHeight > scrollTopMinus
+ ) {
+ setActiveMenuLink(val)
+ }
+ })
+ }
+
+ useEffect(() => {
+ window.addEventListener('scroll', highlightLinks)
+
+ return () => {
+ window.removeEventListener('scroll', highlightLinks)
+ }
+ }, [])
+
+ const [isMenuActive, setMenuActive] = useState(false)
+ const menuLinksEl = useRef(null)
+
+ function inactivateMenu() {
+ setMenuActive(false)
+ if (menuLinksEl.current) {
+ menuLinksEl.current.classList.remove('show')
+ }
+ }
+
+ return (
+ <>
+ setMenuActive(!isMenuActive)}
+ >
+
+
+
+
+
+
+
+
+ {mainMenuLinks.map((navLink) => (
+
+ ))}
+
+
+
+ >
+ )
+}
diff --git a/examples/cms-buttercms/components/markdown-styles.module.css b/examples/cms-buttercms/components/markdown-styles.module.css
deleted file mode 100644
index 95d4f8b04172..000000000000
--- a/examples/cms-buttercms/components/markdown-styles.module.css
+++ /dev/null
@@ -1,18 +0,0 @@
-.markdown {
- @apply text-lg leading-relaxed;
-}
-
-.markdown p,
-.markdown ul,
-.markdown ol,
-.markdown blockquote {
- @apply my-6;
-}
-
-.markdown h2 {
- @apply text-3xl mt-12 mb-4 leading-snug;
-}
-
-.markdown h3 {
- @apply text-2xl mt-8 mb-4 leading-snug;
-}
diff --git a/examples/cms-buttercms/components/meta.js b/examples/cms-buttercms/components/meta.js
deleted file mode 100644
index cec622742089..000000000000
--- a/examples/cms-buttercms/components/meta.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import Head from 'next/head'
-import { CMS_NAME, HOME_OG_IMAGE_URL } from '@/lib/constants'
-
-export default function Meta() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
diff --git a/examples/cms-buttercms/components/missing-token-section.js b/examples/cms-buttercms/components/missing-token-section.js
new file mode 100644
index 000000000000..1f799acba4ed
--- /dev/null
+++ b/examples/cms-buttercms/components/missing-token-section.js
@@ -0,0 +1,38 @@
+import Image from 'next/image'
+
+export default function MissingTokenSection() {
+ return (
+
+
+
+
+
+
Configure your ButterCMS API Token
+
+ Please add your API token to .env
file as{' '}
+ NEXT_PUBLIC_BUTTER_CMS_API_KEY
.
+
+
+ Get your free API token
+
+
+
+
+
+
+
+ )
+}
diff --git a/examples/cms-buttercms/components/more-stories.js b/examples/cms-buttercms/components/more-stories.js
deleted file mode 100644
index b12d7771a65a..000000000000
--- a/examples/cms-buttercms/components/more-stories.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import PostPreview from './post-preview'
-
-export default function MoreStories({ posts }) {
- return (
-
-
- More Stories
-
-
- {posts.map((post) => (
-
- ))}
-
-
- )
-}
diff --git a/examples/cms-buttercms/components/post-body.js b/examples/cms-buttercms/components/post-body.js
deleted file mode 100644
index 4cc2bb52bcce..000000000000
--- a/examples/cms-buttercms/components/post-body.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import markdownStyles from './markdown-styles.module.css'
-
-export default function PostBody({ content }) {
- return (
-
- )
-}
diff --git a/examples/cms-buttercms/components/post-header.js b/examples/cms-buttercms/components/post-header.js
deleted file mode 100644
index 05d86b92c205..000000000000
--- a/examples/cms-buttercms/components/post-header.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import Avatar from '../components/avatar'
-import Date from '../components/date'
-import CoverImage from '../components/cover-image'
-import PostTitle from '../components/post-title'
-
-export default function PostHeader({ title, coverImage, date, author }) {
- return (
- <>
- {title}
-
-
-
-
-
- >
- )
-}
diff --git a/examples/cms-buttercms/components/post-preview.js b/examples/cms-buttercms/components/post-preview.js
deleted file mode 100644
index 175ac094ffcf..000000000000
--- a/examples/cms-buttercms/components/post-preview.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import Avatar from './avatar'
-import Date from './date'
-import CoverImage from './cover-image'
-import Link from 'next/link'
-
-export default function PostPreview({
- title,
- coverImage,
- date,
- excerpt,
- author,
- slug,
-}) {
- return (
-
-
-
-
-
-
-
-
-
{excerpt}
-
-
- )
-}
diff --git a/examples/cms-buttercms/components/post-title.js b/examples/cms-buttercms/components/post-title.js
deleted file mode 100644
index edd8cba65c25..000000000000
--- a/examples/cms-buttercms/components/post-title.js
+++ /dev/null
@@ -1,7 +0,0 @@
-export default function PostTitle({ children }) {
- return (
-
- {children}
-
- )
-}
diff --git a/examples/cms-buttercms/components/preloader.js b/examples/cms-buttercms/components/preloader.js
new file mode 100644
index 000000000000..d20c82d70b93
--- /dev/null
+++ b/examples/cms-buttercms/components/preloader.js
@@ -0,0 +1,20 @@
+export default function Preloader() {
+ return (
+
+ )
+}
diff --git a/examples/cms-buttercms/components/scroll-to-top-button.js b/examples/cms-buttercms/components/scroll-to-top-button.js
new file mode 100644
index 000000000000..cc5da38f8d05
--- /dev/null
+++ b/examples/cms-buttercms/components/scroll-to-top-button.js
@@ -0,0 +1,29 @@
+import { useEffect, useState } from 'react'
+
+export default function ScrollToButtonButton() {
+ const [hasScrollToTopButton, setHasScrollToTopButton] = useState(false)
+
+ function toggleScrollTopButton() {
+ setHasScrollToTopButton(
+ document.body.scrollTop > 50 || document.documentElement.scrollTop > 50
+ )
+ }
+
+ useEffect(() => {
+ window.addEventListener('scroll', toggleScrollTopButton)
+
+ return () => {
+ window.removeEventListener('scroll', toggleScrollTopButton)
+ }
+ }, [])
+
+ return (
+ <>
+ {hasScrollToTopButton && (
+
+
+
+ )}
+ >
+ )
+}
diff --git a/examples/cms-buttercms/components/section-separator.js b/examples/cms-buttercms/components/section-separator.js
deleted file mode 100644
index 4ca5c65fdc6e..000000000000
--- a/examples/cms-buttercms/components/section-separator.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function SectionSeparator() {
- return
-}
diff --git a/examples/cms-buttercms/css/fonts/LineIcons.eot b/examples/cms-buttercms/css/fonts/LineIcons.eot
new file mode 100644
index 000000000000..4cdecea4ce19
Binary files /dev/null and b/examples/cms-buttercms/css/fonts/LineIcons.eot differ
diff --git a/examples/cms-buttercms/css/fonts/LineIcons.svg b/examples/cms-buttercms/css/fonts/LineIcons.svg
new file mode 100644
index 000000000000..3b39e8c49781
--- /dev/null
+++ b/examples/cms-buttercms/css/fonts/LineIcons.svg
@@ -0,0 +1,1616 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/cms-buttercms/css/fonts/LineIcons.ttf b/examples/cms-buttercms/css/fonts/LineIcons.ttf
new file mode 100644
index 000000000000..f0a9570b3ad7
Binary files /dev/null and b/examples/cms-buttercms/css/fonts/LineIcons.ttf differ
diff --git a/examples/cms-buttercms/css/fonts/LineIcons.woff b/examples/cms-buttercms/css/fonts/LineIcons.woff
new file mode 100644
index 000000000000..e6013c729b39
Binary files /dev/null and b/examples/cms-buttercms/css/fonts/LineIcons.woff differ
diff --git a/examples/cms-buttercms/css/fonts/LineIcons.woff2 b/examples/cms-buttercms/css/fonts/LineIcons.woff2
new file mode 100644
index 000000000000..e147716323ed
Binary files /dev/null and b/examples/cms-buttercms/css/fonts/LineIcons.woff2 differ
diff --git a/examples/cms-buttercms/css/lineicons.css b/examples/cms-buttercms/css/lineicons.css
new file mode 100644
index 000000000000..8b733f83e81a
--- /dev/null
+++ b/examples/cms-buttercms/css/lineicons.css
@@ -0,0 +1,2214 @@
+/*--------------------------------
+
+LineIcons Web Font
+Author: lineicons.com
+
+-------------------------------- */
+@font-face {
+ font-family: 'LineIcons';
+ src: url('fonts/LineIcons.eot');
+ src: url('fonts/LineIcons.eot') format('embedded-opentype'),
+ url('fonts/LineIcons.woff2') format('woff2'),
+ url('fonts/LineIcons.woff') format('woff'),
+ url('fonts/LineIcons.ttf') format('truetype'),
+ url('fonts/LineIcons.svg') format('svg');
+ font-weight: normal;
+ font-style: normal;
+}
+/*------------------------
+ base class definition
+-------------------------*/
+.lni {
+ display: inline-block;
+ font: normal normal normal 1em/1 'LineIcons';
+ color: inherit;
+ flex-shrink: 0;
+ speak: none;
+ text-transform: none;
+ line-height: 1;
+ vertical-align: -0.125em;
+ /* Better Font Rendering */
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+/*------------------------
+ change icon size
+-------------------------*/
+/* relative units */
+.lni-sm {
+ font-size: 0.8em;
+}
+.lni-lg {
+ font-size: 1.2em;
+}
+/* absolute units */
+.lni-16 {
+ font-size: 16px;
+}
+.lni-32 {
+ font-size: 32px;
+}
+
+/*------------------------
+ spinning icons
+-------------------------*/
+.lni-is-spinning {
+ animation: lni-spin 1s infinite linear;
+}
+@keyframes lni-spin {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+/*------------------------
+ rotated/flipped icons
+-------------------------*/
+.lni-rotate-90 {
+ transform: rotate(90deg);
+}
+.lni-rotate-180 {
+ transform: rotate(180deg);
+}
+.lni-rotate-270 {
+ transform: rotate(270deg);
+}
+.lni-flip-y {
+ transform: scaleY(-1);
+}
+.lni-flip-x {
+ transform: scaleX(-1);
+}
+/*------------------------
+ icons
+-------------------------*/
+
+.lni-500px::before {
+ content: '\ea03';
+}
+
+.lni-add-files::before {
+ content: '\ea01';
+}
+
+.lni-adobe::before {
+ content: '\ea06';
+}
+
+.lni-agenda::before {
+ content: '\ea02';
+}
+
+.lni-airbnb::before {
+ content: '\ea07';
+}
+
+.lni-alarm-clock::before {
+ content: '\ea08';
+}
+
+.lni-alarm::before {
+ content: '\ea04';
+}
+
+.lni-amazon-original::before {
+ content: '\ea05';
+}
+
+.lni-amazon-pay::before {
+ content: '\ea09';
+}
+
+.lni-amazon::before {
+ content: '\ea0a';
+}
+
+.lni-ambulance::before {
+ content: '\ea0b';
+}
+
+.lni-amex::before {
+ content: '\ea0c';
+}
+
+.lni-anchor::before {
+ content: '\ea0d';
+}
+
+.lni-android-original::before {
+ content: '\ea0e';
+}
+
+.lni-android::before {
+ content: '\ea0f';
+}
+
+.lni-angellist::before {
+ content: '\ea10';
+}
+
+.lni-angle-double-down::before {
+ content: '\ea11';
+}
+
+.lni-angle-double-left::before {
+ content: '\ea12';
+}
+
+.lni-angle-double-right::before {
+ content: '\ea13';
+}
+
+.lni-angle-double-up::before {
+ content: '\ea14';
+}
+
+.lni-angular::before {
+ content: '\ea15';
+}
+
+.lni-apartment::before {
+ content: '\ea16';
+}
+
+.lni-app-store::before {
+ content: '\ea17';
+}
+
+.lni-apple-music::before {
+ content: '\ea18';
+}
+
+.lni-apple-pay::before {
+ content: '\ea19';
+}
+
+.lni-apple::before {
+ content: '\ea1a';
+}
+
+.lni-archive::before {
+ content: '\ea1f';
+}
+
+.lni-arrow-down-circle::before {
+ content: '\ea1b';
+}
+
+.lni-arrow-down::before {
+ content: '\ea1c';
+}
+
+.lni-arrow-left-circle::before {
+ content: '\ea1d';
+}
+
+.lni-arrow-left::before {
+ content: '\ea1e';
+}
+
+.lni-arrow-right-circle::before {
+ content: '\ea20';
+}
+
+.lni-arrow-right::before {
+ content: '\ea21';
+}
+
+.lni-arrow-top-left::before {
+ content: '\ea22';
+}
+
+.lni-arrow-top-right::before {
+ content: '\ea23';
+}
+
+.lni-arrow-up-circle::before {
+ content: '\ea24';
+}
+
+.lni-arrow-up::before {
+ content: '\ea25';
+}
+
+.lni-arrows-horizontal::before {
+ content: '\ea26';
+}
+
+.lni-arrows-vertical::before {
+ content: '\ea27';
+}
+
+.lni-atlassian::before {
+ content: '\ea28';
+}
+
+.lni-aws::before {
+ content: '\ea29';
+}
+
+.lni-azure::before {
+ content: '\ea2a';
+}
+
+.lni-backward::before {
+ content: '\ea2b';
+}
+
+.lni-baloon::before {
+ content: '\ea2c';
+}
+
+.lni-ban::before {
+ content: '\ea2d';
+}
+
+.lni-bar-chart::before {
+ content: '\ea2e';
+}
+
+.lni-basketball::before {
+ content: '\ea2f';
+}
+
+.lni-behance-original::before {
+ content: '\ea30';
+}
+
+.lni-behance::before {
+ content: '\ea31';
+}
+
+.lni-bi-cycle::before {
+ content: '\ea32';
+}
+
+.lni-bitbucket::before {
+ content: '\ea33';
+}
+
+.lni-bitcoin::before {
+ content: '\ea34';
+}
+
+.lni-blackboard::before {
+ content: '\ea35';
+}
+
+.lni-blogger::before {
+ content: '\ea36';
+}
+
+.lni-bluetooth-original::before {
+ content: '\ea37';
+}
+
+.lni-bluetooth::before {
+ content: '\ea38';
+}
+
+.lni-bold::before {
+ content: '\ea39';
+}
+
+.lni-bolt-alt::before {
+ content: '\ea3a';
+}
+
+.lni-bolt::before {
+ content: '\ea40';
+}
+
+.lni-book::before {
+ content: '\ea3b';
+}
+
+.lni-bookmark-alt::before {
+ content: '\ea3c';
+}
+
+.lni-bookmark::before {
+ content: '\ea3d';
+}
+
+.lni-bootstrap::before {
+ content: '\ea3e';
+}
+
+.lni-bricks::before {
+ content: '\ea3f';
+}
+
+.lni-bridge::before {
+ content: '\ea41';
+}
+
+.lni-briefcase::before {
+ content: '\ea42';
+}
+
+.lni-brush-alt::before {
+ content: '\ea43';
+}
+
+.lni-brush::before {
+ content: '\ea44';
+}
+
+.lni-btc::before {
+ content: '\ea45';
+}
+
+.lni-bubble::before {
+ content: '\ea46';
+}
+
+.lni-bug::before {
+ content: '\ea47';
+}
+
+.lni-bulb::before {
+ content: '\ea48';
+}
+
+.lni-bullhorn::before {
+ content: '\ea49';
+}
+
+.lni-burger::before {
+ content: '\ea4a';
+}
+
+.lni-bus::before {
+ content: '\ea4b';
+}
+
+.lni-cake::before {
+ content: '\ea4c';
+}
+
+.lni-calculator::before {
+ content: '\ea4d';
+}
+
+.lni-calendar::before {
+ content: '\ea4e';
+}
+
+.lni-camera::before {
+ content: '\ea4f';
+}
+
+.lni-candy-cane::before {
+ content: '\ea50';
+}
+
+.lni-candy::before {
+ content: '\ea51';
+}
+
+.lni-capsule::before {
+ content: '\ea52';
+}
+
+.lni-car-alt::before {
+ content: '\ea53';
+}
+
+.lni-car::before {
+ content: '\ea54';
+}
+
+.lni-caravan::before {
+ content: '\ea55';
+}
+
+.lni-cart-full::before {
+ content: '\ea56';
+}
+
+.lni-cart::before {
+ content: '\ea57';
+}
+
+.lni-certificate::before {
+ content: '\ea58';
+}
+
+.lni-check-box::before {
+ content: '\ea59';
+}
+
+.lni-checkmark-circle::before {
+ content: '\ea5a';
+}
+
+.lni-checkmark::before {
+ content: '\ea5b';
+}
+
+.lni-chef-hat::before {
+ content: '\ea5c';
+}
+
+.lni-chevron-down-circle::before {
+ content: '\ea5d';
+}
+
+.lni-chevron-down::before {
+ content: '\ea5e';
+}
+
+.lni-chevron-left-circle::before {
+ content: '\ea5f';
+}
+
+.lni-chevron-left::before {
+ content: '\ea60';
+}
+
+.lni-chevron-right-circle::before {
+ content: '\ea61';
+}
+
+.lni-chevron-right::before {
+ content: '\ea62';
+}
+
+.lni-chevron-up-circle::before {
+ content: '\ea63';
+}
+
+.lni-chevron-up::before {
+ content: '\ea64';
+}
+
+.lni-chrome::before {
+ content: '\ea65';
+}
+
+.lni-chromecast::before {
+ content: '\ea66';
+}
+
+.lni-circle-minus::before {
+ content: '\ea67';
+}
+
+.lni-circle-plus::before {
+ content: '\ea68';
+}
+
+.lni-clipboard::before {
+ content: '\ea69';
+}
+
+.lni-close::before {
+ content: '\ea6a';
+}
+
+.lni-cloud-check::before {
+ content: '\ea6b';
+}
+
+.lni-cloud-download::before {
+ content: '\ea6c';
+}
+
+.lni-cloud-network::before {
+ content: '\ea6d';
+}
+
+.lni-cloud-sync::before {
+ content: '\ea6e';
+}
+
+.lni-cloud-upload::before {
+ content: '\ea6f';
+}
+
+.lni-cloud::before {
+ content: '\ea70';
+}
+
+.lni-cloudflare::before {
+ content: '\ea71';
+}
+
+.lni-cloudy-sun::before {
+ content: '\ea72';
+}
+
+.lni-code-alt::before {
+ content: '\ea73';
+}
+
+.lni-code::before {
+ content: '\ea74';
+}
+
+.lni-codepen::before {
+ content: '\ea75';
+}
+
+.lni-coffee-cup::before {
+ content: '\ea76';
+}
+
+.lni-cog::before {
+ content: '\ea77';
+}
+
+.lni-cogs::before {
+ content: '\ea78';
+}
+
+.lni-coin::before {
+ content: '\ea79';
+}
+
+.lni-comments-alt::before {
+ content: '\ea7a';
+}
+
+.lni-comments-reply::before {
+ content: '\ea7b';
+}
+
+.lni-comments::before {
+ content: '\ea7c';
+}
+
+.lni-compass::before {
+ content: '\ea7d';
+}
+
+.lni-connectdevelop::before {
+ content: '\ea7e';
+}
+
+.lni-construction-hammer::before {
+ content: '\ea7f';
+}
+
+.lni-construction::before {
+ content: '\ea80';
+}
+
+.lni-consulting::before {
+ content: '\ea81';
+}
+
+.lni-control-panel::before {
+ content: '\ea82';
+}
+
+.lni-cool::before {
+ content: '\ea83';
+}
+
+.lni-cpanel::before {
+ content: '\ea84';
+}
+
+.lni-creative-commons::before {
+ content: '\ea85';
+}
+
+.lni-credit-cards::before {
+ content: '\ea86';
+}
+
+.lni-crop::before {
+ content: '\ea87';
+}
+
+.lni-cross-circle::before {
+ content: '\ea88';
+}
+
+.lni-crown::before {
+ content: '\ea89';
+}
+
+.lni-css3::before {
+ content: '\ea8a';
+}
+
+.lni-cup::before {
+ content: '\ea8b';
+}
+
+.lni-customer::before {
+ content: '\ea8c';
+}
+
+.lni-cut::before {
+ content: '\ea8d';
+}
+
+.lni-dashboard::before {
+ content: '\ea8e';
+}
+
+.lni-database::before {
+ content: '\ea8f';
+}
+
+.lni-delivery::before {
+ content: '\ea90';
+}
+
+.lni-dev::before {
+ content: '\ea91';
+}
+
+.lni-diamond-alt::before {
+ content: '\ea92';
+}
+
+.lni-diamond::before {
+ content: '\ea93';
+}
+
+.lni-digitalocean::before {
+ content: '\ea94';
+}
+
+.lni-diners-club::before {
+ content: '\ea95';
+}
+
+.lni-dinner::before {
+ content: '\ea96';
+}
+
+.lni-direction-alt::before {
+ content: '\ea97';
+}
+
+.lni-direction-ltr::before {
+ content: '\ea98';
+}
+
+.lni-direction-rtl::before {
+ content: '\ea99';
+}
+
+.lni-direction::before {
+ content: '\ea9a';
+}
+
+.lni-discord::before {
+ content: '\ea9b';
+}
+
+.lni-discover::before {
+ content: '\ea9c';
+}
+
+.lni-display-alt::before {
+ content: '\ea9d';
+}
+
+.lni-display::before {
+ content: '\ea9e';
+}
+
+.lni-docker::before {
+ content: '\ea9f';
+}
+
+.lni-dollar::before {
+ content: '\eaa0';
+}
+
+.lni-domain::before {
+ content: '\eaa1';
+}
+
+.lni-download::before {
+ content: '\eaa2';
+}
+
+.lni-dribbble::before {
+ content: '\eaa3';
+}
+
+.lni-drop::before {
+ content: '\eaa4';
+}
+
+.lni-dropbox-original::before {
+ content: '\eaa5';
+}
+
+.lni-dropbox::before {
+ content: '\eaa6';
+}
+
+.lni-drupal-original::before {
+ content: '\eaa7';
+}
+
+.lni-drupal::before {
+ content: '\eaa8';
+}
+
+.lni-dumbbell::before {
+ content: '\eaa9';
+}
+
+.lni-edge::before {
+ content: '\eaaa';
+}
+
+.lni-empty-file::before {
+ content: '\eaab';
+}
+
+.lni-enter::before {
+ content: '\eaac';
+}
+
+.lni-envato::before {
+ content: '\eaad';
+}
+
+.lni-envelope::before {
+ content: '\eaae';
+}
+
+.lni-eraser::before {
+ content: '\eaaf';
+}
+
+.lni-euro::before {
+ content: '\eab0';
+}
+
+.lni-exit-down::before {
+ content: '\eab1';
+}
+
+.lni-exit-up::before {
+ content: '\eab2';
+}
+
+.lni-exit::before {
+ content: '\eab3';
+}
+
+.lni-eye::before {
+ content: '\eab4';
+}
+
+.lni-facebook-filled::before {
+ content: '\eab5';
+}
+
+.lni-facebook-messenger::before {
+ content: '\eab6';
+}
+
+.lni-facebook-original::before {
+ content: '\eab7';
+}
+
+.lni-facebook-oval::before {
+ content: '\eab8';
+}
+
+.lni-facebook::before {
+ content: '\eab9';
+}
+
+.lni-figma::before {
+ content: '\eaba';
+}
+
+.lni-files::before {
+ content: '\eabb';
+}
+
+.lni-firefox-original::before {
+ content: '\eabc';
+}
+
+.lni-firefox::before {
+ content: '\eabd';
+}
+
+.lni-fireworks::before {
+ content: '\eabe';
+}
+
+.lni-first-aid::before {
+ content: '\eabf';
+}
+
+.lni-flag-alt::before {
+ content: '\eac0';
+}
+
+.lni-flag::before {
+ content: '\eac1';
+}
+
+.lni-flags::before {
+ content: '\eac2';
+}
+
+.lni-flickr::before {
+ content: '\eac3';
+}
+
+.lni-flower::before {
+ content: '\eac4';
+}
+
+.lni-folder::before {
+ content: '\eac5';
+}
+
+.lni-forward::before {
+ content: '\eac6';
+}
+
+.lni-frame-expand::before {
+ content: '\eac7';
+}
+
+.lni-fresh-juice::before {
+ content: '\eac8';
+}
+
+.lni-friendly::before {
+ content: '\eac9';
+}
+
+.lni-full-screen::before {
+ content: '\eaca';
+}
+
+.lni-funnel::before {
+ content: '\eacb';
+}
+
+.lni-gallery::before {
+ content: '\eacc';
+}
+
+.lni-game::before {
+ content: '\eacd';
+}
+
+.lni-gatsby::before {
+ content: '\eace';
+}
+
+.lni-gift::before {
+ content: '\eacf';
+}
+
+.lni-git::before {
+ content: '\ead0';
+}
+
+.lni-github-original::before {
+ content: '\ead1';
+}
+
+.lni-github::before {
+ content: '\ead2';
+}
+
+.lni-goodreads::before {
+ content: '\ead3';
+}
+
+.lni-google-drive::before {
+ content: '\ead4';
+}
+
+.lni-google-pay::before {
+ content: '\ead5';
+}
+
+.lni-google-wallet::before {
+ content: '\ead6';
+}
+
+.lni-google::before {
+ content: '\ead7';
+}
+
+.lni-graduation::before {
+ content: '\ead8';
+}
+
+.lni-graph::before {
+ content: '\ead9';
+}
+
+.lni-grid-alt::before {
+ content: '\eada';
+}
+
+.lni-grid::before {
+ content: '\eadb';
+}
+
+.lni-grow::before {
+ content: '\eadc';
+}
+
+.lni-hacker-news::before {
+ content: '\eadd';
+}
+
+.lni-hammer::before {
+ content: '\eade';
+}
+
+.lni-hand::before {
+ content: '\eadf';
+}
+
+.lni-handshake::before {
+ content: '\eae0';
+}
+
+.lni-happy::before {
+ content: '\eae1';
+}
+
+.lni-harddrive::before {
+ content: '\eae2';
+}
+
+.lni-headphone-alt::before {
+ content: '\eae3';
+}
+
+.lni-headphone::before {
+ content: '\eae4';
+}
+
+.lni-heart-filled::before {
+ content: '\eae5';
+}
+
+.lni-heart-monitor::before {
+ content: '\eae6';
+}
+
+.lni-heart::before {
+ content: '\eae7';
+}
+
+.lni-helicopter::before {
+ content: '\eae8';
+}
+
+.lni-helmet::before {
+ content: '\eae9';
+}
+
+.lni-help::before {
+ content: '\eaea';
+}
+
+.lni-highlight-alt::before {
+ content: '\eaeb';
+}
+
+.lni-highlight::before {
+ content: '\eaec';
+}
+
+.lni-home::before {
+ content: '\eaed';
+}
+
+.lni-hospital::before {
+ content: '\eaee';
+}
+
+.lni-hourglass::before {
+ content: '\eaef';
+}
+
+.lni-html5::before {
+ content: '\eaf0';
+}
+
+.lni-image::before {
+ content: '\eaf1';
+}
+
+.lni-imdb::before {
+ content: '\eaf2';
+}
+
+.lni-inbox::before {
+ content: '\eaf3';
+}
+
+.lni-indent-decrease::before {
+ content: '\eaf4';
+}
+
+.lni-indent-increase::before {
+ content: '\eaf5';
+}
+
+.lni-infinite::before {
+ content: '\eaf6';
+}
+
+.lni-information::before {
+ content: '\eaf7';
+}
+
+.lni-instagram-filled::before {
+ content: '\eaf8';
+}
+
+.lni-instagram-original::before {
+ content: '\eaf9';
+}
+
+.lni-instagram::before {
+ content: '\eafa';
+}
+
+.lni-invention::before {
+ content: '\eafb';
+}
+
+.lni-invest-monitor::before {
+ content: '\eafc';
+}
+
+.lni-investment::before {
+ content: '\eafd';
+}
+
+.lni-island::before {
+ content: '\eafe';
+}
+
+.lni-italic::before {
+ content: '\eaff';
+}
+
+.lni-java::before {
+ content: '\eb00';
+}
+
+.lni-javascript::before {
+ content: '\eb01';
+}
+
+.lni-jcb::before {
+ content: '\eb02';
+}
+
+.lni-joomla-original::before {
+ content: '\eb03';
+}
+
+.lni-joomla::before {
+ content: '\eb04';
+}
+
+.lni-jsfiddle::before {
+ content: '\eb05';
+}
+
+.lni-juice::before {
+ content: '\eb06';
+}
+
+.lni-key::before {
+ content: '\eb07';
+}
+
+.lni-keyboard::before {
+ content: '\eb08';
+}
+
+.lni-keyword-research::before {
+ content: '\eb09';
+}
+
+.lni-laptop-phone::before {
+ content: '\eb0a';
+}
+
+.lni-laptop::before {
+ content: '\eb0b';
+}
+
+.lni-laravel::before {
+ content: '\eb0c';
+}
+
+.lni-layers::before {
+ content: '\eb0d';
+}
+
+.lni-layout::before {
+ content: '\eb0e';
+}
+
+.lni-leaf::before {
+ content: '\eb0f';
+}
+
+.lni-library::before {
+ content: '\eb10';
+}
+
+.lni-license::before {
+ content: '\eb11';
+}
+
+.lni-lifering::before {
+ content: '\eb12';
+}
+
+.lni-line-dashed::before {
+ content: '\eb13';
+}
+
+.lni-line-dotted::before {
+ content: '\eb14';
+}
+
+.lni-line-double::before {
+ content: '\eb15';
+}
+
+.lni-line-spacing::before {
+ content: '\eb16';
+}
+
+.lni-line::before {
+ content: '\eb17';
+}
+
+.lni-lineicons-alt::before {
+ content: '\eb18';
+}
+
+.lni-lineicons::before {
+ content: '\eb19';
+}
+
+.lni-link::before {
+ content: '\eb1a';
+}
+
+.lni-linkedin-original::before {
+ content: '\eb1b';
+}
+
+.lni-linkedin::before {
+ content: '\eb1c';
+}
+
+.lni-list::before {
+ content: '\eb1d';
+}
+
+.lni-lock-alt::before {
+ content: '\eb1e';
+}
+
+.lni-lock::before {
+ content: '\eb1f';
+}
+
+.lni-magento::before {
+ content: '\eb20';
+}
+
+.lni-magnet::before {
+ content: '\eb21';
+}
+
+.lni-magnifier::before {
+ content: '\eb22';
+}
+
+.lni-mailchimp::before {
+ content: '\eb23';
+}
+
+.lni-map-marker::before {
+ content: '\eb24';
+}
+
+.lni-map::before {
+ content: '\eb25';
+}
+
+.lni-markdown::before {
+ content: '\eb26';
+}
+
+.lni-mashroom::before {
+ content: '\eb27';
+}
+
+.lni-mastercard::before {
+ content: '\eb28';
+}
+
+.lni-medium::before {
+ content: '\eb29';
+}
+
+.lni-menu::before {
+ content: '\eb2a';
+}
+
+.lni-mic::before {
+ content: '\eb2b';
+}
+
+.lni-microphone::before {
+ content: '\eb2c';
+}
+
+.lni-microscope::before {
+ content: '\eb2d';
+}
+
+.lni-microsoft-edge::before {
+ content: '\eb2e';
+}
+
+.lni-microsoft::before {
+ content: '\eb2f';
+}
+
+.lni-minus::before {
+ content: '\eb30';
+}
+
+.lni-mobile::before {
+ content: '\eb31';
+}
+
+.lni-money-location::before {
+ content: '\eb32';
+}
+
+.lni-money-protection::before {
+ content: '\eb33';
+}
+
+.lni-more-alt::before {
+ content: '\eb34';
+}
+
+.lni-more::before {
+ content: '\eb35';
+}
+
+.lni-mouse::before {
+ content: '\eb36';
+}
+
+.lni-move::before {
+ content: '\eb37';
+}
+
+.lni-music::before {
+ content: '\eb38';
+}
+
+.lni-netlify::before {
+ content: '\eb39';
+}
+
+.lni-network::before {
+ content: '\eb3a';
+}
+
+.lni-night::before {
+ content: '\eb3b';
+}
+
+.lni-nodejs-alt::before {
+ content: '\eb3c';
+}
+
+.lni-nodejs::before {
+ content: '\eb3d';
+}
+
+.lni-notepad::before {
+ content: '\eb3e';
+}
+
+.lni-npm::before {
+ content: '\eb3f';
+}
+
+.lni-offer::before {
+ content: '\eb40';
+}
+
+.lni-opera::before {
+ content: '\eb41';
+}
+
+.lni-package::before {
+ content: '\eb42';
+}
+
+.lni-page-break::before {
+ content: '\eb43';
+}
+
+.lni-pagination::before {
+ content: '\eb44';
+}
+
+.lni-paint-bucket::before {
+ content: '\eb45';
+}
+
+.lni-paint-roller::before {
+ content: '\eb46';
+}
+
+.lni-pallet::before {
+ content: '\eb47';
+}
+
+.lni-paperclip::before {
+ content: '\eb48';
+}
+
+.lni-patreon::before {
+ content: '\eb49';
+}
+
+.lni-pause::before {
+ content: '\eb4a';
+}
+
+.lni-paypal-original::before {
+ content: '\eb4b';
+}
+
+.lni-paypal::before {
+ content: '\eb4c';
+}
+
+.lni-pencil-alt::before {
+ content: '\eb4d';
+}
+
+.lni-pencil::before {
+ content: '\eb4e';
+}
+
+.lni-phone-set::before {
+ content: '\eb4f';
+}
+
+.lni-phone::before {
+ content: '\eb50';
+}
+
+.lni-php::before {
+ content: '\eb51';
+}
+
+.lni-pie-chart::before {
+ content: '\eb52';
+}
+
+.lni-pilcrow::before {
+ content: '\eb53';
+}
+
+.lni-pin::before {
+ content: '\eb54';
+}
+
+.lni-pinterest::before {
+ content: '\eb55';
+}
+
+.lni-pizza::before {
+ content: '\eb56';
+}
+
+.lni-plane::before {
+ content: '\eb57';
+}
+
+.lni-play-store::before {
+ content: '\eb58';
+}
+
+.lni-play::before {
+ content: '\eb59';
+}
+
+.lni-playstation::before {
+ content: '\eb5a';
+}
+
+.lni-plug::before {
+ content: '\eb5b';
+}
+
+.lni-plus::before {
+ content: '\eb5c';
+}
+
+.lni-pointer-down::before {
+ content: '\eb5d';
+}
+
+.lni-pointer-left::before {
+ content: '\eb5e';
+}
+
+.lni-pointer-right::before {
+ content: '\eb5f';
+}
+
+.lni-pointer-top::before {
+ content: '\eb60';
+}
+
+.lni-pointer::before {
+ content: '\eb61';
+}
+
+.lni-popup::before {
+ content: '\eb62';
+}
+
+.lni-postcard::before {
+ content: '\eb63';
+}
+
+.lni-pound::before {
+ content: '\eb64';
+}
+
+.lni-power-switch::before {
+ content: '\eb65';
+}
+
+.lni-printer::before {
+ content: '\eb66';
+}
+
+.lni-producthunt::before {
+ content: '\eb67';
+}
+
+.lni-protection::before {
+ content: '\eb68';
+}
+
+.lni-pulse::before {
+ content: '\eb69';
+}
+
+.lni-pyramids::before {
+ content: '\eb6a';
+}
+
+.lni-python::before {
+ content: '\eb6b';
+}
+
+.lni-question-circle::before {
+ content: '\eb6c';
+}
+
+.lni-quora::before {
+ content: '\eb6d';
+}
+
+.lni-quotation::before {
+ content: '\eb6e';
+}
+
+.lni-radio-button::before {
+ content: '\eb6f';
+}
+
+.lni-rain::before {
+ content: '\eb70';
+}
+
+.lni-react::before {
+ content: '\eb73';
+}
+
+.lni-reddit::before {
+ content: '\eb71';
+}
+
+.lni-reload::before {
+ content: '\eb72';
+}
+
+.lni-remove-file::before {
+ content: '\eb74';
+}
+
+.lni-reply::before {
+ content: '\eb75';
+}
+
+.lni-restaurant::before {
+ content: '\eb76';
+}
+
+.lni-revenue::before {
+ content: '\eb77';
+}
+
+.lni-road::before {
+ content: '\eb78';
+}
+
+.lni-rocket::before {
+ content: '\eb79';
+}
+
+.lni-rss-feed::before {
+ content: '\eb7a';
+}
+
+.lni-ruler-alt::before {
+ content: '\eb7b';
+}
+
+.lni-ruler-pencil::before {
+ content: '\eb7c';
+}
+
+.lni-ruler::before {
+ content: '\eb7d';
+}
+
+.lni-rupee::before {
+ content: '\eb7e';
+}
+
+.lni-sad::before {
+ content: '\eb7f';
+}
+
+.lni-save::before {
+ content: '\eb80';
+}
+
+.lni-school-bench-alt::before {
+ content: '\eb81';
+}
+
+.lni-school-bench::before {
+ content: '\eb82';
+}
+
+.lni-scooter::before {
+ content: '\eb83';
+}
+
+.lni-scroll-down::before {
+ content: '\eb84';
+}
+
+.lni-search-alt::before {
+ content: '\eb85';
+}
+
+.lni-search::before {
+ content: '\eb86';
+}
+
+.lni-select::before {
+ content: '\eb87';
+}
+
+.lni-seo::before {
+ content: '\eb88';
+}
+
+.lni-service::before {
+ content: '\eb89';
+}
+
+.lni-share-alt-1::before {
+ content: '\eb8a';
+}
+
+.lni-share-alt::before {
+ content: '\eb8b';
+}
+
+.lni-share::before {
+ content: '\eb8c';
+}
+
+.lni-shield::before {
+ content: '\eb8d';
+}
+
+.lni-shift-left::before {
+ content: '\eb8e';
+}
+
+.lni-shift-right::before {
+ content: '\eb8f';
+}
+
+.lni-ship::before {
+ content: '\eb90';
+}
+
+.lni-shopify::before {
+ content: '\eb91';
+}
+
+.lni-shopping-basket::before {
+ content: '\eb92';
+}
+
+.lni-shortcode::before {
+ content: '\eb93';
+}
+
+.lni-shovel::before {
+ content: '\eb94';
+}
+
+.lni-shuffle::before {
+ content: '\eb95';
+}
+
+.lni-signal::before {
+ content: '\eb96';
+}
+
+.lni-sketch::before {
+ content: '\eb97';
+}
+
+.lni-skipping-rope::before {
+ content: '\eb98';
+}
+
+.lni-skype::before {
+ content: '\eb99';
+}
+
+.lni-slack-line::before {
+ content: '\eb9a';
+}
+
+.lni-slack::before {
+ content: '\eb9b';
+}
+
+.lni-slice::before {
+ content: '\eb9c';
+}
+
+.lni-slideshare::before {
+ content: '\eb9d';
+}
+
+.lni-slim::before {
+ content: '\eb9e';
+}
+
+.lni-smile::before {
+ content: '\eb9f';
+}
+
+.lni-snapchat::before {
+ content: '\eba0';
+}
+
+.lni-sort-alpha-asc::before {
+ content: '\eba1';
+}
+
+.lni-sort-amount-asc::before {
+ content: '\eba2';
+}
+
+.lni-sort-amount-dsc::before {
+ content: '\eba3';
+}
+
+.lni-soundcloud-original::before {
+ content: '\eba4';
+}
+
+.lni-soundcloud::before {
+ content: '\eba5';
+}
+
+.lni-speechless::before {
+ content: '\eba6';
+}
+
+.lni-spellcheck::before {
+ content: '\eba7';
+}
+
+.lni-spinner-arrow::before {
+ content: '\eba8';
+}
+
+.lni-spinner-solid::before {
+ content: '\eba9';
+}
+
+.lni-spinner::before {
+ content: '\ebaa';
+}
+
+.lni-spotify-original::before {
+ content: '\ebab';
+}
+
+.lni-spotify::before {
+ content: '\ebac';
+}
+
+.lni-spray::before {
+ content: '\ebad';
+}
+
+.lni-sprout::before {
+ content: '\ebae';
+}
+
+.lni-squarespace::before {
+ content: '\ebaf';
+}
+
+.lni-stackoverflow::before {
+ content: '\ebb0';
+}
+
+.lni-stamp::before {
+ content: '\ebb1';
+}
+
+.lni-star-empty::before {
+ content: '\ebb2';
+}
+
+.lni-star-filled::before {
+ content: '\ebb3';
+}
+
+.lni-star-half::before {
+ content: '\ebb4';
+}
+
+.lni-star::before {
+ content: '\ebb5';
+}
+
+.lni-stats-down::before {
+ content: '\ebb6';
+}
+
+.lni-stats-up::before {
+ content: '\ebb7';
+}
+
+.lni-steam::before {
+ content: '\ebb8';
+}
+
+.lni-sthethoscope::before {
+ content: '\ebb9';
+}
+
+.lni-stop::before {
+ content: '\ebba';
+}
+
+.lni-strikethrough::before {
+ content: '\ebbb';
+}
+
+.lni-stripe::before {
+ content: '\ebbc';
+}
+
+.lni-stumbleupon::before {
+ content: '\ebbd';
+}
+
+.lni-sun::before {
+ content: '\ebbe';
+}
+
+.lni-support::before {
+ content: '\ebbf';
+}
+
+.lni-surf-board::before {
+ content: '\ebc0';
+}
+
+.lni-suspect::before {
+ content: '\ebc1';
+}
+
+.lni-swift::before {
+ content: '\ebc2';
+}
+
+.lni-syringe::before {
+ content: '\ebc3';
+}
+
+.lni-tab::before {
+ content: '\ebc4';
+}
+
+.lni-tag::before {
+ content: '\ebc5';
+}
+
+.lni-target-customer::before {
+ content: '\ebc6';
+}
+
+.lni-target-revenue::before {
+ content: '\ebc7';
+}
+
+.lni-target::before {
+ content: '\ebc8';
+}
+
+.lni-taxi::before {
+ content: '\ebc9';
+}
+
+.lni-teabag::before {
+ content: '\ebca';
+}
+
+.lni-telegram-original::before {
+ content: '\ebcb';
+}
+
+.lni-telegram::before {
+ content: '\ebcc';
+}
+
+.lni-text-align-center::before {
+ content: '\ebcd';
+}
+
+.lni-text-align-justify::before {
+ content: '\ebce';
+}
+
+.lni-text-align-left::before {
+ content: '\ebcf';
+}
+
+.lni-text-align-right::before {
+ content: '\ebd0';
+}
+
+.lni-text-format-remove::before {
+ content: '\ebd4';
+}
+
+.lni-text-format::before {
+ content: '\ebd1';
+}
+
+.lni-thought::before {
+ content: '\ebd2';
+}
+
+.lni-thumbs-down::before {
+ content: '\ebd3';
+}
+
+.lni-thumbs-up::before {
+ content: '\ebd5';
+}
+
+.lni-thunder-alt::before {
+ content: '\ebd6';
+}
+
+.lni-thunder::before {
+ content: '\ebd7';
+}
+
+.lni-ticket-alt::before {
+ content: '\ebd8';
+}
+
+.lni-ticket::before {
+ content: '\ebd9';
+}
+
+.lni-tiktok::before {
+ content: '\ebda';
+}
+
+.lni-timer::before {
+ content: '\ebdb';
+}
+
+.lni-tounge::before {
+ content: '\ebdc';
+}
+
+.lni-train-alt::before {
+ content: '\ebdd';
+}
+
+.lni-train::before {
+ content: '\ebde';
+}
+
+.lni-trash-can::before {
+ content: '\ebdf';
+}
+
+.lni-travel::before {
+ content: '\ebe0';
+}
+
+.lni-tree::before {
+ content: '\ebe1';
+}
+
+.lni-trees::before {
+ content: '\ebe2';
+}
+
+.lni-trello::before {
+ content: '\ebe3';
+}
+
+.lni-trowel::before {
+ content: '\ebe4';
+}
+
+.lni-tshirt::before {
+ content: '\ebe5';
+}
+
+.lni-tumblr::before {
+ content: '\ebe6';
+}
+
+.lni-twitch::before {
+ content: '\ebe7';
+}
+
+.lni-twitter-filled::before {
+ content: '\ebe8';
+}
+
+.lni-twitter-original::before {
+ content: '\ebe9';
+}
+
+.lni-twitter::before {
+ content: '\ebea';
+}
+
+.lni-ubuntu::before {
+ content: '\ebeb';
+}
+
+.lni-underline::before {
+ content: '\ebec';
+}
+
+.lni-unlink::before {
+ content: '\ebed';
+}
+
+.lni-unlock::before {
+ content: '\ebee';
+}
+
+.lni-unsplash::before {
+ content: '\ebef';
+}
+
+.lni-upload::before {
+ content: '\ebf0';
+}
+
+.lni-user::before {
+ content: '\ebf1';
+}
+
+.lni-users::before {
+ content: '\ebf6';
+}
+
+.lni-ux::before {
+ content: '\ebf2';
+}
+
+.lni-vector::before {
+ content: '\ebf3';
+}
+
+.lni-video::before {
+ content: '\ebf4';
+}
+
+.lni-vimeo::before {
+ content: '\ebf5';
+}
+
+.lni-visa::before {
+ content: '\ebf7';
+}
+
+.lni-vk::before {
+ content: '\ebf8';
+}
+
+.lni-volume-high::before {
+ content: '\ebf9';
+}
+
+.lni-volume-low::before {
+ content: '\ebfa';
+}
+
+.lni-volume-medium::before {
+ content: '\ebfb';
+}
+
+.lni-volume-mute::before {
+ content: '\ebfc';
+}
+
+.lni-volume::before {
+ content: '\ebfd';
+}
+
+.lni-wallet::before {
+ content: '\ebfe';
+}
+
+.lni-warning::before {
+ content: '\ebff';
+}
+
+.lni-website-alt::before {
+ content: '\ec00';
+}
+
+.lni-website::before {
+ content: '\ec01';
+}
+
+.lni-wechat::before {
+ content: '\ec02';
+}
+
+.lni-weight::before {
+ content: '\ec03';
+}
+
+.lni-whatsapp::before {
+ content: '\ec04';
+}
+
+.lni-wheelbarrow::before {
+ content: '\ec05';
+}
+
+.lni-wheelchair::before {
+ content: '\ec06';
+}
+
+.lni-windows::before {
+ content: '\ec07';
+}
+
+.lni-wordpress-filled::before {
+ content: '\ec08';
+}
+
+.lni-wordpress::before {
+ content: '\ec09';
+}
+
+.lni-world-alt::before {
+ content: '\ec0a';
+}
+
+.lni-world::before {
+ content: '\ec0c';
+}
+
+.lni-write::before {
+ content: '\ec0b';
+}
+
+.lni-xbox::before {
+ content: '\ec0d';
+}
+
+.lni-yahoo::before {
+ content: '\ec0e';
+}
+
+.lni-ycombinator::before {
+ content: '\ec0f';
+}
+
+.lni-yen::before {
+ content: '\ec10';
+}
+
+.lni-youtube::before {
+ content: '\ec13';
+}
+
+.lni-zip::before {
+ content: '\ec11';
+}
+
+.lni-zoom-in::before {
+ content: '\ec12';
+}
+
+.lni-zoom-out::before {
+ content: '\ec14';
+}
diff --git a/examples/cms-buttercms/css/main.css b/examples/cms-buttercms/css/main.css
new file mode 100644
index 000000000000..3448f3ad5e6f
--- /dev/null
+++ b/examples/cms-buttercms/css/main.css
@@ -0,0 +1,2776 @@
+@charset "UTF-8";
+
+html {
+ scroll-behavior: smooth;
+}
+
+body {
+ font-family: 'Sen', sans-serif;
+ font-weight: normal;
+ font-style: normal;
+ color: rgba(0, 0, 0, 0.7);
+ overflow-x: hidden;
+}
+
+* {
+ margin: 0;
+ padding: 0;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+a:focus,
+input:focus,
+textarea:focus,
+button:focus,
+.btn:focus,
+.btn.focus,
+.btn:not(:disabled):not(.disabled).active,
+.btn:not(:disabled):not(.disabled):active {
+ text-decoration: none;
+ outline: none;
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+}
+
+a:hover {
+ color: #37c2cc;
+}
+
+a {
+ -webkit-transition: all 0.3s ease-out 0s;
+ -moz-transition: all 0.3s ease-out 0s;
+ -ms-transition: all 0.3s ease-out 0s;
+ -o-transition: all 0.3s ease-out 0s;
+ transition: all 0.3s ease-out 0s;
+}
+
+a,
+a:focus,
+a:hover {
+ text-decoration: none;
+}
+
+i,
+span,
+a {
+ display: inline-block;
+}
+
+audio,
+canvas,
+iframe,
+img,
+svg,
+video {
+ vertical-align: middle;
+}
+
+h1,
+h2,
+h3,
+.single-post-header,
+.blog-roll-card-header,
+h4,
+h5,
+h6 {
+ font-weight: 700;
+ margin: 0px;
+}
+
+h1 a,
+h2 a,
+h3 a,
+.single-post-header a,
+.blog-roll-card-header a,
+h4 a,
+h5 a,
+h6 a {
+ color: inherit;
+}
+
+h1 {
+ font-size: 55px;
+}
+
+h2 {
+ font-size: 45px;
+}
+
+@media only screen and (min-width: 992px) and (max-width: 1199px) {
+ .section-title h2 {
+ font-size: 38px;
+ }
+}
+
+@media (max-width: 767px) {
+ .section-title h2 {
+ font-size: 38px;
+ }
+}
+
+h3,
+.single-post-header,
+.blog-roll-card-header {
+ font-size: 25px;
+}
+
+h4 {
+ font-size: 20px;
+}
+
+h5 {
+ font-size: 18px;
+}
+
+h6 {
+ font-size: 16px;
+}
+
+ul,
+ol {
+ margin: 0px;
+ padding: 0px;
+ list-style-type: none;
+}
+
+p {
+ font-size: 16px;
+ font-weight: 400;
+ line-height: 25px;
+ margin: 0px;
+}
+
+.img-bg {
+ background-position: center center;
+ background-size: cover;
+ background-repeat: no-repeat;
+ width: 100%;
+ height: 100%;
+}
+
+.gray-bg-1 {
+ background-color: #fafafa;
+}
+
+.gray-bg-2 {
+ background-color: #f2f2f2;
+}
+
+.error {
+ color: orangered;
+}
+
+.success {
+ color: green;
+}
+
+@media (max-width: 767px) {
+ .container {
+ padding: 0 30px;
+ }
+}
+
+/*===== All Button Style =====*/
+.main-btn {
+ display: inline-block;
+ font-weight: 400;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: middle;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ padding: 18px 28px;
+ font-size: 18px;
+ line-height: 1;
+ border-radius: 10px;
+ color: #fff;
+ cursor: pointer;
+ z-index: 5;
+ transition: all 0.4s ease-in-out;
+ border: 2px solid transparent;
+ background: #37c2cc;
+ overflow: hidden;
+}
+
+.main-btn:hover {
+ color: #fff;
+}
+
+.main-btn.border-btn {
+ border: 2px solid #37c2cc;
+ background: transparent;
+ color: #37c2cc;
+}
+
+.main-btn.border-btn:hover::after {
+ background-color: rgba(55, 194, 204, 0.15);
+}
+
+.btn-hover {
+ position: relative;
+ overflow: hidden;
+}
+
+.btn-hover::after {
+ content: '';
+ position: absolute;
+ width: 0%;
+ height: 0%;
+ border-radius: 50%;
+ background: rgba(0, 0, 0, 0.05);
+ top: 50%;
+ left: 50%;
+ padding: 50%;
+ z-index: -1;
+ -webkit-transition: all 0.3s ease-out 0s;
+ -moz-transition: all 0.3s ease-out 0s;
+ -ms-transition: all 0.3s ease-out 0s;
+ -o-transition: all 0.3s ease-out 0s;
+ transition: all 0.3s ease-out 0s;
+ -webkit-transform: translate3d(-50%, -50%, 0) scale(0);
+ -moz-transform: translate3d(-50%, -50%, 0) scale(0);
+ -ms-transform: translate3d(-50%, -50%, 0) scale(0);
+ -o-transform: translate3d(-50%, -50%, 0) scale(0);
+ transform: translate3d(-50%, -50%, 0) scale(0);
+}
+
+.btn-hover:hover::after {
+ -webkit-transform: translate3d(-50%, -50%, 0) scale(1.3);
+ -moz-transform: translate3d(-50%, -50%, 0) scale(1.3);
+ -ms-transform: translate3d(-50%, -50%, 0) scale(1.3);
+ -o-transform: translate3d(-50%, -50%, 0) scale(1.3);
+ transform: translate3d(-50%, -50%, 0) scale(1.3);
+}
+
+.scroll-top {
+ width: 45px;
+ height: 45px;
+ background: #37c2cc;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-size: 18px;
+ color: #fff;
+ border-radius: 5px;
+ position: fixed;
+ bottom: 30px;
+ right: 30px;
+ z-index: 9;
+ cursor: pointer;
+ -webkit-transition: all 0.3s ease-out 0s;
+ -moz-transition: all 0.3s ease-out 0s;
+ -ms-transition: all 0.3s ease-out 0s;
+ -o-transition: all 0.3s ease-out 0s;
+ transition: all 0.3s ease-out 0s;
+}
+
+.scroll-top:hover {
+ color: #fff;
+ background: rgba(55, 194, 204, 0.8);
+}
+
+@keyframes animation1 {
+ 0% {
+ -webkit-transform: translateY(30px);
+ -moz-transform: translateY(30px);
+ -ms-transform: translateY(30px);
+ -o-transform: translateY(30px);
+ transform: translateY(30px);
+ }
+
+ 50% {
+ -webkit-transform: translateY(-30px);
+ -moz-transform: translateY(-30px);
+ -ms-transform: translateY(-30px);
+ -o-transform: translateY(-30px);
+ transform: translateY(-30px);
+ }
+
+ 100% {
+ -webkit-transform: translateY(30px);
+ -moz-transform: translateY(30px);
+ -ms-transform: translateY(30px);
+ -o-transform: translateY(30px);
+ transform: translateY(30px);
+ }
+}
+
+@-webkit-keyframes animation1 {
+ 0% {
+ -webkit-transform: translateY(30px);
+ -moz-transform: translateY(30px);
+ -ms-transform: translateY(30px);
+ -o-transform: translateY(30px);
+ transform: translateY(30px);
+ }
+
+ 50% {
+ -webkit-transform: translateY(-30px);
+ -moz-transform: translateY(-30px);
+ -ms-transform: translateY(-30px);
+ -o-transform: translateY(-30px);
+ transform: translateY(-30px);
+ }
+
+ 100% {
+ -webkit-transform: translateY(30px);
+ -moz-transform: translateY(30px);
+ -ms-transform: translateY(30px);
+ -o-transform: translateY(30px);
+ transform: translateY(30px);
+ }
+}
+
+/*===== All Preloader Style =====*/
+.preloader {
+ /* Body Overlay */
+ position: fixed;
+ top: 0;
+ left: 0;
+ display: table;
+ height: 100%;
+ width: 100%;
+ /* Change Background Color */
+ background: #fff;
+ z-index: 99999;
+}
+
+.preloader .loader {
+ display: table-cell;
+ vertical-align: middle;
+ text-align: center;
+}
+
+.preloader .loader .spinner {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ width: 64px;
+ margin-left: -32px;
+ z-index: 18;
+ pointer-events: none;
+}
+
+.preloader .loader .spinner .spinner-container {
+ pointer-events: none;
+ position: absolute;
+ width: 100%;
+ padding-bottom: 100%;
+ top: 50%;
+ left: 50%;
+ margin-top: -50%;
+ margin-left: -50%;
+ -webkit-animation: spinner-linspin 1568.23529647ms linear infinite;
+ -moz-animation: spinner-linspin 1568.23529647ms linear infinite;
+ -o-animation: spinner-linspin 1568.23529647ms linear infinite;
+ animation: spinner-linspin 1568.23529647ms linear infinite;
+}
+
+.preloader .loader .spinner .spinner-container .spinner-rotator {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ -webkit-animation: spinner-easespin 5332ms cubic-bezier(0.4, 0, 0.2, 1)
+ infinite both;
+ -moz-animation: spinner-easespin 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite
+ both;
+ -o-animation: spinner-easespin 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite
+ both;
+ animation: spinner-easespin 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+}
+
+.preloader .loader .spinner .spinner-container .spinner-rotator .spinner-left {
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ overflow: hidden;
+ right: 50%;
+}
+
+.preloader .loader .spinner .spinner-container .spinner-rotator .spinner-right {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ overflow: hidden;
+ left: 50%;
+}
+
+.preloader .loader .spinner-circle {
+ box-sizing: border-box;
+ position: absolute;
+ width: 200%;
+ height: 100%;
+ border-style: solid;
+ /* Spinner Color */
+ border-color: #37c2cc #37c2cc rgba(0, 0, 0, 0.1);
+ border-radius: 50%;
+ border-width: 6px;
+}
+
+.preloader .loader .spinner-left .spinner-circle {
+ left: 0;
+ right: -100%;
+ border-right-color: rgba(0, 0, 0, 0.1);
+ -webkit-animation: spinner-left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1)
+ infinite both;
+ -moz-animation: spinner-left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite
+ both;
+ -o-animation: spinner-left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite
+ both;
+ animation: spinner-left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+}
+
+.preloader .loader .spinner-right .spinner-circle {
+ left: -100%;
+ right: 0;
+ border-left-color: rgba(0, 0, 0, 0.1);
+ -webkit-animation: right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite
+ both;
+ -moz-animation: right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+ -o-animation: right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+ animation: right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+}
+
+/* Preloader Animations */
+@-webkit-keyframes spinner-linspin {
+ to {
+ -webkit-transform: rotate(360deg);
+ -moz-transform: rotate(360deg);
+ -ms-transform: rotate(360deg);
+ -o-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
+
+@keyframes spinner-linspin {
+ to {
+ -webkit-transform: rotate(360deg);
+ -moz-transform: rotate(360deg);
+ -ms-transform: rotate(360deg);
+ -o-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
+
+@-webkit-keyframes spinner-easespin {
+ 12.5% {
+ -webkit-transform: rotate(135deg);
+ -moz-transform: rotate(135deg);
+ -ms-transform: rotate(135deg);
+ -o-transform: rotate(135deg);
+ transform: rotate(135deg);
+ }
+
+ 25% {
+ -webkit-transform: rotate(270deg);
+ -moz-transform: rotate(270deg);
+ -ms-transform: rotate(270deg);
+ -o-transform: rotate(270deg);
+ transform: rotate(270deg);
+ }
+
+ 37.5% {
+ -webkit-transform: rotate(405deg);
+ -moz-transform: rotate(405deg);
+ -ms-transform: rotate(405deg);
+ -o-transform: rotate(405deg);
+ transform: rotate(405deg);
+ }
+
+ 50% {
+ -webkit-transform: rotate(540deg);
+ -moz-transform: rotate(540deg);
+ -ms-transform: rotate(540deg);
+ -o-transform: rotate(540deg);
+ transform: rotate(540deg);
+ }
+
+ 62.5% {
+ -webkit-transform: rotate(675deg);
+ -moz-transform: rotate(675deg);
+ -ms-transform: rotate(675deg);
+ -o-transform: rotate(675deg);
+ transform: rotate(675deg);
+ }
+
+ 75% {
+ -webkit-transform: rotate(810deg);
+ -moz-transform: rotate(810deg);
+ -ms-transform: rotate(810deg);
+ -o-transform: rotate(810deg);
+ transform: rotate(810deg);
+ }
+
+ 87.5% {
+ -webkit-transform: rotate(945deg);
+ -moz-transform: rotate(945deg);
+ -ms-transform: rotate(945deg);
+ -o-transform: rotate(945deg);
+ transform: rotate(945deg);
+ }
+
+ to {
+ -webkit-transform: rotate(1080deg);
+ -moz-transform: rotate(1080deg);
+ -ms-transform: rotate(1080deg);
+ -o-transform: rotate(1080deg);
+ transform: rotate(1080deg);
+ }
+}
+
+@keyframes spinner-easespin {
+ 12.5% {
+ -webkit-transform: rotate(135deg);
+ -moz-transform: rotate(135deg);
+ -ms-transform: rotate(135deg);
+ -o-transform: rotate(135deg);
+ transform: rotate(135deg);
+ }
+
+ 25% {
+ -webkit-transform: rotate(270deg);
+ -moz-transform: rotate(270deg);
+ -ms-transform: rotate(270deg);
+ -o-transform: rotate(270deg);
+ transform: rotate(270deg);
+ }
+
+ 37.5% {
+ -webkit-transform: rotate(405deg);
+ -moz-transform: rotate(405deg);
+ -ms-transform: rotate(405deg);
+ -o-transform: rotate(405deg);
+ transform: rotate(405deg);
+ }
+
+ 50% {
+ -webkit-transform: rotate(540deg);
+ -moz-transform: rotate(540deg);
+ -ms-transform: rotate(540deg);
+ -o-transform: rotate(540deg);
+ transform: rotate(540deg);
+ }
+
+ 62.5% {
+ -webkit-transform: rotate(675deg);
+ -moz-transform: rotate(675deg);
+ -ms-transform: rotate(675deg);
+ -o-transform: rotate(675deg);
+ transform: rotate(675deg);
+ }
+
+ 75% {
+ -webkit-transform: rotate(810deg);
+ -moz-transform: rotate(810deg);
+ -ms-transform: rotate(810deg);
+ -o-transform: rotate(810deg);
+ transform: rotate(810deg);
+ }
+
+ 87.5% {
+ -webkit-transform: rotate(945deg);
+ -moz-transform: rotate(945deg);
+ -ms-transform: rotate(945deg);
+ -o-transform: rotate(945deg);
+ transform: rotate(945deg);
+ }
+
+ to {
+ -webkit-transform: rotate(1080deg);
+ -moz-transform: rotate(1080deg);
+ -ms-transform: rotate(1080deg);
+ -o-transform: rotate(1080deg);
+ transform: rotate(1080deg);
+ }
+}
+
+@-webkit-keyframes spinner-left-spin {
+ 0% {
+ -webkit-transform: rotate(130deg);
+ -moz-transform: rotate(130deg);
+ -ms-transform: rotate(130deg);
+ -o-transform: rotate(130deg);
+ transform: rotate(130deg);
+ }
+
+ 50% {
+ -webkit-transform: rotate(-5deg);
+ -moz-transform: rotate(-5deg);
+ -ms-transform: rotate(-5deg);
+ -o-transform: rotate(-5deg);
+ transform: rotate(-5deg);
+ }
+
+ to {
+ -webkit-transform: rotate(130deg);
+ -moz-transform: rotate(130deg);
+ -ms-transform: rotate(130deg);
+ -o-transform: rotate(130deg);
+ transform: rotate(130deg);
+ }
+}
+
+@keyframes spinner-left-spin {
+ 0% {
+ -webkit-transform: rotate(130deg);
+ -moz-transform: rotate(130deg);
+ -ms-transform: rotate(130deg);
+ -o-transform: rotate(130deg);
+ transform: rotate(130deg);
+ }
+
+ 50% {
+ -webkit-transform: rotate(-5deg);
+ -moz-transform: rotate(-5deg);
+ -ms-transform: rotate(-5deg);
+ -o-transform: rotate(-5deg);
+ transform: rotate(-5deg);
+ }
+
+ to {
+ -webkit-transform: rotate(130deg);
+ -moz-transform: rotate(130deg);
+ -ms-transform: rotate(130deg);
+ -o-transform: rotate(130deg);
+ transform: rotate(130deg);
+ }
+}
+
+@-webkit-keyframes right-spin {
+ 0% {
+ -webkit-transform: rotate(-130deg);
+ -moz-transform: rotate(-130deg);
+ -ms-transform: rotate(-130deg);
+ -o-transform: rotate(-130deg);
+ transform: rotate(-130deg);
+ }
+
+ 50% {
+ -webkit-transform: rotate(5deg);
+ -moz-transform: rotate(5deg);
+ -ms-transform: rotate(5deg);
+ -o-transform: rotate(5deg);
+ transform: rotate(5deg);
+ }
+
+ to {
+ -webkit-transform: rotate(-130deg);
+ -moz-transform: rotate(-130deg);
+ -ms-transform: rotate(-130deg);
+ -o-transform: rotate(-130deg);
+ transform: rotate(-130deg);
+ }
+}
+
+@keyframes right-spin {
+ 0% {
+ -webkit-transform: rotate(-130deg);
+ -moz-transform: rotate(-130deg);
+ -ms-transform: rotate(-130deg);
+ -o-transform: rotate(-130deg);
+ transform: rotate(-130deg);
+ }
+
+ 50% {
+ -webkit-transform: rotate(5deg);
+ -moz-transform: rotate(5deg);
+ -ms-transform: rotate(5deg);
+ -o-transform: rotate(5deg);
+ transform: rotate(5deg);
+ }
+
+ to {
+ -webkit-transform: rotate(-130deg);
+ -moz-transform: rotate(-130deg);
+ -ms-transform: rotate(-130deg);
+ -o-transform: rotate(-130deg);
+ transform: rotate(-130deg);
+ }
+}
+
+/*===========================
+ NAVBAR CSS
+============================= */
+.navbar-area {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ z-index: 99;
+ -webkit-transition: all 0.3s ease-out 0s;
+ -moz-transition: all 0.3s ease-out 0s;
+ -ms-transition: all 0.3s ease-out 0s;
+ -o-transition: all 0.3s ease-out 0s;
+ transition: all 0.3s ease-out 0s;
+}
+
+.sticky {
+ position: fixed;
+ z-index: 99;
+ background-color: #fff;
+ -webkit-box-shadow: 0px 20px 50px 0px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: 0px 20px 50px 0px rgba(0, 0, 0, 0.05);
+ box-shadow: 0px 20px 50px 0px rgba(0, 0, 0, 0.05);
+ -webkit-transition: all 0.3s ease-out 0s;
+ -moz-transition: all 0.3s ease-out 0s;
+ -ms-transition: all 0.3s ease-out 0s;
+ -o-transition: all 0.3s ease-out 0s;
+ transition: all 0.3s ease-out 0s;
+}
+
+.sticky .navbar .navbar-nav .nav-item a {
+ color: rgba(0, 0, 0, 0.9);
+}
+
+.sticky .navbar .navbar-nav .nav-item a.active,
+.sticky .navbar .navbar-nav .nav-item a:hover {
+ color: #37c2cc;
+}
+
+.sticky .navbar .navbar-nav .nav-item a.active::before,
+.sticky .navbar .navbar-nav .nav-item a:hover::before {
+ background: #37c2cc;
+}
+
+.sticky .navbar .header-btn .main-btn {
+ color: #fff;
+}
+
+.sticky .navbar .navbar-toggler .toggler-icon {
+ background: rgba(0, 0, 0, 0.9);
+}
+
+.navbar {
+ border-radius: 5px;
+ position: relative;
+ -webkit-transition: all 0.3s ease-out 0s;
+ -moz-transition: all 0.3s ease-out 0s;
+ -ms-transition: all 0.3s ease-out 0s;
+ -o-transition: all 0.3s ease-out 0s;
+ transition: all 0.3s ease-out 0s;
+ padding: 10px 0;
+}
+
+@media only screen and (min-width: 768px) and (max-width: 991px),
+ (max-width: 767px) {
+ .navbar {
+ padding: 15px 0;
+ }
+}
+
+.navbar-brand {
+ padding: 0;
+ display: flex;
+}
+
+.navbar-brand img {
+ max-width: 180px;
+}
+
+.navbar-toggler {
+ padding: 0;
+}
+
+@media only screen and (min-width: 768px) and (max-width: 991px),
+ (max-width: 767px) {
+ .navbar-toggler {
+ position: absolute;
+ right: 0;
+ top: 22px;
+ }
+}
+
+.navbar-toggler:focus {
+ outline: none;
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+}
+
+.navbar-toggler .toggler-icon {
+ width: 30px;
+ height: 2px;
+ background-color: rgba(0, 0, 0, 0.9);
+ display: block;
+ margin: 5px 0;
+ position: relative;
+ -webkit-transition: all 0.3s ease-out 0s;
+ -moz-transition: all 0.3s ease-out 0s;
+ -ms-transition: all 0.3s ease-out 0s;
+ -o-transition: all 0.3s ease-out 0s;
+ transition: all 0.3s ease-out 0s;
+}
+
+.navbar-toggler.active .toggler-icon:nth-of-type(1) {
+ -webkit-transform: rotate(45deg);
+ -moz-transform: rotate(45deg);
+ -ms-transform: rotate(45deg);
+ -o-transform: rotate(45deg);
+ transform: rotate(45deg);
+ top: 7px;
+}
+
+.navbar-toggler.active .toggler-icon:nth-of-type(2) {
+ opacity: 0;
+}
+
+.navbar-toggler.active .toggler-icon:nth-of-type(3) {
+ -webkit-transform: rotate(135deg);
+ -moz-transform: rotate(135deg);
+ -ms-transform: rotate(135deg);
+ -o-transform: rotate(135deg);
+ transform: rotate(135deg);
+ top: -7px;
+}
+
+@media only screen and (min-width: 768px) and (max-width: 991px),
+ (max-width: 767px) {
+ .navbar-collapse {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ width: 100%;
+ background-color: #fff;
+ z-index: 9;
+ -webkit-box-shadow: 0px 15px 20px 0px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: 0px 15px 20px 0px rgba(0, 0, 0, 0.1);
+ box-shadow: 0px 15px 20px 0px rgba(0, 0, 0, 0.1);
+ padding: 5px 12px;
+ }
+}
+
+.navbar-nav .nav-item {
+ position: relative;
+ padding: 8px 0;
+}
+
+@media only screen and (min-width: 768px) and (max-width: 991px),
+ (max-width: 767px) {
+ .navbar-nav .nav-item {
+ padding: 0px;
+ margin-left: 20px;
+ }
+}
+
+@media (max-width: 767px) {
+ .navbar-nav .nav-item:first-child {
+ margin-top: 20px;
+ }
+}
+
+@media only screen and (min-width: 768px) and (max-width: 991px),
+ (max-width: 767px) {
+ .navbar-nav .nav-item:last-child {
+ margin-bottom: 20px;
+ }
+}
+
+.navbar-nav .nav-item a {
+ color: rgba(0, 0, 0, 0.9);
+ -webkit-transition: all 0.3s ease-out 0s;
+ -moz-transition: all 0.3s ease-out 0s;
+ -ms-transition: all 0.3s ease-out 0s;
+ -o-transition: all 0.3s ease-out 0s;
+ transition: all 0.3s ease-out 0s;
+ padding: 8px 17px;
+ position: relative;
+ font-weight: 500;
+ font-size: 18px;
+ text-align: center;
+}
+
+@media only screen and (min-width: 768px) and (max-width: 991px),
+ (max-width: 767px) {
+ .navbar-nav .nav-item a {
+ display: inline-block;
+ padding: 6px 0px;
+ color: rgba(0, 0, 0, 0.9);
+ }
+}
+
+.navbar-nav .nav-item a:hover,
+.navbar-nav .nav-item a.active {
+ color: #37c2cc;
+}
+
+@media (max-width: 767px) {
+ .header-btn {
+ display: none;
+ }
+}
+
+@media only screen and (min-width: 550px) and (max-width: 767px) {
+ .header-btn {
+ display: flex;
+ }
+}
+
+.header-btn .main-btn {
+ color: #fff;
+ background: #37c2cc;
+ padding: 12px 22px;
+ margin-left: 15px;
+}
+
+@media only screen and (min-width: 768px) and (max-width: 991px),
+ (max-width: 767px) {
+ .header-btn .main-btn {
+ padding: 8px 20px;
+ margin-right: 60px;
+ margin-left: 0px;
+ }
+}
+
+/* ====================
+ HERO CSS
+======================= */
+.hero-section {
+ position: relative;
+ overflow: hidden;
+ height: 780px;
+ display: flex;
+ align-items: center;
+ background: linear-gradient(
+ 180deg,
+ #c2fbff 0%,
+ rgba(255, 255, 255, 0) 93.47%
+ );
+ z-index: 1;
+}
+
+@media only screen and (min-width: 1200px) and (max-width: 1399px) {
+ .hero-section {
+ height: 650px;
+ }
+}
+
+@media only screen and (min-width: 992px) and (max-width: 1199px) {
+ .hero-section {
+ height: 640px;
+ }
+}
+
+@media only screen and (min-width: 768px) and (max-width: 991px),
+ (max-width: 767px) {
+ .hero-section {
+ height: auto;
+ padding: 100px 0;
+ }
+}
+
+.hero-section::after {
+ content: '';
+ position: absolute;
+ width: 862px;
+ height: 862px;
+ border-radius: 50%;
+ right: -150px;
+ top: -150px;
+ background: #99ecf2;
+ z-index: -1;
+}
+
+@media only screen and (min-width: 1200px) and (max-width: 1399px) {
+ .hero-section::after {
+ width: 780px;
+ height: 780px;
+ }
+}
+
+@media only screen and (min-width: 992px) and (max-width: 1199px) {
+ .hero-section::after {
+ width: 750px;
+ height: 750px;
+ }
+}
+
+@media only screen and (min-width: 768px) and (max-width: 991px),
+ (max-width: 767px) {
+ .hero-section::after {
+ display: none;
+ }
+}
+
+@media only screen and (min-width: 768px) and (max-width: 991px) {
+ .hero-section .hero-content {
+ padding-top: 50px;
+ }
+}
+
+.hero-section .hero-content h1 {
+ margin-bottom: 15px;
+}
+
+@media only screen and (min-width: 992px) and (max-width: 1199px) {
+ .hero-section .hero-content h1 {
+ font-size: 50px;
+ }
+}
+
+@media (max-width: 767px) {
+ .hero-section .hero-content h1 {
+ font-size: 45px;
+ }
+}
+
+.hero-section .hero-content p {
+ margin-bottom: 30px;
+ color: rgba(0, 0, 0, 0.6);
+ font-size: 18px;
+}
+
+@media only screen and (min-width: 1400px),
+ only screen and (min-width: 1200px) and (max-width: 1399px) {
+ .hero-section .hero-content p {
+ padding-right: 115px;
+ }
+}
+
+.hero-section .hero-image {
+ padding-top: 50px;
+}
+
+@media only screen and (min-width: 992px) and (max-width: 1199px),
+ only screen and (min-width: 1200px) and (max-width: 1399px) {
+ .hero-section .hero-image img {
+ width: 100%;
+ }
+}
+
+@media only screen and (min-width: 768px) and (max-width: 991px),
+ (max-width: 767px) {
+ .hero-section .hero-image img {
+ max-width: 100%;
+ margin: auto;
+ }
+}
+
+/* ===============================
+ FEATURE SECTION ONE CSS
+================================== */
+.feature-section {
+ position: relative;
+ background-image: url('../public/images/common-bg.svg');
+ background-size: cover;
+ background-position: bottom center;
+ padding-top: 180px;
+ padding-bottom: 55px;
+}
+
+.single-feature {
+ margin-bottom: 65px;
+}
+
+@media only screen and (min-width: 1400px) {
+ .single-feature {
+ padding-right: 65px;
+ }
+}
+
+.single-feature:hover .feature-icon::before {
+ background: #37c2cc;
+}
+
+.single-feature:hover .feature-icon i {
+ color: #fff;
+}
+
+.single-feature .feature-icon {
+ width: 62px;
+ height: 66px;
+ position: relative;
+ z-index: 3;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 40px;
+}
+
+.single-feature .feature-icon img {
+ width: 100%;
+ height: auto;
+}
+
+.single-feature .feature-icon::before,
+.single-feature .feature-icon::after {
+ content: '';
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ border-radius: 8px;
+ -webkit-transform: skew(-3deg);
+ -moz-transform: skew(-3deg);
+ -ms-transform: skew(-3deg);
+ -o-transform: skew(-3deg);
+ transform: skew(-3deg);
+}
+
+.single-feature .feature-icon::before {
+ background: #d5f1f3;
+ -webkit-transition: all 0.3s ease-out 0s;
+ -moz-transition: all 0.3s ease-out 0s;
+ -ms-transition: all 0.3s ease-out 0s;
+ -o-transition: all 0.3s ease-out 0s;
+ transition: all 0.3s ease-out 0s;
+ z-index: -1;
+}
+
+.single-feature .feature-icon::after {
+ background: transparent;
+ border: 2px solid #d5f1f3;
+ top: 8px;
+ left: -8px;
+ z-index: -2;
+}
+
+.single-feature .feature-icon i {
+ font-size: 40px;
+ color: black;
+ -webkit-transition: all 0.3s ease-out 0s;
+ -moz-transition: all 0.3s ease-out 0s;
+ -ms-transition: all 0.3s ease-out 0s;
+ -o-transition: all 0.3s ease-out 0s;
+ transition: all 0.3s ease-out 0s;
+}
+
+.single-feature .feature-content h4 {
+ margin-bottom: 15px;
+}
+
+/* ========================
+ CTA SECTION CSS
+=========================== */
+.cta-section {
+ position: relative;
+ z-index: 1;
+ padding-top: 220px;
+}
+
+@media only screen and (min-width: 1200px) and (max-width: 1399px) {
+ .cta-section {
+ padding-top: 150px;
+ }
+}
+
+@media only screen and (min-width: 992px) and (max-width: 1199px) {
+ .cta-section {
+ padding-top: 130px;
+ }
+}
+
+@media only screen and (min-width: 768px) and (max-width: 991px),
+ (max-width: 767px) {
+ .cta-section {
+ padding-top: 0px;
+ }
+}
+
+.cta-section .left-image {
+ left: 0px;
+ bottom: 0;
+}
+
+@media only screen and (min-width: 768px) and (max-width: 991px),
+ (max-width: 767px) {
+ .cta-section .left-image {
+ position: static;
+ width: 100%;
+ margin-top: 50px;
+ }
+}
+
+.cta-section .left-image img {
+ max-width: 100%;
+}
+
+@media only screen and (min-width: 768px) and (max-width: 991px),
+ (max-width: 767px) {
+ .cta-section .left-image img {
+ max-width: 100%;
+ margin: auto;
+ }
+}
+
+@media only screen and (min-width: 1400px) {
+ .cta-section .cta-content-wrapper {
+ padding-right: 100px;
+ }
+}
+
+@media only screen and (min-width: 768px) and (max-width: 991px),
+ (max-width: 767px) {
+ .cta-section .cta-image {
+ margin-top: 50px;
+ }
+}
+
+.cta-section .cta-image img {
+ width: 100%;
+}
+
+/* =====================
+ BLOG CSS
+======================== */
+.blog-section {
+ padding-top: 160px;
+ background-image: url('../public/images/common-bg.svg');
+ background-size: cover;
+ background-position: bottom-center;
+}
+
+.blog-section .section-title {
+ margin-bottom: 60px;
+}
+
+@media only screen and (min-width: 1400px),
+ only screen and (min-width: 1200px) and (max-width: 1399px) {
+ .blog-section .section-title {
+ padding: 0px 40px;
+ }
+}
+
+.blog-section .section-title h2 {
+ margin-bottom: 15px;
+}
+
+.single-blog,
+.single-post,
+.blog-roll-card {
+ background: #fff;
+ box-shadow: 0px 0px 50px rgba(183, 199, 240, 0.25);
+ border-radius: 14px;
+ padding: 52px 30px;
+ margin-bottom: 50px;
+ position: relative;
+ overflow: hidden;
+ z-index: 1;
+}
+
+.single-blog .populer,
+.single-post .populer,
+.blog-roll-card .populer {
+ position: absolute;
+ right: 10px;
+ top: 18px;
+ color: #fff;
+ z-index: 2;
+ font-size: 16px;
+ font-weight: 700;
+}
+
+.single-blog .blog-header,
+.single-post .blog-header,
+.blog-roll-card .blog-header {
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+ /* changes for Image tag */
+ width: 100%;
+ height: 200px;
+ position: relative;
+ /* end of changes for Image tag */
+}
+
+.single-blog .blog-header h5,
+.single-post .blog-header h5,
+.blog-roll-card .blog-header h5 {
+ font-size: 18px;
+ margin-bottom: 20px;
+}
+
+.single-blog .blog-header h2,
+.single-post .blog-header h2,
+.blog-roll-card .blog-header h2 {
+ font-size: 40px;
+ margin-bottom: 30px;
+}
+
+.single-blog .blog-header h2 span,
+.single-post .blog-header h2 span,
+.blog-roll-card .blog-header h2 span {
+ font-size: 16px;
+ color: rgba(0, 0, 0, 0.5);
+ font-weight: 400;
+}
+
+.single-blog .blog-body,
+.single-post .blog-body,
+.blog-roll-card .blog-body {
+ padding-top: 30px;
+ padding-bottom: 25px;
+}
+
+.single-blog .blog-body ul li,
+.single-post .blog-body ul li,
+.blog-roll-card .blog-body ul li {
+ display: flex;
+ align-items: center;
+ margin-bottom: 12px;
+}
+
+.single-blog .blog-body ul li span.bolet,
+.single-post .blog-body ul li span.bolet,
+.blog-roll-card .blog-body ul li span.bolet {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ margin-right: 13px;
+ background: rgba(0, 0, 0, 0.2);
+}
+
+.single-blog .blog-body ul li span.bolet.active,
+.single-post .blog-body ul li span.bolet.active,
+.blog-roll-card .blog-body ul li span.bolet.active {
+ background: #37c2cc;
+}
+
+.single-blog .blog-body ul li p,
+.single-post .blog-body ul li p,
+.blog-roll-card .blog-body ul li p {
+ margin-bottom: 0px;
+}
+
+.single-blog .blog-footer .main-btn,
+.single-blog .blog-roll-card-footer .main-btn,
+.single-post .blog-footer .main-btn,
+.single-post .blog-roll-card-footer .main-btn,
+.blog-roll-card .blog-footer .main-btn,
+.blog-roll-card .blog-roll-card-footer .main-btn {
+ color: #37c2cc;
+ background: #e3fdff;
+}
+
+.single-blog .blog-footer .main-btn:hover,
+.single-blog .blog-roll-card-footer .main-btn:hover,
+.single-post .blog-footer .main-btn:hover,
+.single-post .blog-roll-card-footer .main-btn:hover,
+.blog-roll-card .blog-footer .main-btn:hover,
+.blog-roll-card .blog-roll-card-footer .main-btn:hover {
+ color: #fff;
+}
+
+.single-blog .blog-footer .main-btn:hover::after,
+.single-blog .blog-roll-card-footer .main-btn:hover::after,
+.single-post .blog-footer .main-btn:hover::after,
+.single-post .blog-roll-card-footer .main-btn:hover::after,
+.blog-roll-card .blog-footer .main-btn:hover::after,
+.blog-roll-card .blog-roll-card-footer .main-btn:hover::after {
+ background: #37c2cc;
+}
+
+.single-blog.standard::after,
+.standard.single-post::after,
+.standard.blog-roll-card::after {
+ content: '';
+ position: absolute;
+ background: #37c2cc;
+ border-radius: 50%;
+ z-index: -1;
+ width: 150px;
+ height: 150px;
+ top: -70px;
+ right: -50px;
+}
+
+.single-blog.standard .blog-footer .main-btn,
+.single-blog.standard .blog-roll-card-footer .main-btn,
+.standard.single-post .blog-footer .main-btn,
+.standard.single-post .blog-roll-card-footer .main-btn,
+.standard.blog-roll-card .blog-footer .main-btn,
+.standard.blog-roll-card .blog-roll-card-footer .main-btn {
+ color: #fff;
+ background: #37c2cc;
+}
+
+.single-blog.standard .blog-footer .main-btn:hover::after,
+.single-blog.standard .blog-roll-card-footer .main-btn:hover::after,
+.standard.single-post .blog-footer .main-btn:hover::after,
+.standard.single-post .blog-roll-card-footer .main-btn:hover::after,
+.standard.blog-roll-card .blog-footer .main-btn:hover::after,
+.standard.blog-roll-card .blog-roll-card-footer .main-btn:hover::after {
+ background: rgba(0, 0, 0, 0.05);
+}
+
+.single-blog div:first-of-type,
+.single-post div:first-of-type,
+.blog-roll-card div:first-of-type {
+ padding-top: 0;
+}
+
+.single-post-nav,
+.blog-roll-nav {
+ align-items: center;
+ background: linear-gradient(
+ 180deg,
+ #c2fbff 0%,
+ rgba(255, 255, 255, 0) 93.47%
+ );
+ display: flex;
+ height: 400px;
+ overflow: hidden;
+ position: relative;
+ z-index: 1;
+}
+
+@media only screen and (min-width: 550px) {
+ .single-post-nav,
+ .blog-roll-nav {
+ height: 350px;
+ }
+}
+
+@media only screen and (min-width: 768px) {
+ .single-post-nav,
+ .blog-roll-nav {
+ height: 430px;
+ }
+}
+
+.breadcrumb-nav {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ margin-top: 1em;
+}
+
+@media only screen and (min-width: 550px) {
+ .breadcrumb-nav {
+ flex-direction: row;
+ }
+}
+
+.breadcrumb-nav li a {
+ color: #37c2cc;
+ font-weight: 500;
+ display: inline-block;
+ position: relative;
+ padding-right: 15px;
+ margin-right: 15px;
+ text-transform: capitalize;
+}
+
+.breadcrumb-nav li::after {
+ content: '';
+ font-size: 10px;
+ position: relative;
+ font-family: lineIcons;
+ right: 11px;
+}
+
+.breadcrumb-nav li:last-child::after {
+ content: '';
+}
+
+.single-post,
+.blog-roll-card {
+ padding: 0px;
+}
+
+.single-post div:first-of-type,
+.blog-roll-card div:first-of-type {
+ padding-top: 30px;
+}
+
+.single-post-meta,
+.blog-roll-card-meta {
+ padding: 30px 16px 0 16px;
+}
+
+@media only screen and (min-width: 550px) {
+ .single-post-meta,
+ .blog-roll-card-meta {
+ padding: 42px 24px 0 24px;
+ }
+}
+
+@media only screen and (min-width: 768px) {
+ .single-post-meta,
+ .blog-roll-card-meta {
+ padding: 52px 30px 0 30px;
+ }
+}
+
+.single-post-header,
+.blog-roll-card-header {
+ margin-bottom: 20px;
+}
+
+.single-post-meta-info,
+.blog-roll-card-meta-info {
+ font-size: 14px;
+ display: inline-block;
+ position: relative;
+}
+
+.single-post-meta-info li,
+.blog-roll-card-meta-info li {
+ font-size: 14px;
+ display: inline-block;
+ margin-right: 15px;
+ padding-right: 15px;
+ position: relative;
+}
+
+.single-post-meta-info li a,
+.blog-roll-card-meta-info li a {
+ color: #888;
+ font-size: 14px;
+ font-weight: 500;
+}
+
+.single-post-meta-info li a img,
+.blog-roll-card-meta-info li a img {
+ height: 35px;
+ width: 35px;
+ border-radius: 50%;
+ display: inline-block;
+ margin-right: 12px;
+}
+
+@media only screen and (min-width: 550px) {
+ .single-post-meta-info li a img,
+ .blog-roll-card-meta-info li a img {
+ height: 35px;
+ width: 35px;
+ }
+}
+
+@media only screen and (min-width: 768px) {
+ .single-post-meta-info li a img,
+ .blog-roll-card-meta-info li a img {
+ height: 50px;
+ width: 50px;
+ }
+}
+
+@media only screen and (min-width: 768px) {
+ .single-post-meta-info li::before,
+ .blog-roll-card-meta-info li::before {
+ position: absolute;
+ content: '';
+ right: -5px;
+ top: 50%;
+ background-color: #d2d2d2;
+ height: 5px;
+ width: 5px;
+ border-radius: 50%;
+ -webkit-transform: translateY(-50%);
+ transform: translateY(-50%);
+ }
+}
+
+@media only screen and (min-width: 768px) {
+ .single-post-meta-info li:last-of-type::before,
+ .blog-roll-card-meta-info li:last-of-type::before {
+ content: none;
+ }
+}
+
+.single-post-thumbnail {
+ position: relative;
+ overflow: hidden;
+ border-radius: 0;
+ width: 100%;
+ margin-top: 15px;
+ /* changes for Image tag */
+ height: 300px;
+ /* end of changes for Image tag */
+}
+
+.single-post-thumbnail img {
+ width: 100%;
+}
+
+@media only screen and (min-width: 550px) {
+ .single-post-thumbnail {
+ margin-top: 42px;
+ }
+}
+
+@media only screen and (min-width: 768px) {
+ .single-post-thumbnail {
+ margin-top: 52px;
+ }
+}
+
+.single-post-body,
+.blog-roll-card-body {
+ padding: 30px 16px;
+}
+
+@media only screen and (min-width: 550px) {
+ .single-post-body,
+ .blog-roll-card-body {
+ padding: 42px 24px;
+ }
+}
+
+@media only screen and (min-width: 768px) {
+ .single-post-body,
+ .blog-roll-card-body {
+ padding: 52px 30px;
+ }
+}
+
+.single-post-body h3,
+.blog-roll-card-body h3,
+.single-post-body .single-post-header,
+.blog-roll-card-body .single-post-header,
+.single-post-body .blog-roll-card-header,
+.blog-roll-card-body .blog-roll-card-header {
+ margin-top: 10px;
+ margin-bottom: 20px;
+}
+
+.single-post-body p,
+.blog-roll-card-body p {
+ margin-bottom: 20px;
+}
+
+.single-post-body figure img,
+.blog-roll-card-body figure img {
+ max-width: 100%;
+}
+
+@media only screen and (min-width: 768px) {
+ .blog-roll-card .single-post-thumbnail {
+ margin-top: 30px;
+ }
+}
+
+.blog-roll-card-meta {
+ padding: 15px 15px 0 15px;
+}
+
+@media only screen and (min-width: 768px) {
+ .blog-roll-card-meta {
+ padding: 30px 30px 0 30px;
+ }
+}
+
+.blog-roll-card-header a {
+ color: rgba(0, 0, 0, 0.7);
+}
+
+.blog-roll-card-header a:hover {
+ color: #37c2cc;
+}
+
+.blog-roll-card-body {
+ padding: 15px 15px 5px;
+}
+
+@media only screen and (min-width: 768px) {
+ .blog-roll-card-body {
+ padding: 30px 30px 10px;
+ }
+}
+
+.blog-roll-card-footer {
+ padding: 0px 15px 15px;
+}
+
+@media only screen and (min-width: 768px) {
+ .blog-roll-card-footer {
+ padding: 0 30px 30px;
+ }
+}
+
+/* =====================
+ TEAM SECTION CSS
+======================== */
+.team-section .single-team {
+ position: relative;
+ margin-bottom: 50px;
+}
+
+.team-section .single-team .team-image {
+ max-width: 313px;
+ width: 100%;
+ border-radius: 50%;
+}
+
+.team-section .single-team .team-image img {
+ width: 100%;
+}
+
+.team-section .single-team .team-info {
+ position: absolute;
+ bottom: 30px;
+ right: 0;
+ background: #fff;
+ -webkit-box-shadow: 0px 8px 25px rgba(211, 211, 211, 0.25);
+ -moz-box-shadow: 0px 8px 25px rgba(211, 211, 211, 0.25);
+ box-shadow: 0px 8px 25px rgba(211, 211, 211, 0.25);
+ padding: 20px 30px;
+ min-width: 210px;
+ text-align: center;
+}
+
+.team-section .single-team .team-info h4 {
+ font-size: 20px;
+ margin-bottom: 5px;
+}
+
+.team-section .single-team .team-info p {
+ font-size: 14px;
+}
+
+/* ========================
+ TESTIMONIAL CSS
+=========================== */
+.testimonial-section {
+ background-image: url('../public/images/testimonial/testimonial-bg.svg');
+ background-size: cover;
+ background-position: bottom center;
+ padding-top: 75px;
+ padding-bottom: 130px;
+ border-radius: 333px 0px;
+ position: relative;
+ z-index: 1;
+}
+
+@media only screen and (min-width: 1200px) and (max-width: 1399px) {
+ .testimonial-section {
+ border-radius: 200px 0px;
+ }
+}
+
+@media only screen and (min-width: 992px) and (max-width: 1199px) {
+ .testimonial-section {
+ border-radius: 100px 0px;
+ }
+}
+
+@media only screen and (min-width: 768px) and (max-width: 991px),
+ (max-width: 767px) {
+ .testimonial-section {
+ border-radius: 70px 0px;
+ }
+}
+
+.testimonial-section .testimonial-active-wrapper {
+ position: relative;
+}
+
+@media (max-width: 767px) {
+ .testimonial-section .testimonial-active-wrapper {
+ padding-bottom: 80px;
+ }
+}
+
+.testimonial-section .testimonial-active-wrapper .tns-controls {
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ width: 100%;
+ z-index: 2;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+}
+
+@media (max-width: 767px) {
+ .testimonial-section .testimonial-active-wrapper .tns-controls {
+ justify-content: center;
+ }
+}
+
+.testimonial-section .testimonial-active-wrapper .tns-controls button {
+ width: 55px;
+ height: 55px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ background: #fff;
+ color: rgba(0, 0, 0, 0.9);
+ border: 1px solid #37c2cc;
+ -webkit-transition: all 0.3s ease-out 0s;
+ -moz-transition: all 0.3s ease-out 0s;
+ -ms-transition: all 0.3s ease-out 0s;
+ -o-transition: all 0.3s ease-out 0s;
+ transition: all 0.3s ease-out 0s;
+ margin: 0 5px;
+}
+
+.testimonial-section .testimonial-active-wrapper .tns-controls button:hover {
+ background: #37c2cc;
+ color: #fff;
+}
+
+.testimonial-section .testimonial-active-wrapper .single-testimonial {
+ text-align: center;
+}
+
+.testimonial-section .testimonial-active-wrapper .single-testimonial .quote {
+ font-size: 55px;
+ line-height: 1;
+ color: #37c2cc;
+ margin-bottom: 20px;
+}
+
+.testimonial-section .testimonial-active-wrapper .single-testimonial .content {
+ margin-bottom: 30px;
+}
+
+.testimonial-section
+ .testimonial-active-wrapper
+ .single-testimonial
+ .content
+ p {
+ font-size: 22px;
+ line-height: 35px;
+ color: rgba(0, 0, 0, 0.7);
+ padding: 0 15px;
+}
+
+@media (max-width: 767px) {
+ .testimonial-section
+ .testimonial-active-wrapper
+ .single-testimonial
+ .content
+ p {
+ font-size: 18px;
+ line-height: 28px;
+ padding: 0px;
+ }
+}
+
+.testimonial-section .testimonial-active-wrapper .single-testimonial .info h6 {
+ font-size: 16px;
+ font-weight: 700;
+ margin-bottom: 10px;
+}
+
+.testimonial-section .testimonial-active-wrapper .single-testimonial .info p {
+ font-size: 15px;
+}
+
+.testimonial-section .testimonial-images .testimonial-image {
+ position: absolute;
+ z-index: -1;
+ max-width: 100%;
+}
+
+@media only screen and (min-width: 768px) and (max-width: 991px),
+ (max-width: 767px) {
+ .testimonial-section .testimonial-images .testimonial-image {
+ display: none;
+ }
+}
+
+.testimonial-section .testimonial-images .testimonial-image.image-1 {
+ left: 10%;
+ top: 30%;
+}
+
+.testimonial-section .testimonial-images .testimonial-image.image-2 {
+ bottom: 5%;
+ left: 15%;
+}
+
+.testimonial-section .testimonial-images .testimonial-image.image-3 {
+ top: 5%;
+ right: 10%;
+}
+
+.testimonial-section .testimonial-images .testimonial-image.image-4 {
+ top: 40%;
+ right: 12%;
+}
+
+@media only screen and (min-width: 992px) and (max-width: 1199px) {
+ .testimonial-section .testimonial-images .testimonial-image.image-4 {
+ right: 8%;
+ }
+}
+
+/* ==========================
+ FOOTER CSS
+============================= */
+.footer .footer-widget {
+ margin-bottom: 50px;
+}
+
+.footer .footer-widget .logo {
+ margin-bottom: 30px;
+}
+
+.footer .footer-widget .desc {
+ margin-bottom: 25px;
+}
+
+.footer .footer-widget .social-links {
+ display: flex;
+ align-items: center;
+}
+
+.footer .footer-widget .social-links li a {
+ width: 44px;
+ height: 44px;
+ border-radius: 50%;
+ background: #e3fdff;
+ color: rgba(0, 0, 0, 0.9);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 18px;
+ font-size: 22px;
+}
+
+.footer .footer-widget .social-links li a:hover {
+ background: #37c2cc;
+ color: #fff;
+}
+
+.footer .footer-widget h3,
+.footer .footer-widget .single-post-header,
+.footer .footer-widget .blog-roll-card-header {
+ margin-bottom: 30px;
+ margin-top: 10px;
+}
+
+.footer .footer-widget .links li a {
+ font-size: 16px;
+ line-height: 30px;
+ color: rgba(0, 0, 0, 0.7);
+}
+
+.footer .footer-widget .links li a:hover {
+ padding-left: 8px;
+ color: #37c2cc;
+}
+
+.footer .footer-widget form {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+}
+
+.footer .footer-widget form input {
+ width: 100%;
+ background: #f1feff;
+ border: 1px solid #37c2cc;
+ box-sizing: border-box;
+ border-radius: 10px;
+ margin-bottom: 20px;
+ padding: 15px 20px;
+}
+
+.footer .footer-widget form button {
+ text-align: right;
+}
+
+/* ======================
+ DEFAULT CSS
+========================= */
+.mt-5 {
+ margin-top: 5px;
+}
+
+.mt-10 {
+ margin-top: 10px;
+}
+
+.mt-15 {
+ margin-top: 15px;
+}
+
+.mt-20 {
+ margin-top: 20px;
+}
+
+.mt-25 {
+ margin-top: 25px;
+}
+
+.mt-30 {
+ margin-top: 30px;
+}
+
+.mt-35 {
+ margin-top: 35px;
+}
+
+.mt-40 {
+ margin-top: 40px;
+}
+
+.mt-45 {
+ margin-top: 45px;
+}
+
+.mt-50 {
+ margin-top: 50px;
+}
+
+.mt-55 {
+ margin-top: 55px;
+}
+
+.mt-60 {
+ margin-top: 60px;
+}
+
+.mt-65 {
+ margin-top: 65px;
+}
+
+.mt-70 {
+ margin-top: 70px;
+}
+
+.mt-75 {
+ margin-top: 75px;
+}
+
+.mt-80 {
+ margin-top: 80px;
+}
+
+.mt-85 {
+ margin-top: 85px;
+}
+
+.mt-90 {
+ margin-top: 90px;
+}
+
+.mt-95 {
+ margin-top: 95px;
+}
+
+.mt-100 {
+ margin-top: 100px;
+}
+
+.mt-105 {
+ margin-top: 105px;
+}
+
+.mt-110 {
+ margin-top: 110px;
+}
+
+.mt-115 {
+ margin-top: 115px;
+}
+
+.mt-120 {
+ margin-top: 120px;
+}
+
+.mt-125 {
+ margin-top: 125px;
+}
+
+.mt-130 {
+ margin-top: 130px;
+}
+
+.mt-135 {
+ margin-top: 135px;
+}
+
+.mt-140 {
+ margin-top: 140px;
+}
+
+.mt-145 {
+ margin-top: 145px;
+}
+
+.mt-150 {
+ margin-top: 150px;
+}
+
+.mt-155 {
+ margin-top: 155px;
+}
+
+.mt-160 {
+ margin-top: 160px;
+}
+
+.mt-165 {
+ margin-top: 165px;
+}
+
+.mt-170 {
+ margin-top: 170px;
+}
+
+.mt-175 {
+ margin-top: 175px;
+}
+
+.mt-180 {
+ margin-top: 180px;
+}
+
+.mt-185 {
+ margin-top: 185px;
+}
+
+.mt-190 {
+ margin-top: 190px;
+}
+
+.mt-195 {
+ margin-top: 195px;
+}
+
+.mt-200 {
+ margin-top: 200px;
+}
+
+.mt-205 {
+ margin-top: 205px;
+}
+
+.mt-210 {
+ margin-top: 210px;
+}
+
+.mt-215 {
+ margin-top: 215px;
+}
+
+.mt-220 {
+ margin-top: 220px;
+}
+
+.mt-225 {
+ margin-top: 225px;
+}
+
+.mb-5 {
+ margin-bottom: 5px;
+}
+
+.mb-10 {
+ margin-bottom: 10px;
+}
+
+.mb-15 {
+ margin-bottom: 15px;
+}
+
+.mb-20 {
+ margin-bottom: 20px;
+}
+
+.mb-25 {
+ margin-bottom: 25px;
+}
+
+.mb-30 {
+ margin-bottom: 30px;
+}
+
+.mb-35 {
+ margin-bottom: 35px;
+}
+
+.mb-40 {
+ margin-bottom: 40px;
+}
+
+.mb-45 {
+ margin-bottom: 45px;
+}
+
+.mb-50 {
+ margin-bottom: 50px;
+}
+
+.mb-55 {
+ margin-bottom: 55px;
+}
+
+.mb-60 {
+ margin-bottom: 60px;
+}
+
+.mb-65 {
+ margin-bottom: 65px;
+}
+
+.mb-70 {
+ margin-bottom: 70px;
+}
+
+.mb-75 {
+ margin-bottom: 75px;
+}
+
+.mb-80 {
+ margin-bottom: 80px;
+}
+
+.mb-85 {
+ margin-bottom: 85px;
+}
+
+.mb-90 {
+ margin-bottom: 90px;
+}
+
+.mb-95 {
+ margin-bottom: 95px;
+}
+
+.mb-100 {
+ margin-bottom: 100px;
+}
+
+.mb-105 {
+ margin-bottom: 105px;
+}
+
+.mb-110 {
+ margin-bottom: 110px;
+}
+
+.mb-115 {
+ margin-bottom: 115px;
+}
+
+.mb-120 {
+ margin-bottom: 120px;
+}
+
+.mb-125 {
+ margin-bottom: 125px;
+}
+
+.mb-130 {
+ margin-bottom: 130px;
+}
+
+.mb-135 {
+ margin-bottom: 135px;
+}
+
+.mb-140 {
+ margin-bottom: 140px;
+}
+
+.mb-145 {
+ margin-bottom: 145px;
+}
+
+.mb-150 {
+ margin-bottom: 150px;
+}
+
+.mb-155 {
+ margin-bottom: 155px;
+}
+
+.mb-160 {
+ margin-bottom: 160px;
+}
+
+.mb-165 {
+ margin-bottom: 165px;
+}
+
+.mb-170 {
+ margin-bottom: 170px;
+}
+
+.mb-175 {
+ margin-bottom: 175px;
+}
+
+.mb-180 {
+ margin-bottom: 180px;
+}
+
+.mb-185 {
+ margin-bottom: 185px;
+}
+
+.mb-190 {
+ margin-bottom: 190px;
+}
+
+.mb-195 {
+ margin-bottom: 195px;
+}
+
+.mb-200 {
+ margin-bottom: 200px;
+}
+
+.mb-205 {
+ margin-bottom: 205px;
+}
+
+.mb-210 {
+ margin-bottom: 210px;
+}
+
+.mb-215 {
+ margin-bottom: 215px;
+}
+
+.mb-220 {
+ margin-bottom: 220px;
+}
+
+.mb-225 {
+ margin-bottom: 225px;
+}
+
+.pt-5 {
+ padding-top: 5px;
+}
+
+.pt-10 {
+ padding-top: 10px;
+}
+
+.pt-15 {
+ padding-top: 15px;
+}
+
+.pt-20 {
+ padding-top: 20px;
+}
+
+.pt-25 {
+ padding-top: 25px;
+}
+
+.pt-30 {
+ padding-top: 30px;
+}
+
+.pt-35 {
+ padding-top: 35px;
+}
+
+.pt-40 {
+ padding-top: 40px;
+}
+
+.pt-45 {
+ padding-top: 45px;
+}
+
+.pt-50 {
+ padding-top: 50px;
+}
+
+.pt-55 {
+ padding-top: 55px;
+}
+
+.pt-60 {
+ padding-top: 60px;
+}
+
+.pt-65 {
+ padding-top: 65px;
+}
+
+.pt-70 {
+ padding-top: 70px;
+}
+
+.pt-75 {
+ padding-top: 75px;
+}
+
+.pt-80 {
+ padding-top: 80px;
+}
+
+.pt-85 {
+ padding-top: 85px;
+}
+
+.pt-90 {
+ padding-top: 90px;
+}
+
+.pt-95 {
+ padding-top: 95px;
+}
+
+.pt-100 {
+ padding-top: 100px;
+}
+
+.pt-105 {
+ padding-top: 105px;
+}
+
+.pt-110 {
+ padding-top: 110px;
+}
+
+.pt-115 {
+ padding-top: 115px;
+}
+
+.pt-120 {
+ padding-top: 120px;
+}
+
+.pt-125 {
+ padding-top: 125px;
+}
+
+.pt-130 {
+ padding-top: 130px;
+}
+
+.pt-135 {
+ padding-top: 135px;
+}
+
+.pt-140 {
+ padding-top: 140px;
+}
+
+.pt-145 {
+ padding-top: 145px;
+}
+
+.pt-150 {
+ padding-top: 150px;
+}
+
+.pt-155 {
+ padding-top: 155px;
+}
+
+.pt-160 {
+ padding-top: 160px;
+}
+
+.pt-165 {
+ padding-top: 165px;
+}
+
+.pt-170 {
+ padding-top: 170px;
+}
+
+.pt-175 {
+ padding-top: 175px;
+}
+
+.pt-180 {
+ padding-top: 180px;
+}
+
+.pt-185 {
+ padding-top: 185px;
+}
+
+.pt-190 {
+ padding-top: 190px;
+}
+
+.pt-195 {
+ padding-top: 195px;
+}
+
+.pt-200 {
+ padding-top: 200px;
+}
+
+.pt-205 {
+ padding-top: 205px;
+}
+
+.pt-210 {
+ padding-top: 210px;
+}
+
+.pt-215 {
+ padding-top: 215px;
+}
+
+.pt-220 {
+ padding-top: 220px;
+}
+
+.pt-225 {
+ padding-top: 225px;
+}
+
+.pb-5 {
+ padding-bottom: 5px;
+}
+
+.pb-10 {
+ padding-bottom: 10px;
+}
+
+.pb-15 {
+ padding-bottom: 15px;
+}
+
+.pb-20 {
+ padding-bottom: 20px;
+}
+
+.pb-25 {
+ padding-bottom: 25px;
+}
+
+.pb-30 {
+ padding-bottom: 30px;
+}
+
+.pb-35 {
+ padding-bottom: 35px;
+}
+
+.pb-40 {
+ padding-bottom: 40px;
+}
+
+.pb-45 {
+ padding-bottom: 45px;
+}
+
+.pb-50 {
+ padding-bottom: 50px;
+}
+
+.pb-55 {
+ padding-bottom: 55px;
+}
+
+.pb-60 {
+ padding-bottom: 60px;
+}
+
+.pb-65 {
+ padding-bottom: 65px;
+}
+
+.pb-70 {
+ padding-bottom: 70px;
+}
+
+.pb-75 {
+ padding-bottom: 75px;
+}
+
+.pb-80 {
+ padding-bottom: 80px;
+}
+
+.pb-85 {
+ padding-bottom: 85px;
+}
+
+.pb-90 {
+ padding-bottom: 90px;
+}
+
+.pb-95 {
+ padding-bottom: 95px;
+}
+
+.pb-100 {
+ padding-bottom: 100px;
+}
+
+.pb-105 {
+ padding-bottom: 105px;
+}
+
+.pb-110 {
+ padding-bottom: 110px;
+}
+
+.pb-115 {
+ padding-bottom: 115px;
+}
+
+.pb-120 {
+ padding-bottom: 120px;
+}
+
+.pb-125 {
+ padding-bottom: 125px;
+}
+
+.pb-130 {
+ padding-bottom: 130px;
+}
+
+.pb-135 {
+ padding-bottom: 135px;
+}
+
+.pb-140 {
+ padding-bottom: 140px;
+}
+
+.pb-145 {
+ padding-bottom: 145px;
+}
+
+.pb-150 {
+ padding-bottom: 150px;
+}
+
+.pb-155 {
+ padding-bottom: 155px;
+}
+
+.pb-160 {
+ padding-bottom: 160px;
+}
+
+.pb-165 {
+ padding-bottom: 165px;
+}
+
+.pb-170 {
+ padding-bottom: 170px;
+}
+
+.pb-175 {
+ padding-bottom: 175px;
+}
+
+.pb-180 {
+ padding-bottom: 180px;
+}
+
+.pb-185 {
+ padding-bottom: 185px;
+}
+
+.pb-190 {
+ padding-bottom: 190px;
+}
+
+.pb-195 {
+ padding-bottom: 195px;
+}
+
+.pb-200 {
+ padding-bottom: 200px;
+}
+
+.pb-205 {
+ padding-bottom: 205px;
+}
+
+.pb-210 {
+ padding-bottom: 210px;
+}
+
+.pb-215 {
+ padding-bottom: 215px;
+}
+
+.pb-220 {
+ padding-bottom: 220px;
+}
+
+.pb-225 {
+ padding-bottom: 225px;
+}
+
+.widget {
+ background: #fff;
+ box-shadow: 0px 0px 50px rgba(183, 199, 240, 0.25);
+ border-radius: 14px;
+ padding: 30px;
+ margin-bottom: 30px;
+ position: relative;
+ overflow: hidden;
+ z-index: 1;
+}
+
+.widget form {
+ position: relative;
+}
+
+.widget form input {
+ background-color: transparent;
+ border-radius: 5px;
+ border: 1px solid #37c2cc;
+ font-size: 14px;
+ font-weight: 400;
+ height: 55px;
+ padding: 0 70px 0 30px;
+ width: 100%;
+}
+
+.widget form button {
+ border: none;
+ position: absolute;
+ right: 7px;
+ top: 6px;
+ width: 42px;
+ height: 42px;
+ z-index: 1;
+ color: #fff !important;
+ font-size: 13px;
+ -webkit-transition: all 0.3s ease-out 0s;
+ transition: all 0.3s ease-out 0s;
+ color: #fff;
+ border-radius: 5px;
+ padding: 0 !important;
+ border: none;
+ background: #37c2cc;
+}
+
+.widget-title {
+ font-size: 22px;
+ margin-bottom: 30px;
+ position: relative;
+ font-weight: 600;
+ line-height: 28px;
+ z-index: 1;
+}
+
+.categories-widget .categories-list li {
+ display: inline;
+}
+
+.categories-widget .categories-list li > a {
+ -ms-flex-pack: center;
+ -webkit-box-pack: center;
+ background: 0 0;
+ border-radius: 5px;
+ border: 1px solid #37c2cc;
+ color: rgba(0, 0, 0, 0.7);
+ display: -ms-inline-flexbox;
+ display: -webkit-inline-box;
+ display: inline-flex;
+ font-size: 14px;
+ font-weight: 400;
+ justify-content: center;
+ margin-bottom: 10px;
+ margin-right: 5px;
+ padding: 7px 15px;
+ text-transform: capitalize;
+}
+
+.categories-widget .categories-list li > a:hover {
+ background-color: #37c2cc;
+ color: #fff;
+ border-color: transparent;
+}
+
+/*# sourceMappingURL=main.css.map */
diff --git a/examples/cms-buttercms/css/tiny-slider.min.css b/examples/cms-buttercms/css/tiny-slider.min.css
new file mode 100644
index 000000000000..f2fef5a0a28b
--- /dev/null
+++ b/examples/cms-buttercms/css/tiny-slider.min.css
@@ -0,0 +1,136 @@
+.tns-outer {
+ padding: 0 !important;
+}
+.tns-outer [hidden] {
+ display: none !important;
+}
+.tns-outer [aria-controls],
+.tns-outer [data-action] {
+ cursor: pointer;
+}
+.tns-slider {
+ -webkit-transition: all 0s;
+ -moz-transition: all 0s;
+ transition: all 0s;
+}
+.tns-slider > .tns-item {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.tns-horizontal.tns-subpixel {
+ white-space: nowrap;
+}
+.tns-horizontal.tns-subpixel > .tns-item {
+ display: inline-block;
+ vertical-align: top;
+ white-space: normal;
+}
+.tns-horizontal.tns-no-subpixel:after {
+ content: '';
+ display: table;
+ clear: both;
+}
+.tns-horizontal.tns-no-subpixel > .tns-item {
+ float: left;
+}
+.tns-horizontal.tns-carousel.tns-no-subpixel > .tns-item {
+ margin-right: -100%;
+}
+.tns-no-calc {
+ position: relative;
+ left: 0;
+}
+.tns-gallery {
+ position: relative;
+ left: 0;
+ min-height: 1px;
+}
+.tns-gallery > .tns-item {
+ position: absolute;
+ left: -100%;
+ -webkit-transition: transform 0s, opacity 0s;
+ -moz-transition: transform 0s, opacity 0s;
+ transition: transform 0s, opacity 0s;
+}
+.tns-gallery > .tns-slide-active {
+ position: relative;
+ left: auto !important;
+}
+.tns-gallery > .tns-moving {
+ -webkit-transition: all 0.25s;
+ -moz-transition: all 0.25s;
+ transition: all 0.25s;
+}
+.tns-autowidth {
+ display: inline-block;
+}
+.tns-lazy-img {
+ -webkit-transition: opacity 0.6s;
+ -moz-transition: opacity 0.6s;
+ transition: opacity 0.6s;
+ opacity: 0.6;
+}
+.tns-lazy-img.tns-complete {
+ opacity: 1;
+}
+.tns-ah {
+ -webkit-transition: height 0s;
+ -moz-transition: height 0s;
+ transition: height 0s;
+}
+.tns-ovh {
+ overflow: hidden;
+}
+.tns-visually-hidden {
+ position: absolute;
+ left: -10000em;
+}
+.tns-transparent {
+ opacity: 0;
+ visibility: hidden;
+}
+.tns-fadeIn {
+ opacity: 1;
+ z-index: 0;
+}
+.tns-fadeOut,
+.tns-normal {
+ opacity: 0;
+ z-index: -1;
+}
+.tns-vpfix {
+ white-space: nowrap;
+}
+.tns-vpfix > div,
+.tns-vpfix > li {
+ display: inline-block;
+}
+.tns-t-subp2 {
+ margin: 0 auto;
+ width: 310px;
+ position: relative;
+ height: 10px;
+ overflow: hidden;
+}
+.tns-t-ct {
+ width: 2333.3333333%;
+ width: -webkit-calc(100% * 70 / 3);
+ width: -moz-calc(100% * 70 / 3);
+ width: calc(100% * 70 / 3);
+ position: absolute;
+ right: 0;
+}
+.tns-t-ct:after {
+ content: '';
+ display: table;
+ clear: both;
+}
+.tns-t-ct > div {
+ width: 1.4285714%;
+ width: -webkit-calc(100% / 70);
+ width: -moz-calc(100% / 70);
+ width: calc(100% / 70);
+ height: 10px;
+ float: left;
+}
diff --git a/examples/cms-buttercms/jsconfig.json b/examples/cms-buttercms/jsconfig.json
index 1bac23a7cd55..479b198dfab1 100644
--- a/examples/cms-buttercms/jsconfig.json
+++ b/examples/cms-buttercms/jsconfig.json
@@ -4,7 +4,7 @@
"paths": {
"@/components/*": ["components/*"],
"@/lib/*": ["lib/*"],
- "@/styles/*": ["styles/*"]
+ "@/css/*": ["css/*"]
}
}
}
diff --git a/examples/cms-buttercms/lib/api.js b/examples/cms-buttercms/lib/api.js
index 10b27094d019..5913ae1277ef 100644
--- a/examples/cms-buttercms/lib/api.js
+++ b/examples/cms-buttercms/lib/api.js
@@ -1,33 +1,140 @@
import Butter from 'buttercms'
-const butter = Butter(process.env.BUTTERCMS_API_KEY)
-export async function getPreviewPostBySlug(slug) {
- const postResponse = await butter.post.retrieve(slug, {
- preview: 1,
- })
- return postResponse?.data?.data
+let butter
+
+const previewSetting = process.env.PREVIEW
+// make preview mode by default
+const preview =
+ previewSetting === 'true' || previewSetting === undefined ? 1 : 0
+
+try {
+ butter = Butter(process.env.NEXT_PUBLIC_BUTTER_CMS_API_KEY, preview)
+} catch (e) {
+ console.log(e)
+}
+
+const defaultPageSize = 100
+const defaultPostCount = 10
+
+export async function getLandingPage(slug) {
+ try {
+ const page = await butter.page.retrieve('landing-page', slug)
+
+ return page?.data?.data
+ } catch (e) {
+ throw e.response.data.detail
+ }
+}
+
+export async function getLandingPages() {
+ let paginatedLandingPages = []
+ let currentPage = 1
+ while (!!currentPage) {
+ const landingPagesData = await getLandingPagesData(currentPage)
+ paginatedLandingPages.push(...landingPagesData.pages)
+ currentPage = landingPagesData.nextPage
+ }
+
+ return paginatedLandingPages
}
-export async function getAllPostsWithSlug() {
- // https://buttercms.com/docs/api/node?javascript#get-your-blog-posts
- const response = await butter.post.list()
- return response?.data?.data
+async function getLandingPagesData(page, pageSize = defaultPageSize) {
+ try {
+ const params = {
+ page,
+ page_size: pageSize,
+ }
+ const response = await butter.page.list('landing-page', params)
+
+ return {
+ pages: response?.data?.data,
+ prevPage: response?.data?.meta.previous_page,
+ nextPage: response?.data?.meta.next_page,
+ }
+ } catch (e) {
+ throw e.response.data.detail
+ }
}
-export async function getAllPostsForHome(preview) {
- const response = await butter.post.list({ preview: preview ? 1 : 0 })
- return response?.data?.data
+export async function getPostsData(
+ { page, pageSize, tag, category } = { page: 1, pageSize: defaultPostCount }
+) {
+ try {
+ // https://buttercms.com/docs/api/node?javascript#get-your-blog-posts
+ const params = {
+ page_size: pageSize || defaultPostCount,
+ page: page || 1,
+ }
+
+ if (tag) {
+ params.tag_slug = tag
+ }
+
+ if (category) {
+ params.category_slug = category
+ }
+ const response = await butter.post.list(params)
+
+ return {
+ posts: response?.data?.data,
+ prevPage: response?.data?.meta.previous_page,
+ nextPage: response?.data?.meta.next_page,
+ }
+ } catch (e) {
+ throw e.response.data.detail
+ }
}
-export async function getPostAndMorePosts(slug, preview) {
- const postResponse = await butter.post.retrieve(slug, {
- preview: preview ? 1 : 0,
- })
- const postListResponse = await butter.post.list()
- return {
- post: postResponse?.data?.data,
- morePosts: postListResponse?.data?.data.filter(
- ({ slug: postSlug }) => postSlug !== slug
- ),
+export async function getPost(slug) {
+ try {
+ const response = await butter.post.retrieve(slug)
+
+ return response?.data?.data
+ } catch (e) {
+ throw e.response.data.detail
+ }
+}
+
+export async function getMainMenu() {
+ try {
+ const response = await butter.content.retrieve(['navigation_menu'])
+
+ const mainMenu = response?.data?.data?.navigation_menu.find(
+ (menu) => menu.name === 'Main menu'
+ )
+
+ return mainMenu ? mainMenu.menu_items : []
+ } catch (e) {
+ throw e.response.data.detail
+ }
+}
+
+export async function getCategories() {
+ try {
+ const response = await butter.category.list()
+
+ return response?.data?.data
+ } catch (e) {
+ throw e.response.data.detail
+ }
+}
+
+export async function getTags() {
+ try {
+ const response = await butter.tag.list()
+
+ return response?.data?.data
+ } catch (e) {
+ throw e.response.data.detail
+ }
+}
+
+export async function searchPosts({ query }) {
+ try {
+ const response = await butter.post.search(query)
+
+ return response?.data?.data
+ } catch (e) {
+ throw e.response.data.detail
}
}
diff --git a/examples/cms-buttercms/lib/constants.js b/examples/cms-buttercms/lib/constants.js
deleted file mode 100644
index e0172ebae258..000000000000
--- a/examples/cms-buttercms/lib/constants.js
+++ /dev/null
@@ -1,5 +0,0 @@
-export const EXAMPLE_PATH = 'cms-buttercms'
-export const CMS_NAME = 'ButterCMS'
-export const CMS_URL = 'https://buttercms.com/'
-export const HOME_OG_IMAGE_URL =
- 'https://og-image.vercel.app/Next.js%20Blog%20Example%20with%20**ButterCMS**.png?theme=light&md=1&fontSize=100px&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fv1591431148%2Fnextjs%2Fexamples%2Fbuttercms-logo.svg'
diff --git a/examples/cms-buttercms/next.config.js b/examples/cms-buttercms/next.config.js
new file mode 100644
index 000000000000..faa500f139b1
--- /dev/null
+++ b/examples/cms-buttercms/next.config.js
@@ -0,0 +1,35 @@
+module.exports = {
+ reactStrictMode: true,
+ async rewrites() {
+ return [
+ {
+ source: '/',
+ destination: '/landing-page/landing-page-with-components',
+ },
+ ]
+ },
+ redirects() {
+ const sourcesRequiringAuthToken = [
+ '/',
+ '/landing-page/:slug*',
+ '/blog/:path*',
+ ]
+
+ return process.env.NEXT_PUBLIC_BUTTER_CMS_API_KEY
+ ? [
+ {
+ source: '/missing-token',
+ destination: '/',
+ permanent: false,
+ },
+ ]
+ : sourcesRequiringAuthToken.map((source) => ({
+ source: source,
+ destination: '/missing-token',
+ permanent: false,
+ }))
+ },
+ images: {
+ domains: ['cdn.buttercms.com'],
+ },
+}
diff --git a/examples/cms-buttercms/package.json b/examples/cms-buttercms/package.json
index cce90c70a65b..7f926e2807c1 100644
--- a/examples/cms-buttercms/package.json
+++ b/examples/cms-buttercms/package.json
@@ -1,21 +1,27 @@
{
"private": true,
"scripts": {
- "dev": "next",
+ "dev": "next dev",
"build": "next build",
- "start": "next start"
+ "start": "next start",
+ "lint": "next lint"
},
"dependencies": {
- "buttercms": "1.2.8",
- "classnames": "2.3.1",
- "date-fns": "2.28.0",
- "next": "latest",
+ "@popperjs/core": "^2.10.2",
+ "acorn": "^8.0.0",
+ "bootstrap": "^5.1.3",
+ "buttercms": "^1.2.8",
+ "camelcase-keys": "^7.0.1",
+ "date-fns": "^2.25.0",
+ "next": "^12.1.0",
"react": "^17.0.2",
- "react-dom": "^17.0.2"
+ "react-dom": "^17.0.2",
+ "sharp": "^0.29.3",
+ "tiny-slider": "^2.9.4",
+ "typescript": "^3.3.1"
},
"devDependencies": {
- "autoprefixer": "10.4.2",
- "postcss": "8.4.5",
- "tailwindcss": "^3.0.15"
+ "eslint": "^8.12.0",
+ "eslint-config-next": "^12.1.4"
}
}
diff --git a/examples/cms-buttercms/pages/_app.js b/examples/cms-buttercms/pages/_app.js
index f18e112c7cf7..881a7c2d1410 100644
--- a/examples/cms-buttercms/pages/_app.js
+++ b/examples/cms-buttercms/pages/_app.js
@@ -1,7 +1,102 @@
-import '@/styles/index.css'
+import { useEffect, useState } from 'react'
-function MyApp({ Component, pageProps }) {
- return
+import Router from 'next/router'
+import App from 'next/app'
+import Head from 'next/head'
+import { useRouter } from 'next/router'
+
+import { getMainMenu } from '@/lib/api'
+
+import FooterSection from '@/components/footer-section'
+import HeaderSection from '@/components/header-section'
+import ScrollToButtonButton from '@/components/scroll-to-top-button'
+import Preloader from '@/components/preloader'
+
+import 'bootstrap/dist/css/bootstrap.css'
+import '@/css/lineicons.css'
+
+import '@/css/tiny-slider.min.css'
+import '@/css/main.css'
+
+function MyApp({ Component, pageProps, mainMenu }) {
+ const [isLoading, setIsLoading] = useState(false)
+ const router = useRouter()
+ const authToken = process.env.NEXT_PUBLIC_BUTTER_CMS_API_KEY
+
+ useEffect(() => {
+ import('bootstrap/dist/js/bootstrap.js')
+
+ const showLoader = () => {
+ setIsLoading(true)
+ }
+
+ const removeLoader = () => {
+ setIsLoading(false)
+ }
+
+ Router.events.on('routeChangeStart', showLoader)
+ Router.events.on('routeChangeComplete', removeLoader)
+ Router.events.on('routeChangeError', removeLoader)
+
+ return () => {
+ Router.events.off('routeChangeStart', showLoader)
+ Router.events.off('routeChangeComplete', removeLoader)
+ Router.events.off('routeChangeError', removeLoader)
+ }
+ }, [authToken, router])
+
+ const pageLayout = authToken ? (
+ <>
+
+
+
+
+ >
+ ) : (
+
+ )
+
+ return (
+ <>
+
+
+
+
+ Sample Landing Page with Components - powered by ButterCMS
+
+
+
+
+
+
+ {isLoading && }
+
+ {!isLoading && pageLayout}
+ >
+ )
+}
+
+MyApp.getInitialProps = async (appContext) => {
+ const appProps = await App.getInitialProps(appContext)
+ const authToken = process.env.NEXT_PUBLIC_BUTTER_CMS_API_KEY
+ let mainMenu = []
+
+ if (authToken) {
+ try {
+ mainMenu = await getMainMenu()
+ } catch (e) {
+ console.error("Couldn't load main menu links.", e)
+ }
+ }
+
+ return { ...appProps, mainMenu }
}
export default MyApp
diff --git a/examples/cms-buttercms/pages/_document.js b/examples/cms-buttercms/pages/_document.js
index c55951c0d5da..e2437f5e8e7c 100644
--- a/examples/cms-buttercms/pages/_document.js
+++ b/examples/cms-buttercms/pages/_document.js
@@ -1,15 +1,18 @@
-import Document, { Html, Head, Main, NextScript } from 'next/document'
+import { Html, Head, Main, NextScript } from 'next/document'
-export default class MyDocument extends Document {
- render() {
- return (
-
-
-
-
-
-
-
- )
- }
+export default function Document() {
+ return (
+
+
+
+
+
+
+
+
+
+ )
}
diff --git a/examples/cms-buttercms/pages/api/exit-preview.js b/examples/cms-buttercms/pages/api/exit-preview.js
deleted file mode 100644
index 6c63a0a6e8a4..000000000000
--- a/examples/cms-buttercms/pages/api/exit-preview.js
+++ /dev/null
@@ -1,8 +0,0 @@
-export default async function exit(_, res) {
- // Exit the current user from "Preview Mode". This function accepts no args.
- res.clearPreviewData()
-
- // Redirect the user back to the index page.
- res.writeHead(307, { Location: '/' })
- res.end()
-}
diff --git a/examples/cms-buttercms/pages/api/preview.js b/examples/cms-buttercms/pages/api/preview.js
deleted file mode 100644
index 08499d150397..000000000000
--- a/examples/cms-buttercms/pages/api/preview.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import { getPreviewPostBySlug } from '@/lib/api'
-
-export default async function preview(req, res) {
- // Check the secret and next parameters
- // This secret should only be known to this API route and the CMS
- if (
- req.query.secret !== process.env.BUTTERCMS_PREVIEW_SECRET ||
- !req.query.slug
- ) {
- return res.status(401).json({ message: 'Invalid token' })
- }
-
- // Fetch the headless CMS to check if the provided `slug` exists
- const post = await getPreviewPostBySlug(req.query.slug)
-
- // If the slug doesn't exist prevent preview mode from being enabled
- if (!post) {
- return res.status(401).json({ message: 'Invalid slug' })
- }
-
- // Enable Preview Mode by setting the cookies
- res.setPreviewData({})
-
- // Redirect to the path from the fetched post
- // We don't redirect to req.query.slug as that might lead to open redirect vulnerabilities
- res.writeHead(307, { Location: `/posts/${post.slug}` })
- res.end()
-}
diff --git a/examples/cms-buttercms/pages/blog.js b/examples/cms-buttercms/pages/blog.js
new file mode 100644
index 000000000000..6363c069868a
--- /dev/null
+++ b/examples/cms-buttercms/pages/blog.js
@@ -0,0 +1,68 @@
+import Link from 'next/link'
+
+import camelcaseKeys from 'camelcase-keys'
+
+import PostsList from '@/components/blog/posts-list'
+
+import { getPostsData, getCategories } from '@/lib/api'
+import CategoriesWidget from '@/components/blog/categories-widget'
+import SearchWidget from '@/components/blog/search-widget'
+
+export default function Blog({ posts, categories }) {
+ return (
+ <>
+
+
+
+
+
+
All Blog Posts
+
+
+
+ Home
+
+
+ All blog posts
+
+
+
+
+
+
+
+
+ >
+ )
+}
+
+export async function getStaticProps() {
+ const butterToken = process.env.NEXT_PUBLIC_BUTTER_CMS_API_KEY
+
+ if (butterToken) {
+ try {
+ const blogPosts = (await getPostsData()).posts
+ const categories = await getCategories()
+
+ return { props: { posts: camelcaseKeys(blogPosts), categories } }
+ } catch (e) {
+ console.log('Could not get posts', e)
+
+ return {
+ props: { posts: [], categories: [] },
+ }
+ }
+ }
+
+ return { props: { posts: [], categories: [] } }
+}
diff --git a/examples/cms-buttercms/pages/blog/[slug].js b/examples/cms-buttercms/pages/blog/[slug].js
new file mode 100644
index 000000000000..3ff4a5e798d1
--- /dev/null
+++ b/examples/cms-buttercms/pages/blog/[slug].js
@@ -0,0 +1,165 @@
+import Image from 'next/image'
+import Head from 'next/head'
+import Link from 'next/link'
+import { useRouter } from 'next/router'
+import ErrorPage from 'next/error'
+import camelcaseKeys from 'camelcase-keys'
+
+import { getPost, getPostsData, getCategories } from '@/lib/api'
+
+import HumanDate from '@/components/human-date'
+import CategoriesWidget from '@/components/blog/categories-widget'
+import SearchWidget from '@/components/blog/search-widget'
+import AuthorCard from '@/components/author-card'
+import Preloader from '@/components/preloader'
+
+export default function BlogPost({ post, categories }) {
+ const router = useRouter()
+ if (router.isFallback) {
+ return
+ }
+
+ if (!post) {
+ return
+ }
+
+ return (
+ <>
+
+
+
+ {post.seoTitle}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {post.featuredImage && (
+
+
+
+ )}
+
+
+
+
+
+
+
+
+ >
+ )
+}
+
+export async function getStaticProps({ params }) {
+ try {
+ const post = await getPost(params.slug)
+ const categories = await getCategories()
+ return { props: { post: camelcaseKeys(post), categories } }
+ } catch (e) {
+ console.error(`Couldn't load post or categories data.`, e)
+
+ return {
+ notFound: true,
+ }
+ }
+}
+
+export async function getStaticPaths() {
+ const butterToken = process.env.NEXT_PUBLIC_BUTTER_CMS_API_KEY
+
+ if (butterToken) {
+ try {
+ const posts = (await getPostsData()).posts
+
+ return {
+ paths: posts.map((post) => `/blog/${post.slug}`),
+ fallback: true,
+ }
+ } catch (e) {
+ console.error(`Couldn't load posts.`, e)
+ }
+ }
+
+ return {
+ paths: [],
+ fallback: false,
+ }
+}
diff --git a/examples/cms-buttercms/pages/blog/category/[slug].js b/examples/cms-buttercms/pages/blog/category/[slug].js
new file mode 100644
index 000000000000..e90834837a24
--- /dev/null
+++ b/examples/cms-buttercms/pages/blog/category/[slug].js
@@ -0,0 +1,95 @@
+import Link from 'next/link'
+
+import camelcaseKeys from 'camelcase-keys'
+
+import PostsList from '@/components/blog/posts-list'
+
+import { getPostsData, getCategories } from '@/lib/api'
+import CategoriesWidget from '@/components/blog/categories-widget'
+import SearchWidget from '@/components/blog/search-widget'
+
+export default function Category({ posts, categories, slug }) {
+ return (
+ <>
+
+
+
+
+
+
Blog Posts by Category
+
+
+
+ Home
+
+
+
+
+ Blog
+
+
+ Category: {slug}
+
+
+
+
+
+
+
+
+ >
+ )
+}
+
+export async function getStaticProps({ params: { slug } }) {
+ try {
+ const blogPosts = (await getPostsData({ category: slug })).posts
+ const categories = await getCategories()
+
+ return {
+ props: { posts: camelcaseKeys(blogPosts), categories, slug },
+ revalidate: 1,
+ }
+ } catch (e) {
+ return {
+ notFound: true,
+ }
+ }
+}
+
+export async function getStaticPaths() {
+ const butterToken = process.env.NEXT_PUBLIC_BUTTER_CMS_API_KEY
+
+ if (butterToken) {
+ try {
+ const categories = await getCategories()
+
+ return {
+ paths: categories.map((category) => `/blog/category/${category.slug}`),
+ fallback: true,
+ }
+ } catch (e) {
+ console.error(`Couldn't load categories.`, e)
+
+ return {
+ paths: [],
+ fallback: false,
+ }
+ }
+ }
+
+ return {
+ paths: [],
+ fallback: false,
+ }
+}
diff --git a/examples/cms-buttercms/pages/blog/search.js b/examples/cms-buttercms/pages/blog/search.js
new file mode 100644
index 000000000000..85c666566d2f
--- /dev/null
+++ b/examples/cms-buttercms/pages/blog/search.js
@@ -0,0 +1,61 @@
+import Link from 'next/link'
+
+import camelcaseKeys from 'camelcase-keys'
+
+import PostsList from '@/components/blog/posts-list'
+
+import { getCategories, searchPosts } from '@/lib/api'
+import CategoriesWidget from '@/components/blog/categories-widget'
+import SearchWidget from '@/components/blog/search-widget'
+
+export default function Search({ posts, categories, query }) {
+ return (
+ <>
+
+
+
+
+
+
Search Results
+
+
+
+ Home
+
+
+
+
+ Blog
+
+
+ Search: "{query}"
+
+
+
+
+
+
+
+
+ >
+ )
+}
+
+export async function getServerSideProps({ query: { query } }) {
+ const blogPosts = await searchPosts({ query })
+ const categories = await getCategories()
+
+ return {
+ props: { posts: camelcaseKeys(blogPosts), categories, query },
+ }
+}
diff --git a/examples/cms-buttercms/pages/blog/tag/[slug].js b/examples/cms-buttercms/pages/blog/tag/[slug].js
new file mode 100644
index 000000000000..283c59d1ec00
--- /dev/null
+++ b/examples/cms-buttercms/pages/blog/tag/[slug].js
@@ -0,0 +1,94 @@
+import Link from 'next/link'
+
+import camelcaseKeys from 'camelcase-keys'
+
+import PostsList from '@/components/blog/posts-list'
+
+import { getPostsData, getCategories, getTags } from '@/lib/api'
+import CategoriesWidget from '@/components/blog/categories-widget'
+import SearchWidget from '@/components/blog/search-widget'
+
+export default function Tag({ posts, categories, slug }) {
+ return (
+ <>
+
+
+
+
+
+
Blog Posts by Tag
+
+
+
+ Home
+
+
+
+
+ Blog
+
+
+ Tag: {slug}
+
+
+
+
+
+
+
+
+ >
+ )
+}
+
+export async function getStaticProps({ params: { slug } }) {
+ try {
+ const blogPosts = (await getPostsData({ tag: slug })).posts
+ const categories = await getCategories()
+
+ return {
+ props: { posts: camelcaseKeys(blogPosts), categories, slug },
+ revalidate: 1,
+ }
+ } catch (e) {
+ return {
+ notFound: true,
+ }
+ }
+}
+
+export async function getStaticPaths() {
+ const butterToken = process.env.NEXT_PUBLIC_BUTTER_CMS_API_KEY
+ if (butterToken) {
+ try {
+ const tags = await getTags()
+
+ return {
+ paths: tags.map((tag) => `/blog/tag/${tag.slug}`),
+ fallback: true,
+ }
+ } catch (e) {
+ console.error(`Couldn't load tags.`, e)
+
+ return {
+ paths: [],
+ fallback: false,
+ }
+ }
+ }
+
+ return {
+ paths: [],
+ fallback: false,
+ }
+}
diff --git a/examples/cms-buttercms/pages/index.js b/examples/cms-buttercms/pages/index.js
deleted file mode 100644
index 17dc4630aec0..000000000000
--- a/examples/cms-buttercms/pages/index.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import Container from '@/components/container'
-import MoreStories from '@/components/more-stories'
-import HeroPost from '@/components/hero-post'
-import Intro from '@/components/intro'
-import Layout from '@/components/layout'
-import { getAllPostsForHome } from '@/lib/api'
-import Head from 'next/head'
-import { CMS_NAME } from '@/lib/constants'
-
-export default function Index({ allPosts, preview }) {
- const heroPost = allPosts[0]
- const morePosts = allPosts.slice(1)
- return (
- <>
-
-
- Next.js Blog Example with {CMS_NAME}
-
-
-
- {heroPost && (
-
- )}
- {morePosts.length > 0 && }
-
-
- >
- )
-}
-
-export async function getStaticProps({ preview = null }) {
- const allPosts = (await getAllPostsForHome(preview)) || []
- return {
- props: { allPosts, preview },
- }
-}
diff --git a/examples/cms-buttercms/pages/landing-page/[slug].js b/examples/cms-buttercms/pages/landing-page/[slug].js
new file mode 100644
index 000000000000..3c2f913fc608
--- /dev/null
+++ b/examples/cms-buttercms/pages/landing-page/[slug].js
@@ -0,0 +1,83 @@
+import Head from 'next/head'
+import { useRouter } from 'next/router'
+import ErrorPage from 'next/error'
+
+import camelcaseKeys from 'camelcase-keys'
+
+import { getLandingPage, getLandingPages, getPostsData } from '@/lib/api'
+
+import LandingPageSection from '@/components/landing-page-sections/landing-page-section'
+import Blog from '@/components/blog/blog'
+import Preloader from '@/components/preloader'
+
+export default function LandingPage({ page, blogPosts }) {
+ const router = useRouter()
+ if (router.isFallback) {
+ return
+ }
+
+ if (!page) {
+ return
+ }
+
+ return (
+ <>
+
+
+
+ {page.fields.seo.title}
+
+
+
+
+
+ {page.fields.body.map(({ type, fields: sectionData }, index) => (
+
+ ))}
+
+ >
+ )
+}
+
+export async function getStaticProps({ params }) {
+ try {
+ const page = await getLandingPage(params.slug)
+ const blogPosts = (await getPostsData({ page: 1, pageSize: 2 })).posts
+
+ return {
+ props: { page: camelcaseKeys(page), blogPosts: camelcaseKeys(blogPosts) },
+ }
+ } catch (e) {
+ console.error(`Couldn't load content for Landing page ${params.slug}.`, e)
+
+ return {
+ notFound: true,
+ }
+ }
+}
+
+export async function getStaticPaths() {
+ const butterToken = process.env.NEXT_PUBLIC_BUTTER_CMS_API_KEY
+
+ if (butterToken) {
+ try {
+ const landingPages = await getLandingPages()
+
+ return {
+ paths: landingPages.map((page) => `/landing-page/${page.slug}`),
+ fallback: true,
+ }
+ } catch (e) {
+ console.error("Couldn't load content for Landing pages.", e)
+ }
+
+ return {
+ paths: [],
+ fallback: false,
+ }
+ }
+}
diff --git a/examples/cms-buttercms/pages/missing-token.js b/examples/cms-buttercms/pages/missing-token.js
new file mode 100644
index 000000000000..a6b1ef1b6271
--- /dev/null
+++ b/examples/cms-buttercms/pages/missing-token.js
@@ -0,0 +1,11 @@
+import MissingTokenSection from '@/components/missing-token-section'
+
+export default function MissingToken() {
+ return
+}
+
+export async function getStaticProps() {
+ return {
+ props: {},
+ }
+}
diff --git a/examples/cms-buttercms/pages/posts/[slug].js b/examples/cms-buttercms/pages/posts/[slug].js
deleted file mode 100644
index 2bee60449b1c..000000000000
--- a/examples/cms-buttercms/pages/posts/[slug].js
+++ /dev/null
@@ -1,70 +0,0 @@
-import { useRouter } from 'next/router'
-import ErrorPage from 'next/error'
-import Container from '@/components/container'
-import PostBody from '@/components/post-body'
-import MoreStories from '@/components/more-stories'
-import Header from '@/components/header'
-import PostHeader from '@/components/post-header'
-import SectionSeparator from '@/components/section-separator'
-import Layout from '@/components/layout'
-import { getAllPostsWithSlug, getPostAndMorePosts } from '@/lib/api'
-import PostTitle from '@/components/post-title'
-import Head from 'next/head'
-import { CMS_NAME } from '@/lib/constants'
-
-export default function Post({ post, morePosts, preview }) {
- const router = useRouter()
- if (!router.isFallback && !post?.slug) {
- return
- }
- return (
-
-
-
- {router.isFallback ? (
- Loading…
- ) : (
- <>
-
-
-
- {post.title} | Next.js Blog Example with {CMS_NAME}
-
-
-
-
-
-
-
- {morePosts.length > 0 && }
- >
- )}
-
-
- )
-}
-
-export async function getStaticProps({ params, preview = null }) {
- const { post, morePosts } = await getPostAndMorePosts(params.slug, preview)
-
- return {
- props: {
- preview,
- post,
- morePosts,
- },
- }
-}
-
-export async function getStaticPaths() {
- const allPosts = await getAllPostsWithSlug()
- return {
- paths: allPosts?.map((post) => `/posts/${post.slug}`) || [],
- fallback: true,
- }
-}
diff --git a/examples/cms-buttercms/postcss.config.js b/examples/cms-buttercms/postcss.config.js
deleted file mode 100644
index 3fa0a9514dc9..000000000000
--- a/examples/cms-buttercms/postcss.config.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// If you want to use other PostCSS plugins, see the following:
-// https://tailwindcss.com/docs/using-with-preprocessors
-module.exports = {
- plugins: {
- tailwindcss: {},
- autoprefixer: {},
- },
-}
diff --git a/examples/cms-buttercms/public/favicon.svg b/examples/cms-buttercms/public/favicon.svg
new file mode 100644
index 000000000000..8a491f838601
--- /dev/null
+++ b/examples/cms-buttercms/public/favicon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/cms-buttercms/public/favicon/android-chrome-192x192.png b/examples/cms-buttercms/public/favicon/android-chrome-192x192.png
deleted file mode 100644
index 2f07282a59cd..000000000000
Binary files a/examples/cms-buttercms/public/favicon/android-chrome-192x192.png and /dev/null differ
diff --git a/examples/cms-buttercms/public/favicon/android-chrome-512x512.png b/examples/cms-buttercms/public/favicon/android-chrome-512x512.png
deleted file mode 100644
index dbb0faea8404..000000000000
Binary files a/examples/cms-buttercms/public/favicon/android-chrome-512x512.png and /dev/null differ
diff --git a/examples/cms-buttercms/public/favicon/apple-touch-icon.png b/examples/cms-buttercms/public/favicon/apple-touch-icon.png
deleted file mode 100644
index 8f4033b2a8b3..000000000000
Binary files a/examples/cms-buttercms/public/favicon/apple-touch-icon.png and /dev/null differ
diff --git a/examples/cms-buttercms/public/favicon/browserconfig.xml b/examples/cms-buttercms/public/favicon/browserconfig.xml
deleted file mode 100644
index 9824d87b1151..000000000000
--- a/examples/cms-buttercms/public/favicon/browserconfig.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
- #000000
-
-
-
diff --git a/examples/cms-buttercms/public/favicon/favicon-16x16.png b/examples/cms-buttercms/public/favicon/favicon-16x16.png
deleted file mode 100644
index 29deaf6716e7..000000000000
Binary files a/examples/cms-buttercms/public/favicon/favicon-16x16.png and /dev/null differ
diff --git a/examples/cms-buttercms/public/favicon/favicon-32x32.png b/examples/cms-buttercms/public/favicon/favicon-32x32.png
deleted file mode 100644
index e3b4277bf093..000000000000
Binary files a/examples/cms-buttercms/public/favicon/favicon-32x32.png and /dev/null differ
diff --git a/examples/cms-buttercms/public/favicon/favicon.ico b/examples/cms-buttercms/public/favicon/favicon.ico
deleted file mode 100644
index ea2f437d9db6..000000000000
Binary files a/examples/cms-buttercms/public/favicon/favicon.ico and /dev/null differ
diff --git a/examples/cms-buttercms/public/favicon/mstile-150x150.png b/examples/cms-buttercms/public/favicon/mstile-150x150.png
deleted file mode 100644
index f2dfd904bf1b..000000000000
Binary files a/examples/cms-buttercms/public/favicon/mstile-150x150.png and /dev/null differ
diff --git a/examples/cms-buttercms/public/favicon/safari-pinned-tab.svg b/examples/cms-buttercms/public/favicon/safari-pinned-tab.svg
deleted file mode 100644
index 72ab6e050cb1..000000000000
--- a/examples/cms-buttercms/public/favicon/safari-pinned-tab.svg
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
-Created by potrace 1.11, written by Peter Selinger 2001-2013
-
-
-
-
-
-
-
-
-
-
diff --git a/examples/cms-buttercms/public/favicon/site.webmanifest b/examples/cms-buttercms/public/favicon/site.webmanifest
deleted file mode 100644
index a672d9a233c5..000000000000
--- a/examples/cms-buttercms/public/favicon/site.webmanifest
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "name": "Next.js",
- "short_name": "Next.js",
- "icons": [
- {
- "src": "/favicons/android-chrome-192x192.png",
- "sizes": "192x192",
- "type": "image/png"
- },
- {
- "src": "/favicons/android-chrome-512x512.png",
- "sizes": "512x512",
- "type": "image/png"
- }
- ],
- "theme_color": "#000000",
- "background_color": "#000000",
- "display": "standalone"
-}
diff --git a/examples/cms-buttercms/public/images/common-bg.svg b/examples/cms-buttercms/public/images/common-bg.svg
new file mode 100644
index 000000000000..7257692df766
--- /dev/null
+++ b/examples/cms-buttercms/public/images/common-bg.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/cms-buttercms/public/images/team/team-1.png b/examples/cms-buttercms/public/images/team/team-1.png
new file mode 100644
index 000000000000..5c82b3e0704b
Binary files /dev/null and b/examples/cms-buttercms/public/images/team/team-1.png differ
diff --git a/examples/cms-buttercms/public/images/testimonial/testimonial-bg.svg b/examples/cms-buttercms/public/images/testimonial/testimonial-bg.svg
new file mode 100644
index 000000000000..06d2dc23c562
--- /dev/null
+++ b/examples/cms-buttercms/public/images/testimonial/testimonial-bg.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/cms-buttercms/styles/index.css b/examples/cms-buttercms/styles/index.css
deleted file mode 100644
index 73048d39686b..000000000000
--- a/examples/cms-buttercms/styles/index.css
+++ /dev/null
@@ -1,7 +0,0 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
-
-.grayscale {
- filter: grayscale(1);
-}
diff --git a/examples/cms-buttercms/tailwind.config.js b/examples/cms-buttercms/tailwind.config.js
deleted file mode 100644
index 21253ff3793e..000000000000
--- a/examples/cms-buttercms/tailwind.config.js
+++ /dev/null
@@ -1,37 +0,0 @@
-module.exports = {
- content: [
- './pages/**/*.{js,ts,jsx,tsx}',
- './components/**/*.{js,ts,jsx,tsx}',
- ],
- theme: {
- extend: {
- colors: {
- 'accent-1': '#FAFAFA',
- 'accent-2': '#EAEAEA',
- 'accent-7': '#333',
- success: '#0070f3',
- cyan: '#79FFE1',
- },
- spacing: {
- 28: '7rem',
- },
- letterSpacing: {
- tighter: '-.04em',
- },
- lineHeight: {
- tight: 1.2,
- },
- fontSize: {
- '5xl': '2.5rem',
- '6xl': '2.75rem',
- '7xl': '4.5rem',
- '8xl': '6.25rem',
- },
- boxShadow: {
- small: '0 5px 10px rgba(0, 0, 0, 0.12)',
- medium: '0 8px 30px rgba(0, 0, 0, 0.12)',
- },
- },
- },
- plugins: [],
-}
diff --git a/examples/with-playwright/styles/Home.module.css b/examples/with-playwright/styles/Home.module.css
index 35454bb74819..ec63ce5c9b45 100644
--- a/examples/with-playwright/styles/Home.module.css
+++ b/examples/with-playwright/styles/Home.module.css
@@ -5,7 +5,6 @@
flex-direction: column;
justify-content: center;
align-items: center;
- height: 100vh;
}
.main {
diff --git a/lerna.json b/lerna.json
index 6df4d9f93cfe..def43d985a14 100644
--- a/lerna.json
+++ b/lerna.json
@@ -16,5 +16,5 @@
"registry": "https://registry.npmjs.org/"
}
},
- "version": "12.1.6"
+ "version": "12.1.7-canary.2"
}
diff --git a/package.json b/package.json
index a364f9fa38b3..9dadec67da76 100644
--- a/package.json
+++ b/package.json
@@ -64,6 +64,7 @@
"@types/selenium-webdriver": "4.0.15",
"@types/sharp": "0.29.3",
"@types/string-hash": "1.1.1",
+ "@types/trusted-types": "2.0.2",
"@typescript-eslint/eslint-plugin": "4.29.1",
"@typescript-eslint/parser": "4.29.1",
"@vercel/fetch": "6.1.1",
@@ -180,25 +181,5 @@
},
"engines": {
"node": ">=12.22.0"
- },
- "turbo": {
- "pipeline": {
- "build-native": {
- "dependsOn": [
- "^build-native"
- ],
- "outputs": [
- "native/*.node"
- ]
- },
- "build-wasm": {
- "dependsOn": [
- "^build-wasm"
- ],
- "outputs": [
- "crates/wasm/pkg/*"
- ]
- }
- }
}
}
diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json
index d307c4d6d3ce..fc6c94ccb815 100644
--- a/packages/create-next-app/package.json
+++ b/packages/create-next-app/package.json
@@ -1,6 +1,6 @@
{
"name": "create-next-app",
- "version": "12.1.6",
+ "version": "12.1.7-canary.2",
"keywords": [
"react",
"next",
diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json
index 7e06c3436ee2..3386e07675a7 100644
--- a/packages/eslint-config-next/package.json
+++ b/packages/eslint-config-next/package.json
@@ -1,6 +1,6 @@
{
"name": "eslint-config-next",
- "version": "12.1.6",
+ "version": "12.1.7-canary.2",
"description": "ESLint configuration used by NextJS.",
"main": "index.js",
"license": "MIT",
@@ -9,7 +9,7 @@
"directory": "packages/eslint-config-next"
},
"dependencies": {
- "@next/eslint-plugin-next": "12.1.6",
+ "@next/eslint-plugin-next": "12.1.7-canary.2",
"@rushstack/eslint-patch": "^1.1.3",
"@typescript-eslint/parser": "^5.21.0",
"eslint-import-resolver-node": "^0.3.6",
diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json
index cf08afd32cea..a04257730c79 100644
--- a/packages/eslint-plugin-next/package.json
+++ b/packages/eslint-plugin-next/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/eslint-plugin-next",
- "version": "12.1.6",
+ "version": "12.1.7-canary.2",
"description": "ESLint plugin for NextJS.",
"main": "lib/index.js",
"license": "MIT",
diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json
index 55db754135e7..fb16c23a6a08 100644
--- a/packages/next-bundle-analyzer/package.json
+++ b/packages/next-bundle-analyzer/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/bundle-analyzer",
- "version": "12.1.6",
+ "version": "12.1.7-canary.2",
"main": "index.js",
"license": "MIT",
"repository": {
diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json
index 258e4b2d0a6c..75ff9fc1b541 100644
--- a/packages/next-codemod/package.json
+++ b/packages/next-codemod/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/codemod",
- "version": "12.1.6",
+ "version": "12.1.7-canary.2",
"license": "MIT",
"dependencies": {
"chalk": "4.1.0",
diff --git a/packages/next-env/package.json b/packages/next-env/package.json
index d8be7b56b0f3..5cc6906f67d6 100644
--- a/packages/next-env/package.json
+++ b/packages/next-env/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/env",
- "version": "12.1.6",
+ "version": "12.1.7-canary.2",
"keywords": [
"react",
"next",
diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json
index 16a2acca4fe8..5f226722c15d 100644
--- a/packages/next-mdx/package.json
+++ b/packages/next-mdx/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/mdx",
- "version": "12.1.6",
+ "version": "12.1.7-canary.2",
"main": "index.js",
"license": "MIT",
"repository": {
diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json
index 469e5c370b89..5207b41c7e87 100644
--- a/packages/next-plugin-storybook/package.json
+++ b/packages/next-plugin-storybook/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/plugin-storybook",
- "version": "12.1.6",
+ "version": "12.1.7-canary.2",
"repository": {
"url": "vercel/next.js",
"directory": "packages/next-plugin-storybook"
diff --git a/packages/next-polyfill-module/package.json b/packages/next-polyfill-module/package.json
index b7e225c59f3f..ae673d685fe4 100644
--- a/packages/next-polyfill-module/package.json
+++ b/packages/next-polyfill-module/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/polyfill-module",
- "version": "12.1.6",
+ "version": "12.1.7-canary.2",
"description": "A standard library polyfill for ES Modules supporting browsers (Edge 16+, Firefox 60+, Chrome 61+, Safari 10.1+)",
"main": "dist/polyfill-module.js",
"license": "MIT",
diff --git a/packages/next-polyfill-nomodule/package.json b/packages/next-polyfill-nomodule/package.json
index 1f4314be0110..6d85b8b8b3f8 100644
--- a/packages/next-polyfill-nomodule/package.json
+++ b/packages/next-polyfill-nomodule/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/polyfill-nomodule",
- "version": "12.1.6",
+ "version": "12.1.7-canary.2",
"description": "A polyfill for non-dead, nomodule browsers.",
"main": "dist/polyfill-nomodule.js",
"license": "MIT",
diff --git a/packages/next-swc/Cargo.lock b/packages/next-swc/Cargo.lock
index 2de4a78596dc..87e15cb8c10f 100644
--- a/packages/next-swc/Cargo.lock
+++ b/packages/next-swc/Cargo.lock
@@ -717,6 +717,12 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "json"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd"
+
[[package]]
name = "json_comments"
version = "0.2.1"
@@ -1043,6 +1049,10 @@ dependencies = [
"swc_ecma_loader",
"swc_ecmascript",
"swc_node_base",
+ "tracing",
+ "tracing-chrome",
+ "tracing-futures",
+ "tracing-subscriber",
]
[[package]]
@@ -1303,6 +1313,26 @@ dependencies = [
"siphasher",
]
+[[package]]
+name = "pin-project"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "pin-project-lite"
version = "0.2.8"
@@ -2555,7 +2585,9 @@ dependencies = [
"swc_common",
"swc_ecma_transforms_testing",
"swc_ecmascript",
+ "swc_trace_macro",
"testing",
+ "tracing",
]
[[package]]
@@ -2851,6 +2883,17 @@ dependencies = [
"syn",
]
+[[package]]
+name = "tracing-chrome"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcb13184244c7cd22758b79e7c993c515ad67a8e730edcb7e05fe7bcabb283c7"
+dependencies = [
+ "json",
+ "tracing",
+ "tracing-subscriber",
+]
+
[[package]]
name = "tracing-core"
version = "0.1.26"
@@ -2861,6 +2904,16 @@ dependencies = [
"valuable",
]
+[[package]]
+name = "tracing-futures"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
+dependencies = [
+ "pin-project",
+ "tracing",
+]
+
[[package]]
name = "tracing-log"
version = "0.1.2"
diff --git a/packages/next-swc/crates/core/Cargo.toml b/packages/next-swc/crates/core/Cargo.toml
index f2328655479c..b1bf2d334fcd 100644
--- a/packages/next-swc/crates/core/Cargo.toml
+++ b/packages/next-swc/crates/core/Cargo.toml
@@ -27,8 +27,7 @@ swc_common = { version = "0.17.25", features = ["concurrent", "sourcemap"] }
swc_ecma_loader = { version = "0.29.1", features = ["node", "lru"] }
swc_ecmascript = { version = "0.150.0", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"] }
swc_cached = "0.1.1"
-tracing = { version = "0.1.32", features = ["release_max_level_off"] }
-
+tracing = { version = "0.1.32", features = ["release_max_level_info"] }
[dev-dependencies]
swc_ecma_transforms_testing = "0.82.0"
diff --git a/packages/next-swc/crates/emotion/Cargo.toml b/packages/next-swc/crates/emotion/Cargo.toml
index 545b5b0689a5..175fdcda5704 100644
--- a/packages/next-swc/crates/emotion/Cargo.toml
+++ b/packages/next-swc/crates/emotion/Cargo.toml
@@ -21,6 +21,8 @@ sourcemap = "6.0.1"
swc_atoms = "0.2.11"
swc_common = { version = "0.17.25", features = ["concurrent", "sourcemap"] }
swc_ecmascript = { version = "0.150.0", features = ["codegen", "utils", "visit"] }
+swc_trace_macro = "0.1.1"
+tracing = { version = "0.1.32", features = ["release_max_level_info"] }
[dev-dependencies]
swc_ecma_transforms_testing = "0.82.0"
diff --git a/packages/next-swc/crates/emotion/src/lib.rs b/packages/next-swc/crates/emotion/src/lib.rs
index ff89be748a3e..795070aa3a51 100644
--- a/packages/next-swc/crates/emotion/src/lib.rs
+++ b/packages/next-swc/crates/emotion/src/lib.rs
@@ -25,6 +25,7 @@ use swc_ecmascript::{
codegen::util::SourceMapperExt,
visit::{Fold, FoldWith},
};
+use swc_trace_macro::swc_trace;
mod hash;
@@ -164,6 +165,7 @@ pub struct EmotionTransformer {
in_jsx_element: bool,
}
+#[swc_trace]
impl EmotionTransformer {
pub fn new(options: EmotionOptions, path: &Path, cm: Arc, comments: C) -> Self {
EmotionTransformer {
diff --git a/packages/next-swc/crates/napi/Cargo.toml b/packages/next-swc/crates/napi/Cargo.toml
index 36113153c3dd..30a8df56de35 100644
--- a/packages/next-swc/crates/napi/Cargo.toml
+++ b/packages/next-swc/crates/napi/Cargo.toml
@@ -24,6 +24,11 @@ swc_common = { version = "0.17.25", features = ["concurrent", "sourcemap"] }
swc_ecma_loader = { version = "0.29.1", features = ["node", "lru"] }
swc_ecmascript = { version = "0.150.0", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"] }
swc_node_base = "0.5.2"
+tracing = { version = "0.1.32", features = ["release_max_level_info"] }
+tracing-futures = "0.2.5"
+tracing-subscriber = "0.3.9"
+
+tracing-chrome = "0.5.0"
[build-dependencies]
napi-build = "1"
diff --git a/packages/next-swc/crates/napi/src/lib.rs b/packages/next-swc/crates/napi/src/lib.rs
index 477108aabefb..12f1c1e4c0cd 100644
--- a/packages/next-swc/crates/napi/src/lib.rs
+++ b/packages/next-swc/crates/napi/src/lib.rs
@@ -73,6 +73,11 @@ fn init(mut exports: JsObject) -> napi::Result<()> {
exports.create_named_method("parse", parse::parse)?;
exports.create_named_method("getTargetTriple", util::get_target_triple)?;
+ exports.create_named_method(
+ "initCustomTraceSubscriber",
+ util::init_custom_trace_subscriber,
+ )?;
+ exports.create_named_method("teardownTraceSubscriber", util::teardown_trace_subscriber)?;
Ok(())
}
diff --git a/packages/next-swc/crates/napi/src/util.rs b/packages/next-swc/crates/napi/src/util.rs
index 868079445413..8476ff55d6e6 100644
--- a/packages/next-swc/crates/napi/src/util.rs
+++ b/packages/next-swc/crates/napi/src/util.rs
@@ -26,13 +26,14 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
-use anyhow::{Context, Error};
-use napi::{CallContext, Env, JsBuffer, JsString, Status};
+use anyhow::{anyhow, Context, Error};
+use napi::{CallContext, Env, JsBuffer, JsExternal, JsString, JsUndefined, JsUnknown, Status};
use serde::de::DeserializeOwned;
-use std::any::type_name;
+use std::{any::type_name, cell::RefCell, convert::TryFrom, path::PathBuf};
+use tracing_chrome::{ChromeLayerBuilder, FlushGuard};
+use tracing_subscriber::{filter, prelude::*, util::SubscriberInitExt, Layer};
static TARGET_TRIPLE: &str = include_str!(concat!(env!("OUT_DIR"), "/triple.txt"));
-
#[contextless_function]
pub fn get_target_triple(env: Env) -> napi::ContextlessResult {
env.create_string(TARGET_TRIPLE).map(Some)
@@ -89,3 +90,57 @@ where
serde_json::from_str(s)
.with_context(|| format!("failed to deserialize as {}\nJSON: {}", type_name::(), s))
}
+
+/// Initialize tracing subscriber to emit traces. This configures subscribers
+/// for Trace Event Format (https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview).
+#[js_function(1)]
+pub fn init_custom_trace_subscriber(cx: CallContext) -> napi::Result {
+ let optional_trace_out_file_path = cx.get::(0)?;
+ let trace_out_file_path = match optional_trace_out_file_path.get_type()? {
+ napi::ValueType::String => Some(PathBuf::from(
+ JsString::try_from(optional_trace_out_file_path)?
+ .into_utf8()?
+ .as_str()?
+ .to_owned(),
+ )),
+ _ => None,
+ };
+
+ let mut layer = ChromeLayerBuilder::new().include_args(true);
+ if let Some(trace_out_file) = trace_out_file_path {
+ let dir = trace_out_file
+ .parent()
+ .ok_or_else(|| anyhow!("Not able to find path to the trace output"))
+ .convert_err()?;
+ std::fs::create_dir_all(dir)?;
+
+ layer = layer.file(trace_out_file);
+ }
+
+ let (chrome_layer, guard) = layer.build();
+ tracing_subscriber::registry()
+ .with(chrome_layer.with_filter(filter::filter_fn(|metadata| {
+ !metadata.target().contains("cranelift") && !metadata.name().contains("log ")
+ })))
+ .try_init()
+ .expect("Failed to register tracing subscriber");
+
+ let guard_cell = RefCell::new(Some(guard));
+ cx.env.create_external(guard_cell, None)
+}
+
+/// Teardown currently running tracing subscriber to flush out remaining traces.
+/// This should be called when parent node.js process exits, otherwise generated
+/// trace may drop traces in the buffer.
+#[js_function(1)]
+pub fn teardown_trace_subscriber(cx: CallContext) -> napi::Result {
+ let guard_external = cx.get::(0)?;
+ let guard_cell = &*cx
+ .env
+ .get_value_external::>>(&guard_external)?;
+
+ if let Some(guard) = guard_cell.take() {
+ drop(guard);
+ }
+ cx.env.get_undefined()
+}
diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json
index a748fc257b39..c2bd27437e9e 100644
--- a/packages/next-swc/package.json
+++ b/packages/next-swc/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/swc",
- "version": "12.1.6",
+ "version": "12.1.7-canary.2",
"private": true,
"scripts": {
"build-native": "napi build --platform --cargo-name next_swc_napi native",
diff --git a/packages/next/build/entries.ts b/packages/next/build/entries.ts
index 7aa7b83f6230..1e0eacd25890 100644
--- a/packages/next/build/entries.ts
+++ b/packages/next/build/entries.ts
@@ -10,8 +10,19 @@ import fs from 'fs'
import chalk from 'next/dist/compiled/chalk'
import { posix, join } from 'path'
import { stringify } from 'querystring'
-import { API_ROUTE, DOT_NEXT_ALIAS, PAGES_DIR_ALIAS } from '../lib/constants'
-import { EDGE_RUNTIME_WEBPACK } from '../shared/lib/constants'
+import {
+ API_ROUTE,
+ DOT_NEXT_ALIAS,
+ PAGES_DIR_ALIAS,
+ VIEWS_DIR_ALIAS,
+} from '../lib/constants'
+import {
+ CLIENT_STATIC_FILES_RUNTIME_AMP,
+ CLIENT_STATIC_FILES_RUNTIME_MAIN,
+ CLIENT_STATIC_FILES_RUNTIME_MAIN_ROOT,
+ CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH,
+ EDGE_RUNTIME_WEBPACK,
+} from '../shared/lib/constants'
import { MIDDLEWARE_ROUTE } from '../lib/constants'
import { __ApiPreviewProps } from '../server/api-utils'
import { isTargetLikeServerless } from '../server/utils'
@@ -33,9 +44,11 @@ export function getPageFromPath(pagePath: string, pageExtensions: string[]) {
? withoutRSCExtensions(pageExtensions)
: pageExtensions
- const page = normalizePathSep(
+ let page = normalizePathSep(
pagePath.replace(new RegExp(`\\.+(${extensions.join('|')})$`), '')
- ).replace(/\/index$/, '')
+ )
+
+ page = page.replace(/\/index$/, '')
return page === '' ? '/' : page
}
@@ -43,15 +56,18 @@ export function getPageFromPath(pagePath: string, pageExtensions: string[]) {
export function createPagesMapping({
hasServerComponents,
isDev,
+ isViews,
pageExtensions,
pagePaths,
}: {
hasServerComponents: boolean
isDev: boolean
+ isViews?: boolean
pageExtensions: string[]
pagePaths: string[]
}): { [page: string]: string } {
const previousPages: { [key: string]: string } = {}
+ const pathAlias = isViews ? VIEWS_DIR_ALIAS : PAGES_DIR_ALIAS
const pages = pagePaths.reduce<{ [key: string]: string }>(
(result, pagePath) => {
// Do not process .d.ts files inside the `pages` folder
@@ -80,7 +96,7 @@ export function createPagesMapping({
previousPages[pageKey] = pagePath
}
- result[pageKey] = normalizePathSep(join(PAGES_DIR_ALIAS, pagePath))
+ result[pageKey] = normalizePathSep(join(pathAlias, pagePath))
return result
},
{}
@@ -89,6 +105,11 @@ export function createPagesMapping({
// In development we always alias these to allow Webpack to fallback to
// the correct source file so that HMR can work properly when a file is
// added or removed.
+
+ if (isViews) {
+ return pages
+ }
+
if (isDev) {
delete pages['/_app']
delete pages['/_app.server']
@@ -222,6 +243,8 @@ interface CreateEntrypointsParams {
pagesDir: string
previewMode: __ApiPreviewProps
target: 'server' | 'serverless' | 'experimental-serverless-trace'
+ viewsDir?: string
+ viewPaths?: Record
}
export function getEdgeServerEntry(opts: {
@@ -326,29 +349,41 @@ export function getClientEntry(opts: {
}
export async function createEntrypoints(params: CreateEntrypointsParams) {
- const { config, pages, pagesDir, isDev, target } = params
+ const { config, pages, pagesDir, isDev, target, viewsDir, viewPaths } = params
const edgeServer: webpack5.EntryObject = {}
const server: webpack5.EntryObject = {}
const client: webpack5.EntryObject = {}
- await Promise.all(
- Object.keys(pages).map(async (page) => {
+ const getEntryHandler =
+ (mappings: Record, isViews: boolean) =>
+ async (page: string) => {
const bundleFile = normalizePagePath(page)
const clientBundlePath = posix.join('pages', bundleFile)
- const serverBundlePath = posix.join('pages', bundleFile)
+ const serverBundlePath = posix.join(
+ isViews ? 'views' : 'pages',
+ bundleFile
+ )
+
+ // Handle paths that have aliases
+ const pageFilePath = (() => {
+ const absolutePagePath = mappings[page]
+ if (absolutePagePath.startsWith(PAGES_DIR_ALIAS)) {
+ return absolutePagePath.replace(PAGES_DIR_ALIAS, pagesDir)
+ }
+
+ if (absolutePagePath.startsWith(VIEWS_DIR_ALIAS) && viewsDir) {
+ return absolutePagePath.replace(VIEWS_DIR_ALIAS, viewsDir)
+ }
+
+ return require.resolve(absolutePagePath)
+ })()
runDependingOnPageType({
page,
- pageRuntime: await getPageRuntime(
- !pages[page].startsWith(PAGES_DIR_ALIAS)
- ? require.resolve(pages[page])
- : join(pagesDir, pages[page].replace(PAGES_DIR_ALIAS, '')),
- config,
- isDev
- ),
+ pageRuntime: await getPageRuntime(pageFilePath, config, isDev),
onClient: () => {
client[clientBundlePath] = getClientEntry({
- absolutePagePath: pages[page],
+ absolutePagePath: mappings[page],
page,
})
},
@@ -357,26 +392,31 @@ export async function createEntrypoints(params: CreateEntrypointsParams) {
if (page !== '/_app' && page !== '/_document') {
server[serverBundlePath] = getServerlessEntry({
...params,
- absolutePagePath: pages[page],
+ absolutePagePath: mappings[page],
page,
})
}
} else {
- server[serverBundlePath] = [pages[page]]
+ server[serverBundlePath] = [mappings[page]]
}
},
onEdgeServer: () => {
edgeServer[serverBundlePath] = getEdgeServerEntry({
...params,
- absolutePagePath: pages[page],
+ absolutePagePath: mappings[page],
bundlePath: clientBundlePath,
isDev: false,
page,
})
},
})
- })
- )
+ }
+
+ if (viewsDir && viewPaths) {
+ const entryHandler = getEntryHandler(viewPaths, true)
+ await Promise.all(Object.keys(viewPaths).map(entryHandler))
+ }
+ await Promise.all(Object.keys(pages).map(getEntryHandler(pages, false)))
return {
client,
@@ -450,9 +490,10 @@ export function finalizeEntrypoint({
if (
// Client special cases
name !== 'polyfills' &&
- name !== 'main' &&
- name !== 'amp' &&
- name !== 'react-refresh'
+ name !== CLIENT_STATIC_FILES_RUNTIME_MAIN &&
+ name !== CLIENT_STATIC_FILES_RUNTIME_MAIN_ROOT &&
+ name !== CLIENT_STATIC_FILES_RUNTIME_AMP &&
+ name !== CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH
) {
return {
dependOn:
diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts
index 1e2bd262d40c..119a686c0be4 100644
--- a/packages/next/build/index.ts
+++ b/packages/next/build/index.ts
@@ -113,7 +113,7 @@ import { TelemetryPlugin } from './webpack/plugins/telemetry-plugin'
import { MiddlewareManifest } from './webpack/plugins/middleware-plugin'
import { recursiveCopy } from '../lib/recursive-copy'
import { recursiveReadDir } from '../lib/recursive-readdir'
-import { lockfilePatchPromise } from './swc'
+import { lockfilePatchPromise, teardownTraceSubscriber } from './swc'
export type SsgRoute = {
initialRevalidateSeconds: number | false
@@ -206,9 +206,9 @@ export default async function build(
setGlobal('telemetry', telemetry)
const publicDir = path.join(dir, 'public')
- const { pages: pagesDir, root: rootDir } = findPagesDir(
+ const { pages: pagesDir, views: viewsDir } = findPagesDir(
dir,
- config.experimental.rootDir
+ config.experimental.viewsDir
)
const hasPublicDir = await fileExists(publicDir)
@@ -244,7 +244,7 @@ export default async function build(
.traceAsyncFn(() =>
verifyTypeScriptSetup(
dir,
- [pagesDir, rootDir].filter(Boolean) as string[],
+ [pagesDir, viewsDir].filter(Boolean) as string[],
!ignoreTypeScriptErrors,
config,
cacheDir
@@ -309,6 +309,19 @@ export default async function build(
new RegExp(`\\.(?:${config.pageExtensions.join('|')})$`)
)
)
+
+ let viewPaths: string[] | undefined
+
+ if (viewsDir) {
+ viewPaths = await nextBuildSpan
+ .traceChild('collect-view-paths')
+ .traceAsyncFn(() =>
+ recursiveReadDir(
+ viewsDir,
+ new RegExp(`\\.(?:${config.pageExtensions.join('|')})$`)
+ )
+ )
+ }
// needed for static exporting since we want to replace with HTML
// files
@@ -332,6 +345,22 @@ export default async function build(
})
)
+ let mappedViewPaths: ReturnType | undefined
+
+ if (viewPaths && viewsDir) {
+ mappedViewPaths = nextBuildSpan
+ .traceChild('create-views-mapping')
+ .traceFn(() =>
+ createPagesMapping({
+ pagePaths: viewPaths!,
+ hasServerComponents,
+ isDev: false,
+ isViews: true,
+ pageExtensions: config.pageExtensions,
+ })
+ )
+ }
+
const entrypoints = await nextBuildSpan
.traceChild('create-entrypoints')
.traceAsyncFn(() =>
@@ -344,6 +373,8 @@ export default async function build(
pagesDir,
previewMode: previewProps,
target,
+ viewsDir,
+ viewPaths: mappedViewPaths,
})
)
@@ -649,6 +680,7 @@ export default async function build(
rewrites,
runWebpackSpan,
target,
+ viewsDir,
}
const configs = await runWebpackSpan
@@ -2092,6 +2124,8 @@ export default async function build(
const images = { ...config.images }
const { deviceSizes, imageSizes } = images
;(images as any).sizes = [...deviceSizes, ...imageSizes]
+ ;(images as any).remotePatterns =
+ config?.experimental?.images?.remotePatterns || []
await promises.writeFile(
path.join(distDir, IMAGES_MANIFEST),
@@ -2212,6 +2246,7 @@ export default async function build(
// Ensure all traces are flushed before finishing the command
await flushAllTraces()
+ teardownTraceSubscriber()
}
}
diff --git a/packages/next/build/output/store.ts b/packages/next/build/output/store.ts
index d2520f71f015..4397f6271de0 100644
--- a/packages/next/build/output/store.ts
+++ b/packages/next/build/output/store.ts
@@ -1,7 +1,7 @@
import createStore from 'next/dist/compiled/unistore'
import stripAnsi from 'next/dist/compiled/strip-ansi'
import { flushAllTraces } from '../../trace'
-
+import { teardownTraceSubscriber } from '../swc'
import * as Log from './log'
export type OutputState =
@@ -91,6 +91,7 @@ store.subscribe((state) => {
// Ensure traces are flushed after each compile in development mode
flushAllTraces()
+ teardownTraceSubscriber()
return
}
@@ -117,6 +118,7 @@ store.subscribe((state) => {
Log.warn(state.warnings.join('\n\n'))
// Ensure traces are flushed after each compile in development mode
flushAllTraces()
+ teardownTraceSubscriber()
return
}
@@ -132,4 +134,5 @@ store.subscribe((state) => {
)
// Ensure traces are flushed after each compile in development mode
flushAllTraces()
+ teardownTraceSubscriber()
})
diff --git a/packages/next/build/swc/index.d.ts b/packages/next/build/swc/index.d.ts
index 162a1b55e847..564090d59b22 100644
--- a/packages/next/build/swc/index.d.ts
+++ b/packages/next/build/swc/index.d.ts
@@ -6,3 +6,5 @@ export function minifySync(src: string, options: any): string
export function bundle(options: any): Promise
export function parse(src: string, options: any): any
export const lockfilePatchPromise: { cur?: Promise }
+export function initCustomTraceSubscriber(traceFileName?: string): void
+export function teardownTraceSubscriber(): void
diff --git a/packages/next/build/swc/index.js b/packages/next/build/swc/index.js
index e5fd01e61063..b3488bf9495b 100644
--- a/packages/next/build/swc/index.js
+++ b/packages/next/build/swc/index.js
@@ -1,10 +1,13 @@
+import path from 'path'
+import { pathToFileURL } from 'url'
import { platform, arch } from 'os'
import { platformArchTriples } from 'next/dist/compiled/@napi-rs/triples'
-import { version as nextVersion, optionalDependencies } from 'next/package.json'
import * as Log from '../output/log'
import { getParserOptions } from './options'
import { eventSwcLoadFailure } from '../../telemetry/events/swc-load-failure'
import { patchIncorrectLockfile } from '../../lib/patch-incorrect-lockfile'
+import { downloadWasmSwc } from '../../lib/download-wasm-swc'
+import { version as nextVersion } from 'next/package.json'
const ArchName = arch()
const PlatformName = platform()
@@ -12,35 +15,68 @@ const triples = platformArchTriples[PlatformName][ArchName] || []
let nativeBindings
let wasmBindings
+let downloadWasmPromise
+let pendingBindings
+let swcTraceFlushGuard
export const lockfilePatchPromise = {}
async function loadBindings() {
- if (!lockfilePatchPromise.cur) {
- // always run lockfile check once so that it gets patched
- // even if it doesn't fail to load locally
- lockfilePatchPromise.cur = patchIncorrectLockfile(process.cwd()).catch(
- console.error
- )
+ if (pendingBindings) {
+ return pendingBindings
}
+ pendingBindings = new Promise(async (resolve, reject) => {
+ if (!lockfilePatchPromise.cur) {
+ // always run lockfile check once so that it gets patched
+ // even if it doesn't fail to load locally
+ lockfilePatchPromise.cur = patchIncorrectLockfile(process.cwd()).catch(
+ console.error
+ )
+ }
- let attempts = []
- try {
- return loadNative()
- } catch (a) {
- attempts = attempts.concat(a)
- }
+ let attempts = []
+ try {
+ return resolve(loadNative())
+ } catch (a) {
+ attempts = attempts.concat(a)
+ }
- // TODO: fetch wasm and fallback when loading native fails
- // so that users aren't blocked on this, we still want to
- // report the native load failure so we can patch though
- try {
- let bindings = await loadWasm()
- return bindings
- } catch (a) {
- attempts = attempts.concat(a)
- }
+ try {
+ let bindings = await loadWasm()
+ eventSwcLoadFailure({ wasm: 'enabled' })
+ return resolve(bindings)
+ } catch (a) {
+ attempts = attempts.concat(a)
+ }
- logLoadFailure(attempts)
+ try {
+ // if not installed already download wasm package on-demand
+ // we download to a custom directory instead of to node_modules
+ // as node_module import attempts are cached and can't be re-attempted
+ // x-ref: https://github.com/nodejs/modules/issues/307
+ const wasmDirectory = path.join(
+ path.dirname(require.resolve('next/package.json')),
+ 'wasm'
+ )
+ if (!downloadWasmPromise) {
+ downloadWasmPromise = downloadWasmSwc(nextVersion, wasmDirectory)
+ }
+ await downloadWasmPromise
+ let bindings = await loadWasm(pathToFileURL(wasmDirectory).href)
+ eventSwcLoadFailure({ wasm: 'fallback' })
+
+ // still log native load attempts so user is
+ // aware it failed and should be fixed
+ for (const attempt of attempts) {
+ Log.warn(attempt)
+ }
+ return resolve(bindings)
+ } catch (a) {
+ attempts = attempts.concat(a)
+ }
+
+ logLoadFailure(attempts, true)
+ })
+ return pendingBindings
}
function loadBindingsSync() {
@@ -56,7 +92,7 @@ function loadBindingsSync() {
let loggingLoadFailure = false
-function logLoadFailure(attempts) {
+function logLoadFailure(attempts, triedWasm = false) {
// make sure we only emit the event and log the failure once
if (loggingLoadFailure) return
loggingLoadFailure = true
@@ -64,39 +100,8 @@ function logLoadFailure(attempts) {
for (let attempt of attempts) {
Log.warn(attempt)
}
- let glibcVersion
- let installedSwcPackages
-
- try {
- glibcVersion = process.report?.getReport().header.glibcVersionRuntime
- } catch (_) {}
- try {
- const pkgNames = Object.keys(optionalDependencies || {}).filter((pkg) =>
- pkg.startsWith('@next/swc')
- )
- const installedPkgs = []
-
- for (const pkg of pkgNames) {
- try {
- const { version } = require(`${pkg}/package.json`)
- installedPkgs.push(`${pkg}@${version}`)
- } catch (_) {}
- }
-
- if (installedPkgs.length > 0) {
- installedSwcPackages = installedPkgs.sort().join(',')
- }
- } catch (_) {}
-
- eventSwcLoadFailure({
- nextVersion,
- glibcVersion,
- installedSwcPackages,
- arch: process.arch,
- platform: process.platform,
- nodeVersion: process.versions.node,
- })
+ eventSwcLoadFailure({ wasm: triedWasm ? 'failed' : undefined })
.then(() => lockfilePatchPromise.cur || Promise.resolve())
.finally(() => {
Log.error(
@@ -106,7 +111,7 @@ function logLoadFailure(attempts) {
})
}
-async function loadWasm() {
+async function loadWasm(importPath = '') {
if (wasmBindings) {
return wasmBindings
}
@@ -114,7 +119,13 @@ async function loadWasm() {
let attempts = []
for (let pkg of ['@next/swc-wasm-nodejs', '@next/swc-wasm-web']) {
try {
- let bindings = await import(pkg)
+ let pkgPath = pkg
+
+ if (importPath) {
+ // the import path must be exact when not in node_modules
+ pkgPath = path.join(importPath, pkg, 'wasm.js')
+ }
+ let bindings = await import(pkgPath)
if (pkg === '@next/swc-wasm-web') {
bindings = await bindings.default()
}
@@ -139,14 +150,16 @@ async function loadWasm() {
}
return wasmBindings
} catch (e) {
- // Do not report attempts to load wasm when it is still experimental
- // if (e?.code === 'ERR_MODULE_NOT_FOUND') {
- // attempts.push(`Attempted to load ${pkg}, but it was not installed`)
- // } else {
- // attempts.push(
- // `Attempted to load ${pkg}, but an error occurred: ${e.message ?? e}`
- // )
- // }
+ // Only log attempts for loading wasm when loading as fallback
+ if (importPath) {
+ if (e?.code === 'ERR_MODULE_NOT_FOUND') {
+ attempts.push(`Attempted to load ${pkg}, but it was not installed`)
+ } else {
+ attempts.push(
+ `Attempted to load ${pkg}, but an error occurred: ${e.message ?? e}`
+ )
+ }
+ }
}
}
@@ -249,6 +262,8 @@ function loadNative() {
},
getTargetTriple: bindings.getTargetTriple,
+ initCustomTraceSubscriber: bindings.initCustomTraceSubscriber,
+ teardownTraceSubscriber: bindings.teardownTraceSubscriber,
}
return nativeBindings
}
@@ -308,3 +323,43 @@ export function getBinaryMetadata() {
target: bindings?.getTargetTriple?.(),
}
}
+
+/**
+ * Initialize trace subscriber to emit traces.
+ *
+ */
+export const initCustomTraceSubscriber = (() => {
+ return (filename) => {
+ if (!swcTraceFlushGuard) {
+ // Wasm binary doesn't support trace emission
+ let bindings = loadNative()
+ swcTraceFlushGuard = bindings.initCustomTraceSubscriber(filename)
+ }
+ }
+})()
+
+/**
+ * Teardown swc's trace subscriber if there's an initialized flush guard exists.
+ *
+ * This is workaround to amend behavior with process.exit
+ * (https://github.com/vercel/next.js/blob/4db8c49cc31e4fc182391fae6903fb5ef4e8c66e/packages/next/bin/next.ts#L134=)
+ * seems preventing napi's cleanup hook execution (https://github.com/swc-project/swc/blob/main/crates/node/src/util.rs#L48-L51=),
+ *
+ * instead parent process manually drops guard when process gets signal to exit.
+ */
+export const teardownTraceSubscriber = (() => {
+ let flushed = false
+ return () => {
+ if (!flushed) {
+ flushed = true
+ try {
+ let bindings = loadNative()
+ if (swcTraceFlushGuard) {
+ bindings.teardownTraceSubscriber(swcTraceFlushGuard)
+ }
+ } catch (e) {
+ // Suppress exceptions, this fn allows to fail to load native bindings
+ }
+ }
+ }
+})()
diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts
index fab5cbf8f8a9..724f96aa0ffa 100644
--- a/packages/next/build/webpack-config.ts
+++ b/packages/next/build/webpack-config.ts
@@ -10,12 +10,14 @@ import {
NEXT_PROJECT_ROOT,
NEXT_PROJECT_ROOT_DIST_CLIENT,
PAGES_DIR_ALIAS,
+ VIEWS_DIR_ALIAS,
} from '../lib/constants'
import { fileExists } from '../lib/file-exists'
import { CustomRoutes } from '../lib/load-custom-routes.js'
import {
CLIENT_STATIC_FILES_RUNTIME_AMP,
CLIENT_STATIC_FILES_RUNTIME_MAIN,
+ CLIENT_STATIC_FILES_RUNTIME_MAIN_ROOT,
CLIENT_STATIC_FILES_RUNTIME_POLYFILLS_SYMBOL,
CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH,
CLIENT_STATIC_FILES_RUNTIME_WEBPACK,
@@ -312,6 +314,7 @@ export default async function getBaseWebpackConfig(
rewrites,
runWebpackSpan,
target = 'server',
+ viewsDir,
}: {
buildId: string
config: NextConfigComplete
@@ -325,6 +328,7 @@ export default async function getBaseWebpackConfig(
rewrites: CustomRoutes['rewrites']
runWebpackSpan: Span
target?: string
+ viewsDir?: string
}
): Promise {
const isClient = compilerType === 'client'
@@ -436,6 +440,16 @@ export default async function getBaseWebpackConfig(
}
const getBabelOrSwcLoader = () => {
+ if (useSWCLoader && config?.experimental?.swcTraceProfiling) {
+ // This will init subscribers once only in a single process lifecycle,
+ // even though it can be called multiple times.
+ // Subscriber need to be initialized _before_ any actual swc's call (transform, etcs)
+ // to collect correct trace spans when they are called.
+ require('./swc')?.initCustomTraceSubscriber?.(
+ path.join(distDir, `swc-trace-profile-${Date.now()}.json`)
+ )
+ }
+
return useSWCLoader
? {
loader: 'next-swc-loader',
@@ -527,6 +541,18 @@ export default async function getBaseWebpackConfig(
)
)
.replace(/\\/g, '/'),
+ ...(config.experimental.viewsDir
+ ? {
+ [CLIENT_STATIC_FILES_RUNTIME_MAIN_ROOT]:
+ `./` +
+ path
+ .relative(
+ dir,
+ path.join(NEXT_PROJECT_ROOT_DIST_CLIENT, 'root-next.js')
+ )
+ .replace(/\\/g, '/'),
+ }
+ : {}),
} as ClientEntries)
: undefined
@@ -549,6 +575,7 @@ export default async function getBaseWebpackConfig(
const customAppAliases: { [key: string]: string[] } = {}
const customErrorAlias: { [key: string]: string[] } = {}
const customDocumentAliases: { [key: string]: string[] } = {}
+ const customRootAliases: { [key: string]: string[] } = {}
if (dev) {
customAppAliases[`${PAGES_DIR_ALIAS}/_app`] = [
@@ -610,8 +637,14 @@ export default async function getBaseWebpackConfig(
...customAppAliases,
...customErrorAlias,
...customDocumentAliases,
+ ...customRootAliases,
[PAGES_DIR_ALIAS]: pagesDir,
+ ...(viewsDir
+ ? {
+ [VIEWS_DIR_ALIAS]: viewsDir,
+ }
+ : {}),
[DOT_NEXT_ALIAS]: distDir,
...(isClient || isEdgeServer ? getOptimizedAliases() : {}),
...getReactProfilingInProduction(),
@@ -1145,6 +1178,7 @@ export default async function getBaseWebpackConfig(
'next-middleware-loader',
'next-middleware-ssr-loader',
'next-middleware-wasm-loader',
+ 'next-view-loader',
].reduce((alias, loader) => {
// using multiple aliases to replace `resolveLoader.modules`
alias[loader] = path.join(__dirname, 'webpack', 'loaders', loader)
@@ -1422,6 +1456,8 @@ export default async function getBaseWebpackConfig(
? {
// pass domains in development to allow validating on the client
domains: config.images.domains,
+ experimentalRemotePatterns:
+ config.experimental?.images?.remotePatterns,
}
: {}),
}),
@@ -1513,6 +1549,7 @@ export default async function getBaseWebpackConfig(
serverless: isLikeServerless,
dev,
isEdgeRuntime: isEdgeServer,
+ rootEnabled: !!config.experimental.viewsDir,
}),
// MiddlewarePlugin should be after DefinePlugin so NEXT_PUBLIC_*
// replacement is done before its process.env.* handling
@@ -1523,6 +1560,7 @@ export default async function getBaseWebpackConfig(
rewrites,
isDevFallback,
exportRuntime: hasConcurrentFeatures,
+ rootEnabled: !!config.experimental.viewsDir,
}),
new ProfilingPlugin({ runWebpackSpan }),
config.optimizeFonts &&
diff --git a/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts b/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts
index 341334e3d4d5..e186b66087d2 100644
--- a/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts
+++ b/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts
@@ -54,6 +54,7 @@ export function getRender({
}
const server = new WebServer({
+ dev,
conf: config,
minimalMode: true,
webServerConfig: {
diff --git a/packages/next/build/webpack/loaders/next-view-loader.ts b/packages/next/build/webpack/loaders/next-view-loader.ts
new file mode 100644
index 000000000000..af12ebee0070
--- /dev/null
+++ b/packages/next/build/webpack/loaders/next-view-loader.ts
@@ -0,0 +1,17 @@
+import type webpack from 'webpack5'
+
+const nextViewLoader: webpack.LoaderDefinitionFunction<{
+ components: string[]
+}> = function nextViewLoader() {
+ const loaderOptions = this.getOptions() || {}
+
+ return `
+ export const components = {
+ ${loaderOptions.components
+ .map((component) => `'${component}': () => import('${component}')`)
+ .join(',\n')}
+ }
+ `
+}
+
+export default nextViewLoader
diff --git a/packages/next/build/webpack/plugins/build-manifest-plugin.ts b/packages/next/build/webpack/plugins/build-manifest-plugin.ts
index d69cbde5d13c..7d02aeb08d56 100644
--- a/packages/next/build/webpack/plugins/build-manifest-plugin.ts
+++ b/packages/next/build/webpack/plugins/build-manifest-plugin.ts
@@ -5,6 +5,7 @@ import {
MIDDLEWARE_BUILD_MANIFEST,
CLIENT_STATIC_FILES_PATH,
CLIENT_STATIC_FILES_RUNTIME_MAIN,
+ CLIENT_STATIC_FILES_RUNTIME_MAIN_ROOT,
CLIENT_STATIC_FILES_RUNTIME_POLYFILLS_SYMBOL,
CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH,
CLIENT_STATIC_FILES_RUNTIME_AMP,
@@ -95,12 +96,14 @@ export default class BuildManifestPlugin {
private rewrites: CustomRoutes['rewrites']
private isDevFallback: boolean
private exportRuntime: boolean
+ private rootEnabled: boolean
constructor(options: {
buildId: string
rewrites: CustomRoutes['rewrites']
isDevFallback?: boolean
exportRuntime?: boolean
+ rootEnabled: boolean
}) {
this.buildId = options.buildId
this.isDevFallback = !!options.isDevFallback
@@ -109,6 +112,7 @@ export default class BuildManifestPlugin {
afterFiles: [],
fallback: [],
}
+ this.rootEnabled = options.rootEnabled
this.rewrites.beforeFiles = options.rewrites.beforeFiles.map(processRoute)
this.rewrites.afterFiles = options.rewrites.afterFiles.map(processRoute)
this.rewrites.fallback = options.rewrites.fallback.map(processRoute)
@@ -127,6 +131,7 @@ export default class BuildManifestPlugin {
devFiles: [],
ampDevFiles: [],
lowPriorityFiles: [],
+ rootMainFiles: [],
pages: { '/_app': [] },
ampFirstPages: [],
}
@@ -147,6 +152,16 @@ export default class BuildManifestPlugin {
getEntrypointFiles(entrypoints.get(CLIENT_STATIC_FILES_RUNTIME_MAIN))
)
+ if (this.rootEnabled) {
+ assetMap.rootMainFiles = [
+ ...new Set(
+ getEntrypointFiles(
+ entrypoints.get(CLIENT_STATIC_FILES_RUNTIME_MAIN_ROOT)
+ )
+ ),
+ ]
+ }
+
const compilationAssets: {
name: string
source: typeof sources.RawSource
@@ -178,6 +193,7 @@ export default class BuildManifestPlugin {
CLIENT_STATIC_FILES_RUNTIME_MAIN,
CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH,
CLIENT_STATIC_FILES_RUNTIME_AMP,
+ ...(this.rootEnabled ? [CLIENT_STATIC_FILES_RUNTIME_MAIN_ROOT] : []),
])
for (const entrypoint of compilation.entrypoints.values()) {
diff --git a/packages/next/build/webpack/plugins/pages-manifest-plugin.ts b/packages/next/build/webpack/plugins/pages-manifest-plugin.ts
index 723e841c8145..4cb3e3640511 100644
--- a/packages/next/build/webpack/plugins/pages-manifest-plugin.ts
+++ b/packages/next/build/webpack/plugins/pages-manifest-plugin.ts
@@ -1,11 +1,17 @@
import { webpack, sources } from 'next/dist/compiled/webpack/webpack'
-import { PAGES_MANIFEST } from '../../../shared/lib/constants'
+import {
+ PAGES_MANIFEST,
+ VIEW_PATHS_MANIFEST,
+} from '../../../shared/lib/constants'
import getRouteFromEntrypoint from '../../../server/get-route-from-entrypoint'
+import { normalizePathSep } from '../../../shared/lib/page-path/normalize-path-sep'
export type PagesManifest = { [page: string]: string }
let edgeServerPages = {}
let nodeServerPages = {}
+let edgeServerRootPaths = {}
+let nodeServerRootPaths = {}
// This plugin creates a pages-manifest.json from page entrypoints.
// This is used for mapping paths like `/` to `.next/server/static//pages/index.js` when doing SSR
@@ -14,27 +20,32 @@ export default class PagesManifestPlugin implements webpack.Plugin {
serverless: boolean
dev: boolean
isEdgeRuntime: boolean
+ rootEnabled: boolean
constructor({
serverless,
dev,
isEdgeRuntime,
+ rootEnabled,
}: {
serverless: boolean
dev: boolean
isEdgeRuntime: boolean
+ rootEnabled: boolean
}) {
this.serverless = serverless
this.dev = dev
this.isEdgeRuntime = isEdgeRuntime
+ this.rootEnabled = rootEnabled
}
createAssets(compilation: any, assets: any) {
const entrypoints = compilation.entrypoints
const pages: PagesManifest = {}
+ const rootPaths: PagesManifest = {}
for (const entrypoint of entrypoints.values()) {
- const pagePath = getRouteFromEntrypoint(entrypoint.name)
+ const pagePath = getRouteFromEntrypoint(entrypoint.name, this.rootEnabled)
if (!pagePath) {
continue
@@ -54,22 +65,30 @@ export default class PagesManifestPlugin implements webpack.Plugin {
continue
}
// Write filename, replace any backslashes in path (on windows) with forwardslashes for cross-platform consistency.
- pages[pagePath] = files[files.length - 1]
+ let file = files[files.length - 1]
if (!this.dev) {
if (!this.isEdgeRuntime) {
- pages[pagePath] = pages[pagePath].slice(3)
+ file = file.slice(3)
}
}
- pages[pagePath] = pages[pagePath].replace(/\\/g, '/')
+ file = normalizePathSep(file)
+
+ if (entrypoint.name.startsWith('views/')) {
+ rootPaths[pagePath] = file
+ } else {
+ pages[pagePath] = file
+ }
}
// This plugin is used by both the Node server and Edge server compilers,
// we need to merge both pages to generate the full manifest.
if (this.isEdgeRuntime) {
edgeServerPages = pages
+ edgeServerRootPaths = rootPaths
} else {
nodeServerPages = pages
+ nodeServerRootPaths = rootPaths
}
assets[
@@ -84,6 +103,21 @@ export default class PagesManifestPlugin implements webpack.Plugin {
2
)
)
+
+ if (this.rootEnabled) {
+ assets[
+ `${!this.dev && !this.isEdgeRuntime ? '../' : ''}` + VIEW_PATHS_MANIFEST
+ ] = new sources.RawSource(
+ JSON.stringify(
+ {
+ ...edgeServerRootPaths,
+ ...nodeServerRootPaths,
+ },
+ null,
+ 2
+ )
+ )
+ }
}
apply(compiler: webpack.Compiler): void {
diff --git a/packages/next/client/dev/fouc.ts b/packages/next/client/dev/fouc.ts
index 07e09f2c4bc1..bca480e22719 100644
--- a/packages/next/client/dev/fouc.ts
+++ b/packages/next/client/dev/fouc.ts
@@ -1,9 +1,12 @@
+// This wrapper function is used to avoid raising a Trusted Types violation.
+const safeSetTimeout = (callback: () => void) => setTimeout(callback)
+
// This function is used to remove Next.js' no-FOUC styles workaround for using
// `style-loader` in development. It must be called before hydration, or else
// rendering won't have the correct computed values in effects.
export function displayContent(): Promise {
return new Promise((resolve) => {
- ;(window.requestAnimationFrame || setTimeout)(function () {
+ ;(window.requestAnimationFrame || safeSetTimeout)(function () {
for (
var x = document.querySelectorAll('[data-next-hide-fouc]'),
i = x.length;
diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx
index 35fbb6adbd0c..052e38fe58b6 100644
--- a/packages/next/client/image.tsx
+++ b/packages/next/client/image.tsx
@@ -18,8 +18,8 @@ import { ImageConfigContext } from '../shared/lib/image-config-context'
import { warnOnce } from '../shared/lib/utils'
import { normalizePathTrailingSlash } from './normalize-trailing-slash'
-const experimentalLayoutRaw = (process.env.__NEXT_IMAGE_OPTS as any)
- ?.experimentalLayoutRaw
+const { experimentalLayoutRaw = false, experimentalRemotePatterns = [] } =
+ (process.env.__NEXT_IMAGE_OPTS as any) || {}
const configEnv = process.env.__NEXT_IMAGE_OPTS as any as ImageConfigComplete
const loadedImageURLs = new Set()
const allImgs = new Map<
@@ -1063,7 +1063,10 @@ function defaultLoader({
)
}
- if (!src.startsWith('/') && config.domains) {
+ if (
+ !src.startsWith('/') &&
+ (config.domains || experimentalRemotePatterns)
+ ) {
let parsedSrc: URL
try {
parsedSrc = new URL(src)
@@ -1074,14 +1077,15 @@ function defaultLoader({
)
}
- if (
- process.env.NODE_ENV !== 'test' &&
- !config.domains.includes(parsedSrc.hostname)
- ) {
- throw new Error(
- `Invalid src prop (${src}) on \`next/image\`, hostname "${parsedSrc.hostname}" is not configured under images in your \`next.config.js\`\n` +
- `See more info: https://nextjs.org/docs/messages/next-image-unconfigured-host`
- )
+ if (process.env.NODE_ENV !== 'test') {
+ // We use dynamic require because this should only error in development
+ const { hasMatch } = require('../shared/lib/match-remote-pattern')
+ if (!hasMatch(config.domains, experimentalRemotePatterns, parsedSrc)) {
+ throw new Error(
+ `Invalid src prop (${src}) on \`next/image\`, hostname "${parsedSrc.hostname}" is not configured under images in your \`next.config.js\`\n` +
+ `See more info: https://nextjs.org/docs/messages/next-image-unconfigured-host`
+ )
+ }
}
}
}
diff --git a/packages/next/client/index.tsx b/packages/next/client/index.tsx
index c4ec6dfe7aa5..f966b50224db 100644
--- a/packages/next/client/index.tsx
+++ b/packages/next/client/index.tsx
@@ -1,7 +1,6 @@
/* global location */
import '../build/polyfills/polyfill-module'
import React, { useState } from 'react'
-import ReactDOM from 'react-dom'
import { HeadManagerContext } from '../shared/lib/head-manager-context'
import mitt, { MittEmitter } from '../shared/lib/mitt'
import { RouterContext } from '../shared/lib/router-context'
@@ -41,6 +40,10 @@ import { RefreshContext } from './streaming/refresh'
import { ImageConfigContext } from '../shared/lib/image-config-context'
import { ImageConfigComplete } from '../shared/lib/image-config'
+const ReactDOM = process.env.__NEXT_REACT_ROOT
+ ? require('react-dom/client')
+ : require('react-dom')
+
///
declare let __webpack_public_path__: string
@@ -549,8 +552,7 @@ function renderReactElement(
if (process.env.__NEXT_REACT_ROOT) {
if (!reactRoot) {
// Unlike with createRoot, you don't need a separate root.render() call here
- const ReactDOMClient = require('react-dom/client')
- reactRoot = ReactDOMClient.hydrateRoot(domEl, reactEl)
+ reactRoot = ReactDOM.hydrateRoot(domEl, reactEl)
// TODO: Remove shouldHydrate variable when React 18 is stable as it can depend on `reactRoot` existing
shouldHydrate = false
} else {
diff --git a/packages/next/client/route-loader.ts b/packages/next/client/route-loader.ts
index d1198fc2a024..56d0a0200ba8 100644
--- a/packages/next/client/route-loader.ts
+++ b/packages/next/client/route-loader.ts
@@ -1,5 +1,6 @@
import type { ComponentType } from 'react'
import getAssetPathFromRoute from '../shared/lib/router/utils/get-asset-path-from-route'
+import { __unsafeCreateTrustedScriptURL } from './trusted-types'
import { requestIdleCallback } from './request-idle-callback'
// 3.8s was arbitrarily chosen as it's what https://web.dev/interactive
@@ -135,7 +136,7 @@ export function isAssetError(err?: Error): boolean | undefined {
}
function appendScript(
- src: string,
+ src: TrustedScriptURL | string,
script?: HTMLScriptElement
): Promise {
return new Promise((resolve, reject) => {
@@ -154,7 +155,7 @@ function appendScript(
// 3. Finally, set the source and inject into the DOM in case the child
// must be appended for fetching to start.
- script.src = src
+ script.src = src as string
document.body.appendChild(script)
})
}
@@ -254,7 +255,7 @@ export function getMiddlewareManifest() {
}
interface RouteFiles {
- scripts: string[]
+ scripts: (TrustedScriptURL | string)[]
css: string[]
}
function getFilesForRoute(
@@ -262,12 +263,12 @@ function getFilesForRoute(
route: string
): Promise {
if (process.env.NODE_ENV === 'development') {
+ const scriptUrl =
+ assetPrefix +
+ '/_next/static/chunks/pages' +
+ encodeURI(getAssetPathFromRoute(route, '.js'))
return Promise.resolve({
- scripts: [
- assetPrefix +
- '/_next/static/chunks/pages' +
- encodeURI(getAssetPathFromRoute(route, '.js')),
- ],
+ scripts: [__unsafeCreateTrustedScriptURL(scriptUrl)],
// Styles are handled by `style-loader` in development:
css: [],
})
@@ -280,7 +281,9 @@ function getFilesForRoute(
(entry) => assetPrefix + '/_next/' + encodeURI(entry)
)
return {
- scripts: allFiles.filter((v) => v.endsWith('.js')),
+ scripts: allFiles
+ .filter((v) => v.endsWith('.js'))
+ .map((v) => __unsafeCreateTrustedScriptURL(v)),
css: allFiles.filter((v) => v.endsWith('.css')),
}
})
@@ -294,12 +297,14 @@ export function createRouteLoader(assetPrefix: string): RouteLoader {
const routes: Map | RouteLoaderEntry> =
new Map()
- function maybeExecuteScript(src: string): Promise {
+ function maybeExecuteScript(
+ src: TrustedScriptURL | string
+ ): Promise {
// With HMR we might need to "reload" scripts when they are
// disposed and readded. Executing scripts twice has no functional
// differences
if (process.env.NODE_ENV !== 'development') {
- let prom: Promise | undefined = loadedScripts.get(src)
+ let prom: Promise | undefined = loadedScripts.get(src.toString())
if (prom) {
return prom
}
@@ -309,7 +314,7 @@ export function createRouteLoader(assetPrefix: string): RouteLoader {
return Promise.resolve()
}
- loadedScripts.set(src, (prom = appendScript(src)))
+ loadedScripts.set(src.toString(), (prom = appendScript(src)))
return prom
} else {
return appendScript(src)
@@ -432,7 +437,9 @@ export function createRouteLoader(assetPrefix: string): RouteLoader {
.then((output) =>
Promise.all(
canPrefetch
- ? output.scripts.map((script) => prefetchViaDom(script, 'script'))
+ ? output.scripts.map((script) =>
+ prefetchViaDom(script.toString(), 'script')
+ )
: []
)
)
diff --git a/packages/next/client/trusted-types.ts b/packages/next/client/trusted-types.ts
new file mode 100644
index 000000000000..9682635eb945
--- /dev/null
+++ b/packages/next/client/trusted-types.ts
@@ -0,0 +1,37 @@
+/**
+ * Stores the Trusted Types Policy. Starts as undefined and can be set to null
+ * if Trusted Types is not supported in the browser.
+ */
+let policy: TrustedTypePolicy | null | undefined
+
+/**
+ * Getter for the Trusted Types Policy. If it is undefined, it is instantiated
+ * here or set to null if Trusted Types is not supported in the browser.
+ */
+function getPolicy() {
+ if (typeof policy === 'undefined' && typeof window !== 'undefined') {
+ policy =
+ window.trustedTypes?.createPolicy('nextjs', {
+ createHTML: (input) => input,
+ createScript: (input) => input,
+ createScriptURL: (input) => input,
+ }) || null
+ }
+
+ return policy
+}
+
+/**
+ * Unsafely promote a string to a TrustedScriptURL, falling back to strings
+ * when Trusted Types are not available.
+ * This is a security-sensitive function; any use of this function
+ * must go through security review. In particular, it must be assured that the
+ * provided string will never cause an XSS vulnerability if used in a context
+ * that will cause a browser to load and execute a resource, e.g. when
+ * assigning to script.src.
+ */
+export function __unsafeCreateTrustedScriptURL(
+ url: string
+): TrustedScriptURL | string {
+ return getPolicy()?.createScriptURL(url) || url
+}
diff --git a/packages/next/compiled/tar/LICENSE b/packages/next/compiled/tar/LICENSE
new file mode 100644
index 000000000000..19129e315fe5
--- /dev/null
+++ b/packages/next/compiled/tar/LICENSE
@@ -0,0 +1,15 @@
+The ISC License
+
+Copyright (c) Isaac Z. Schlueter and Contributors
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/packages/next/compiled/tar/index.js b/packages/next/compiled/tar/index.js
new file mode 100644
index 000000000000..ae49d0f70e23
--- /dev/null
+++ b/packages/next/compiled/tar/index.js
@@ -0,0 +1 @@
+(()=>{var t={5952:(t,e,s)=>{"use strict";const i=s(7147);const n=s(1017);const r=i.lchown?"lchown":"chown";const o=i.lchownSync?"lchownSync":"chownSync";const h=i.lchown&&!process.version.match(/v1[1-9]+\./)&&!process.version.match(/v10\.[6-9]/);const lchownSync=(t,e,s)=>{try{return i[o](t,e,s)}catch(t){if(t.code!=="ENOENT")throw t}};const chownSync=(t,e,s)=>{try{return i.chownSync(t,e,s)}catch(t){if(t.code!=="ENOENT")throw t}};const l=h?(t,e,s,n)=>r=>{if(!r||r.code!=="EISDIR")n(r);else i.chown(t,e,s,n)}:(t,e,s,i)=>i;const a=h?(t,e,s)=>{try{return lchownSync(t,e,s)}catch(i){if(i.code!=="EISDIR")throw i;chownSync(t,e,s)}}:(t,e,s)=>lchownSync(t,e,s);const c=process.version;let readdir=(t,e,s)=>i.readdir(t,e,s);let readdirSync=(t,e)=>i.readdirSync(t,e);if(/^v4\./.test(c))readdir=(t,e,s)=>i.readdir(t,s);const chown=(t,e,s,n)=>{i[r](t,e,s,l(t,e,s,(t=>{n(t&&t.code!=="ENOENT"?t:null)})))};const chownrKid=(t,e,s,r,o)=>{if(typeof e==="string")return i.lstat(n.resolve(t,e),((i,n)=>{if(i)return o(i.code!=="ENOENT"?i:null);n.name=e;chownrKid(t,n,s,r,o)}));if(e.isDirectory()){chownr(n.resolve(t,e.name),s,r,(i=>{if(i)return o(i);const h=n.resolve(t,e.name);chown(h,s,r,o)}))}else{const i=n.resolve(t,e.name);chown(i,s,r,o)}};const chownr=(t,e,s,i)=>{readdir(t,{withFileTypes:true},((n,r)=>{if(n){if(n.code==="ENOENT")return i();else if(n.code!=="ENOTDIR"&&n.code!=="ENOTSUP")return i(n)}if(n||!r.length)return chown(t,e,s,i);let o=r.length;let h=null;const then=n=>{if(h)return;if(n)return i(h=n);if(--o===0)return chown(t,e,s,i)};r.forEach((i=>chownrKid(t,i,e,s,then)))}))};const chownrKidSync=(t,e,s,r)=>{if(typeof e==="string"){try{const s=i.lstatSync(n.resolve(t,e));s.name=e;e=s}catch(t){if(t.code==="ENOENT")return;else throw t}}if(e.isDirectory())chownrSync(n.resolve(t,e.name),s,r);a(n.resolve(t,e.name),s,r)};const chownrSync=(t,e,s)=>{let i;try{i=readdirSync(t,{withFileTypes:true})}catch(i){if(i.code==="ENOENT")return;else if(i.code==="ENOTDIR"||i.code==="ENOTSUP")return a(t,e,s);else throw i}if(i&&i.length)i.forEach((i=>chownrKidSync(t,i,e,s)));return a(t,e,s)};t.exports=chownr;chownr.sync=chownrSync},3597:(t,e,s)=>{"use strict";const i=s(4591);const n=s(2361).EventEmitter;const r=s(7147);let o=r.writev;if(!o){const t=process.binding("fs");const e=t.FSReqWrap||t.FSReqCallback;o=(s,i,n,r)=>{const done=(t,e)=>r(t,e,i);const o=new e;o.oncomplete=done;t.writeBuffers(s,i,n,o)}}const h=Symbol("_autoClose");const l=Symbol("_close");const a=Symbol("_ended");const c=Symbol("_fd");const u=Symbol("_finished");const f=Symbol("_flags");const d=Symbol("_flush");const p=Symbol("_handleChunk");const m=Symbol("_makeBuf");const y=Symbol("_mode");const b=Symbol("_needDrain");const w=Symbol("_onerror");const _=Symbol("_onopen");const E=Symbol("_onread");const g=Symbol("_onwrite");const S=Symbol("_open");const R=Symbol("_path");const v=Symbol("_pos");const O=Symbol("_queue");const k=Symbol("_read");const T=Symbol("_readSize");const x=Symbol("_reading");const L=Symbol("_remain");const A=Symbol("_size");const N=Symbol("_write");const I=Symbol("_writing");const D=Symbol("_defaultFlag");const B=Symbol("_errored");class ReadStream extends i{constructor(t,e){e=e||{};super(e);this.readable=true;this.writable=false;if(typeof t!=="string")throw new TypeError("path must be a string");this[B]=false;this[c]=typeof e.fd==="number"?e.fd:null;this[R]=t;this[T]=e.readSize||16*1024*1024;this[x]=false;this[A]=typeof e.size==="number"?e.size:Infinity;this[L]=this[A];this[h]=typeof e.autoClose==="boolean"?e.autoClose:true;if(typeof this[c]==="number")this[k]();else this[S]()}get fd(){return this[c]}get path(){return this[R]}write(){throw new TypeError("this is a readable stream")}end(){throw new TypeError("this is a readable stream")}[S](){r.open(this[R],"r",((t,e)=>this[_](t,e)))}[_](t,e){if(t)this[w](t);else{this[c]=e;this.emit("open",e);this[k]()}}[m](){return Buffer.allocUnsafe(Math.min(this[T],this[L]))}[k](){if(!this[x]){this[x]=true;const t=this[m]();if(t.length===0)return process.nextTick((()=>this[E](null,0,t)));r.read(this[c],t,0,t.length,null,((t,e,s)=>this[E](t,e,s)))}}[E](t,e,s){this[x]=false;if(t)this[w](t);else if(this[p](e,s))this[k]()}[l](){if(this[h]&&typeof this[c]==="number"){const t=this[c];this[c]=null;r.close(t,(t=>t?this.emit("error",t):this.emit("close")))}}[w](t){this[x]=true;this[l]();this.emit("error",t)}[p](t,e){let s=false;this[L]-=t;if(t>0)s=super.write(tthis[_](t,e)))}[_](t,e){if(this[D]&&this[f]==="r+"&&t&&t.code==="ENOENT"){this[f]="w";this[S]()}else if(t)this[w](t);else{this[c]=e;this.emit("open",e);this[d]()}}end(t,e){if(t)this.write(t,e);this[a]=true;if(!this[I]&&!this[O].length&&typeof this[c]==="number")this[g](null,0);return this}write(t,e){if(typeof t==="string")t=Buffer.from(t,e);if(this[a]){this.emit("error",new Error("write() after end()"));return false}if(this[c]===null||this[I]||this[O].length){this[O].push(t);this[b]=true;return false}this[I]=true;this[N](t);return true}[N](t){r.write(this[c],t,0,t.length,this[v],((t,e)=>this[g](t,e)))}[g](t,e){if(t)this[w](t);else{if(this[v]!==null)this[v]+=e;if(this[O].length)this[d]();else{this[I]=false;if(this[a]&&!this[u]){this[u]=true;this[l]();this.emit("finish")}else if(this[b]){this[b]=false;this.emit("drain")}}}}[d](){if(this[O].length===0){if(this[a])this[g](null,0)}else if(this[O].length===1)this[N](this[O].pop());else{const t=this[O];this[O]=[];o(this[c],t,this[v],((t,e)=>this[g](t,e)))}}[l](){if(this[h]&&typeof this[c]==="number"){const t=this[c];this[c]=null;r.close(t,(t=>t?this.emit("error",t):this.emit("close")))}}}class WriteStreamSync extends WriteStream{[S](){let t;if(this[D]&&this[f]==="r+"){try{t=r.openSync(this[R],this[f],this[y])}catch(t){if(t.code==="ENOENT"){this[f]="w";return this[S]()}else throw t}}else t=r.openSync(this[R],this[f],this[y]);this[_](null,t)}[l](){if(this[h]&&typeof this[c]==="number"){const t=this[c];this[c]=null;r.closeSync(t);this.emit("close")}}[N](t){let e=true;try{this[g](null,r.writeSync(this[c],t,0,t.length,this[v]));e=false}finally{if(e)try{this[l]()}catch(t){}}}}e.ReadStream=ReadStream;e.ReadStreamSync=ReadStreamSync;e.WriteStream=WriteStream;e.WriteStreamSync=WriteStreamSync},4591:(t,e,s)=>{"use strict";const i=s(2361);const n=s(2781);const r=s(4461);const o=s(1576).StringDecoder;const h=Symbol("EOF");const l=Symbol("maybeEmitEnd");const a=Symbol("emittedEnd");const c=Symbol("emittingEnd");const u=Symbol("closed");const f=Symbol("read");const d=Symbol("flush");const p=Symbol("flushChunk");const m=Symbol("encoding");const y=Symbol("decoder");const b=Symbol("flowing");const w=Symbol("paused");const _=Symbol("resume");const E=Symbol("bufferLength");const g=Symbol("bufferPush");const S=Symbol("bufferShift");const R=Symbol("objectMode");const v=Symbol("destroyed");const O=global._MP_NO_ITERATOR_SYMBOLS_!=="1";const k=O&&Symbol.asyncIterator||Symbol("asyncIterator not implemented");const T=O&&Symbol.iterator||Symbol("iterator not implemented");const isEndish=t=>t==="end"||t==="finish"||t==="prefinish";const isArrayBuffer=t=>t instanceof ArrayBuffer||typeof t==="object"&&t.constructor&&t.constructor.name==="ArrayBuffer"&&t.byteLength>=0;const isArrayBufferView=t=>!Buffer.isBuffer(t)&&ArrayBuffer.isView(t);t.exports=class Minipass extends n{constructor(t){super();this[b]=false;this[w]=false;this.pipes=new r;this.buffer=new r;this[R]=t&&t.objectMode||false;if(this[R])this[m]=null;else this[m]=t&&t.encoding||null;if(this[m]==="buffer")this[m]=null;this[y]=this[m]?new o(this[m]):null;this[h]=false;this[a]=false;this[c]=false;this[u]=false;this.writable=true;this.readable=true;this[E]=0;this[v]=false}get bufferLength(){return this[E]}get encoding(){return this[m]}set encoding(t){if(this[R])throw new Error("cannot set encoding in objectMode");if(this[m]&&t!==this[m]&&(this[y]&&this[y].lastNeed||this[E]))throw new Error("cannot change encoding");if(this[m]!==t){this[y]=t?new o(t):null;if(this.buffer.length)this.buffer=this.buffer.map((t=>this[y].write(t)))}this[m]=t}setEncoding(t){this.encoding=t}get objectMode(){return this[R]}set objectMode(t){this[R]=this[R]||!!t}write(t,e,s){if(this[h])throw new Error("write after end");if(this[v]){this.emit("error",Object.assign(new Error("Cannot call write after a stream was destroyed"),{code:"ERR_STREAM_DESTROYED"}));return true}if(typeof e==="function")s=e,e="utf8";if(!e)e="utf8";if(!this[R]&&!Buffer.isBuffer(t)){if(isArrayBufferView(t))t=Buffer.from(t.buffer,t.byteOffset,t.byteLength);else if(isArrayBuffer(t))t=Buffer.from(t);else if(typeof t!=="string")this.objectMode=true}if(!this.objectMode&&!t.length){const t=this.flowing;if(this[E]!==0)this.emit("readable");if(s)s();return t}if(typeof t==="string"&&!this[R]&&!(e===this[m]&&!this[y].lastNeed)){t=Buffer.from(t,e)}if(Buffer.isBuffer(t)&&this[m])t=this[y].write(t);try{return this.flowing?(this.emit("data",t),this.flowing):(this[g](t),false)}finally{if(this[E]!==0)this.emit("readable");if(s)s()}}read(t){if(this[v])return null;try{if(this[E]===0||t===0||t>this[E])return null;if(this[R])t=null;if(this.buffer.length>1&&!this[R]){if(this.encoding)this.buffer=new r([Array.from(this.buffer).join("")]);else this.buffer=new r([Buffer.concat(Array.from(this.buffer),this[E])])}return this[f](t||null,this.buffer.head.value)}finally{this[l]()}}[f](t,e){if(t===e.length||t===null)this[S]();else{this.buffer.head.value=e.slice(t);e=e.slice(0,t);this[E]-=t}this.emit("data",e);if(!this.buffer.length&&!this[h])this.emit("drain");return e}end(t,e,s){if(typeof t==="function")s=t,t=null;if(typeof e==="function")s=e,e="utf8";if(t)this.write(t,e);if(s)this.once("end",s);this[h]=true;this.writable=false;if(this.flowing||!this[w])this[l]();return this}[_](){if(this[v])return;this[w]=false;this[b]=true;this.emit("resume");if(this.buffer.length)this[d]();else if(this[h])this[l]();else this.emit("drain")}resume(){return this[_]()}pause(){this[b]=false;this[w]=true}get destroyed(){return this[v]}get flowing(){return this[b]}get paused(){return this[w]}[g](t){if(this[R])this[E]+=1;else this[E]+=t.length;return this.buffer.push(t)}[S](){if(this.buffer.length){if(this[R])this[E]-=1;else this[E]-=this.buffer.head.value.length}return this.buffer.shift()}[d](){do{}while(this[p](this[S]()));if(!this.buffer.length&&!this[h])this.emit("drain")}[p](t){return t?(this.emit("data",t),this.flowing):false}pipe(t,e){if(this[v])return;const s=this[a];e=e||{};if(t===process.stdout||t===process.stderr)e.end=false;else e.end=e.end!==false;const i={dest:t,opts:e,ondrain:t=>this[_]()};this.pipes.push(i);t.on("drain",i.ondrain);this[_]();if(s&&i.opts.end)i.dest.end();return t}addListener(t,e){return this.on(t,e)}on(t,e){try{return super.on(t,e)}finally{if(t==="data"&&!this.pipes.length&&!this.flowing)this[_]();else if(isEndish(t)&&this[a]){super.emit(t);this.removeAllListeners(t)}}}get emittedEnd(){return this[a]}[l](){if(!this[c]&&!this[a]&&!this[v]&&this.buffer.length===0&&this[h]){this[c]=true;this.emit("end");this.emit("prefinish");this.emit("finish");if(this[u])this.emit("close");this[c]=false}}emit(t,e){if(t!=="error"&&t!=="close"&&t!==v&&this[v])return;else if(t==="data"){if(!e)return;if(this.pipes.length)this.pipes.forEach((t=>t.dest.write(e)===false&&this.pause()))}else if(t==="end"){if(this[a]===true)return;this[a]=true;this.readable=false;if(this[y]){e=this[y].end();if(e){this.pipes.forEach((t=>t.dest.write(e)));super.emit("data",e)}}this.pipes.forEach((t=>{t.dest.removeListener("drain",t.ondrain);if(t.opts.end)t.dest.end()}))}else if(t==="close"){this[u]=true;if(!this[a]&&!this[v])return}const s=new Array(arguments.length);s[0]=t;s[1]=e;if(arguments.length>2){for(let t=2;t{t.push(e);if(!this[R])t.dataLength+=e.length}));return e.then((()=>t))}concat(){return this[R]?Promise.reject(new Error("cannot concat in objectMode")):this.collect().then((t=>this[R]?Promise.reject(new Error("cannot concat in objectMode")):this[m]?t.join(""):Buffer.concat(t,t.dataLength)))}promise(){return new Promise(((t,e)=>{this.on(v,(()=>e(new Error("stream destroyed"))));this.on("end",(()=>t()));this.on("error",(t=>e(t)))}))}[k](){const next=()=>{const t=this.read();if(t!==null)return Promise.resolve({done:false,value:t});if(this[h])return Promise.resolve({done:true});let e=null;let s=null;const onerr=t=>{this.removeListener("data",ondata);this.removeListener("end",onend);s(t)};const ondata=t=>{this.removeListener("error",onerr);this.removeListener("end",onend);this.pause();e({value:t,done:!!this[h]})};const onend=()=>{this.removeListener("error",onerr);this.removeListener("data",ondata);e({done:true})};const ondestroy=()=>onerr(new Error("stream destroyed"));return new Promise(((t,i)=>{s=i;e=t;this.once(v,ondestroy);this.once("error",onerr);this.once("end",onend);this.once("data",ondata)}))};return{next:next}}[T](){const next=()=>{const t=this.read();const e=t===null;return{value:t,done:e}};return{next:next}}destroy(t){if(this[v]){if(t)this.emit("error",t);else this.emit(v);return this}this[v]=true;this.buffer=new r;this[E]=0;if(typeof this.close==="function"&&!this[u])this.close();if(t)this.emit("error",t);else this.emit(v);return this}static isStream(t){return!!t&&(t instanceof Minipass||t instanceof n||t instanceof i&&(typeof t.pipe==="function"||typeof t.write==="function"&&typeof t.end==="function"))}}},8319:(t,e,s)=>{const i=s(9796).constants||{ZLIB_VERNUM:4736};t.exports=Object.freeze(Object.assign(Object.create(null),{Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_MEM_ERROR:-4,Z_BUF_ERROR:-5,Z_VERSION_ERROR:-6,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,DEFLATE:1,INFLATE:2,GZIP:3,GUNZIP:4,DEFLATERAW:5,INFLATERAW:6,UNZIP:7,BROTLI_DECODE:8,BROTLI_ENCODE:9,Z_MIN_WINDOWBITS:8,Z_MAX_WINDOWBITS:15,Z_DEFAULT_WINDOWBITS:15,Z_MIN_CHUNK:64,Z_MAX_CHUNK:Infinity,Z_DEFAULT_CHUNK:16384,Z_MIN_MEMLEVEL:1,Z_MAX_MEMLEVEL:9,Z_DEFAULT_MEMLEVEL:8,Z_MIN_LEVEL:-1,Z_MAX_LEVEL:9,Z_DEFAULT_LEVEL:-1,BROTLI_OPERATION_PROCESS:0,BROTLI_OPERATION_FLUSH:1,BROTLI_OPERATION_FINISH:2,BROTLI_OPERATION_EMIT_METADATA:3,BROTLI_MODE_GENERIC:0,BROTLI_MODE_TEXT:1,BROTLI_MODE_FONT:2,BROTLI_DEFAULT_MODE:0,BROTLI_MIN_QUALITY:0,BROTLI_MAX_QUALITY:11,BROTLI_DEFAULT_QUALITY:11,BROTLI_MIN_WINDOW_BITS:10,BROTLI_MAX_WINDOW_BITS:24,BROTLI_LARGE_MAX_WINDOW_BITS:30,BROTLI_DEFAULT_WINDOW:22,BROTLI_MIN_INPUT_BLOCK_BITS:16,BROTLI_MAX_INPUT_BLOCK_BITS:24,BROTLI_PARAM_MODE:0,BROTLI_PARAM_QUALITY:1,BROTLI_PARAM_LGWIN:2,BROTLI_PARAM_LGBLOCK:3,BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING:4,BROTLI_PARAM_SIZE_HINT:5,BROTLI_PARAM_LARGE_WINDOW:6,BROTLI_PARAM_NPOSTFIX:7,BROTLI_PARAM_NDIRECT:8,BROTLI_DECODER_RESULT_ERROR:0,BROTLI_DECODER_RESULT_SUCCESS:1,BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT:2,BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT:3,BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION:0,BROTLI_DECODER_PARAM_LARGE_WINDOW:1,BROTLI_DECODER_NO_ERROR:0,BROTLI_DECODER_SUCCESS:1,BROTLI_DECODER_NEEDS_MORE_INPUT:2,BROTLI_DECODER_NEEDS_MORE_OUTPUT:3,BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE:-1,BROTLI_DECODER_ERROR_FORMAT_RESERVED:-2,BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE:-3,BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET:-4,BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME:-5,BROTLI_DECODER_ERROR_FORMAT_CL_SPACE:-6,BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE:-7,BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT:-8,BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1:-9,BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2:-10,BROTLI_DECODER_ERROR_FORMAT_TRANSFORM:-11,BROTLI_DECODER_ERROR_FORMAT_DICTIONARY:-12,BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS:-13,BROTLI_DECODER_ERROR_FORMAT_PADDING_1:-14,BROTLI_DECODER_ERROR_FORMAT_PADDING_2:-15,BROTLI_DECODER_ERROR_FORMAT_DISTANCE:-16,BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET:-19,BROTLI_DECODER_ERROR_INVALID_ARGUMENTS:-20,BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES:-21,BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS:-22,BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP:-25,BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1:-26,BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2:-27,BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES:-30,BROTLI_DECODER_ERROR_UNREACHABLE:-31},i))},7547:(t,e,s)=>{"use strict";const i=s(9491);const n=s(4300).Buffer;const r=s(9796);const o=e.constants=s(8319);const h=s(4591);const l=n.concat;const a=Symbol("_superWrite");class ZlibError extends Error{constructor(t){super("zlib: "+t.message);this.code=t.code;this.errno=t.errno;if(!this.code)this.code="ZLIB_ERROR";this.message="zlib: "+t.message;Error.captureStackTrace(this,this.constructor)}get name(){return"ZlibError"}}const c=Symbol("opts");const u=Symbol("flushFlag");const f=Symbol("finishFlushFlag");const d=Symbol("fullFlushFlag");const p=Symbol("handle");const m=Symbol("onError");const y=Symbol("sawError");const b=Symbol("level");const w=Symbol("strategy");const _=Symbol("ended");const E=Symbol("_defaultFullFlush");class ZlibBase extends h{constructor(t,e){if(!t||typeof t!=="object")throw new TypeError("invalid options for ZlibBase constructor");super(t);this[y]=false;this[_]=false;this[c]=t;this[u]=t.flush;this[f]=t.finishFlush;try{this[p]=new r[e](t)}catch(t){throw new ZlibError(t)}this[m]=t=>{if(this[y])return;this[y]=true;this.close();this.emit("error",t)};this[p].on("error",(t=>this[m](new ZlibError(t))));this.once("end",(()=>this.close))}close(){if(this[p]){this[p].close();this[p]=null;this.emit("close")}}reset(){if(!this[y]){i(this[p],"zlib binding closed");return this[p].reset()}}flush(t){if(this.ended)return;if(typeof t!=="number")t=this[d];this.write(Object.assign(n.alloc(0),{[u]:t}))}end(t,e,s){if(t)this.write(t,e);this.flush(this[f]);this[_]=true;return super.end(null,null,s)}get ended(){return this[_]}write(t,e,s){if(typeof e==="function")s=e,e="utf8";if(typeof t==="string")t=n.from(t,e);if(this[y])return;i(this[p],"zlib binding closed");const r=this[p]._handle;const o=r.close;r.close=()=>{};const h=this[p].close;this[p].close=()=>{};n.concat=t=>t;let c;try{const e=typeof t[u]==="number"?t[u]:this[u];c=this[p]._processChunk(t,e);n.concat=l}catch(t){n.concat=l;this[m](new ZlibError(t))}finally{if(this[p]){this[p]._handle=r;r.close=o;this[p].close=h;this[p].removeAllListeners("error")}}if(this[p])this[p].on("error",(t=>this[m](new ZlibError(t))));let f;if(c){if(Array.isArray(c)&&c.length>0){f=this[a](n.from(c[0]));for(let t=1;t{this.flush(t);e()};try{this[p].params(t,e)}finally{this[p].flush=s}if(this[p]){this[b]=t;this[w]=e}}}}class Deflate extends Zlib{constructor(t){super(t,"Deflate")}}class Inflate extends Zlib{constructor(t){super(t,"Inflate")}}const g=Symbol("_portable");class Gzip extends Zlib{constructor(t){super(t,"Gzip");this[g]=t&&!!t.portable}[a](t){if(!this[g])return super[a](t);this[g]=false;t[9]=255;return super[a](t)}}class Gunzip extends Zlib{constructor(t){super(t,"Gunzip")}}class DeflateRaw extends Zlib{constructor(t){super(t,"DeflateRaw")}}class InflateRaw extends Zlib{constructor(t){super(t,"InflateRaw")}}class Unzip extends Zlib{constructor(t){super(t,"Unzip")}}class Brotli extends ZlibBase{constructor(t,e){t=t||{};t.flush=t.flush||o.BROTLI_OPERATION_PROCESS;t.finishFlush=t.finishFlush||o.BROTLI_OPERATION_FINISH;super(t,e);this[d]=o.BROTLI_OPERATION_FLUSH}}class BrotliCompress extends Brotli{constructor(t){super(t,"BrotliCompress")}}class BrotliDecompress extends Brotli{constructor(t){super(t,"BrotliDecompress")}}e.Deflate=Deflate;e.Inflate=Inflate;e.Gzip=Gzip;e.Gunzip=Gunzip;e.DeflateRaw=DeflateRaw;e.InflateRaw=InflateRaw;e.Unzip=Unzip;if(typeof r.BrotliCompress==="function"){e.BrotliCompress=BrotliCompress;e.BrotliDecompress=BrotliDecompress}else{e.BrotliCompress=e.BrotliDecompress=class{constructor(){throw new Error("Brotli is not supported in this version of Node.js")}}}},1389:t=>{"use strict";t.exports=function(t){t.prototype[Symbol.iterator]=function*(){for(let t=this.head;t;t=t.next){yield t.value}}}},4461:(t,e,s)=>{"use strict";t.exports=Yallist;Yallist.Node=Node;Yallist.create=Yallist;function Yallist(t){var e=this;if(!(e instanceof Yallist)){e=new Yallist}e.tail=null;e.head=null;e.length=0;if(t&&typeof t.forEach==="function"){t.forEach((function(t){e.push(t)}))}else if(arguments.length>0){for(var s=0,i=arguments.length;s1){s=e}else if(this.head){i=this.head.next;s=this.head.value}else{throw new TypeError("Reduce of empty list with no initial value")}for(var n=0;i!==null;n++){s=t(s,i.value,n);i=i.next}return s};Yallist.prototype.reduceReverse=function(t,e){var s;var i=this.tail;if(arguments.length>1){s=e}else if(this.tail){i=this.tail.prev;s=this.tail.value}else{throw new TypeError("Reduce of empty list with no initial value")}for(var n=this.length-1;i!==null;n--){s=t(s,i.value,n);i=i.prev}return s};Yallist.prototype.toArray=function(){var t=new Array(this.length);for(var e=0,s=this.head;s!==null;e++){t[e]=s.value;s=s.next}return t};Yallist.prototype.toArrayReverse=function(){var t=new Array(this.length);for(var e=0,s=this.tail;s!==null;e++){t[e]=s.value;s=s.prev}return t};Yallist.prototype.slice=function(t,e){e=e||this.length;if(e<0){e+=this.length}t=t||0;if(t<0){t+=this.length}var s=new Yallist;if(ethis.length){e=this.length}for(var i=0,n=this.head;n!==null&&ithis.length){e=this.length}for(var i=this.length,n=this.tail;n!==null&&i>e;i--){n=n.prev}for(;n!==null&&i>t;i--,n=n.prev){s.push(n.value)}return s};Yallist.prototype.splice=function(t,e,...s){if(t>this.length){t=this.length-1}if(t<0){t=this.length+t}for(var i=0,n=this.head;n!==null&&i{const i=s(334);const n=s(2339);const{mkdirpNative:r,mkdirpNativeSync:o}=s(2302);const{mkdirpManual:h,mkdirpManualSync:l}=s(9435);const{useNative:a,useNativeSync:c}=s(416);const mkdirp=(t,e)=>{t=n(t);e=i(e);return a(e)?r(t,e):h(t,e)};const mkdirpSync=(t,e)=>{t=n(t);e=i(e);return c(e)?o(t,e):l(t,e)};mkdirp.sync=mkdirpSync;mkdirp.native=(t,e)=>r(n(t),i(e));mkdirp.manual=(t,e)=>h(n(t),i(e));mkdirp.nativeSync=(t,e)=>o(n(t),i(e));mkdirp.manualSync=(t,e)=>l(n(t),i(e));t.exports=mkdirp},3423:(t,e,s)=>{const{dirname:i}=s(1017);const findMade=(t,e,s=undefined)=>{if(s===e)return Promise.resolve();return t.statAsync(e).then((t=>t.isDirectory()?s:undefined),(s=>s.code==="ENOENT"?findMade(t,i(e),e):undefined))};const findMadeSync=(t,e,s=undefined)=>{if(s===e)return undefined;try{return t.statSync(e).isDirectory()?s:undefined}catch(s){return s.code==="ENOENT"?findMadeSync(t,i(e),e):undefined}};t.exports={findMade:findMade,findMadeSync:findMadeSync}},9435:(t,e,s)=>{const{dirname:i}=s(1017);const mkdirpManual=(t,e,s)=>{e.recursive=false;const n=i(t);if(n===t){return e.mkdirAsync(t,e).catch((t=>{if(t.code!=="EISDIR")throw t}))}return e.mkdirAsync(t,e).then((()=>s||t),(i=>{if(i.code==="ENOENT")return mkdirpManual(n,e).then((s=>mkdirpManual(t,e,s)));if(i.code!=="EEXIST"&&i.code!=="EROFS")throw i;return e.statAsync(t).then((t=>{if(t.isDirectory())return s;else throw i}),(()=>{throw i}))}))};const mkdirpManualSync=(t,e,s)=>{const n=i(t);e.recursive=false;if(n===t){try{return e.mkdirSync(t,e)}catch(t){if(t.code!=="EISDIR")throw t;else return}}try{e.mkdirSync(t,e);return s||t}catch(i){if(i.code==="ENOENT")return mkdirpManualSync(t,e,mkdirpManualSync(n,e,s));if(i.code!=="EEXIST"&&i.code!=="EROFS")throw i;try{if(!e.statSync(t).isDirectory())throw i}catch(t){throw i}}};t.exports={mkdirpManual:mkdirpManual,mkdirpManualSync:mkdirpManualSync}},2302:(t,e,s)=>{const{dirname:i}=s(1017);const{findMade:n,findMadeSync:r}=s(3423);const{mkdirpManual:o,mkdirpManualSync:h}=s(9435);const mkdirpNative=(t,e)=>{e.recursive=true;const s=i(t);if(s===t)return e.mkdirAsync(t,e);return n(e,t).then((s=>e.mkdirAsync(t,e).then((()=>s)).catch((s=>{if(s.code==="ENOENT")return o(t,e);else throw s}))))};const mkdirpNativeSync=(t,e)=>{e.recursive=true;const s=i(t);if(s===t)return e.mkdirSync(t,e);const n=r(e,t);try{e.mkdirSync(t,e);return n}catch(s){if(s.code==="ENOENT")return h(t,e);else throw s}};t.exports={mkdirpNative:mkdirpNative,mkdirpNativeSync:mkdirpNativeSync}},334:(t,e,s)=>{const{promisify:i}=s(3837);const n=s(7147);const optsArg=t=>{if(!t)t={mode:511,fs:n};else if(typeof t==="object")t={mode:511,fs:n,...t};else if(typeof t==="number")t={mode:t,fs:n};else if(typeof t==="string")t={mode:parseInt(t,8),fs:n};else throw new TypeError("invalid options argument");t.mkdir=t.mkdir||t.fs.mkdir||n.mkdir;t.mkdirAsync=i(t.mkdir);t.stat=t.stat||t.fs.stat||n.stat;t.statAsync=i(t.stat);t.statSync=t.statSync||t.fs.statSync||n.statSync;t.mkdirSync=t.mkdirSync||t.fs.mkdirSync||n.mkdirSync;return t};t.exports=optsArg},2339:(t,e,s)=>{const i=process.env.__TESTING_MKDIRP_PLATFORM__||process.platform;const{resolve:n,parse:r}=s(1017);const pathArg=t=>{if(/\0/.test(t)){throw Object.assign(new TypeError("path must be a string without null bytes"),{path:t,code:"ERR_INVALID_ARG_VALUE"})}t=n(t);if(i==="win32"){const e=/[*|"<>?:]/;const{root:s}=r(t);if(e.test(t.substr(s.length))){throw Object.assign(new Error("Illegal characters in path."),{path:t,code:"EINVAL"})}}return t};t.exports=pathArg},416:(t,e,s)=>{const i=s(7147);const n=process.env.__TESTING_MKDIRP_NODE_VERSION__||process.version;const r=n.replace(/^v/,"").split(".");const o=+r[0]>10||+r[0]===10&&+r[1]>=12;const h=!o?()=>false:t=>t.mkdir===i.mkdir;const l=!o?()=>false:t=>t.mkdirSync===i.mkdirSync;t.exports={useNative:h,useNativeSync:l}},6036:(t,e,s)=>{"use strict";const i=s(6924);const n=s(9698);const r=s(3597);const o=s(9650);const h=s(1017);t.exports=(t,e,s)=>{if(typeof e==="function")s=e;if(Array.isArray(t))e=t,t={};if(!e||!Array.isArray(e)||!e.length)throw new TypeError("no files or directories specified");e=Array.from(e);const n=i(t);if(n.sync&&typeof s==="function")throw new TypeError("callback not supported for sync tar functions");if(!n.file&&typeof s==="function")throw new TypeError("callback only supported with file option");return n.file&&n.sync?createFileSync(n,e):n.file?createFile(n,e,s):n.sync?createSync(n,e):create(n,e)};const createFileSync=(t,e)=>{const s=new n.Sync(t);const i=new r.WriteStreamSync(t.file,{mode:t.mode||438});s.pipe(i);addFilesSync(s,e)};const createFile=(t,e,s)=>{const i=new n(t);const o=new r.WriteStream(t.file,{mode:t.mode||438});i.pipe(o);const h=new Promise(((t,e)=>{o.on("error",e);o.on("close",t);i.on("error",e)}));addFilesAsync(i,e);return s?h.then(s,s):h};const addFilesSync=(t,e)=>{e.forEach((e=>{if(e.charAt(0)==="@"){o({file:h.resolve(t.cwd,e.substr(1)),sync:true,noResume:true,onentry:e=>t.add(e)})}else t.add(e)}));t.end()};const addFilesAsync=(t,e)=>{while(e.length){const s=e.shift();if(s.charAt(0)==="@"){return o({file:h.resolve(t.cwd,s.substr(1)),noResume:true,onentry:e=>t.add(e)}).then((s=>addFilesAsync(t,e)))}else t.add(s)}t.end()};const createSync=(t,e)=>{const s=new n.Sync(t);addFilesSync(s,e);return s};const create=(t,e)=>{const s=new n(t);addFilesAsync(s,e);return s}},7171:(t,e,s)=>{"use strict";const i=s(6924);const n=s(7932);const r=s(7147);const o=s(3597);const h=s(1017);const l=s(4387);t.exports=(t,e,s)=>{if(typeof t==="function")s=t,e=null,t={};else if(Array.isArray(t))e=t,t={};if(typeof e==="function")s=e,e=null;if(!e)e=[];else e=Array.from(e);const n=i(t);if(n.sync&&typeof s==="function")throw new TypeError("callback not supported for sync tar functions");if(!n.file&&typeof s==="function")throw new TypeError("callback only supported with file option");if(e.length)filesFilter(n,e);return n.file&&n.sync?extractFileSync(n):n.file?extractFile(n,s):n.sync?extractSync(n):extract(n)};const filesFilter=(t,e)=>{const s=new Map(e.map((t=>[l(t),true])));const i=t.filter;const mapHas=(t,e)=>{const i=e||h.parse(t).root||".";const n=t===i?false:s.has(t)?s.get(t):mapHas(h.dirname(t),i);s.set(t,n);return n};t.filter=i?(t,e)=>i(t,e)&&mapHas(l(t)):t=>mapHas(l(t))};const extractFileSync=t=>{const e=new n.Sync(t);const s=t.file;const i=r.statSync(s);const h=t.maxReadSize||16*1024*1024;const l=new o.ReadStreamSync(s,{readSize:h,size:i.size});l.pipe(e)};const extractFile=(t,e)=>{const s=new n(t);const i=t.maxReadSize||16*1024*1024;const h=t.file;const l=new Promise(((t,e)=>{s.on("error",e);s.on("close",t);r.stat(h,((t,n)=>{if(t)e(t);else{const t=new o.ReadStream(h,{readSize:i,size:n.size});t.on("error",e);t.pipe(s)}}))}));return e?l.then(e,e):l};const extractSync=t=>new n.Sync(t);const extract=t=>new n(t)},2142:(t,e,s)=>{const i=process.env.__FAKE_PLATFORM__||process.platform;const n=i==="win32";const r=global.__FAKE_TESTING_FS__||s(7147);const{O_CREAT:o,O_TRUNC:h,O_WRONLY:l,UV_FS_O_FILEMAP:a=0}=r.constants;const c=n&&!!a;const u=512*1024;const f=a|h|o|l;t.exports=!c?()=>"w":t=>t{"use strict";const i=s(8318);const n=s(1017).posix;const r=s(9038);const o=Symbol("slurp");const h=Symbol("type");class Header{constructor(t,e,s,i){this.cksumValid=false;this.needPax=false;this.nullBlock=false;this.block=null;this.path=null;this.mode=null;this.uid=null;this.gid=null;this.size=null;this.mtime=null;this.cksum=null;this[h]="0";this.linkpath=null;this.uname=null;this.gname=null;this.devmaj=0;this.devmin=0;this.atime=null;this.ctime=null;if(Buffer.isBuffer(t))this.decode(t,e||0,s,i);else if(t)this.set(t)}decode(t,e,s,i){if(!e)e=0;if(!t||!(t.length>=e+512))throw new Error("need 512 bytes for header");this.path=decString(t,e,100);this.mode=decNumber(t,e+100,8);this.uid=decNumber(t,e+108,8);this.gid=decNumber(t,e+116,8);this.size=decNumber(t,e+124,12);this.mtime=decDate(t,e+136,12);this.cksum=decNumber(t,e+148,12);this[o](s);this[o](i,true);this[h]=decString(t,e+156,1);if(this[h]==="")this[h]="0";if(this[h]==="0"&&this.path.substr(-1)==="/")this[h]="5";if(this[h]==="5")this.size=0;this.linkpath=decString(t,e+157,100);if(t.slice(e+257,e+265).toString()==="ustar\x0000"){this.uname=decString(t,e+265,32);this.gname=decString(t,e+297,32);this.devmaj=decNumber(t,e+329,8);this.devmin=decNumber(t,e+337,8);if(t[e+475]!==0){const s=decString(t,e+345,155);this.path=s+"/"+this.path}else{const s=decString(t,e+345,130);if(s)this.path=s+"/"+this.path;this.atime=decDate(t,e+476,12);this.ctime=decDate(t,e+488,12)}}let n=8*32;for(let s=e;s=e+512))throw new Error("need 512 bytes for header");const s=this.ctime||this.atime?130:155;const i=splitPrefix(this.path||"",s);const n=i[0];const r=i[1];this.needPax=i[2];this.needPax=encString(t,e,100,n)||this.needPax;this.needPax=encNumber(t,e+100,8,this.mode)||this.needPax;this.needPax=encNumber(t,e+108,8,this.uid)||this.needPax;this.needPax=encNumber(t,e+116,8,this.gid)||this.needPax;this.needPax=encNumber(t,e+124,12,this.size)||this.needPax;this.needPax=encDate(t,e+136,12,this.mtime)||this.needPax;t[e+156]=this[h].charCodeAt(0);this.needPax=encString(t,e+157,100,this.linkpath)||this.needPax;t.write("ustar\x0000",e+257,8);this.needPax=encString(t,e+265,32,this.uname)||this.needPax;this.needPax=encString(t,e+297,32,this.gname)||this.needPax;this.needPax=encNumber(t,e+329,8,this.devmaj)||this.needPax;this.needPax=encNumber(t,e+337,8,this.devmin)||this.needPax;this.needPax=encString(t,e+345,s,r)||this.needPax;if(t[e+475]!==0)this.needPax=encString(t,e+345,155,r)||this.needPax;else{this.needPax=encString(t,e+345,130,r)||this.needPax;this.needPax=encDate(t,e+476,12,this.atime)||this.needPax;this.needPax=encDate(t,e+488,12,this.ctime)||this.needPax}let o=8*32;for(let s=e;s{const s=100;let i=t;let r="";let o;const h=n.parse(t).root||".";if(Buffer.byteLength(i)s&&Buffer.byteLength(r)<=e)o=[i.substr(0,s-1),r,true];else{i=n.join(n.basename(r),i);r=n.dirname(r)}}while(r!==h&&!o);if(!o)o=[t.substr(0,s-1),"",true]}return o};const decString=(t,e,s)=>t.slice(e,e+s).toString("utf8").replace(/\0.*/,"");const decDate=(t,e,s)=>numToDate(decNumber(t,e,s));const numToDate=t=>t===null?null:new Date(t*1e3);const decNumber=(t,e,s)=>t[e]&128?r.parse(t.slice(e,e+s)):decSmallNumber(t,e,s);const nanNull=t=>isNaN(t)?null:t;const decSmallNumber=(t,e,s)=>nanNull(parseInt(t.slice(e,e+s).toString("utf8").replace(/\0.*$/,"").trim(),8));const l={12:8589934591,8:2097151};const encNumber=(t,e,s,i)=>i===null?false:i>l[s]||i<0?(r.encode(i,t.slice(e,e+s)),true):(encSmallNumber(t,e,s,i),false);const encSmallNumber=(t,e,s,i)=>t.write(octalString(i,s),e,s,"ascii");const octalString=(t,e)=>padOctal(Math.floor(t).toString(8),e);const padOctal=(t,e)=>(t.length===e-1?t:new Array(e-t.length-1).join("0")+t+" ")+"\0";const encDate=(t,e,s,i)=>i===null?false:encNumber(t,e,s,i.getTime()/1e3);const a=new Array(156).join("\0");const encString=(t,e,s,i)=>i===null?false:(t.write(i+a,e,s,"utf8"),i.length!==Buffer.byteLength(i)||i.length>s);t.exports=Header},6924:t=>{"use strict";const e=new Map([["C","cwd"],["f","file"],["z","gzip"],["P","preservePaths"],["U","unlink"],["strip-components","strip"],["stripComponents","strip"],["keep-newer","newer"],["keepNewer","newer"],["keep-newer-files","newer"],["keepNewerFiles","newer"],["k","keep"],["keep-existing","keep"],["keepExisting","keep"],["m","noMtime"],["no-mtime","noMtime"],["p","preserveOwner"],["L","follow"],["h","follow"]]);t.exports=t=>t?Object.keys(t).map((s=>[e.has(s)?e.get(s):s,t[s]])).reduce(((t,e)=>(t[e[0]]=e[1],t)),Object.create(null)):{}},9038:t=>{"use strict";const encode=(t,e)=>{if(!Number.isSafeInteger(t))throw Error("cannot encode number outside of javascript safe integer range");else if(t<0)encodeNegative(t,e);else encodePositive(t,e);return e};const encodePositive=(t,e)=>{e[0]=128;for(var s=e.length;s>1;s--){e[s-1]=t&255;t=Math.floor(t/256)}};const encodeNegative=(t,e)=>{e[0]=255;var s=false;t=t*-1;for(var i=e.length;i>1;i--){var n=t&255;t=Math.floor(t/256);if(s)e[i-1]=onesComp(n);else if(n===0)e[i-1]=0;else{s=true;e[i-1]=twosComp(n)}}};const parse=t=>{const e=t[0];const s=e===128?pos(t.slice(1,t.length)):e===255?twos(t):null;if(s===null)throw Error("invalid base256 encoding");if(!Number.isSafeInteger(s))throw Error("parsed number outside of javascript safe integer range");return s};const twos=t=>{var e=t.length;var s=0;var i=false;for(var n=e-1;n>-1;n--){var r=t[n];var o;if(i)o=onesComp(r);else if(r===0)o=r;else{i=true;o=twosComp(r)}if(o!==0)s-=o*Math.pow(256,e-n-1)}return s};const pos=t=>{var e=t.length;var s=0;for(var i=e-1;i>-1;i--){var n=t[i];if(n!==0)s+=n*Math.pow(256,e-i-1)}return s};const onesComp=t=>(255^t)&255;const twosComp=t=>(255^t)+1&255;t.exports={encode:encode,parse:parse}},9650:(t,e,s)=>{"use strict";const i=s(6924);const n=s(2801);const r=s(7147);const o=s(3597);const h=s(1017);const l=s(4387);t.exports=(t,e,s)=>{if(typeof t==="function")s=t,e=null,t={};else if(Array.isArray(t))e=t,t={};if(typeof e==="function")s=e,e=null;if(!e)e=[];else e=Array.from(e);const n=i(t);if(n.sync&&typeof s==="function")throw new TypeError("callback not supported for sync tar functions");if(!n.file&&typeof s==="function")throw new TypeError("callback only supported with file option");if(e.length)filesFilter(n,e);if(!n.noResume)onentryFunction(n);return n.file&&n.sync?listFileSync(n):n.file?listFile(n,s):list(n)};const onentryFunction=t=>{const e=t.onentry;t.onentry=e?t=>{e(t);t.resume()}:t=>t.resume()};const filesFilter=(t,e)=>{const s=new Map(e.map((t=>[l(t),true])));const i=t.filter;const mapHas=(t,e)=>{const i=e||h.parse(t).root||".";const n=t===i?false:s.has(t)?s.get(t):mapHas(h.dirname(t),i);s.set(t,n);return n};t.filter=i?(t,e)=>i(t,e)&&mapHas(l(t)):t=>mapHas(l(t))};const listFileSync=t=>{const e=list(t);const s=t.file;let i=true;let n;try{const o=r.statSync(s);const h=t.maxReadSize||16*1024*1024;if(o.size{const s=new n(t);const i=t.maxReadSize||16*1024*1024;const h=t.file;const l=new Promise(((t,e)=>{s.on("error",e);s.on("end",t);r.stat(h,((t,n)=>{if(t)e(t);else{const t=new o.ReadStream(h,{readSize:i,size:n.size});t.on("error",e);t.pipe(s)}}))}));return e?l.then(e,e):l};const list=t=>new n(t)},3967:(t,e,s)=>{"use strict";const i=s(8828);const n=s(7147);const r=s(1017);const o=s(5952);const h=s(9408);class SymlinkError extends Error{constructor(t,e){super("Cannot extract through symbolic link");this.path=e;this.symlink=t}get name(){return"SylinkError"}}class CwdError extends Error{constructor(t,e){super(e+": Cannot cd into '"+t+"'");this.path=t;this.code=e}get name(){return"CwdError"}}const cGet=(t,e)=>t.get(h(e));const cSet=(t,e,s)=>t.set(h(e),s);const checkCwd=(t,e)=>{n.stat(t,((s,i)=>{if(s||!i.isDirectory())s=new CwdError(t,s&&s.code||"ENOTDIR");e(s)}))};t.exports=(t,e,s)=>{t=h(t);const l=e.umask;const a=e.mode|448;const c=(a&l)!==0;const u=e.uid;const f=e.gid;const d=typeof u==="number"&&typeof f==="number"&&(u!==e.processUid||f!==e.processGid);const p=e.preserve;const m=e.unlink;const y=e.cache;const b=h(e.cwd);const done=(e,i)=>{if(e)s(e);else{cSet(y,t,true);if(i&&d)o(i,u,f,(t=>done(t)));else if(c)n.chmod(t,a,s);else s()}};if(y&&cGet(y,t)===true)return done();if(t===b)return checkCwd(t,done);if(p)return i(t,{mode:a}).then((t=>done(null,t)),done);const w=h(r.relative(b,t));const _=w.split("/");mkdir_(b,_,a,y,m,b,null,done)};const mkdir_=(t,e,s,i,o,l,a,c)=>{if(!e.length)return c(null,a);const u=e.shift();const f=h(r.resolve(t+"/"+u));if(cGet(i,f))return mkdir_(f,e,s,i,o,l,a,c);n.mkdir(f,s,onmkdir(f,e,s,i,o,l,a,c))};const onmkdir=(t,e,s,i,r,o,l,a)=>c=>{if(c){n.lstat(t,((u,f)=>{if(u){u.path=u.path&&h(u.path);a(u)}else if(f.isDirectory())mkdir_(t,e,s,i,r,o,l,a);else if(r){n.unlink(t,(h=>{if(h)return a(h);n.mkdir(t,s,onmkdir(t,e,s,i,r,o,l,a))}))}else if(f.isSymbolicLink())return a(new SymlinkError(t,t+"/"+e.join("/")));else a(c)}))}else{l=l||t;mkdir_(t,e,s,i,r,o,l,a)}};const checkCwdSync=t=>{let e=false;let s="ENOTDIR";try{e=n.statSync(t).isDirectory()}catch(t){s=t.code}finally{if(!e)throw new CwdError(t,s)}};t.exports.sync=(t,e)=>{t=h(t);const s=e.umask;const l=e.mode|448;const a=(l&s)!==0;const c=e.uid;const u=e.gid;const f=typeof c==="number"&&typeof u==="number"&&(c!==e.processUid||u!==e.processGid);const d=e.preserve;const p=e.unlink;const m=e.cache;const y=h(e.cwd);const done=e=>{cSet(m,t,true);if(e&&f)o.sync(e,c,u);if(a)n.chmodSync(t,l)};if(m&&cGet(m,t)===true)return done();if(t===y){checkCwdSync(y);return done()}if(d)return done(i.sync(t,l));const b=h(r.relative(y,t));const w=b.split("/");let _=null;for(let t=w.shift(),e=y;t&&(e+="/"+t);t=w.shift()){e=h(r.resolve(e));if(cGet(m,e))continue;try{n.mkdirSync(e,l);_=_||e;cSet(m,e,true)}catch(t){const s=n.lstatSync(e);if(s.isDirectory()){cSet(m,e,true);continue}else if(p){n.unlinkSync(e);n.mkdirSync(e,l);_=_||e;cSet(m,e,true);continue}else if(s.isSymbolicLink())return new SymlinkError(e,e+"/"+w.join("/"))}}return done(_)}},4770:t=>{"use strict";t.exports=(t,e,s)=>{t&=4095;if(s)t=(t|384)&~18;if(e){if(t&256)t|=64;if(t&32)t|=8;if(t&4)t|=1}return t}},5042:t=>{const e=Object.create(null);const{hasOwnProperty:s}=Object.prototype;t.exports=t=>{if(!s.call(e,t))e[t]=t.normalize("NFKD");return e[t]}},9408:t=>{const e=process.env.TESTING_TAR_FAKE_PLATFORM||process.platform;t.exports=e!=="win32"?t=>t:t=>t&&t.replace(/\\/g,"/")},9698:(t,e,s)=>{"use strict";class PackJob{constructor(t,e){this.path=t||"./";this.absolute=e;this.entry=null;this.stat=null;this.readdir=null;this.pending=false;this.ignore=false;this.piped=false}}const i=s(4591);const n=s(7547);const r=s(2946);const o=s(9144);const h=o.Sync;const l=o.Tar;const a=s(1071);const c=Buffer.alloc(1024);const u=Symbol("onStat");const f=Symbol("ended");const d=Symbol("queue");const p=Symbol("current");const m=Symbol("process");const y=Symbol("processing");const b=Symbol("processJob");const w=Symbol("jobs");const _=Symbol("jobDone");const E=Symbol("addFSEntry");const g=Symbol("addTarEntry");const S=Symbol("stat");const R=Symbol("readdir");const v=Symbol("onreaddir");const O=Symbol("pipe");const k=Symbol("entry");const T=Symbol("entryOpt");const x=Symbol("writeEntryClass");const L=Symbol("write");const A=Symbol("ondrain");const N=s(7147);const I=s(1017);const D=s(430);const B=s(9408);const C=D(class Pack extends i{constructor(t){super(t);t=t||Object.create(null);this.opt=t;this.file=t.file||"";this.cwd=t.cwd||process.cwd();this.maxReadSize=t.maxReadSize;this.preservePaths=!!t.preservePaths;this.strict=!!t.strict;this.noPax=!!t.noPax;this.prefix=B(t.prefix||"");this.linkCache=t.linkCache||new Map;this.statCache=t.statCache||new Map;this.readdirCache=t.readdirCache||new Map;this[x]=o;if(typeof t.onwarn==="function")this.on("warn",t.onwarn);this.portable=!!t.portable;this.zip=null;if(t.gzip){if(typeof t.gzip!=="object")t.gzip={};if(this.portable)t.gzip.portable=true;this.zip=new n.Gzip(t.gzip);this.zip.on("data",(t=>super.write(t)));this.zip.on("end",(t=>super.end()));this.zip.on("drain",(t=>this[A]()));this.on("resume",(t=>this.zip.resume()))}else this.on("drain",this[A]);this.noDirRecurse=!!t.noDirRecurse;this.follow=!!t.follow;this.noMtime=!!t.noMtime;this.mtime=t.mtime||null;this.filter=typeof t.filter==="function"?t.filter:t=>true;this[d]=new a;this[w]=0;this.jobs=+t.jobs||4;this[y]=false;this[f]=false}[L](t){return super.write(t)}add(t){this.write(t);return this}end(t){if(t)this.write(t);this[f]=true;this[m]();return this}write(t){if(this[f])throw new Error("write after end");if(t instanceof r)this[g](t);else this[E](t);return this.flowing}[g](t){const e=B(I.resolve(this.cwd,t.path));if(!this.filter(t.path,t))t.resume();else{const s=new PackJob(t.path,e,false);s.entry=new l(t,this[T](s));s.entry.on("end",(t=>this[_](s)));this[w]+=1;this[d].push(s)}this[m]()}[E](t){const e=B(I.resolve(this.cwd,t));this[d].push(new PackJob(t,e));this[m]()}[S](t){t.pending=true;this[w]+=1;const e=this.follow?"stat":"lstat";N[e](t.absolute,((e,s)=>{t.pending=false;this[w]-=1;if(e)this.emit("error",e);else this[u](t,s)}))}[u](t,e){this.statCache.set(t.absolute,e);t.stat=e;if(!this.filter(t.path,e))t.ignore=true;this[m]()}[R](t){t.pending=true;this[w]+=1;N.readdir(t.absolute,((e,s)=>{t.pending=false;this[w]-=1;if(e)return this.emit("error",e);this[v](t,s)}))}[v](t,e){this.readdirCache.set(t.absolute,e);t.readdir=e;this[m]()}[m](){if(this[y])return;this[y]=true;for(let t=this[d].head;t!==null&&this[w]this.warn(t,e,s),noPax:this.noPax,cwd:this.cwd,absolute:t.absolute,preservePaths:this.preservePaths,maxReadSize:this.maxReadSize,strict:this.strict,portable:this.portable,linkCache:this.linkCache,statCache:this.statCache,noMtime:this.noMtime,mtime:this.mtime,prefix:this.prefix}}[k](t){this[w]+=1;try{return new this[x](t.path,this[T](t)).on("end",(()=>this[_](t))).on("error",(t=>this.emit("error",t)))}catch(t){this.emit("error",t)}}[A](){if(this[p]&&this[p].entry)this[p].entry.resume()}[O](t){t.piped=true;if(t.readdir){t.readdir.forEach((e=>{const s=t.path;const i=s==="./"?"":s.replace(/\/*$/,"/");this[E](i+e)}))}const e=t.entry;const s=this.zip;if(s){e.on("data",(t=>{if(!s.write(t))e.pause()}))}else{e.on("data",(t=>{if(!super.write(t))e.pause()}))}}pause(){if(this.zip)this.zip.pause();return super.pause()}});class PackSync extends C{constructor(t){super(t);this[x]=h}pause(){}resume(){}[S](t){const e=this.follow?"statSync":"lstatSync";this[u](t,N[e](t.absolute))}[R](t,e){this[v](t,N.readdirSync(t.absolute))}[O](t){const e=t.entry;const s=this.zip;if(t.readdir){t.readdir.forEach((e=>{const s=t.path;const i=s==="./"?"":s.replace(/\/*$/,"/");this[E](i+e)}))}if(s){e.on("data",(t=>{s.write(t)}))}else{e.on("data",(t=>{super[L](t)}))}}}C.Sync=PackSync;t.exports=C},2801:(t,e,s)=>{"use strict";const i=s(430);const n=s(3970);const r=s(2361);const o=s(1071);const h=1024*1024;const l=s(2946);const a=s(4065);const c=s(7547);const u=Buffer.from([31,139]);const f=Symbol("state");const d=Symbol("writeEntry");const p=Symbol("readEntry");const m=Symbol("nextEntry");const y=Symbol("processEntry");const b=Symbol("extendedHeader");const w=Symbol("globalExtendedHeader");const _=Symbol("meta");const E=Symbol("emitMeta");const g=Symbol("buffer");const S=Symbol("queue");const R=Symbol("ended");const v=Symbol("emittedEnd");const O=Symbol("emit");const k=Symbol("unzip");const T=Symbol("consumeChunk");const x=Symbol("consumeChunkSub");const L=Symbol("consumeBody");const A=Symbol("consumeMeta");const N=Symbol("consumeHeader");const I=Symbol("consuming");const D=Symbol("bufferConcat");const B=Symbol("maybeEnd");const C=Symbol("writing");const M=Symbol("aborted");const F=Symbol("onDone");const P=Symbol("sawValidEntry");const z=Symbol("sawNullBlock");const U=Symbol("sawEOF");const noop=t=>true;t.exports=i(class Parser extends r{constructor(t){t=t||{};super(t);this.file=t.file||"";this[P]=null;this.on(F,(t=>{if(this[f]==="begin"||this[P]===false){this.warn("TAR_BAD_ARCHIVE","Unrecognized archive format")}}));if(t.ondone)this.on(F,t.ondone);else{this.on(F,(t=>{this.emit("prefinish");this.emit("finish");this.emit("end");this.emit("close")}))}this.strict=!!t.strict;this.maxMetaEntrySize=t.maxMetaEntrySize||h;this.filter=typeof t.filter==="function"?t.filter:noop;this.writable=true;this.readable=false;this[S]=new o;this[g]=null;this[p]=null;this[d]=null;this[f]="begin";this[_]="";this[b]=null;this[w]=null;this[R]=false;this[k]=null;this[M]=false;this[z]=false;this[U]=false;if(typeof t.onwarn==="function")this.on("warn",t.onwarn);if(typeof t.onentry==="function")this.on("entry",t.onentry)}[N](t,e){if(this[P]===null)this[P]=false;let s;try{s=new n(t,e,this[b],this[w])}catch(t){return this.warn("TAR_ENTRY_INVALID",t)}if(s.nullBlock){if(this[z]){this[U]=true;if(this[f]==="begin")this[f]="header";this[O]("eof")}else{this[z]=true;this[O]("nullBlock")}}else{this[z]=false;if(!s.cksumValid)this.warn("TAR_ENTRY_INVALID","checksum failure",{header:s});else if(!s.path)this.warn("TAR_ENTRY_INVALID","path is required",{header:s});else{const t=s.type;if(/^(Symbolic)?Link$/.test(t)&&!s.linkpath)this.warn("TAR_ENTRY_INVALID","linkpath required",{header:s});else if(!/^(Symbolic)?Link$/.test(t)&&s.linkpath)this.warn("TAR_ENTRY_INVALID","linkpath forbidden",{header:s});else{const t=this[d]=new l(s,this[b],this[w]);if(!this[P]){if(t.remain){const onend=()=>{if(!t.invalid)this[P]=true};t.on("end",onend)}else this[P]=true}if(t.meta){if(t.size>this.maxMetaEntrySize){t.ignore=true;this[O]("ignoredEntry",t);this[f]="ignore";t.resume()}else if(t.size>0){this[_]="";t.on("data",(t=>this[_]+=t));this[f]="meta"}}else{this[b]=null;t.ignore=t.ignore||!this.filter(t.path,t);if(t.ignore){this[O]("ignoredEntry",t);this[f]=t.remain?"ignore":"header";t.resume()}else{if(t.remain)this[f]="body";else{this[f]="header";t.end()}if(!this[p]){this[S].push(t);this[m]()}else this[S].push(t)}}}}}}[y](t){let e=true;if(!t){this[p]=null;e=false}else if(Array.isArray(t))this.emit.apply(this,t);else{this[p]=t;this.emit("entry",t);if(!t.emittedEnd){t.on("end",(t=>this[m]()));e=false}}return e}[m](){do{}while(this[y](this[S].shift()));if(!this[S].length){const t=this[p];const e=!t||t.flowing||t.size===t.remain;if(e){if(!this[C])this.emit("drain")}else t.once("drain",(t=>this.emit("drain")))}}[L](t,e){const s=this[d];const i=s.blockRemain;const n=i>=t.length&&e===0?t:t.slice(e,e+i);s.write(n);if(!s.blockRemain){this[f]="header";this[d]=null;s.end()}return n.length}[A](t,e){const s=this[d];const i=this[L](t,e);if(!this[d])this[E](s);return i}[O](t,e,s){if(!this[S].length&&!this[p])this.emit(t,e,s);else this[S].push([t,e,s])}[E](t){this[O]("meta",this[_]);switch(t.type){case"ExtendedHeader":case"OldExtendedHeader":this[b]=a.parse(this[_],this[b],false);break;case"GlobalExtendedHeader":this[w]=a.parse(this[_],this[w],true);break;case"NextFileHasLongPath":case"OldGnuLongPath":this[b]=this[b]||Object.create(null);this[b].path=this[_].replace(/\0.*/,"");break;case"NextFileHasLongLinkpath":this[b]=this[b]||Object.create(null);this[b].linkpath=this[_].replace(/\0.*/,"");break;default:throw new Error("unknown meta: "+t.type)}}abort(t){this[M]=true;this.emit("abort",t);this.warn("TAR_ABORT",t,{recoverable:false})}write(t){if(this[M])return;if(this[k]===null&&t){if(this[g]){t=Buffer.concat([this[g],t]);this[g]=null}if(t.lengththis[T](t)));this[k].on("error",(t=>this.abort(t)));this[k].on("end",(t=>{this[R]=true;this[T]()}));this[C]=true;const s=this[k][e?"end":"write"](t);this[C]=false;return s}}this[C]=true;if(this[k])this[k].write(t);else this[T](t);this[C]=false;const e=this[S].length?false:this[p]?this[p].flowing:true;if(!e&&!this[S].length)this[p].once("drain",(t=>this.emit("drain")));return e}[D](t){if(t&&!this[M])this[g]=this[g]?Buffer.concat([this[g],t]):t}[B](){if(this[R]&&!this[v]&&!this[M]&&!this[I]){this[v]=true;const t=this[d];if(t&&t.blockRemain){const e=this[g]?this[g].length:0;this.warn("TAR_BAD_ARCHIVE",`Truncated input (needed ${t.blockRemain} more bytes, only ${e} available)`,{entry:t});if(this[g])t.write(this[g]);t.end()}this[O](F)}}[T](t){if(this[I])this[D](t);else if(!t&&!this[g])this[B]();else{this[I]=true;if(this[g]){this[D](t);const e=this[g];this[g]=null;this[x](e)}else this[x](t);while(this[g]&&this[g].length>=512&&!this[M]&&!this[U]){const t=this[g];this[g]=null;this[x](t)}this[I]=false}if(!this[g]||this[R])this[B]()}[x](t){let e=0;const s=t.length;while(e+512<=s&&!this[M]&&!this[U]){switch(this[f]){case"begin":case"header":this[N](t,e);e+=512;break;case"ignore":case"body":e+=this[L](t,e);break;case"meta":e+=this[A](t,e);break;default:throw new Error("invalid state: "+this[f])}}if(e{const i=s(9491);const n=s(5042);const r=s(4387);const{join:o}=s(1017);const h=process.env.TESTING_TAR_FAKE_PLATFORM||process.platform;const l=h==="win32";t.exports=()=>{const t=new Map;const e=new Map;const getDirs=t=>{const e=t.split("/").slice(0,-1).reduce(((t,e)=>{if(t.length)e=o(t[t.length-1],e);t.push(e||"/");return t}),[]);return e};const s=new Set;const getQueues=s=>{const i=e.get(s);if(!i)throw new Error("function does not have any path reservations");return{paths:i.paths.map((e=>t.get(e))),dirs:[...i.dirs].map((e=>t.get(e)))}};const check=t=>{const{paths:e,dirs:s}=getQueues(t);return e.every((e=>e[0]===t))&&s.every((e=>e[0]instanceof Set&&e[0].has(t)))};const run=t=>{if(s.has(t)||!check(t))return false;s.add(t);t((()=>clear(t)));return true};const clear=n=>{if(!s.has(n))return false;const{paths:r,dirs:o}=e.get(n);const h=new Set;r.forEach((e=>{const s=t.get(e);i.equal(s[0],n);if(s.length===1)t.delete(e);else{s.shift();if(typeof s[0]==="function")h.add(s[0]);else s[0].forEach((t=>h.add(t)))}}));o.forEach((e=>{const s=t.get(e);i(s[0]instanceof Set);if(s[0].size===1&&s.length===1)t.delete(e);else if(s[0].size===1){s.shift();h.add(s[0])}else s[0].delete(n)}));s.delete(n);h.forEach((t=>run(t)));return true};const reserve=(s,i)=>{s=l?["win32 parallelization disabled"]:s.map((t=>n(r(o(t))).toLowerCase()));const h=new Set(s.map((t=>getDirs(t))).reduce(((t,e)=>t.concat(e))));e.set(i,{dirs:h,paths:s});s.forEach((e=>{const s=t.get(e);if(!s)t.set(e,[i]);else s.push(i)}));h.forEach((e=>{const s=t.get(e);if(!s)t.set(e,[new Set([i])]);else if(s[s.length-1]instanceof Set)s[s.length-1].add(i);else s.push(new Set([i]))}));return run(i)};return{check:check,reserve:reserve}}},4065:(t,e,s)=>{"use strict";const i=s(3970);const n=s(1017);class Pax{constructor(t,e){this.atime=t.atime||null;this.charset=t.charset||null;this.comment=t.comment||null;this.ctime=t.ctime||null;this.gid=t.gid||null;this.gname=t.gname||null;this.linkpath=t.linkpath||null;this.mtime=t.mtime||null;this.path=t.path||null;this.size=t.size||null;this.uid=t.uid||null;this.uname=t.uname||null;this.dev=t.dev||null;this.ino=t.ino||null;this.nlink=t.nlink||null;this.global=e||false}encode(){const t=this.encodeBody();if(t==="")return null;const e=Buffer.byteLength(t);const s=512*Math.ceil(1+e/512);const r=Buffer.allocUnsafe(s);for(let t=0;t<512;t++)r[t]=0;new i({path:("PaxHeader/"+n.basename(this.path)).slice(0,99),mode:this.mode||420,uid:this.uid||null,gid:this.gid||null,size:e,mtime:this.mtime||null,type:this.global?"GlobalExtendedHeader":"ExtendedHeader",linkpath:"",uname:this.uname||"",gname:this.gname||"",devmaj:0,devmin:0,atime:this.atime||null,ctime:this.ctime||null}).encode(r);r.write(t,512,e,"utf8");for(let t=e+512;t=Math.pow(10,n))n+=1;const r=n+i;return r+s}}Pax.parse=(t,e,s)=>new Pax(merge(parseKV(t),e),s);const merge=(t,e)=>e?Object.keys(t).reduce(((e,s)=>(e[s]=t[s],e)),e):t;const parseKV=t=>t.replace(/\n$/,"").split("\n").reduce(parseKVLine,Object.create(null));const parseKVLine=(t,e)=>{const s=parseInt(e,10);if(s!==Buffer.byteLength(e)+1)return t;e=e.substr((s+" ").length);const i=e.split("=");const n=i.shift().replace(/^SCHILY\.(dev|ino|nlink)/,"$1");if(!n)return t;const r=i.join("=");t[n]=/^([A-Z]+\.)?([mac]|birth|creation)time$/.test(n)?new Date(r*1e3):/^[0-9]+$/.test(r)?+r:r;return t};t.exports=Pax},2946:(t,e,s)=>{"use strict";const i=s(4591);const n=s(9408);const r=Symbol("slurp");t.exports=class ReadEntry extends i{constructor(t,e,s){super();this.pause();this.extended=e;this.globalExtended=s;this.header=t;this.startBlockSize=512*Math.ceil(t.size/512);this.blockRemain=this.startBlockSize;this.remain=t.size;this.type=t.type;this.meta=false;this.ignore=false;switch(this.type){case"File":case"OldFile":case"Link":case"SymbolicLink":case"CharacterDevice":case"BlockDevice":case"Directory":case"FIFO":case"ContiguousFile":case"GNUDumpDir":break;case"NextFileHasLongLinkpath":case"NextFileHasLongPath":case"OldGnuLongPath":case"GlobalExtendedHeader":case"ExtendedHeader":case"OldExtendedHeader":this.meta=true;break;default:this.ignore=true}this.path=n(t.path);this.mode=t.mode;if(this.mode)this.mode=this.mode&4095;this.uid=t.uid;this.gid=t.gid;this.uname=t.uname;this.gname=t.gname;this.size=t.size;this.mtime=t.mtime;this.atime=t.atime;this.ctime=t.ctime;this.linkpath=n(t.linkpath);this.uname=t.uname;this.gname=t.gname;if(e)this[r](e);if(s)this[r](s,true)}write(t){const e=t.length;if(e>this.blockRemain)throw new Error("writing more to entry than is appropriate");const s=this.remain;const i=this.blockRemain;this.remain=Math.max(0,s-e);this.blockRemain=Math.max(0,i-e);if(this.ignore)return true;if(s>=e)return super.write(t);return super.write(t.slice(0,s))}[r](t,e){for(const s in t){if(t[s]!==null&&t[s]!==undefined&&!(e&&s==="path"))this[s]=s==="path"||s==="linkpath"?n(t[s]):t[s]}}}},8291:(t,e,s)=>{"use strict";const i=s(6924);const n=s(9698);const r=s(7147);const o=s(3597);const h=s(9650);const l=s(1017);const a=s(3970);t.exports=(t,e,s)=>{const n=i(t);if(!n.file)throw new TypeError("file is required");if(n.gzip)throw new TypeError("cannot append to compressed archives");if(!e||!Array.isArray(e)||!e.length)throw new TypeError("no files or directories specified");e=Array.from(e);return n.sync?replaceSync(n,e):replace(n,e,s)};const replaceSync=(t,e)=>{const s=new n.Sync(t);let i=true;let o;let h;try{try{o=r.openSync(t.file,"r+")}catch(e){if(e.code==="ENOENT")o=r.openSync(t.file,"w+");else throw e}const n=r.fstatSync(o);const l=Buffer.alloc(512);t:for(h=0;hn.size)break;h+=s;if(t.mtimeCache)t.mtimeCache.set(e.path,e.mtime)}i=false;streamSync(t,s,h,o,e)}finally{if(i){try{r.closeSync(o)}catch(t){}}}};const streamSync=(t,e,s,i,n)=>{const r=new o.WriteStreamSync(t.file,{fd:i,start:s});e.pipe(r);addFilesSync(e,n)};const replace=(t,e,s)=>{e=Array.from(e);const i=new n(t);const getPos=(e,s,i)=>{const cb=(t,s)=>{if(t)r.close(e,(e=>i(t)));else i(null,s)};let n=0;if(s===0)return cb(null,0);let o=0;const h=Buffer.alloc(512);const onread=(i,l)=>{if(i)return cb(i);o+=l;if(o<512&&l){return r.read(e,h,o,h.length-o,n+o,onread)}if(n===0&&h[0]===31&&h[1]===139)return cb(new Error("cannot append to compressed archives"));if(o<512)return cb(null,n);const c=new a(h);if(!c.cksumValid)return cb(null,n);const u=512*Math.ceil(c.size/512);if(n+u+512>s)return cb(null,n);n+=u+512;if(n>=s)return cb(null,n);if(t.mtimeCache)t.mtimeCache.set(c.path,c.mtime);o=0;r.read(e,h,0,512,n,onread)};r.read(e,h,0,512,n,onread)};const h=new Promise(((s,n)=>{i.on("error",n);let h="r+";const onopen=(l,a)=>{if(l&&l.code==="ENOENT"&&h==="r+"){h="w+";return r.open(t.file,h,onopen)}if(l)return n(l);r.fstat(a,((h,l)=>{if(h)return r.close(a,(()=>n(h)));getPos(a,l.size,((r,h)=>{if(r)return n(r);const l=new o.WriteStream(t.file,{fd:a,start:h});i.pipe(l);l.on("error",n);l.on("close",s);addFilesAsync(i,e)}))}))};r.open(t.file,h,onopen)}));return s?h.then(s,s):h};const addFilesSync=(t,e)=>{e.forEach((e=>{if(e.charAt(0)==="@"){h({file:l.resolve(t.cwd,e.substr(1)),sync:true,noResume:true,onentry:e=>t.add(e)})}else t.add(e)}));t.end()};const addFilesAsync=(t,e)=>{while(e.length){const s=e.shift();if(s.charAt(0)==="@"){return h({file:l.resolve(t.cwd,s.substr(1)),noResume:true,onentry:e=>t.add(e)}).then((s=>addFilesAsync(t,e)))}else t.add(s)}t.end()}},8117:(t,e,s)=>{const{isAbsolute:i,parse:n}=s(1017).win32;t.exports=t=>{let e="";let s=n(t);while(i(t)||s.root){const i=t.charAt(0)==="/"&&t.slice(0,4)!=="//?/"?"/":s.root;t=t.substr(i.length);e+=i;s=n(t)}return[e,t]}},4387:t=>{t.exports=t=>{let e=t.length-1;let s=-1;while(e>-1&&t.charAt(e)==="/"){s=e;e--}return s===-1?t:t.slice(0,s)}},8318:(t,e)=>{"use strict";e.name=new Map([["0","File"],["","OldFile"],["1","Link"],["2","SymbolicLink"],["3","CharacterDevice"],["4","BlockDevice"],["5","Directory"],["6","FIFO"],["7","ContiguousFile"],["g","GlobalExtendedHeader"],["x","ExtendedHeader"],["A","SolarisACL"],["D","GNUDumpDir"],["I","Inode"],["K","NextFileHasLongLinkpath"],["L","NextFileHasLongPath"],["M","ContinuationFile"],["N","OldGnuLongPath"],["S","SparseFile"],["V","TapeVolumeHeader"],["X","OldExtendedHeader"]]);e.code=new Map(Array.from(e.name).map((t=>[t[1],t[0]])))},7932:(t,e,s)=>{"use strict";const i=s(9491);const n=s(2801);const r=s(7147);const o=s(3597);const h=s(1017);const l=s(3967);const a=s(1676);const c=s(2778);const u=s(8117);const f=s(9408);const d=s(4387);const p=s(5042);const m=Symbol("onEntry");const y=Symbol("checkFs");const b=Symbol("checkFs2");const w=Symbol("pruneCache");const _=Symbol("isReusable");const E=Symbol("makeFs");const g=Symbol("file");const S=Symbol("directory");const R=Symbol("link");const v=Symbol("symlink");const O=Symbol("hardlink");const k=Symbol("unsupported");const T=Symbol("checkPath");const x=Symbol("mkdir");const L=Symbol("onError");const A=Symbol("pending");const N=Symbol("pend");const I=Symbol("unpend");const D=Symbol("ended");const B=Symbol("maybeClose");const C=Symbol("skip");const M=Symbol("doChown");const F=Symbol("uid");const P=Symbol("gid");const z=Symbol("checkedCwd");const U=s(6113);const Y=s(2142);const Z=process.env.TESTING_TAR_FAKE_PLATFORM||process.platform;const j=Z==="win32";const unlinkFile=(t,e)=>{if(!j)return r.unlink(t,e);const s=t+".DELETE."+U.randomBytes(16).toString("hex");r.rename(t,s,(t=>{if(t)return e(t);r.unlink(s,e)}))};const unlinkFileSync=t=>{if(!j)return r.unlinkSync(t);const e=t+".DELETE."+U.randomBytes(16).toString("hex");r.renameSync(t,e);r.unlinkSync(e)};const uint32=(t,e,s)=>t===t>>>0?t:e===e>>>0?e:s;const cacheKeyNormalize=t=>p(d(f(t))).toLowerCase();const pruneCache=(t,e)=>{e=cacheKeyNormalize(e);for(const s of t.keys()){const i=cacheKeyNormalize(s);if(i===e||i.indexOf(e+"/")===0)t.delete(s)}};const dropCache=t=>{for(const e of t.keys())t.delete(e)};class Unpack extends n{constructor(t){if(!t)t={};t.ondone=t=>{this[D]=true;this[B]()};super(t);this[z]=false;this.reservations=c();this.transform=typeof t.transform==="function"?t.transform:null;this.writable=true;this.readable=false;this[A]=0;this[D]=false;this.dirCache=t.dirCache||new Map;if(typeof t.uid==="number"||typeof t.gid==="number"){if(typeof t.uid!=="number"||typeof t.gid!=="number")throw new TypeError("cannot set owner without number uid and gid");if(t.preserveOwner){throw new TypeError("cannot preserve owner in archive and also set owner explicitly")}this.uid=t.uid;this.gid=t.gid;this.setOwner=true}else{this.uid=null;this.gid=null;this.setOwner=false}if(t.preserveOwner===undefined&&typeof t.uid!=="number")this.preserveOwner=process.getuid&&process.getuid()===0;else this.preserveOwner=!!t.preserveOwner;this.processUid=(this.preserveOwner||this.setOwner)&&process.getuid?process.getuid():null;this.processGid=(this.preserveOwner||this.setOwner)&&process.getgid?process.getgid():null;this.forceChown=t.forceChown===true;this.win32=!!t.win32||j;this.newer=!!t.newer;this.keep=!!t.keep;this.noMtime=!!t.noMtime;this.preservePaths=!!t.preservePaths;this.unlink=!!t.unlink;this.cwd=f(h.resolve(t.cwd||process.cwd()));this.strip=+t.strip||0;this.processUmask=t.noChmod?0:process.umask();this.umask=typeof t.umask==="number"?t.umask:this.processUmask;this.dmode=t.dmode||511&~this.umask;this.fmode=t.fmode||438&~this.umask;this.on("entry",(t=>this[m](t)))}warn(t,e,s={}){if(t==="TAR_BAD_ARCHIVE"||t==="TAR_ABORT")s.recoverable=false;return super.warn(t,e,s)}[B](){if(this[D]&&this[A]===0){this.emit("prefinish");this.emit("finish");this.emit("end");this.emit("close")}}[T](t){if(this.strip){const e=f(t.path).split("/");if(e.length=this.strip)t.linkpath=e.slice(this.strip).join("/");else return false}}if(!this.preservePaths){const e=f(t.path);const s=e.split("/");if(s.includes("..")||j&&/^[a-z]:\.\.$/i.test(s[0])){this.warn("TAR_ENTRY_ERROR",`path contains '..'`,{entry:t,path:e});return false}const[i,n]=u(e);if(i){t.path=n;this.warn("TAR_ENTRY_INFO",`stripping ${i} from absolute path`,{entry:t,path:e})}}if(h.isAbsolute(t.path))t.absolute=f(h.resolve(t.path));else t.absolute=f(h.resolve(this.cwd,t.path));if(!this.preservePaths&&t.absolute.indexOf(this.cwd+"/")!==0&&t.absolute!==this.cwd){this.warn("TAR_ENTRY_ERROR","path escaped extraction target",{entry:t,path:f(t.path),resolvedPath:t.absolute,cwd:this.cwd});return false}if(t.absolute===this.cwd&&t.type!=="Directory"&&t.type!=="GNUDumpDir")return false;if(this.win32){const{root:e}=h.win32.parse(t.absolute);t.absolute=e+a.encode(t.absolute.substr(e.length));const{root:s}=h.win32.parse(t.path);t.path=s+a.encode(t.path.substr(s.length))}return true}[m](t){if(!this[T](t))return t.resume();i.equal(typeof t.absolute,"string");switch(t.type){case"Directory":case"GNUDumpDir":if(t.mode)t.mode=t.mode|448;case"File":case"OldFile":case"ContiguousFile":case"Link":case"SymbolicLink":return this[y](t);case"CharacterDevice":case"BlockDevice":case"FIFO":default:return this[k](t)}}[L](t,e){if(t.name==="CwdError")this.emit("error",t);else{this.warn("TAR_ENTRY_ERROR",t,{entry:e});this[I]();e.resume()}}[x](t,e,s){l(f(t),{uid:this.uid,gid:this.gid,processUid:this.processUid,processGid:this.processGid,umask:this.processUmask,preserve:this.preservePaths,unlink:this.unlink,cache:this.dirCache,cwd:this.cwd,mode:e,noChmod:this.noChmod},s)}[M](t){return this.forceChown||this.preserveOwner&&(typeof t.uid==="number"&&t.uid!==this.processUid||typeof t.gid==="number"&&t.gid!==this.processGid)||(typeof this.uid==="number"&&this.uid!==this.processUid||typeof this.gid==="number"&&this.gid!==this.processGid)}[F](t){return uint32(this.uid,t.uid,this.processUid)}[P](t){return uint32(this.gid,t.gid,this.processGid)}[g](t,e){const s=t.mode&4095||this.fmode;const i=new o.WriteStream(t.absolute,{flags:Y(t.size),mode:s,autoClose:false});i.on("error",(s=>{if(i.fd)r.close(i.fd,(()=>{}));i.write=()=>true;this[L](s,t);e()}));let n=1;const done=s=>{if(s){if(i.fd)r.close(i.fd,(()=>{}));this[L](s,t);e();return}if(--n===0){r.close(i.fd,(s=>{if(s)this[L](s,t);else this[I]();e()}))}};i.on("finish",(e=>{const s=t.absolute;const o=i.fd;if(t.mtime&&!this.noMtime){n++;const e=t.atime||new Date;const i=t.mtime;r.futimes(o,e,i,(t=>t?r.utimes(s,e,i,(e=>done(e&&t))):done()))}if(this[M](t)){n++;const e=this[F](t);const i=this[P](t);r.fchown(o,e,i,(t=>t?r.chown(s,e,i,(e=>done(e&&t))):done()))}done()}));const h=this.transform?this.transform(t)||t:t;if(h!==t){h.on("error",(s=>{this[L](s,t);e()}));t.pipe(h)}h.pipe(i)}[S](t,e){const s=t.mode&4095||this.dmode;this[x](t.absolute,s,(s=>{if(s){this[L](s,t);e();return}let i=1;const done=s=>{if(--i===0){e();this[I]();t.resume()}};if(t.mtime&&!this.noMtime){i++;r.utimes(t.absolute,t.atime||new Date,t.mtime,done)}if(this[M](t)){i++;r.chown(t.absolute,this[F](t),this[P](t),done)}done()}))}[k](t){t.unsupported=true;this.warn("TAR_ENTRY_UNSUPPORTED",`unsupported entry type: ${t.type}`,{entry:t});t.resume()}[v](t,e){this[R](t,t.linkpath,"symlink",e)}[O](t,e){const s=f(h.resolve(this.cwd,t.linkpath));this[R](t,s,"link",e)}[N](){this[A]++}[I](){this[A]--;this[B]()}[C](t){this[I]();t.resume()}[_](t,e){return t.type==="File"&&!this.unlink&&e.isFile()&&e.nlink<=1&&!j}[y](t){this[N]();const e=[t.path];if(t.linkpath)e.push(t.linkpath);this.reservations.reserve(e,(e=>this[b](t,e)))}[w](t){if(t.type==="SymbolicLink")dropCache(this.dirCache);else if(t.type!=="Directory")pruneCache(this.dirCache,t.absolute)}[b](t,e){this[w](t);const done=s=>{this[w](t);e(s)};const checkCwd=()=>{this[x](this.cwd,this.dmode,(e=>{if(e){this[L](e,t);done();return}this[z]=true;start()}))};const start=()=>{if(t.absolute!==this.cwd){const e=f(h.dirname(t.absolute));if(e!==this.cwd){return this[x](e,this.dmode,(e=>{if(e){this[L](e,t);done();return}afterMakeParent()}))}}afterMakeParent()};const afterMakeParent=()=>{r.lstat(t.absolute,((e,s)=>{if(s&&(this.keep||this.newer&&s.mtime>t.mtime)){this[C](t);done();return}if(e||this[_](t,s))return this[E](null,t,done);if(s.isDirectory()){if(t.type==="Directory"){const e=!this.noChmod&&t.mode&&(s.mode&4095)!==t.mode;const afterChmod=e=>this[E](e,t,done);if(!e)return afterChmod();return r.chmod(t.absolute,t.mode,afterChmod)}if(t.absolute!==this.cwd){return r.rmdir(t.absolute,(e=>this[E](e,t,done)))}}if(t.absolute===this.cwd)return this[E](null,t,done);unlinkFile(t.absolute,(e=>this[E](e,t,done)))}))};if(this[z])start();else checkCwd()}[E](t,e,s){if(t){this[L](t,e);s();return}switch(e.type){case"File":case"OldFile":case"ContiguousFile":return this[g](e,s);case"Link":return this[O](e,s);case"SymbolicLink":return this[v](e,s);case"Directory":case"GNUDumpDir":return this[S](e,s)}}[R](t,e,s,i){r[s](e,t.absolute,(e=>{if(e)this[L](e,t);else{this[I]();t.resume()}i()}))}}const callSync=t=>{try{return[null,t()]}catch(t){return[t,null]}};class UnpackSync extends Unpack{[E](t,e){return super[E](t,e,(()=>{}))}[y](t){this[w](t);if(!this[z]){const e=this[x](this.cwd,this.dmode);if(e)return this[L](e,t);this[z]=true}if(t.absolute!==this.cwd){const e=f(h.dirname(t.absolute));if(e!==this.cwd){const s=this[x](e,this.dmode);if(s)return this[L](s,t)}}const[e,s]=callSync((()=>r.lstatSync(t.absolute)));if(s&&(this.keep||this.newer&&s.mtime>t.mtime))return this[C](t);if(e||this[_](t,s))return this[E](null,t);if(s.isDirectory()){if(t.type==="Directory"){const e=!this.noChmod&&t.mode&&(s.mode&4095)!==t.mode;const[i]=e?callSync((()=>{r.chmodSync(t.absolute,t.mode)})):[];return this[E](i,t)}const[e]=callSync((()=>r.rmdirSync(t.absolute)));this[E](e,t)}const[i]=t.absolute===this.cwd?[]:callSync((()=>unlinkFileSync(t.absolute)));this[E](i,t)}[g](t,e){const s=t.mode&4095||this.fmode;const oner=s=>{let n;try{r.closeSync(i)}catch(t){n=t}if(s||n)this[L](s||n,t);e()};let i;try{i=r.openSync(t.absolute,Y(t.size),s)}catch(t){return oner(t)}const n=this.transform?this.transform(t)||t:t;if(n!==t){n.on("error",(e=>this[L](e,t)));t.pipe(n)}n.on("data",(t=>{try{r.writeSync(i,t,0,t.length)}catch(t){oner(t)}}));n.on("end",(e=>{let s=null;if(t.mtime&&!this.noMtime){const e=t.atime||new Date;const n=t.mtime;try{r.futimesSync(i,e,n)}catch(i){try{r.utimesSync(t.absolute,e,n)}catch(t){s=i}}}if(this[M](t)){const e=this[F](t);const n=this[P](t);try{r.fchownSync(i,e,n)}catch(i){try{r.chownSync(t.absolute,e,n)}catch(t){s=s||i}}}oner(s)}))}[S](t,e){const s=t.mode&4095||this.dmode;const i=this[x](t.absolute,s);if(i){this[L](i,t);e();return}if(t.mtime&&!this.noMtime){try{r.utimesSync(t.absolute,t.atime||new Date,t.mtime)}catch(i){}}if(this[M](t)){try{r.chownSync(t.absolute,this[F](t),this[P](t))}catch(i){}}e();t.resume()}[x](t,e){try{return l.sync(f(t),{uid:this.uid,gid:this.gid,processUid:this.processUid,processGid:this.processGid,umask:this.processUmask,preserve:this.preservePaths,unlink:this.unlink,cache:this.dirCache,cwd:this.cwd,mode:e})}catch(t){return t}}[R](t,e,s,i){try{r[s+"Sync"](e,t.absolute);i();t.resume()}catch(e){return this[L](e,t)}}}Unpack.Sync=UnpackSync;t.exports=Unpack},8489:(t,e,s)=>{"use strict";const i=s(6924);const n=s(8291);t.exports=(t,e,s)=>{const r=i(t);if(!r.file)throw new TypeError("file is required");if(r.gzip)throw new TypeError("cannot append to compressed archives");if(!e||!Array.isArray(e)||!e.length)throw new TypeError("no files or directories specified");e=Array.from(e);mtimeFilter(r);return n(r,e,s)};const mtimeFilter=t=>{const e=t.filter;if(!t.mtimeCache)t.mtimeCache=new Map;t.filter=e?(s,i)=>e(s,i)&&!(t.mtimeCache.get(s)>i.mtime):(e,s)=>!(t.mtimeCache.get(e)>s.mtime)}},430:t=>{"use strict";t.exports=t=>class extends t{warn(t,e,s={}){if(this.file)s.file=this.file;if(this.cwd)s.cwd=this.cwd;s.code=e instanceof Error&&e.code||t;s.tarCode=t;if(!this.strict&&s.recoverable!==false){if(e instanceof Error){s=Object.assign(e,s);e=e.message}this.emit("warn",s.tarCode,e,s)}else if(e instanceof Error)this.emit("error",Object.assign(e,s));else this.emit("error",Object.assign(new Error(`${t}: ${e}`),s))}}},1676:t=>{"use strict";const e=["|","<",">","?",":"];const s=e.map((t=>String.fromCharCode(61440+t.charCodeAt(0))));const i=new Map(e.map(((t,e)=>[t,s[e]])));const n=new Map(s.map(((t,s)=>[t,e[s]])));t.exports={encode:t=>e.reduce(((t,e)=>t.split(e).join(i.get(e))),t),decode:t=>s.reduce(((t,e)=>t.split(e).join(n.get(e))),t)}},9144:(t,e,s)=>{"use strict";const i=s(4591);const n=s(4065);const r=s(3970);const o=s(7147);const h=s(1017);const l=s(9408);const a=s(4387);const prefixPath=(t,e)=>{if(!e)return l(t);t=l(t).replace(/^\.(\/|$)/,"");return a(e)+"/"+t};const c=16*1024*1024;const u=Symbol("process");const f=Symbol("file");const d=Symbol("directory");const p=Symbol("symlink");const m=Symbol("hardlink");const y=Symbol("header");const b=Symbol("read");const w=Symbol("lstat");const _=Symbol("onlstat");const E=Symbol("onread");const g=Symbol("onreadlink");const S=Symbol("openfile");const R=Symbol("onopenfile");const v=Symbol("close");const O=Symbol("mode");const k=Symbol("awaitDrain");const T=Symbol("ondrain");const x=Symbol("prefix");const L=Symbol("hadError");const A=s(430);const N=s(1676);const I=s(8117);const D=s(4770);const B=A(class WriteEntry extends i{constructor(t,e){e=e||{};super(e);if(typeof t!=="string")throw new TypeError("path is required");this.path=l(t);this.portable=!!e.portable;this.myuid=process.getuid&&process.getuid()||0;this.myuser=process.env.USER||"";this.maxReadSize=e.maxReadSize||c;this.linkCache=e.linkCache||new Map;this.statCache=e.statCache||new Map;this.preservePaths=!!e.preservePaths;this.cwd=l(e.cwd||process.cwd());this.strict=!!e.strict;this.noPax=!!e.noPax;this.noMtime=!!e.noMtime;this.mtime=e.mtime||null;this.prefix=e.prefix?l(e.prefix):null;this.fd=null;this.blockLen=null;this.blockRemain=null;this.buf=null;this.offset=null;this.length=null;this.pos=null;this.remain=null;if(typeof e.onwarn==="function")this.on("warn",e.onwarn);let s=false;if(!this.preservePaths){const[t,e]=I(this.path);if(t){this.path=e;s=t}}this.win32=!!e.win32||process.platform==="win32";if(this.win32){this.path=N.decode(this.path.replace(/\\/g,"/"));t=t.replace(/\\/g,"/")}this.absolute=l(e.absolute||h.resolve(this.cwd,t));if(this.path==="")this.path="./";if(s){this.warn("TAR_ENTRY_INFO",`stripping ${s} from absolute path`,{entry:this,path:s+this.path})}if(this.statCache.has(this.absolute))this[_](this.statCache.get(this.absolute));else this[w]()}emit(t,...e){if(t==="error")this[L]=true;return super.emit(t,...e)}[w](){o.lstat(this.absolute,((t,e)=>{if(t)return this.emit("error",t);this[_](e)}))}[_](t){this.statCache.set(this.absolute,t);this.stat=t;if(!t.isFile())t.size=0;this.type=getType(t);this.emit("stat",t);this[u]()}[u](){switch(this.type){case"File":return this[f]();case"Directory":return this[d]();case"SymbolicLink":return this[p]();default:return this.end()}}[O](t){return D(t,this.type==="Directory",this.portable)}[x](t){return prefixPath(t,this.prefix)}[y](){if(this.type==="Directory"&&this.portable)this.noMtime=true;this.header=new r({path:this[x](this.path),linkpath:this.type==="Link"?this[x](this.linkpath):this.linkpath,mode:this[O](this.stat.mode),uid:this.portable?null:this.stat.uid,gid:this.portable?null:this.stat.gid,size:this.stat.size,mtime:this.noMtime?null:this.mtime||this.stat.mtime,type:this.type,uname:this.portable?null:this.stat.uid===this.myuid?this.myuser:"",atime:this.portable?null:this.stat.atime,ctime:this.portable?null:this.stat.ctime});if(this.header.encode()&&!this.noPax){super.write(new n({atime:this.portable?null:this.header.atime,ctime:this.portable?null:this.header.ctime,gid:this.portable?null:this.header.gid,mtime:this.noMtime?null:this.mtime||this.header.mtime,path:this[x](this.path),linkpath:this.type==="Link"?this[x](this.linkpath):this.linkpath,size:this.header.size,uid:this.portable?null:this.header.uid,uname:this.portable?null:this.header.uname,dev:this.portable?null:this.stat.dev,ino:this.portable?null:this.stat.ino,nlink:this.portable?null:this.stat.nlink}).encode())}super.write(this.header.block)}[d](){if(this.path.substr(-1)!=="/")this.path+="/";this.stat.size=0;this[y]();this.end()}[p](){o.readlink(this.absolute,((t,e)=>{if(t)return this.emit("error",t);this[g](e)}))}[g](t){this.linkpath=l(t);this[y]();this.end()}[m](t){this.type="Link";this.linkpath=l(h.relative(this.cwd,t));this.stat.size=0;this[y]();this.end()}[f](){if(this.stat.nlink>1){const t=this.stat.dev+":"+this.stat.ino;if(this.linkCache.has(t)){const e=this.linkCache.get(t);if(e.indexOf(this.cwd)===0)return this[m](e)}this.linkCache.set(t,this.absolute)}this[y]();if(this.stat.size===0)return this.end();this[S]()}[S](){o.open(this.absolute,"r",((t,e)=>{if(t)return this.emit("error",t);this[R](e)}))}[R](t){this.fd=t;if(this[L])return this[v]();this.blockLen=512*Math.ceil(this.stat.size/512);this.blockRemain=this.blockLen;const e=Math.min(this.blockLen,this.maxReadSize);this.buf=Buffer.allocUnsafe(e);this.offset=0;this.pos=0;this.remain=this.stat.size;this.length=this.buf.length;this[b]()}[b](){const{fd:t,buf:e,offset:s,length:i,pos:n}=this;o.read(t,e,s,i,n,((t,e)=>{if(t){return this[v]((()=>this.emit("error",t)))}this[E](e)}))}[v](t){o.close(this.fd,t)}[E](t){if(t<=0&&this.remain>0){const t=new Error("encountered unexpected EOF");t.path=this.absolute;t.syscall="read";t.code="EOF";return this[v]((()=>this.emit("error",t)))}if(t>this.remain){const t=new Error("did not encounter expected EOF");t.path=this.absolute;t.syscall="read";t.code="EOF";return this[v]((()=>this.emit("error",t)))}if(t===this.remain){for(let e=t;ethis[T]()));else this[T]()}[k](t){this.once("drain",t)}write(t){if(this.blockRemaint?this.emit("error",t):this.end()))}if(this.offset>=this.length){this.buf=Buffer.allocUnsafe(Math.min(this.blockRemain,this.buf.length));this.offset=0}this.length=this.buf.length-this.offset;this[b]()}});class WriteEntrySync extends B{[w](){this[_](o.lstatSync(this.absolute))}[p](){this[g](o.readlinkSync(this.absolute))}[S](){this[R](o.openSync(this.absolute,"r"))}[b](){let t=true;try{const{fd:e,buf:s,offset:i,length:n,pos:r}=this;const h=o.readSync(e,s,i,n,r);this[E](h);t=false}finally{if(t){try{this[v]((()=>{}))}catch(t){}}}}[k](t){t()}[v](t){o.closeSync(this.fd);t()}}const C=A(class WriteEntryTar extends i{constructor(t,e){e=e||{};super(e);this.preservePaths=!!e.preservePaths;this.portable=!!e.portable;this.strict=!!e.strict;this.noPax=!!e.noPax;this.noMtime=!!e.noMtime;this.readEntry=t;this.type=t.type;if(this.type==="Directory"&&this.portable)this.noMtime=true;this.prefix=e.prefix||null;this.path=l(t.path);this.mode=this[O](t.mode);this.uid=this.portable?null:t.uid;this.gid=this.portable?null:t.gid;this.uname=this.portable?null:t.uname;this.gname=this.portable?null:t.gname;this.size=t.size;this.mtime=this.noMtime?null:e.mtime||t.mtime;this.atime=this.portable?null:t.atime;this.ctime=this.portable?null:t.ctime;this.linkpath=l(t.linkpath);if(typeof e.onwarn==="function")this.on("warn",e.onwarn);let s=false;if(!this.preservePaths){const[t,e]=I(this.path);if(t){this.path=e;s=t}}this.remain=t.size;this.blockRemain=t.startBlockSize;this.header=new r({path:this[x](this.path),linkpath:this.type==="Link"?this[x](this.linkpath):this.linkpath,mode:this.mode,uid:this.portable?null:this.uid,gid:this.portable?null:this.gid,size:this.size,mtime:this.noMtime?null:this.mtime,type:this.type,uname:this.portable?null:this.uname,atime:this.portable?null:this.atime,ctime:this.portable?null:this.ctime});if(s){this.warn("TAR_ENTRY_INFO",`stripping ${s} from absolute path`,{entry:this,path:s+this.path})}if(this.header.encode()&&!this.noPax){super.write(new n({atime:this.portable?null:this.atime,ctime:this.portable?null:this.ctime,gid:this.portable?null:this.gid,mtime:this.noMtime?null:this.mtime,path:this[x](this.path),linkpath:this.type==="Link"?this[x](this.linkpath):this.linkpath,size:this.size,uid:this.portable?null:this.uid,uname:this.portable?null:this.uname,dev:this.portable?null:this.readEntry.dev,ino:this.portable?null:this.readEntry.ino,nlink:this.portable?null:this.readEntry.nlink}).encode())}super.write(this.header.block);t.pipe(this)}[x](t){return prefixPath(t,this.prefix)}[O](t){return D(t,this.type==="Directory",this.portable)}write(t){const e=t.length;if(e>this.blockRemain)throw new Error("writing more to entry than is appropriate");this.blockRemain-=e;return super.write(t)}end(){if(this.blockRemain)super.write(Buffer.alloc(this.blockRemain));return super.end()}});B.Sync=WriteEntrySync;B.Tar=C;const getType=t=>t.isFile()?"File":t.isDirectory()?"Directory":t.isSymbolicLink()?"SymbolicLink":"Unsupported";t.exports=B},6861:t=>{"use strict";t.exports=function(t){t.prototype[Symbol.iterator]=function*(){for(let t=this.head;t;t=t.next){yield t.value}}}},1071:(t,e,s)=>{"use strict";t.exports=Yallist;Yallist.Node=Node;Yallist.create=Yallist;function Yallist(t){var e=this;if(!(e instanceof Yallist)){e=new Yallist}e.tail=null;e.head=null;e.length=0;if(t&&typeof t.forEach==="function"){t.forEach((function(t){e.push(t)}))}else if(arguments.length>0){for(var s=0,i=arguments.length;s1){s=e}else if(this.head){i=this.head.next;s=this.head.value}else{throw new TypeError("Reduce of empty list with no initial value")}for(var n=0;i!==null;n++){s=t(s,i.value,n);i=i.next}return s};Yallist.prototype.reduceReverse=function(t,e){var s;var i=this.tail;if(arguments.length>1){s=e}else if(this.tail){i=this.tail.prev;s=this.tail.value}else{throw new TypeError("Reduce of empty list with no initial value")}for(var n=this.length-1;i!==null;n--){s=t(s,i.value,n);i=i.prev}return s};Yallist.prototype.toArray=function(){var t=new Array(this.length);for(var e=0,s=this.head;s!==null;e++){t[e]=s.value;s=s.next}return t};Yallist.prototype.toArrayReverse=function(){var t=new Array(this.length);for(var e=0,s=this.tail;s!==null;e++){t[e]=s.value;s=s.prev}return t};Yallist.prototype.slice=function(t,e){e=e||this.length;if(e<0){e+=this.length}t=t||0;if(t<0){t+=this.length}var s=new Yallist;if(ethis.length){e=this.length}for(var i=0,n=this.head;n!==null&&ithis.length){e=this.length}for(var i=this.length,n=this.tail;n!==null&&i>e;i--){n=n.prev}for(;n!==null&&i>t;i--,n=n.prev){s.push(n.value)}return s};Yallist.prototype.splice=function(t,e,...s){if(t>this.length){t=this.length-1}if(t<0){t=this.length+t}for(var i=0,n=this.head;n!==null&&i{"use strict";t.exports=require("assert")},4300:t=>{"use strict";t.exports=require("buffer")},6113:t=>{"use strict";t.exports=require("crypto")},2361:t=>{"use strict";t.exports=require("events")},7147:t=>{"use strict";t.exports=require("fs")},1017:t=>{"use strict";t.exports=require("path")},2781:t=>{"use strict";t.exports=require("stream")},1576:t=>{"use strict";t.exports=require("string_decoder")},3837:t=>{"use strict";t.exports=require("util")},9796:t=>{"use strict";t.exports=require("zlib")}};var e={};function __nccwpck_require__(s){var i=e[s];if(i!==undefined){return i.exports}var n=e[s]={exports:{}};var r=true;try{t[s](n,n.exports,__nccwpck_require__);r=false}finally{if(r)delete e[s]}return n.exports}if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=__dirname+"/";var s={};(()=>{"use strict";var t=s;t.c=t.create=__nccwpck_require__(6036);t.r=t.replace=__nccwpck_require__(8291);t.t=t.list=__nccwpck_require__(9650);t.u=t.update=__nccwpck_require__(8489);t.x=t.extract=__nccwpck_require__(7171);t.Pack=__nccwpck_require__(9698);t.Unpack=__nccwpck_require__(7932);t.Parse=__nccwpck_require__(2801);t.ReadEntry=__nccwpck_require__(2946);t.WriteEntry=__nccwpck_require__(9144);t.Header=__nccwpck_require__(3970);t.Pax=__nccwpck_require__(4065);t.types=__nccwpck_require__(8318)})();module.exports=s})();
\ No newline at end of file
diff --git a/packages/next/compiled/tar/package.json b/packages/next/compiled/tar/package.json
new file mode 100644
index 000000000000..f39487ec7481
--- /dev/null
+++ b/packages/next/compiled/tar/package.json
@@ -0,0 +1 @@
+{"name":"tar","main":"index.js","author":"Isaac Z. Schlueter (http://blog.izs.me/)","license":"ISC"}
diff --git a/packages/next/export/index.ts b/packages/next/export/index.ts
index 8191dd75e7f3..366a00e4e1ee 100644
--- a/packages/next/export/index.ts
+++ b/packages/next/export/index.ts
@@ -581,6 +581,7 @@ export default async function exportApp(
outDir,
pagesDataDir,
renderOpts,
+ viewsDir: nextConfig.experimental.viewsDir,
serverRuntimeConfig,
subFolders,
buildExport: options.buildExport,
diff --git a/packages/next/export/worker.ts b/packages/next/export/worker.ts
index e68d20456d08..e8db5c26e467 100644
--- a/packages/next/export/worker.ts
+++ b/packages/next/export/worker.ts
@@ -60,6 +60,7 @@ interface ExportPageInput {
parentSpanId: any
httpAgentOptions: NextConfigComplete['httpAgentOptions']
serverComponents?: boolean
+ viewsDir?: boolean
}
interface ExportPageResults {
@@ -84,6 +85,7 @@ interface RenderOpts {
locale?: string
defaultLocale?: string
trailingSlash?: boolean
+ viewsDir?: boolean
}
type ComponentModule = ComponentType<{}> & {
@@ -97,6 +99,7 @@ export default async function exportPage({
pathMap,
distDir,
outDir,
+ viewsDir,
pagesDataDir,
renderOpts,
buildExport,
@@ -262,7 +265,13 @@ export default async function exportPage({
getServerSideProps,
getStaticProps,
pageConfig,
- } = await loadComponents(distDir, page, serverless, serverComponents)
+ } = await loadComponents(
+ distDir,
+ page,
+ serverless,
+ serverComponents,
+ viewsDir
+ )
const ampState = {
ampFirst: pageConfig?.amp === true,
hasQuery: Boolean(query.amp),
diff --git a/packages/next/lib/constants.ts b/packages/next/lib/constants.ts
index 92746a3d3ae7..354b62581704 100644
--- a/packages/next/lib/constants.ts
+++ b/packages/next/lib/constants.ts
@@ -25,6 +25,7 @@ export const MIDDLEWARE_ROUTE = /_middleware$/
// we have to use a private alias
export const PAGES_DIR_ALIAS = 'private-next-pages'
export const DOT_NEXT_ALIAS = 'private-dot-next'
+export const VIEWS_DIR_ALIAS = 'private-next-views-dir'
export const PUBLIC_DIR_MIDDLEWARE_CONFLICT = `You can not have a '_next' folder inside of your public folder. This conflicts with the internal '/_next' route. https://nextjs.org/docs/messages/public-next-folder-conflict`
diff --git a/packages/next/lib/download-wasm-swc.ts b/packages/next/lib/download-wasm-swc.ts
new file mode 100644
index 000000000000..926ed912ff0e
--- /dev/null
+++ b/packages/next/lib/download-wasm-swc.ts
@@ -0,0 +1,118 @@
+import os from 'os'
+import fs from 'fs'
+import path from 'path'
+import * as Log from '../build/output/log'
+import { execSync } from 'child_process'
+import tar from 'next/dist/compiled/tar'
+import fetch from 'next/dist/compiled/node-fetch'
+import { fileExists } from './file-exists'
+
+const MAX_VERSIONS_TO_CACHE = 5
+
+export async function downloadWasmSwc(
+ version: string,
+ wasmDirectory: string,
+ variant: 'nodejs' | 'web' = 'nodejs'
+) {
+ const pkgName = `@next/swc-wasm-${variant}`
+ const tarFileName = `${pkgName.substring(6)}-${version}.tgz`
+ const outputDirectory = path.join(wasmDirectory, pkgName)
+
+ if (await fileExists(outputDirectory)) {
+ // if the package is already downloaded a different
+ // failure occurred than not being present
+ return
+ }
+
+ // get platform specific cache directory adapted from playwright's handling
+ // https://github.com/microsoft/playwright/blob/7d924470d397975a74a19184c136b3573a974e13/packages/playwright-core/src/utils/registry.ts#L141
+ const cacheDirectory = (() => {
+ let result
+ const envDefined = process.env['NEXT_SWC_PATH']
+
+ if (envDefined) {
+ result = envDefined
+ } else {
+ let systemCacheDirectory
+ if (process.platform === 'linux') {
+ systemCacheDirectory =
+ process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache')
+ } else if (process.platform === 'darwin') {
+ systemCacheDirectory = path.join(os.homedir(), 'Library', 'Caches')
+ } else if (process.platform === 'win32') {
+ systemCacheDirectory =
+ process.env.LOCALAPPDATA ||
+ path.join(os.homedir(), 'AppData', 'Local')
+ } else {
+ console.error(new Error('Unsupported platform: ' + process.platform))
+ process.exit(0)
+ }
+ result = path.join(systemCacheDirectory, 'next-swc')
+ }
+
+ if (!path.isAbsolute(result)) {
+ // It is important to resolve to the absolute path:
+ // - for unzipping to work correctly;
+ // - so that registry directory matches between installation and execution.
+ // INIT_CWD points to the root of `npm/yarn install` and is probably what
+ // the user meant when typing the relative path.
+ result = path.resolve(process.env['INIT_CWD'] || process.cwd(), result)
+ }
+ return result
+ })()
+
+ await fs.promises.mkdir(outputDirectory, { recursive: true })
+
+ const extractFromTar = async () => {
+ await tar.x({
+ file: path.join(cacheDirectory, tarFileName),
+ cwd: outputDirectory,
+ strip: 1,
+ })
+ }
+
+ if (!(await fileExists(path.join(cacheDirectory, tarFileName)))) {
+ Log.info('Downloading WASM swc package...')
+ await fs.promises.mkdir(cacheDirectory, { recursive: true })
+ const tempFile = path.join(
+ cacheDirectory,
+ `${tarFileName}.temp-${Date.now()}`
+ )
+ let registry = `https://registry.npmjs.org/`
+
+ try {
+ const output = execSync('npm config get registry').toString().trim()
+ if (output.startsWith('http')) {
+ registry = output
+ }
+ } catch (_) {}
+
+ await fetch(`${registry}${pkgName}/-/${tarFileName}`).then((res) => {
+ if (!res.ok) {
+ throw new Error(`request failed with status ${res.status}`)
+ }
+ const cacheWriteStream = fs.createWriteStream(tempFile)
+
+ return new Promise((resolve, reject) => {
+ res.body
+ .pipe(cacheWriteStream)
+ .on('error', (err) => reject(err))
+ .on('finish', () => resolve())
+ }).finally(() => cacheWriteStream.close())
+ })
+ await fs.promises.rename(tempFile, path.join(cacheDirectory, tarFileName))
+ }
+ await extractFromTar()
+
+ const cacheFiles = await fs.promises.readdir(cacheDirectory)
+
+ if (cacheFiles.length > MAX_VERSIONS_TO_CACHE) {
+ cacheFiles.sort()
+
+ for (let i = MAX_VERSIONS_TO_CACHE - 1; i++; i < cacheFiles.length) {
+ await fs.promises
+ .unlink(path.join(cacheDirectory, cacheFiles[i]))
+ .catch(() => {})
+ }
+ }
+}
diff --git a/packages/next/lib/find-pages-dir.ts b/packages/next/lib/find-pages-dir.ts
index 862849fd00de..26a6799687ea 100644
--- a/packages/next/lib/find-pages-dir.ts
+++ b/packages/next/lib/find-pages-dir.ts
@@ -10,7 +10,7 @@ export const existsSync = (f: string): boolean => {
}
}
-function findDir(dir: string, name: 'pages' | 'root'): string | null {
+function findDir(dir: string, name: 'pages' | 'views'): string | null {
// prioritize ./${name} over ./src/${name}
let curDir = path.join(dir, name)
if (existsSync(curDir)) return curDir
@@ -23,13 +23,13 @@ function findDir(dir: string, name: 'pages' | 'root'): string | null {
export function findPagesDir(
dir: string,
- root?: boolean
-): { pages: string; root?: string } {
+ views?: boolean
+): { pages: string; views?: string } {
const pagesDir = findDir(dir, 'pages')
- let rootDir: undefined | string
+ let viewsDir: undefined | string
- if (root) {
- rootDir = findDir(dir, 'root') || undefined
+ if (views) {
+ viewsDir = findDir(dir, 'views') || undefined
}
// TODO: allow "root" dir without pages dir
@@ -41,6 +41,6 @@ export function findPagesDir(
return {
pages: pagesDir,
- root: rootDir,
+ views: viewsDir,
}
}
diff --git a/packages/next/lib/recursive-delete.ts b/packages/next/lib/recursive-delete.ts
index 76ed97712fb3..ccd443c4c2fa 100644
--- a/packages/next/lib/recursive-delete.ts
+++ b/packages/next/lib/recursive-delete.ts
@@ -1,9 +1,9 @@
import { Dirent, promises } from 'fs'
import { join, isAbsolute, dirname } from 'path'
-import { promisify } from 'util'
import isError from './is-error'
-const sleep = promisify(setTimeout)
+const sleep = (timeout: number) =>
+ new Promise((resolve) => setTimeout(resolve, timeout))
const unlinkPath = async (p: string, isDir = false, t = 1): Promise => {
try {
diff --git a/packages/next/lib/views-layout.tsx b/packages/next/lib/views-layout.tsx
new file mode 100644
index 000000000000..bbbbecd42693
--- /dev/null
+++ b/packages/next/lib/views-layout.tsx
@@ -0,0 +1,21 @@
+import React from 'react'
+
+export type LayoutProps = {
+ headChildren: any
+ bodyChildren: any
+}
+
+export default function ViewsLayout({
+ headChildren,
+ bodyChildren,
+}: LayoutProps) {
+ return (
+
+
+ {headChildren}
+ Test
+
+ {bodyChildren}
+
+ )
+}
diff --git a/packages/next/package.json b/packages/next/package.json
index 595f40d80107..5b1dc3be2409 100644
--- a/packages/next/package.json
+++ b/packages/next/package.json
@@ -1,6 +1,6 @@
{
"name": "next",
- "version": "12.1.6",
+ "version": "12.1.7-canary.2",
"description": "The React Framework",
"main": "./dist/server/next.js",
"license": "MIT",
@@ -69,7 +69,7 @@
]
},
"dependencies": {
- "@next/env": "12.1.6",
+ "@next/env": "12.1.7-canary.2",
"caniuse-lite": "^1.0.30001332",
"postcss": "8.4.5",
"styled-jsx": "5.0.2"
@@ -117,11 +117,11 @@
"@hapi/accept": "5.0.2",
"@napi-rs/cli": "2.4.4",
"@napi-rs/triples": "1.1.0",
- "@next/polyfill-module": "12.1.6",
- "@next/polyfill-nomodule": "12.1.6",
- "@next/react-dev-overlay": "12.1.6",
- "@next/react-refresh-utils": "12.1.6",
- "@next/swc": "12.1.6",
+ "@next/polyfill-module": "12.1.7-canary.2",
+ "@next/polyfill-nomodule": "12.1.7-canary.2",
+ "@next/react-dev-overlay": "12.1.7-canary.2",
+ "@next/react-refresh-utils": "12.1.7-canary.2",
+ "@next/swc": "12.1.7-canary.2",
"@peculiar/webcrypto": "1.3.1",
"@taskr/clear": "1.1.0",
"@taskr/esnext": "1.1.0",
@@ -252,6 +252,7 @@
"string-hash": "1.1.3",
"string_decoder": "1.3.0",
"strip-ansi": "6.0.0",
+ "tar": "6.1.11",
"taskr": "1.1.0",
"terser": "5.10.0",
"text-table": "0.2.0",
diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts
index f84d608e4c57..d80fd6fde386 100644
--- a/packages/next/server/base-server.ts
+++ b/packages/next/server/base-server.ts
@@ -70,6 +70,7 @@ import { createHeaderRoute, createRedirectRoute } from './server-route-utils'
import { PrerenderManifest } from '../build'
import { ImageConfigComplete } from '../shared/lib/image-config'
import { replaceBasePath } from './router-utils'
+import { normalizeViewPath } from '../shared/lib/router/utils/view-paths'
export type FindComponentsResult = {
components: LoadComponentsReturnType
@@ -141,6 +142,7 @@ export default abstract class Server {
protected publicDir: string
protected hasStaticDir: boolean
protected pagesManifest?: PagesManifest
+ protected viewPathsManifest?: PagesManifest
protected buildId: string
protected minimalMode: boolean
protected renderOpts: {
@@ -180,6 +182,7 @@ export default abstract class Server {
private responseCache: ResponseCache
protected router: Router
protected dynamicRoutes?: DynamicRoutes
+ protected viewPathRoutes?: Record
protected customRoutes: CustomRoutes
protected middlewareManifest?: MiddlewareManifest
protected middleware?: RoutingItem[]
@@ -190,6 +193,7 @@ export default abstract class Server {
protected abstract getPublicDir(): string
protected abstract getHasStaticDir(): boolean
protected abstract getPagesManifest(): PagesManifest | undefined
+ protected abstract getViewPathsManifest(): PagesManifest | undefined
protected abstract getBuildId(): string
protected abstract generatePublicRoutes(): Route[]
protected abstract generateImageRoutes(): Route[]
@@ -352,6 +356,7 @@ export default abstract class Server {
})
this.pagesManifest = this.getPagesManifest()
+ this.viewPathsManifest = this.getViewPathsManifest()
this.middlewareManifest = this.getMiddlewareManifest()
this.customRoutes = this.getCustomRoutes()
@@ -460,8 +465,10 @@ export default abstract class Server {
: matchedPathname
let srcPathname = isDataUrl
- ? parsedMatchedPath.pathname?.replace(/\.json$/, '') ||
- matchedPathnameNoExt
+ ? this.stripNextDataPath(
+ parsedMatchedPath.pathname?.replace(/\.json$/, '') ||
+ matchedPathnameNoExt
+ ) || '/'
: matchedPathnameNoExt
if (this.nextConfig.i18n) {
@@ -869,6 +876,7 @@ export default abstract class Server {
const { useFileSystemPublicRoutes } = this.nextConfig
if (useFileSystemPublicRoutes) {
+ this.viewPathRoutes = this.getViewPathRoutes()
this.dynamicRoutes = this.getDynamicRoutes()
if (!this.minimalMode) {
this.middleware = this.getMiddleware()
@@ -962,7 +970,10 @@ export default abstract class Server {
const addedPages = new Set()
return getSortedRoutes(
- Object.keys(this.pagesManifest!).map(
+ [
+ ...Object.keys(this.viewPathRoutes || {}),
+ ...Object.keys(this.pagesManifest!),
+ ].map(
(page) =>
normalizeLocalePath(page, this.nextConfig.i18n?.locales).pathname
)
@@ -978,6 +989,37 @@ export default abstract class Server {
.filter((item): item is RoutingItem => Boolean(item))
}
+ protected getViewPathRoutes(): Record {
+ const viewPathRoutes: Record = {}
+
+ Object.keys(this.viewPathsManifest || {}).forEach((entry) => {
+ viewPathRoutes[normalizeViewPath(entry)] = entry
+ })
+ return viewPathRoutes
+ }
+
+ protected getViewPathLayouts(pathname: string): string[] {
+ const layoutPaths: string[] = []
+
+ if (this.viewPathRoutes) {
+ const paths = Object.values(this.viewPathRoutes)
+ const parts = pathname.split('/').filter(Boolean)
+
+ for (let i = 1; i < parts.length; i++) {
+ const layoutPath = `/${parts.slice(0, i).join('/')}/layout`
+
+ if (paths.includes(layoutPath)) {
+ layoutPaths.push(layoutPath)
+ }
+ }
+
+ if (this.viewPathRoutes['/layout']) {
+ layoutPaths.unshift('/layout')
+ }
+ }
+ return layoutPaths
+ }
+
protected async run(
req: BaseNextRequest,
res: BaseNextResponse,
@@ -1276,21 +1318,6 @@ export default abstract class Server {
this.nextConfig.i18n?.locales
).pathname
- const stripNextDataPath = (path: string) => {
- if (path.includes(this.buildId)) {
- const splitPath = path.substring(
- path.indexOf(this.buildId) + this.buildId.length
- )
-
- path = denormalizePagePath(splitPath.replace(/\.json$/, ''))
- }
-
- if (this.nextConfig.i18n) {
- return normalizeLocalePath(path, locales).pathname
- }
- return path
- }
-
const handleRedirect = (pageData: any) => {
const redirect = {
destination: pageData.pageProps.__N_REDIRECT,
@@ -1321,8 +1348,8 @@ export default abstract class Server {
// remove /_next/data prefix from urlPathname so it matches
// for direct page visit and /_next/data visit
if (isDataReq) {
- resolvedUrlPathname = stripNextDataPath(resolvedUrlPathname)
- urlPathname = stripNextDataPath(urlPathname)
+ resolvedUrlPathname = this.stripNextDataPath(resolvedUrlPathname)
+ urlPathname = this.stripNextDataPath(urlPathname)
}
let ssgCacheKey =
@@ -1681,6 +1708,22 @@ export default abstract class Server {
}
}
+ private stripNextDataPath(path: string) {
+ if (path.includes(this.buildId)) {
+ const splitPath = path.substring(
+ path.indexOf(this.buildId) + this.buildId.length
+ )
+
+ path = denormalizePagePath(splitPath.replace(/\.json$/, ''))
+ }
+
+ if (this.nextConfig.i18n) {
+ const { locales } = this.nextConfig.i18n
+ return normalizeLocalePath(path, locales).pathname
+ }
+ return path
+ }
+
private async renderToResponse(
ctx: RequestContext
): Promise {
@@ -1688,14 +1731,56 @@ export default abstract class Server {
let page = pathname
const bubbleNoFallback = !!query._nextBubbleNoFallback
delete query._nextBubbleNoFallback
+ // map the route to the actual bundle name e.g.
+ // `/dashboard/rootonly/hello` -> `/dashboard+rootonly/hello`
+ const getOriginalViewPath = (viewPath: string) => {
+ if (this.nextConfig.experimental.viewsDir) {
+ const originalViewPath =
+ this.viewPathRoutes?.[`${viewPath}/index`] ||
+ this.viewPathRoutes?.[`${viewPath}`]
+
+ if (!originalViewPath) {
+ return null
+ }
+
+ return originalViewPath
+ }
+ return null
+ }
+
+ const gatherViewLayouts = async (
+ viewPath: string,
+ result: FindComponentsResult
+ ): Promise => {
+ const layoutPaths = this.getViewPathLayouts(viewPath)
+ result.components.viewLayouts = await Promise.all(
+ layoutPaths.map(async (path) => {
+ const layoutRes = await this.findPageComponents(path)
+ return {
+ isRootLayout: path === '/layout',
+ Component: layoutRes?.components.Component!,
+ getStaticProps: layoutRes?.components.getStaticProps,
+ getServerSideProps: layoutRes?.components.getServerSideProps,
+ }
+ })
+ )
+ }
try {
// Ensure a request to the URL /accounts/[id] will be treated as a dynamic
// route correctly and not loaded immediately without parsing params.
if (!isDynamicRoute(pathname)) {
- const result = await this.findPageComponents(pathname, query)
+ const viewPath = getOriginalViewPath(pathname)
+
+ if (typeof viewPath === 'string') {
+ page = viewPath
+ }
+ const result = await this.findPageComponents(page, query)
if (result) {
try {
+ if (result.components.isViewPath) {
+ await gatherViewLayouts(page, result)
+ }
return await this.renderToResponseWithComponents(ctx, result)
} catch (err) {
const isNoFallbackError = err instanceof NoFallbackError
@@ -1713,19 +1798,27 @@ export default abstract class Server {
if (!params) {
continue
}
+ page = dynamicRoute.page
+ const viewPath = getOriginalViewPath(page)
+
+ if (typeof viewPath === 'string') {
+ page = viewPath
+ }
const dynamicRouteResult = await this.findPageComponents(
- dynamicRoute.page,
+ page,
query,
params
)
if (dynamicRouteResult) {
try {
- page = dynamicRoute.page
+ if (dynamicRouteResult.components.isViewPath) {
+ await gatherViewLayouts(page, dynamicRouteResult)
+ }
return await this.renderToResponseWithComponents(
{
...ctx,
- pathname: dynamicRoute.page,
+ pathname: page,
renderOpts: {
...ctx.renderOpts,
params,
diff --git a/packages/next/server/config-shared.ts b/packages/next/server/config-shared.ts
index c90d19cebba8..8b1c10c8581d 100644
--- a/packages/next/server/config-shared.ts
+++ b/packages/next/server/config-shared.ts
@@ -5,6 +5,7 @@ import {
ImageConfig,
ImageConfigComplete,
imageConfigDefault,
+ RemotePattern,
} from '../shared/lib/image-config'
export type PageRuntime = 'nodejs' | 'edge' | undefined
@@ -95,7 +96,7 @@ export interface ExperimentalConfig {
scrollRestoration?: boolean
externalDir?: boolean
conformance?: boolean
- rootDir?: boolean
+ viewsDir?: boolean
amp?: {
optimizer?: any
validator?: string
@@ -115,6 +116,7 @@ export interface ExperimentalConfig {
outputStandalone?: boolean
images?: {
layoutRaw: boolean
+ remotePatterns: RemotePattern[]
}
middlewareSourceMaps?: boolean
emotion?:
@@ -132,6 +134,7 @@ export interface ExperimentalConfig {
skipDefaultConversion?: boolean
}
>
+ swcTraceProfiling?: boolean
}
/**
@@ -491,7 +494,7 @@ export const defaultConfig: NextConfig = {
swcFileReading: true,
craCompat: false,
esmExternals: true,
- rootDir: false,
+ viewsDir: false,
// default to 50MB limit
isrMemoryCacheSize: 50 * 1024 * 1024,
serverComponents: false,
@@ -500,6 +503,7 @@ export const defaultConfig: NextConfig = {
outputStandalone: !!process.env.NEXT_PRIVATE_STANDALONE,
images: {
layoutRaw: false,
+ remotePatterns: [],
},
},
}
diff --git a/packages/next/server/config.ts b/packages/next/server/config.ts
index 4ab419fa84fe..40b74df35c10 100644
--- a/packages/next/server/config.ts
+++ b/packages/next/server/config.ts
@@ -234,6 +234,43 @@ function assignDefaults(userConfig: { [key: string]: any }) {
)
}
}
+
+ const remotePatterns = result.experimental?.images?.remotePatterns
+ if (remotePatterns) {
+ if (!Array.isArray(remotePatterns)) {
+ throw new Error(
+ `Specified images.remotePatterns should be an Array received ${typeof remotePatterns}.\nSee more info here: https://nextjs.org/docs/messages/invalid-images-config`
+ )
+ }
+
+ if (remotePatterns.length > 50) {
+ throw new Error(
+ `Specified images.remotePatterns exceeds length of 50, received length (${remotePatterns.length}), please reduce the length of the array to continue.\nSee more info here: https://nextjs.org/docs/messages/invalid-images-config`
+ )
+ }
+
+ const validProps = new Set(['protocol', 'hostname', 'pathname', 'port'])
+ const requiredProps = ['hostname']
+ const invalidPatterns = remotePatterns.filter(
+ (d: unknown) =>
+ !d ||
+ typeof d !== 'object' ||
+ Object.entries(d).some(
+ ([k, v]) => !validProps.has(k) || typeof v !== 'string'
+ ) ||
+ requiredProps.some((k) => !(k in d))
+ )
+ if (invalidPatterns.length > 0) {
+ throw new Error(
+ `Invalid images.remotePatterns values:\n${invalidPatterns
+ .map((item) => JSON.stringify(item))
+ .join(
+ '\n'
+ )}\n\nremotePatterns value must follow format { protocol: 'https', hostname: 'example.com', port: '', pathname: '/imgs/**' }.\nSee more info here: https://nextjs.org/docs/messages/invalid-images-config`
+ )
+ }
+ }
+
if (images.deviceSizes) {
const { deviceSizes } = images
if (!Array.isArray(deviceSizes)) {
diff --git a/packages/next/server/dev/hot-reloader.ts b/packages/next/server/dev/hot-reloader.ts
index d17049d47966..b0035377844b 100644
--- a/packages/next/server/dev/hot-reloader.ts
+++ b/packages/next/server/dev/hot-reloader.ts
@@ -168,6 +168,7 @@ export default class HotReloader {
private fallbackWatcher: any
private hotReloaderSpan: Span
private pagesMapping: { [key: string]: string } = {}
+ private viewsDir?: string
constructor(
dir: string,
@@ -178,6 +179,7 @@ export default class HotReloader {
buildId,
previewProps,
rewrites,
+ viewsDir,
}: {
config: NextConfigComplete
pagesDir: string
@@ -185,12 +187,14 @@ export default class HotReloader {
buildId: string
previewProps: __ApiPreviewProps
rewrites: CustomRoutes['rewrites']
+ viewsDir?: string
}
) {
this.buildId = buildId
this.dir = dir
this.middlewares = []
this.pagesDir = pagesDir
+ this.viewsDir = viewsDir
this.distDir = distDir
this.clientStats = null
this.serverStats = null
@@ -423,6 +427,7 @@ export default class HotReloader {
pagesDir: this.pagesDir,
rewrites: this.rewrites,
runWebpackSpan: this.hotReloaderSpan,
+ viewsDir: this.viewsDir,
}
return webpackConfigSpan
@@ -834,6 +839,7 @@ export default class HotReloader {
multiCompiler,
watcher: this.watcher,
pagesDir: this.pagesDir,
+ viewsDir: this.viewsDir,
nextConfig: this.config,
...(this.config.onDemandEntries as {
maxInactiveAge: number
diff --git a/packages/next/server/dev/next-dev-server.ts b/packages/next/server/dev/next-dev-server.ts
index c935563c58b0..7a5c28597ddd 100644
--- a/packages/next/server/dev/next-dev-server.ts
+++ b/packages/next/server/dev/next-dev-server.ts
@@ -65,6 +65,8 @@ import { getMiddlewareRegex } from '../../shared/lib/router/utils/get-middleware
import { isCustomErrorPage, isReservedPage } from '../../build/utils'
import { NodeNextResponse, NodeNextRequest } from '../base-http/node'
import { getPageRuntime, invalidatePageRuntimeCache } from '../../build/entries'
+import { normalizePathSep } from '../../shared/lib/page-path/normalize-path-sep'
+import { normalizeViewPath } from '../../shared/lib/router/utils/view-paths'
// Load ReactDevOverlay only when needed
let ReactDevOverlayImpl: React.FunctionComponent
@@ -96,8 +98,7 @@ export default class DevServer extends Server {
protected sortedRoutes?: string[]
private addedUpgradeListener = false
private pagesDir: string
- // @ts-ignore TODO: add implementation
- private rootDir?: string
+ private viewsDir?: string
protected staticPathsWorker?: { [key: string]: any } & {
loadStaticPaths: typeof import('./static-paths-worker').loadStaticPaths
@@ -178,12 +179,12 @@ export default class DevServer extends Server {
this.isCustomServer = !options.isNextDevCommand
// TODO: hot-reload root/pages dirs?
- const { pages: pagesDir, root: rootDir } = findPagesDir(
+ const { pages: pagesDir, views: viewsDir } = findPagesDir(
this.dir,
- this.nextConfig.experimental.rootDir
+ this.nextConfig.experimental.viewsDir
)
this.pagesDir = pagesDir
- this.rootDir = rootDir
+ this.viewsDir = viewsDir
}
protected getBuildId(): string {
@@ -264,24 +265,61 @@ export default class DevServer extends Server {
})
let wp = (this.webpackWatcher = new Watchpack())
- wp.watch([], [this.pagesDir], 0)
+ const toWatch = [this.pagesDir!]
+
+ if (this.viewsDir) {
+ toWatch.push(this.viewsDir)
+ }
+ wp.watch([], toWatch, 0)
wp.on('aggregated', async () => {
const routedMiddleware = []
- const routedPages = []
+ const routedPages: string[] = []
const knownFiles = wp.getTimeInfoEntries()
+ const viewPaths: Record = {}
const ssrMiddleware = new Set()
for (const [fileName, { accuracy, safeTime }] of knownFiles) {
if (accuracy === undefined || !regexPageExtension.test(fileName)) {
continue
}
+ let pageName: string = ''
+ let isViewPath = false
- const pageName = absolutePathToPage(
- this.pagesDir,
- fileName,
- this.nextConfig.pageExtensions
- )
+ if (
+ this.viewsDir &&
+ normalizePathSep(fileName).startsWith(
+ normalizePathSep(this.viewsDir)
+ )
+ ) {
+ isViewPath = true
+ pageName = absolutePathToPage(
+ this.viewsDir,
+ fileName,
+ this.nextConfig.pageExtensions,
+ false
+ )
+ } else {
+ pageName = absolutePathToPage(
+ this.pagesDir,
+ fileName,
+ this.nextConfig.pageExtensions
+ )
+ }
+
+ if (isViewPath) {
+ // TODO: should only routes ending in /index.js be route-able?
+ const originalPageName = pageName
+ pageName = normalizeViewPath(pageName)
+ viewPaths[pageName] = originalPageName
+
+ if (routedPages.includes(pageName)) {
+ continue
+ }
+ } else {
+ // /index is preserved for root folder
+ pageName = pageName.replace(/\/index$/, '') || '/'
+ }
if (regexMiddleware.test(fileName)) {
routedMiddleware.push(
@@ -310,6 +348,7 @@ export default class DevServer extends Server {
routedPages.push(pageName)
}
+ this.viewPathRoutes = viewPaths
this.middleware = getSortedRoutes(routedMiddleware).map((page) => ({
match: getRouteMatcher(
getMiddlewareRegex(page, !ssrMiddleware.has(page))
@@ -371,7 +410,7 @@ export default class DevServer extends Server {
setGlobal('phase', PHASE_DEVELOPMENT_SERVER)
await verifyTypeScriptSetup(
this.dir,
- [this.pagesDir!, this.rootDir].filter(Boolean) as string[],
+ [this.pagesDir!, this.viewsDir].filter(Boolean) as string[],
false,
this.nextConfig
)
@@ -398,6 +437,7 @@ export default class DevServer extends Server {
previewProps: this.getPreviewProps(),
buildId: this.buildId,
rewrites,
+ viewsDir: this.viewsDir,
})
await super.prepare()
await this.addExportPathMapRoutes()
@@ -445,7 +485,6 @@ export default class DevServer extends Server {
protected async hasPage(pathname: string): Promise {
let normalizedPath: string
-
try {
normalizedPath = normalizePagePath(pathname)
} catch (err) {
@@ -456,6 +495,16 @@ export default class DevServer extends Server {
return false
}
+ // check viewsDir first if enabled
+ if (this.viewsDir) {
+ const pageFile = await findPageFile(
+ this.viewsDir,
+ normalizedPath,
+ this.nextConfig.pageExtensions
+ )
+ if (pageFile) return true
+ }
+
const pageFile = await findPageFile(
this.pagesDir,
normalizedPath,
@@ -737,6 +786,10 @@ export default class DevServer extends Server {
return undefined
}
+ protected getViewPathsManifest(): undefined {
+ return undefined
+ }
+
protected getMiddleware(): never[] {
return []
}
diff --git a/packages/next/server/dev/on-demand-entry-handler.ts b/packages/next/server/dev/on-demand-entry-handler.ts
index 08eabdff3a53..116136adb030 100644
--- a/packages/next/server/dev/on-demand-entry-handler.ts
+++ b/packages/next/server/dev/on-demand-entry-handler.ts
@@ -54,6 +54,7 @@ export function onDemandEntryHandler({
nextConfig,
pagesBufferLength,
pagesDir,
+ viewsDir,
watcher,
}: {
maxInactiveAge: number
@@ -61,6 +62,7 @@ export function onDemandEntryHandler({
nextConfig: NextConfigComplete
pagesBufferLength: number
pagesDir: string
+ viewsDir?: string
watcher: any
}) {
const invalidator = new Invalidator(watcher)
@@ -78,13 +80,16 @@ export function onDemandEntryHandler({
function getPagePathsFromEntrypoints(
type: 'client' | 'server' | 'edge-server',
- entrypoints: Map
+ entrypoints: Map,
+ root?: boolean
) {
const pagePaths: string[] = []
for (const entrypoint of entrypoints.values()) {
- const page = getRouteFromEntrypoint(entrypoint.name!)
+ const page = getRouteFromEntrypoint(entrypoint.name!, root)
if (page) {
pagePaths.push(`${type}${page}`)
+ } else if (root && entrypoint.name === 'root') {
+ pagePaths.push(`${type}/${entrypoint.name}`)
}
}
@@ -96,19 +101,23 @@ export function onDemandEntryHandler({
return invalidator.doneBuilding()
}
const [clientStats, serverStats, edgeServerStats] = multiStats.stats
+ const root = !!viewsDir
const pagePaths = [
...getPagePathsFromEntrypoints(
'client',
- clientStats.compilation.entrypoints
+ clientStats.compilation.entrypoints,
+ root
),
...getPagePathsFromEntrypoints(
'server',
- serverStats.compilation.entrypoints
+ serverStats.compilation.entrypoints,
+ root
),
...(edgeServerStats
? getPagePathsFromEntrypoints(
'edge-server',
- edgeServerStats.compilation.entrypoints
+ edgeServerStats.compilation.entrypoints,
+ root
)
: []),
]
@@ -172,7 +181,8 @@ export function onDemandEntryHandler({
const pagePathData = await findPagePathData(
pagesDir,
page,
- nextConfig.pageExtensions
+ nextConfig.pageExtensions,
+ viewsDir
)
let entryAdded = false
@@ -329,18 +339,37 @@ class Invalidator {
async function findPagePathData(
pagesDir: string,
page: string,
- extensions: string[]
+ extensions: string[],
+ viewsDir?: string
) {
const normalizedPagePath = tryToNormalizePagePath(page)
- const pagePath = await findPageFile(pagesDir, normalizedPagePath, extensions)
+ let pagePath: string | null = null
+ let isView = false
+
+ // check viewsDir first
+ if (viewsDir) {
+ pagePath = await findPageFile(viewsDir, normalizedPagePath, extensions)
+
+ if (pagePath) {
+ isView = true
+ }
+ }
+
+ if (!pagePath) {
+ pagePath = await findPageFile(pagesDir, normalizedPagePath, extensions)
+ }
+
if (pagePath !== null) {
const pageUrl = ensureLeadingSlash(
- removePagePathTail(normalizePathSep(pagePath), extensions)
+ removePagePathTail(normalizePathSep(pagePath), extensions, !isView)
)
+ const bundleFile = normalizePagePath(pageUrl)
+ const bundlePath = posix.join(isView ? 'views' : 'pages', bundleFile)
+ const absolutePagePath = join(isView ? viewsDir! : pagesDir, pagePath)
return {
- absolutePagePath: join(pagesDir, pagePath),
- bundlePath: posix.join('pages', normalizePagePath(pageUrl)),
+ absolutePagePath,
+ bundlePath,
page: posix.normalize(pageUrl),
}
}
diff --git a/packages/next/server/get-page-files.ts b/packages/next/server/get-page-files.ts
index 5e6945cbdf53..229729b3e397 100644
--- a/packages/next/server/get-page-files.ts
+++ b/packages/next/server/get-page-files.ts
@@ -6,6 +6,7 @@ export type BuildManifest = {
ampDevFiles: readonly string[]
polyfillFiles: readonly string[]
lowPriorityFiles: readonly string[]
+ rootMainFiles: readonly string[]
pages: {
'/_app': readonly string[]
[page: string]: readonly string[]
diff --git a/packages/next/server/get-route-from-entrypoint.ts b/packages/next/server/get-route-from-entrypoint.ts
index c412e2d886cc..30e40e9858a3 100644
--- a/packages/next/server/get-route-from-entrypoint.ts
+++ b/packages/next/server/get-route-from-entrypoint.ts
@@ -2,6 +2,8 @@ import getRouteFromAssetPath from '../shared/lib/router/utils/get-route-from-ass
// matches pages/:page*.js
const SERVER_ROUTE_NAME_REGEX = /^pages[/\\](.*)$/
+// matches root/:path*.js
+const ROOT_ROUTE_NAME_REGEX = /^views[/\\](.*)$/
// matches static/pages/:page*.js
const BROWSER_ROUTE_NAME_REGEX = /^static[/\\]pages[/\\](.*)$/
@@ -16,7 +18,8 @@ function matchBundle(regex: RegExp, input: string): string | null {
}
export default function getRouteFromEntrypoint(
- entryFile: string
+ entryFile: string,
+ root?: boolean
): string | null {
let pagePath = matchBundle(SERVER_ROUTE_NAME_REGEX, entryFile)
@@ -24,6 +27,11 @@ export default function getRouteFromEntrypoint(
return pagePath
}
+ if (root) {
+ pagePath = matchBundle(ROOT_ROUTE_NAME_REGEX, entryFile)
+ if (pagePath) return pagePath
+ }
+
// Potentially the passed item is a browser bundle so we try to match that also
return matchBundle(BROWSER_ROUTE_NAME_REGEX, entryFile)
}
diff --git a/packages/next/server/image-optimizer.ts b/packages/next/server/image-optimizer.ts
index ce8017b066f0..f177efbe4599 100644
--- a/packages/next/server/image-optimizer.ts
+++ b/packages/next/server/image-optimizer.ts
@@ -17,6 +17,7 @@ import chalk from 'next/dist/compiled/chalk'
import { NextUrlWithParsedQuery } from './request-meta'
import { IncrementalCacheEntry, IncrementalCacheValue } from './response-cache'
import { mockRequest } from './lib/mock-request'
+import { hasMatch } from '../shared/lib/match-remote-pattern'
type XCacheHeader = 'MISS' | 'HIT' | 'STALE'
@@ -75,6 +76,7 @@ export class ImageOptimizerCache {
minimumCacheTTL = 60,
formats = ['image/webp'],
} = imageData
+ const remotePatterns = nextConfig.experimental.images?.remotePatterns || []
const { url, w, q } = query
let href: string
@@ -104,7 +106,7 @@ export class ImageOptimizerCache {
return { errorMessage: '"url" parameter is invalid' }
}
- if (!domains || !domains.includes(hrefParsed.hostname)) {
+ if (!hasMatch(domains, remotePatterns, hrefParsed)) {
return { errorMessage: '"url" parameter is not allowed' }
}
}
diff --git a/packages/next/server/load-components.ts b/packages/next/server/load-components.ts
index 881598c769ee..7f44ccac93f1 100644
--- a/packages/next/server/load-components.ts
+++ b/packages/next/server/load-components.ts
@@ -9,7 +9,7 @@ import {
MIDDLEWARE_FLIGHT_MANIFEST,
} from '../shared/lib/constants'
import { join } from 'path'
-import { requirePage } from './require'
+import { requirePage, getPagePath } from './require'
import { BuildManifest } from './get-page-files'
import { interopDefault } from '../lib/interop-default'
import {
@@ -40,6 +40,13 @@ export type LoadComponentsReturnType = {
ComponentMod: any
AppMod: any
AppServerMod: any
+ isViewPath?: boolean
+ viewLayouts?: Array<{
+ isRootLayout?: boolean
+ Component: NextComponentType
+ getStaticProps?: GetStaticProps
+ getServerSideProps?: GetServerSideProps
+ }>
}
export async function loadDefaultErrorComponents(distDir: string) {
@@ -67,7 +74,8 @@ export async function loadComponents(
distDir: string,
pathname: string,
serverless: boolean,
- serverComponents?: boolean
+ serverComponents?: boolean,
+ rootEnabled?: boolean
): Promise {
if (serverless) {
const ComponentMod = await requirePage(pathname, distDir, serverless)
@@ -103,10 +111,20 @@ export async function loadComponents(
}
const [DocumentMod, AppMod, ComponentMod, AppServerMod] = await Promise.all([
- requirePage('/_document', distDir, serverless),
- requirePage('/_app', distDir, serverless),
- requirePage(pathname, distDir, serverless),
- serverComponents ? requirePage('/_app.server', distDir, serverless) : null,
+ Promise.resolve().then(() =>
+ requirePage('/_document', distDir, serverless, rootEnabled)
+ ),
+ Promise.resolve().then(() =>
+ requirePage('/_app', distDir, serverless, rootEnabled)
+ ),
+ Promise.resolve().then(() =>
+ requirePage(pathname, distDir, serverless, rootEnabled)
+ ),
+ serverComponents
+ ? Promise.resolve().then(() =>
+ requirePage('/_app.server', distDir, serverless, rootEnabled)
+ )
+ : null,
])
const [buildManifest, reactLoadableManifest, serverComponentManifest] =
@@ -124,6 +142,20 @@ export async function loadComponents(
const { getServerSideProps, getStaticProps, getStaticPaths } = ComponentMod
+ let isViewPath = false
+
+ if (rootEnabled) {
+ const pagePath = getPagePath(
+ pathname,
+ distDir,
+ serverless,
+ false,
+ undefined,
+ rootEnabled
+ )
+ isViewPath = !!pagePath?.match(/server[/\\]views[/\\]/)
+ }
+
return {
App,
Document,
@@ -138,5 +170,6 @@ export async function loadComponents(
getStaticProps,
getStaticPaths,
serverComponentManifest,
+ isViewPath,
}
}
diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts
index c0a5e91ccef7..3ce99c042e62 100644
--- a/packages/next/server/next-server.ts
+++ b/packages/next/server/next-server.ts
@@ -31,6 +31,7 @@ import {
ROUTES_MANIFEST,
MIDDLEWARE_FLIGHT_MANIFEST,
CLIENT_PUBLIC_FILES_PATH,
+ VIEW_PATHS_MANIFEST,
} from '../shared/lib/constants'
import { recursiveReadDirSync } from './lib/recursive-readdir-sync'
import { format as formatUrl, UrlWithParsedQuery } from 'url'
@@ -45,6 +46,7 @@ import { getExtension, serveStatic } from './serve-static'
import { ParsedUrlQuery } from 'querystring'
import { apiResolver } from './api-utils/node'
import { RenderOpts, renderToHTML } from './render'
+import { renderToHTML as viewRenderToHTML } from './view-render'
import { ParsedUrl, parseUrl } from '../shared/lib/router/utils/parse-url'
import * as Log from '../build/output/log'
@@ -124,7 +126,7 @@ export default class NextNodeServer extends BaseServer {
)
}
- if (!this.renderOpts.dev) {
+ if (!options.dev) {
// pre-warm _document and _app as these will be
// needed for most requests
loadComponents(this.distDir, '/_document', this._isLikeServerless).catch(
@@ -157,6 +159,16 @@ export default class NextNodeServer extends BaseServer {
return require(join(this.serverDistDir, PAGES_MANIFEST))
}
+ protected getViewPathsManifest(): PagesManifest | undefined {
+ if (this.nextConfig.experimental.viewsDir) {
+ const viewPathsManifestPath = join(
+ this.serverDistDir,
+ VIEW_PATHS_MANIFEST
+ )
+ return require(viewPathsManifestPath)
+ }
+ }
+
protected getBuildId(): string {
const buildIdFile = join(this.distDir, BUILD_ID_FILE)
try {
@@ -572,6 +584,16 @@ export default class NextNodeServer extends BaseServer {
// https://github.com/vercel/next.js/blob/df7cbd904c3bd85f399d1ce90680c0ecf92d2752/packages/next/server/render.tsx#L947-L952
renderOpts.serverComponentManifest = this.serverComponentManifest
+ if (renderOpts.isViewPath) {
+ return viewRenderToHTML(
+ req.originalRequest,
+ res.originalResponse,
+ pathname,
+ query,
+ renderOpts
+ )
+ }
+
return renderToHTML(
req.originalRequest,
res.originalResponse,
@@ -619,7 +641,8 @@ export default class NextNodeServer extends BaseServer {
this.distDir,
this._isLikeServerless,
this.renderOpts.dev,
- locales
+ locales,
+ this.nextConfig.experimental.viewsDir
)
}
@@ -649,7 +672,8 @@ export default class NextNodeServer extends BaseServer {
this.distDir,
pagePath!,
!this.renderOpts.dev && this._isLikeServerless,
- this.renderOpts.serverComponents
+ this.renderOpts.serverComponents,
+ this.nextConfig.experimental.viewsDir
)
if (
diff --git a/packages/next/server/next.ts b/packages/next/server/next.ts
index 31f6f7502438..f2d46252a9a6 100644
--- a/packages/next/server/next.ts
+++ b/packages/next/server/next.ts
@@ -183,6 +183,14 @@ function createServer(options: NextServerOptions): NextServer {
)
}
+ // Make sure env of custom server is overridden.
+ // Use dynamic require to make sure it's executed in it's own context.
+ const ReactDOMServer = require('react-dom/server.browser')
+ const shouldUseReactRoot = !!ReactDOMServer.renderToReadableStream
+ if (shouldUseReactRoot) {
+ ;(process.env as any).__NEXT_REACT_ROOT = 'true'
+ }
+
return new NextServer(options)
}
diff --git a/packages/next/server/require.ts b/packages/next/server/require.ts
index ee436ab9b64e..c5bebb5aca48 100644
--- a/packages/next/server/require.ts
+++ b/packages/next/server/require.ts
@@ -6,6 +6,7 @@ import {
PAGES_MANIFEST,
SERVER_DIRECTORY,
SERVERLESS_DIRECTORY,
+ VIEW_PATHS_MANIFEST,
} from '../shared/lib/constants'
import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path'
import { normalizePagePath } from '../shared/lib/page-path/normalize-page-path'
@@ -25,12 +26,21 @@ export function getPagePath(
distDir: string,
serverless: boolean,
dev?: boolean,
- locales?: string[]
+ locales?: string[],
+ rootEnabled?: boolean
): string {
const serverBuildPath = join(
distDir,
serverless && !dev ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY
)
+ let rootPathsManifest: undefined | PagesManifest
+
+ if (rootEnabled) {
+ if (page === '/_root') {
+ return join(serverBuildPath, 'root.js')
+ }
+ rootPathsManifest = require(join(serverBuildPath, VIEW_PATHS_MANIFEST))
+ }
const pagesManifest = require(join(
serverBuildPath,
PAGES_MANIFEST
@@ -42,31 +52,51 @@ export function getPagePath(
console.error(err)
throw pageNotFoundError(page)
}
- let pagePath = pagesManifest[page]
- if (!pagesManifest[page] && locales) {
- const manifestNoLocales: typeof pagesManifest = {}
+ const checkManifest = (manifest: PagesManifest) => {
+ let curPath = manifest[page]
+
+ if (!manifest[curPath] && locales) {
+ const manifestNoLocales: typeof pagesManifest = {}
- for (const key of Object.keys(pagesManifest)) {
- manifestNoLocales[normalizeLocalePath(key, locales).pathname] =
- pagesManifest[key]
+ for (const key of Object.keys(manifest)) {
+ manifestNoLocales[normalizeLocalePath(key, locales).pathname] =
+ pagesManifest[key]
+ }
+ curPath = manifestNoLocales[page]
}
- pagePath = manifestNoLocales[page]
+ return curPath
+ }
+ let pagePath: string | undefined
+
+ if (rootPathsManifest) {
+ pagePath = checkManifest(rootPathsManifest)
}
if (!pagePath) {
- throw pageNotFoundError(page)
+ pagePath = checkManifest(pagesManifest)
}
+ if (!pagePath) {
+ throw pageNotFoundError(page)
+ }
return join(serverBuildPath, pagePath)
}
export function requirePage(
page: string,
distDir: string,
- serverless: boolean
+ serverless: boolean,
+ rootEnabled?: boolean
): any {
- const pagePath = getPagePath(page, distDir, serverless)
+ const pagePath = getPagePath(
+ page,
+ distDir,
+ serverless,
+ false,
+ undefined,
+ rootEnabled
+ )
if (pagePath.endsWith('.html')) {
return promises.readFile(pagePath, 'utf8')
}
diff --git a/packages/next/server/view-render.tsx b/packages/next/server/view-render.tsx
new file mode 100644
index 000000000000..d8bedf081b0f
--- /dev/null
+++ b/packages/next/server/view-render.tsx
@@ -0,0 +1,498 @@
+import type { IncomingMessage, ServerResponse } from 'http'
+import type { LoadComponentsReturnType } from './load-components'
+
+import React from 'react'
+import { ParsedUrlQuery, stringify as stringifyQuery } from 'querystring'
+import { createFromReadableStream } from 'next/dist/compiled/react-server-dom-webpack'
+import { renderToReadableStream } from 'next/dist/compiled/react-server-dom-webpack/writer.browser.server'
+import { StyleRegistry, createStyleRegistry } from 'styled-jsx'
+import { NextParsedUrlQuery } from './request-meta'
+import RenderResult from './render-result'
+import {
+ readableStreamTee,
+ encodeText,
+ decodeText,
+ renderToInitialStream,
+ createBufferedTransformStream,
+ continueFromInitialStream,
+} from './node-web-streams-helper'
+import { FlushEffectsContext } from '../shared/lib/flush-effects'
+// @ts-ignore react-dom/client exists when using React 18
+import ReactDOMServer from 'react-dom/server.browser'
+import { isDynamicRoute } from '../shared/lib/router/utils'
+import { tryGetPreviewData } from './api-utils/node'
+import DefaultRootLayout from '../lib/views-layout'
+
+export type RenderOptsPartial = {
+ err?: Error | null
+ dev?: boolean
+ serverComponentManifest?: any
+ supportsDynamicHTML?: boolean
+ runtime?: 'nodejs' | 'edge'
+ serverComponents?: boolean
+ reactRoot: boolean
+}
+
+export type RenderOpts = LoadComponentsReturnType & RenderOptsPartial
+
+const rscCache = new Map()
+
+// Shadowing check does not work with TypeScript enums
+// eslint-disable-next-line no-shadow
+const enum RecordStatus {
+ Pending,
+ Resolved,
+ Rejected,
+}
+
+type Record = {
+ status: RecordStatus
+ value: any
+}
+
+function createRecordFromThenable(thenable: Promise) {
+ const record: Record = {
+ status: RecordStatus.Pending,
+ value: thenable,
+ }
+ thenable.then(
+ function (value) {
+ if (record.status === RecordStatus.Pending) {
+ const resolvedRecord = record
+ resolvedRecord.status = RecordStatus.Resolved
+ resolvedRecord.value = value
+ }
+ },
+ function (err) {
+ if (record.status === RecordStatus.Pending) {
+ const rejectedRecord = record
+ rejectedRecord.status = RecordStatus.Rejected
+ rejectedRecord.value = err
+ }
+ }
+ )
+ return record
+}
+
+function readRecordValue(record: Record) {
+ if (record.status === RecordStatus.Resolved) {
+ return record.value
+ } else {
+ throw record.value
+ }
+}
+
+function preloadDataFetchingRecord(
+ map: Map,
+ key: string,
+ fetcher: () => Promise | any
+) {
+ let record = map.get(key)
+
+ if (!record) {
+ const thenable = fetcher()
+ record = createRecordFromThenable(thenable)
+ map.set(key, record)
+ }
+
+ return record
+}
+
+function createFlightHook() {
+ return (
+ writable: WritableStream,
+ id: string,
+ req: ReadableStream,
+ bootstrap: boolean
+ ) => {
+ let entry = rscCache.get(id)
+ if (!entry) {
+ const [renderStream, forwardStream] = readableStreamTee(req)
+ entry = createFromReadableStream(renderStream)
+ rscCache.set(id, entry)
+
+ let bootstrapped = false
+ const forwardReader = forwardStream.getReader()
+ const writer = writable.getWriter()
+ function process() {
+ forwardReader.read().then(({ done, value }) => {
+ if (bootstrap && !bootstrapped) {
+ bootstrapped = true
+ writer.write(
+ encodeText(
+ ``
+ )
+ )
+ }
+ if (done) {
+ rscCache.delete(id)
+ writer.close()
+ } else {
+ writer.write(
+ encodeText(
+ ``
+ )
+ )
+ process()
+ }
+ })
+ }
+ process()
+ }
+ return entry
+ }
+}
+
+const useFlightResponse = createFlightHook()
+
+// Create the wrapper component for a Flight stream.
+function createServerComponentRenderer(
+ ComponentToRender: React.ComponentType,
+ ComponentMod: any,
+ {
+ cachePrefix,
+ transformStream,
+ serverComponentManifest,
+ }: {
+ cachePrefix: string
+ transformStream: TransformStream
+ serverComponentManifest: NonNullable
+ }
+) {
+ // We need to expose the `__webpack_require__` API globally for
+ // react-server-dom-webpack. This is a hack until we find a better way.
+ if (ComponentMod.__next_rsc__) {
+ // @ts-ignore
+ globalThis.__webpack_require__ =
+ ComponentMod.__next_rsc__.__webpack_require__
+
+ // @ts-ignore
+ globalThis.__webpack_chunk_load__ = () => Promise.resolve()
+ }
+
+ const writable = transformStream.writable
+ const ServerComponentWrapper = (props: any) => {
+ const id = (React as any).useId()
+ const reqStream: ReadableStream = renderToReadableStream(
+ ,
+ serverComponentManifest
+ )
+
+ const response = useFlightResponse(
+ writable,
+ cachePrefix + ',' + id,
+ reqStream,
+ true
+ )
+ const root = response.readRoot()
+ rscCache.delete(id)
+ return root
+ }
+
+ return ServerComponentWrapper
+}
+
+export async function renderToHTML(
+ req: IncomingMessage,
+ res: ServerResponse,
+ pathname: string,
+ query: NextParsedUrlQuery,
+ renderOpts: RenderOpts
+): Promise {
+ // don't modify original query object
+ query = Object.assign({}, query)
+
+ const {
+ buildManifest,
+ serverComponentManifest,
+ supportsDynamicHTML,
+ runtime,
+ ComponentMod,
+ } = renderOpts
+
+ const hasConcurrentFeatures = !!runtime
+ const pageIsDynamic = isDynamicRoute(pathname)
+ const layouts = renderOpts.viewLayouts || []
+
+ layouts.push({
+ Component: renderOpts.Component,
+ getStaticProps: renderOpts.getStaticProps,
+ getServerSideProps: renderOpts.getServerSideProps,
+ })
+
+ // Reads of this are cached on the `req` object, so this should resolve
+ // instantly. There's no need to pass this data down from a previous
+ // invoke, where we'd have to consider server & serverless.
+ const previewData = tryGetPreviewData(
+ req,
+ res,
+ (renderOpts as any).previewProps
+ )
+ const isPreview = previewData !== false
+
+ let WrappedComponent: any
+ let RootLayout: any
+
+ const dataCache = new Map()
+
+ for (let i = layouts.length - 1; i >= 0; i--) {
+ const dataCacheKey = i.toString()
+ const layout = layouts[i]
+
+ if (layout.isRootLayout) {
+ RootLayout = layout.Component
+ continue
+ }
+ let fetcher: any
+
+ // TODO: pass a shared cache from previous getStaticProps/
+ // getServerSideProps calls?
+ if (layout.getServerSideProps) {
+ fetcher = () =>
+ Promise.resolve(
+ layout.getServerSideProps!({
+ req: req as any,
+ res: res,
+ query,
+ resolvedUrl: (renderOpts as any).resolvedUrl as string,
+ ...(pageIsDynamic
+ ? { params: (renderOpts as any).params as ParsedUrlQuery }
+ : undefined),
+ ...(isPreview
+ ? { preview: true, previewData: previewData }
+ : undefined),
+ locales: (renderOpts as any).locales,
+ locale: (renderOpts as any).locale,
+ defaultLocale: (renderOpts as any).defaultLocale,
+ })
+ )
+ }
+ // TODO: implement layout specific caching for getStaticProps
+ if (layout.getStaticProps) {
+ fetcher = () =>
+ Promise.resolve(
+ layout.getStaticProps!({
+ ...(pageIsDynamic
+ ? { params: query as ParsedUrlQuery }
+ : undefined),
+ ...(isPreview
+ ? { preview: true, previewData: previewData }
+ : undefined),
+ locales: (renderOpts as any).locales,
+ locale: (renderOpts as any).locale,
+ defaultLocale: (renderOpts as any).defaultLocale,
+ })
+ )
+ }
+
+ if (fetcher) {
+ // Kick off data fetching before rendering, this ensures there is no waterfall for layouts as
+ // all data fetching required to render the page is kicked off simultaneously
+ preloadDataFetchingRecord(dataCache, dataCacheKey, fetcher)
+ }
+
+ // eslint-disable-next-line no-loop-func
+ const lastComponent = WrappedComponent
+ WrappedComponent = () => {
+ let props: any
+ if (fetcher) {
+ // The data fetching was kicked off before rendering (see above)
+ // if the data was not resolved yet the layout rendering will be suspended
+ const record = preloadDataFetchingRecord(
+ dataCache,
+ dataCacheKey,
+ fetcher
+ )
+ // Result of calling getStaticProps or getServerSideProps. If promise is not resolve yet it will suspend.
+ const recordValue = readRecordValue(record)
+ props = recordValue.props
+ }
+
+ return React.createElement(
+ layout.Component,
+ props,
+ React.createElement(lastComponent || React.Fragment, {}, null)
+ )
+ }
+ // TODO: loading state
+ // const AfterWrap = WrappedComponent
+ // WrappedComponent = () => {
+ // return (
+ // Loading...>}>
+ //
+ //
+ // )
+ // }
+ }
+
+ if (!RootLayout) {
+ // TODO: fallback to our own root layout?
+ // throw new Error('invariant RootLayout not loaded')
+ RootLayout = DefaultRootLayout
+ }
+
+ const headChildren = buildManifest.rootMainFiles.map((src) => (
+
+ ))
+
+ let serverComponentsInlinedTransformStream: TransformStream<
+ Uint8Array,
+ Uint8Array
+ > | null = null
+
+ serverComponentsInlinedTransformStream = new TransformStream()
+ const search = stringifyQuery(query)
+
+ const Component = createServerComponentRenderer(RootLayout, ComponentMod, {
+ cachePrefix: pathname + (search ? `?${search}` : ''),
+ transformStream: serverComponentsInlinedTransformStream,
+ serverComponentManifest,
+ })
+
+ // const serverComponentProps = query.__props__
+ // ? JSON.parse(query.__props__ as string)
+ // : undefined
+
+ const jsxStyleRegistry = createStyleRegistry()
+
+ const styledJsxFlushEffect = () => {
+ const styles = jsxStyleRegistry.styles()
+ jsxStyleRegistry.flush()
+ return <>{styles}>
+ }
+
+ let flushEffects: Array<() => React.ReactNode> | null = null
+ function FlushEffectContainer({ children }: { children: JSX.Element }) {
+ // If the client tree suspends, this component will be rendered multiple
+ // times before we flush. To ensure we don't call old callbacks corresponding
+ // to a previous render, we clear any registered callbacks whenever we render.
+ flushEffects = null
+
+ const flushEffectsImpl = React.useCallback(
+ (callbacks: Array<() => React.ReactNode>) => {
+ if (flushEffects) {
+ throw new Error(
+ 'The `useFlushEffects` hook cannot be used more than once.' +
+ '\nRead more: https://nextjs.org/docs/messages/multiple-flush-effects'
+ )
+ }
+ flushEffects = callbacks
+ },
+ []
+ )
+
+ return (
+
+ {children}
+
+ )
+ }
+
+ const AppContainer = ({ children }: { children: JSX.Element }) => (
+
+ {children}
+
+ )
+
+ const renderServerComponentData = query.__flight__ !== undefined
+ if (renderServerComponentData) {
+ return new RenderResult(
+ renderToReadableStream(
+ }
+ />,
+ serverComponentManifest
+ ).pipeThrough(createBufferedTransformStream())
+ )
+ }
+
+ /**
+ * Rules of Static & Dynamic HTML:
+ *
+ * 1.) We must generate static HTML unless the caller explicitly opts
+ * in to dynamic HTML support.
+ *
+ * 2.) If dynamic HTML support is requested, we must honor that request
+ * or throw an error. It is the sole responsibility of the caller to
+ * ensure they aren't e.g. requesting dynamic HTML for an AMP page.
+ *
+ * These rules help ensure that other existing features like request caching,
+ * coalescing, and ISR continue working as intended.
+ */
+ const generateStaticHTML = supportsDynamicHTML !== true
+ const bodyResult = async () => {
+ const content = (
+
+ }
+ />
+
+ )
+
+ const renderStream = await renderToInitialStream({
+ ReactDOMServer,
+ element: content,
+ })
+
+ const flushEffectHandler = (): string => {
+ const allFlushEffects = [styledJsxFlushEffect, ...(flushEffects || [])]
+ const flushed = ReactDOMServer.renderToString(
+ <>
+ {allFlushEffects.map((flushEffect, i) => (
+ {flushEffect()}
+ ))}
+ >
+ )
+ return flushed
+ }
+
+ // Handle static data for server components.
+ // async function generateStaticFlightDataIfNeeded() {
+ // if (serverComponentsPageDataTransformStream) {
+ // // If it's a server component with the Node.js runtime, we also
+ // // statically generate the page data.
+ // let data = ''
+
+ // const readable = serverComponentsPageDataTransformStream.readable
+ // const reader = readable.getReader()
+ // const textDecoder = new TextDecoder()
+
+ // while (true) {
+ // const { done, value } = await reader.read()
+ // if (done) {
+ // break
+ // }
+ // data += decodeText(value, textDecoder)
+ // }
+
+ // ;(renderOpts as any).pageData = {
+ // ...(renderOpts as any).pageData,
+ // __flight__: data,
+ // }
+ // return data
+ // }
+ // }
+
+ // @TODO: A potential improvement would be to reuse the inlined
+ // data stream, or pass a callback inside as this doesn't need to
+ // be streamed.
+ // Do not use `await` here.
+ // generateStaticFlightDataIfNeeded()
+
+ return await continueFromInitialStream({
+ renderStream,
+ suffix: '',
+ dataStream: serverComponentsInlinedTransformStream?.readable,
+ generateStaticHTML: generateStaticHTML || !hasConcurrentFeatures,
+ flushEffectHandler,
+ })
+ }
+
+ return new RenderResult(await bodyResult())
+}
diff --git a/packages/next/server/web-server.ts b/packages/next/server/web-server.ts
index 1ae3386768df..4747f91ca122 100644
--- a/packages/next/server/web-server.ts
+++ b/packages/next/server/web-server.ts
@@ -103,6 +103,11 @@ export default class NextWebServer extends BaseServer {
[this.serverOptions.webServerConfig.page]: '',
}
}
+ protected getViewPathsManifest() {
+ return {
+ [this.serverOptions.webServerConfig.page]: '',
+ }
+ }
protected getFilesystemPaths() {
return new Set()
}
diff --git a/packages/next/shared/lib/constants.ts b/packages/next/shared/lib/constants.ts
index 7f3dc9acd928..8c17e406a3e7 100644
--- a/packages/next/shared/lib/constants.ts
+++ b/packages/next/shared/lib/constants.ts
@@ -4,6 +4,7 @@ export const PHASE_PRODUCTION_SERVER = 'phase-production-server'
export const PHASE_DEVELOPMENT_SERVER = 'phase-development-server'
export const PHASE_TEST = 'phase-test'
export const PAGES_MANIFEST = 'pages-manifest.json'
+export const VIEW_PATHS_MANIFEST = 'view-paths-manifest.json'
export const BUILD_MANIFEST = 'build-manifest.json'
export const EXPORT_MARKER = 'export-marker.json'
export const EXPORT_DETAIL = 'export-detail.json'
@@ -37,6 +38,7 @@ export const MIDDLEWARE_REACT_LOADABLE_MANIFEST =
// static/runtime/main.js
export const CLIENT_STATIC_FILES_RUNTIME_MAIN = `main`
+export const CLIENT_STATIC_FILES_RUNTIME_MAIN_ROOT = `${CLIENT_STATIC_FILES_RUNTIME_MAIN}-root`
// static/runtime/react-refresh.js
export const CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH = `react-refresh`
// static/runtime/amp.js
diff --git a/packages/next/shared/lib/i18n/get-locale-metadata.ts b/packages/next/shared/lib/i18n/get-locale-metadata.ts
index f62273bd6758..91d1c0e9adb2 100644
--- a/packages/next/shared/lib/i18n/get-locale-metadata.ts
+++ b/packages/next/shared/lib/i18n/get-locale-metadata.ts
@@ -88,8 +88,8 @@ function getRedirect({
nextConfig: { basePath?: string; i18n: I18NConfig; trailingSlash?: boolean }
url: { hostname?: string | null; pathname: string }
}) {
- const isRootPath = denormalizePagePath(url.pathname) === '/'
- if (nextConfig.i18n.localeDetection !== false && isRootPath) {
+ const isHomePage = denormalizePagePath(url.pathname) === '/'
+ if (nextConfig.i18n.localeDetection !== false && isHomePage) {
const preferredDomain = detectDomainLocale(
nextConfig.i18n.domains,
undefined,
diff --git a/packages/next/shared/lib/image-config.ts b/packages/next/shared/lib/image-config.ts
index 001745447c1a..d60e966ccf30 100644
--- a/packages/next/shared/lib/image-config.ts
+++ b/packages/next/shared/lib/image-config.ts
@@ -8,6 +8,33 @@ export const VALID_LOADERS = [
export type LoaderValue = typeof VALID_LOADERS[number]
+export type RemotePattern = {
+ /**
+ * Must be `http` or `https`.
+ */
+ protocol?: 'http' | 'https'
+
+ /**
+ * Can be literal or wildcard.
+ * Single `*` matches a single subdomain.
+ * Double `**` matches any number of subdomains.
+ */
+ hostname: string
+
+ /**
+ * Can be literal port such as `8080` or empty string
+ * meaning no port.
+ */
+ port?: string
+
+ /**
+ * Can be literal or wildcard.
+ * Single `*` matches a single path segment.
+ * Double `**` matches any number of path segments.
+ */
+ pathname?: string
+}
+
type ImageFormat = 'image/avif' | 'image/webp'
/**
@@ -28,7 +55,9 @@ export type ImageConfigComplete = {
/** @see [Image loader configuration](https://nextjs.org/docs/api-reference/next/image#loader-configuration) */
path: string
- /** @see [Image domains configuration](https://nextjs.org/docs/basic-features/image-optimization#domains) */
+ /**
+ * @see [Image domains configuration](https://nextjs.org/docs/api-reference/next/image#domains)
+ */
domains: string[]
/** @see [Cache behavior](https://nextjs.org/docs/api-reference/next/image#caching-behavior) */
diff --git a/packages/next/shared/lib/match-remote-pattern.ts b/packages/next/shared/lib/match-remote-pattern.ts
new file mode 100644
index 000000000000..4db09ee4451e
--- /dev/null
+++ b/packages/next/shared/lib/match-remote-pattern.ts
@@ -0,0 +1,84 @@
+import type { RemotePattern } from './image-config'
+
+export function matchRemotePattern(pattern: RemotePattern, url: URL): boolean {
+ if (pattern.protocol !== undefined) {
+ const actualProto = url.protocol.slice(0, -1)
+ if (pattern.protocol !== actualProto) {
+ return false
+ }
+ }
+ if (pattern.port !== undefined) {
+ if (pattern.port !== url.port) {
+ return false
+ }
+ }
+ if (pattern.pathname !== undefined) {
+ const patternParts = pattern.pathname.split('/')
+ const actualParts = url.pathname.split('/')
+ const len = Math.max(patternParts.length, actualParts.length)
+ for (let i = 0; i < len; i++) {
+ if (patternParts[i] === '**' && actualParts[i] !== undefined) {
+ // Double asterisk means "match everything until the end of the path"
+ // so we can break the loop early. But we throw
+ // if the double asterisk is not the last part.
+ if (patternParts.length - 1 > i) {
+ throw new Error(
+ `Pattern can only contain ** at end of pathname but found "${pattern.pathname}"`
+ )
+ }
+ break
+ }
+ if (patternParts[i] === '*') {
+ // Single asterisk means "match this part" so we can
+ // continue to the next part of the loop
+ continue
+ }
+ if (patternParts[i] !== actualParts[i]) {
+ return false
+ }
+ }
+ }
+
+ if (pattern.hostname === undefined) {
+ throw new Error(
+ `Pattern should define hostname but found\n${JSON.stringify(pattern)}`
+ )
+ } else {
+ const patternParts = pattern.hostname.split('.').reverse()
+ const actualParts = url.hostname.split('.').reverse()
+ const len = Math.max(patternParts.length, actualParts.length)
+ for (let i = 0; i < len; i++) {
+ if (patternParts[i] === '**' && actualParts[i] !== undefined) {
+ // Double asterisk means "match every subdomain"
+ // so we can break the loop early. But we throw
+ // if the double asterisk is not the last part.
+ if (patternParts.length - 1 > i) {
+ throw new Error(
+ `Pattern can only contain ** at start of hostname but found "${pattern.hostname}"`
+ )
+ }
+ break
+ }
+ if (patternParts[i] === '*') {
+ // Single asterisk means "match this subdomain" so we can
+ // continue to the next part of the loop
+ continue
+ }
+ if (patternParts[i] !== actualParts[i]) {
+ return false
+ }
+ }
+ }
+ return true
+}
+
+export function hasMatch(
+ domains: string[],
+ remotePatterns: RemotePattern[],
+ url: URL
+): boolean {
+ return (
+ domains.some((domain) => url.hostname === domain) ||
+ remotePatterns.some((p) => matchRemotePattern(p, url))
+ )
+}
diff --git a/packages/next/shared/lib/page-path/absolute-path-to-page.ts b/packages/next/shared/lib/page-path/absolute-path-to-page.ts
index 6b2cd91a6e3d..9e9b7a2b5536 100644
--- a/packages/next/shared/lib/page-path/absolute-path-to-page.ts
+++ b/packages/next/shared/lib/page-path/absolute-path-to-page.ts
@@ -16,10 +16,12 @@ import { removePagePathTail } from './remove-page-path-tail'
export function absolutePathToPage(
pagesDir: string,
pagePath: string,
- extensions: string[]
+ extensions: string[],
+ stripIndex = true
) {
return removePagePathTail(
normalizePathSep(ensureLeadingSlash(relative(pagesDir, pagePath))),
- extensions
+ extensions,
+ stripIndex
)
}
diff --git a/packages/next/shared/lib/page-path/get-page-paths.ts b/packages/next/shared/lib/page-path/get-page-paths.ts
index bdf9db212420..4d4906e3d868 100644
--- a/packages/next/shared/lib/page-path/get-page-paths.ts
+++ b/packages/next/shared/lib/page-path/get-page-paths.ts
@@ -12,6 +12,7 @@ import { join } from '../isomorphic/path'
*/
export function getPagePaths(normalizedPagePath: string, extensions: string[]) {
const page = denormalizePagePath(normalizedPagePath)
+
return flatten(
extensions.map((extension) => {
return !normalizedPagePath.endsWith('/index')
diff --git a/packages/next/shared/lib/page-path/remove-page-path-tail.ts b/packages/next/shared/lib/page-path/remove-page-path-tail.ts
index 3b0d94df46a1..d945b52d9eb6 100644
--- a/packages/next/shared/lib/page-path/remove-page-path-tail.ts
+++ b/packages/next/shared/lib/page-path/remove-page-path-tail.ts
@@ -10,10 +10,19 @@ import { normalizePathSep } from './normalize-path-sep'
* @param pagePath A page to a page file (absolute or relative)
* @param extensions Extensions allowed for the page.
*/
-export function removePagePathTail(pagePath: string, extensions: string[]) {
- return (
- normalizePathSep(pagePath)
- .replace(new RegExp(`\\.+(?:${extensions.join('|')})$`), '')
- .replace(/\/index$/, '') || '/'
+export function removePagePathTail(
+ pagePath: string,
+ extensions: string[],
+ stripIndex?: boolean
+) {
+ pagePath = normalizePathSep(pagePath).replace(
+ new RegExp(`\\.+(?:${extensions.join('|')})$`),
+ ''
)
+
+ if (stripIndex) {
+ pagePath = pagePath.replace(/\/index$/, '') || '/'
+ }
+
+ return pagePath
}
diff --git a/packages/next/shared/lib/router/utils/view-paths.ts b/packages/next/shared/lib/router/utils/view-paths.ts
new file mode 100644
index 000000000000..f34eb96c3bf1
--- /dev/null
+++ b/packages/next/shared/lib/router/utils/view-paths.ts
@@ -0,0 +1,17 @@
+// remove (name) from pathname as it's not considered for routing
+export function normalizeViewPath(pathname: string) {
+ let normalized = ''
+ const segments = pathname.split('/')
+
+ segments.forEach((segment, index) => {
+ if (!segment) return
+ if (segment.startsWith('(') && segment.endsWith(')')) {
+ return
+ }
+ if (segment === 'page' && index === segments.length - 1) {
+ return
+ }
+ normalized += `/${segment}`
+ })
+ return normalized
+}
diff --git a/packages/next/taskfile.js b/packages/next/taskfile.js
index 1a3a7286b8ac..3edcde3e5a6a 100644
--- a/packages/next/taskfile.js
+++ b/packages/next/taskfile.js
@@ -1410,6 +1410,16 @@ export async function ncc_nft(task, opts) {
.ncc({ packageName: '@vercel/nft', externals })
.target('compiled/@vercel/nft')
}
+
+// eslint-disable-next-line camelcase
+externals['tar'] = 'next/dist/compiled/tar'
+export async function ncc_tar(task, opts) {
+ await task
+ .source(opts.src || relative(__dirname, require.resolve('tar')))
+ .ncc({ packageName: 'tar', externals })
+ .target('compiled/tar')
+}
+
// eslint-disable-next-line camelcase
externals['terser'] = 'next/dist/compiled/terser'
export async function ncc_terser(task, opts) {
@@ -1729,6 +1739,7 @@ export async function ncc(task, opts) {
'ncc_string_hash',
'ncc_strip_ansi',
'ncc_nft',
+ 'ncc_tar',
'ncc_terser',
'ncc_text_table',
'ncc_unistore',
diff --git a/packages/next/telemetry/events/swc-load-failure.ts b/packages/next/telemetry/events/swc-load-failure.ts
index 26a348a28dd3..6414043b1ae4 100644
--- a/packages/next/telemetry/events/swc-load-failure.ts
+++ b/packages/next/telemetry/events/swc-load-failure.ts
@@ -1,5 +1,7 @@
import { traceGlobals } from '../../trace/shared'
import { Telemetry } from '../storage'
+// @ts-ignore JSON
+import { version as nextVersion, optionalDependencies } from 'next/package.json'
const EVENT_PLUGIN_PRESENT = 'NEXT_SWC_LOAD_FAILURE'
export type EventSwcLoadFailure = {
@@ -9,22 +11,56 @@ export type EventSwcLoadFailure = {
arch: string
nodeVersion: string
nextVersion: string
- wasm?: string
+ wasm?: 'enabled' | 'fallback' | 'failed'
glibcVersion?: string
installedSwcPackages?: string
}
}
export async function eventSwcLoadFailure(
- event: EventSwcLoadFailure['payload']
+ event?: EventSwcLoadFailure['payload']
): Promise {
const telemetry: Telemetry = traceGlobals.get('telemetry')
// can't continue if telemetry isn't set
if (!telemetry) return
+ let glibcVersion
+ let installedSwcPackages
+
+ try {
+ // @ts-ignore
+ glibcVersion = process.report?.getReport().header.glibcVersionRuntime
+ } catch (_) {}
+
+ try {
+ const pkgNames = Object.keys(optionalDependencies || {}).filter((pkg) =>
+ pkg.startsWith('@next/swc')
+ )
+ const installedPkgs = []
+
+ for (const pkg of pkgNames) {
+ try {
+ const { version } = require(`${pkg}/package.json`)
+ installedPkgs.push(`${pkg}@${version}`)
+ } catch (_) {}
+ }
+
+ if (installedPkgs.length > 0) {
+ installedSwcPackages = installedPkgs.sort().join(',')
+ }
+ } catch (_) {}
+
telemetry.record({
eventName: EVENT_PLUGIN_PRESENT,
- payload: event,
+ payload: {
+ nextVersion,
+ glibcVersion,
+ installedSwcPackages,
+ arch: process.arch,
+ platform: process.platform,
+ nodeVersion: process.versions.node,
+ wasm: event?.wasm,
+ },
})
// ensure this event is flushed before process exits
await telemetry.flush()
diff --git a/packages/next/telemetry/events/version.ts b/packages/next/telemetry/events/version.ts
index c7928e2cda71..f8c313363e41 100644
--- a/packages/next/telemetry/events/version.ts
+++ b/packages/next/telemetry/events/version.ts
@@ -21,6 +21,7 @@ type EventCliSessionStarted = {
localeDomainsCount: number | null
localeDetectionEnabled: boolean | null
imageDomainsCount: number | null
+ imageRemotePatternsCount: number | null
imageSizes: string | null
imageLoader: string | null
imageFormats: string | null
@@ -65,6 +66,7 @@ export function eventCliSession(
| 'localeDomainsCount'
| 'localeDetectionEnabled'
| 'imageDomainsCount'
+ | 'imageRemotePatternsCount'
| 'imageSizes'
| 'imageLoader'
| 'imageFormats'
@@ -77,7 +79,7 @@ export function eventCliSession(
return []
}
- const { images, i18n } = nextConfig || {}
+ const { images, i18n, experimental } = nextConfig || {}
const payload: EventCliSessionStarted = {
nextVersion: process.env.__NEXT_VERSION,
@@ -97,6 +99,9 @@ export function eventCliSession(
localeDomainsCount: i18n?.domains ? i18n.domains.length : null,
localeDetectionEnabled: !i18n ? null : i18n.localeDetection !== false,
imageDomainsCount: images?.domains ? images.domains.length : null,
+ imageRemotePatternsCount: experimental?.images?.remotePatterns
+ ? experimental.images.remotePatterns.length
+ : null,
imageSizes: images?.imageSizes ? images.imageSizes.join(',') : null,
imageLoader: images?.loader,
imageFormats: images?.formats ? images.formats.join(',') : null,
diff --git a/packages/next/types/misc.d.ts b/packages/next/types/misc.d.ts
index 9d68b3d69d37..8195d117d8a0 100644
--- a/packages/next/types/misc.d.ts
+++ b/packages/next/types/misc.d.ts
@@ -293,6 +293,12 @@ declare module 'next/dist/compiled/@vercel/nft' {
import m from '@vercel/nft'
export = m
}
+
+declare module 'next/dist/compiled/tar' {
+ import m from 'tar'
+ export = m
+}
+
declare module 'next/dist/compiled/terser' {
import m from 'terser'
export = m
diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json
index aac7828b25fb..8bb0d4486bcd 100644
--- a/packages/react-dev-overlay/package.json
+++ b/packages/react-dev-overlay/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/react-dev-overlay",
- "version": "12.1.6",
+ "version": "12.1.7-canary.2",
"description": "A development-only overlay for developing React applications.",
"repository": {
"url": "vercel/next.js",
diff --git a/packages/react-dev-overlay/src/internal/components/Overlay/maintain--tab-focus.ts b/packages/react-dev-overlay/src/internal/components/Overlay/maintain--tab-focus.ts
index 879db0409401..a9ef31097ce6 100644
--- a/packages/react-dev-overlay/src/internal/components/Overlay/maintain--tab-focus.ts
+++ b/packages/react-dev-overlay/src/internal/components/Overlay/maintain--tab-focus.ts
@@ -804,11 +804,18 @@ var focusSummary = {
}
function makeFocusableForeignObject() {
- var fragment = document.createElement('div')
- fragment.innerHTML =
- '\n \n '
+ // Constructs
+ // without raising a Trusted Types violation
+ var foreignObject = document.createElementNS(
+ 'http://www.w3.org/2000/svg',
+ 'foreignObject'
+ )
+ foreignObject.width.baseVal.value = 30
+ foreignObject.height.baseVal.value = 30
+ foreignObject.appendChild(document.createElement('input'))
+ foreignObject.lastChild.type = 'text'
- return fragment.firstChild.firstChild
+ return foreignObject
}
function focusSvgForeignObjectHack(element) {
diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json
index 34e90dc80a60..67c3dc926836 100644
--- a/packages/react-refresh-utils/package.json
+++ b/packages/react-refresh-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/react-refresh-utils",
- "version": "12.1.6",
+ "version": "12.1.7-canary.2",
"description": "An experimental package providing utilities for React Refresh.",
"repository": {
"url": "vercel/next.js",
diff --git a/test/e2e/root-dir/app/root/conditional/[slug].server.js b/test/e2e/root-dir/app/root/conditional/[slug].server.js
deleted file mode 100644
index 14dae08e2713..000000000000
--- a/test/e2e/root-dir/app/root/conditional/[slug].server.js
+++ /dev/null
@@ -1,27 +0,0 @@
-export async function getServerSideProps({ params }) {
- if (params.slug === 'nonexistent') {
- return {
- notFound: true,
- }
- }
- return {
- props: {
- isUser: params.slug === 'tim',
- isBoth: params.slug === 'both',
- },
- }
-}
-
-export default function UserOrTeam({ isUser, isBoth, user, team }) {
- return (
- <>
- {isUser && !isBoth ? user : team}
- {isBoth ? (
- <>
- {user}
- {team}
- >
- ) : null}
- >
- )
-}
diff --git a/test/e2e/root-dir/app/root/conditional/[slug]@team/index.js b/test/e2e/root-dir/app/root/conditional/[slug]@team/index.js
deleted file mode 100644
index 02119380f338..000000000000
--- a/test/e2e/root-dir/app/root/conditional/[slug]@team/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-export default function TeamHomePage(props) {
- return (
- <>
- hello from team homepage
- >
- )
-}
diff --git a/test/e2e/root-dir/app/root/conditional/[slug]@team/members.js b/test/e2e/root-dir/app/root/conditional/[slug]@team/members.js
deleted file mode 100644
index 2c3ba112bead..000000000000
--- a/test/e2e/root-dir/app/root/conditional/[slug]@team/members.js
+++ /dev/null
@@ -1,7 +0,0 @@
-export default function TeamMembersPage(props) {
- return (
- <>
- hello from team/members
- >
- )
-}
diff --git a/test/e2e/root-dir/app/root/conditional/[slug]@user/index.js b/test/e2e/root-dir/app/root/conditional/[slug]@user/index.js
deleted file mode 100644
index 81100777dae2..000000000000
--- a/test/e2e/root-dir/app/root/conditional/[slug]@user/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-export default function UserHomePage(props) {
- return (
- <>
- hello from user homepage
- >
- )
-}
diff --git a/test/e2e/root-dir/app/root/conditional/[slug]@user/teams.js b/test/e2e/root-dir/app/root/conditional/[slug]@user/teams.js
deleted file mode 100644
index 294c3bf316da..000000000000
--- a/test/e2e/root-dir/app/root/conditional/[slug]@user/teams.js
+++ /dev/null
@@ -1,7 +0,0 @@
-export default function UserTeamsPage(props) {
- return (
- <>
- hello from user/teams
- >
- )
-}
diff --git a/test/e2e/root-dir/app/next.config.js b/test/e2e/views-dir/app/next.config.js
similarity index 85%
rename from test/e2e/root-dir/app/next.config.js
rename to test/e2e/views-dir/app/next.config.js
index 9abd7b65b73a..4131d5b60875 100644
--- a/test/e2e/root-dir/app/next.config.js
+++ b/test/e2e/views-dir/app/next.config.js
@@ -1,6 +1,6 @@
module.exports = {
experimental: {
- rootDir: true,
+ viewsDir: true,
runtime: 'nodejs',
reactRoot: true,
serverComponents: true,
diff --git a/test/e2e/root-dir/app/pages/blog/[slug].js b/test/e2e/views-dir/app/pages/blog/[slug].js
similarity index 100%
rename from test/e2e/root-dir/app/pages/blog/[slug].js
rename to test/e2e/views-dir/app/pages/blog/[slug].js
diff --git a/test/e2e/root-dir/app/pages/index.js b/test/e2e/views-dir/app/pages/index.js
similarity index 100%
rename from test/e2e/root-dir/app/pages/index.js
rename to test/e2e/views-dir/app/pages/index.js
diff --git a/test/e2e/root-dir/app/public/hello.txt b/test/e2e/views-dir/app/public/hello.txt
similarity index 100%
rename from test/e2e/root-dir/app/public/hello.txt
rename to test/e2e/views-dir/app/public/hello.txt
diff --git a/test/e2e/root-dir/app/root/dashboard+changelog.server.js b/test/e2e/views-dir/app/views/(rootonly)/dashboard/changelog/page.server.js
similarity index 100%
rename from test/e2e/root-dir/app/root/dashboard+changelog.server.js
rename to test/e2e/views-dir/app/views/(rootonly)/dashboard/changelog/page.server.js
diff --git a/test/e2e/root-dir/app/root/dashboard+rootonly/hello.server.js b/test/e2e/views-dir/app/views/(rootonly)/dashboard/hello/page.server.js
similarity index 100%
rename from test/e2e/root-dir/app/root/dashboard+rootonly/hello.server.js
rename to test/e2e/views-dir/app/views/(rootonly)/dashboard/hello/page.server.js
diff --git a/test/e2e/root-dir/app/root/client-component-route.client.js b/test/e2e/views-dir/app/views/client-component-route/page.client.js
similarity index 100%
rename from test/e2e/root-dir/app/root/client-component-route.client.js
rename to test/e2e/views-dir/app/views/client-component-route/page.client.js
diff --git a/test/e2e/root-dir/app/root/client-nested/index.server.js b/test/e2e/views-dir/app/views/client-nested/index/page.server.js
similarity index 100%
rename from test/e2e/root-dir/app/root/client-nested/index.server.js
rename to test/e2e/views-dir/app/views/client-nested/index/page.server.js
diff --git a/test/e2e/root-dir/app/root/client-nested.client.js b/test/e2e/views-dir/app/views/client-nested/layout.client.js
similarity index 100%
rename from test/e2e/root-dir/app/root/client-nested.client.js
rename to test/e2e/views-dir/app/views/client-nested/layout.client.js
diff --git a/test/e2e/root-dir/app/root/dashboard/deployments/[id].server.js b/test/e2e/views-dir/app/views/dashboard/deployments/[id]/page.server.js
similarity index 100%
rename from test/e2e/root-dir/app/root/dashboard/deployments/[id].server.js
rename to test/e2e/views-dir/app/views/dashboard/deployments/[id]/page.server.js
diff --git a/test/e2e/root-dir/app/root/dashboard/deployments/info.server.js b/test/e2e/views-dir/app/views/dashboard/deployments/info/page.server.js
similarity index 100%
rename from test/e2e/root-dir/app/root/dashboard/deployments/info.server.js
rename to test/e2e/views-dir/app/views/dashboard/deployments/info/page.server.js
diff --git a/test/e2e/root-dir/app/root/dashboard/deployments.server.js b/test/e2e/views-dir/app/views/dashboard/deployments/layout.server.js
similarity index 100%
rename from test/e2e/root-dir/app/root/dashboard/deployments.server.js
rename to test/e2e/views-dir/app/views/dashboard/deployments/layout.server.js
diff --git a/test/e2e/root-dir/app/root/dashboard/index.server.js b/test/e2e/views-dir/app/views/dashboard/index/page.server.js
similarity index 100%
rename from test/e2e/root-dir/app/root/dashboard/index.server.js
rename to test/e2e/views-dir/app/views/dashboard/index/page.server.js
diff --git a/test/e2e/root-dir/app/root/dashboard/integrations/index.server.js b/test/e2e/views-dir/app/views/dashboard/integrations/index/page.server.js
similarity index 100%
rename from test/e2e/root-dir/app/root/dashboard/integrations/index.server.js
rename to test/e2e/views-dir/app/views/dashboard/integrations/index/page.server.js
diff --git a/test/e2e/root-dir/app/root/dashboard.server.js b/test/e2e/views-dir/app/views/dashboard/layout.server.js
similarity index 100%
rename from test/e2e/root-dir/app/root/dashboard.server.js
rename to test/e2e/views-dir/app/views/dashboard/layout.server.js
diff --git a/test/e2e/root-dir/app/root.server.js b/test/e2e/views-dir/app/views/layout.server.js
similarity index 100%
rename from test/e2e/root-dir/app/root.server.js
rename to test/e2e/views-dir/app/views/layout.server.js
diff --git a/test/e2e/root-dir/app/root/partial-match-[id].server.js b/test/e2e/views-dir/app/views/partial-match-[id]/page.server.js
similarity index 100%
rename from test/e2e/root-dir/app/root/partial-match-[id].server.js
rename to test/e2e/views-dir/app/views/partial-match-[id]/page.server.js
diff --git a/test/e2e/root-dir/app/root/shared-component-route.js b/test/e2e/views-dir/app/views/shared-component-route/page.js
similarity index 100%
rename from test/e2e/root-dir/app/root/shared-component-route.js
rename to test/e2e/views-dir/app/views/shared-component-route/page.js
diff --git a/test/e2e/root-dir/app/root/should-not-serve-client.client.js b/test/e2e/views-dir/app/views/should-not-serve-client/page.client.js
similarity index 100%
rename from test/e2e/root-dir/app/root/should-not-serve-client.client.js
rename to test/e2e/views-dir/app/views/should-not-serve-client/page.client.js
diff --git a/test/e2e/root-dir/app/root/should-not-serve-server.server.js b/test/e2e/views-dir/app/views/should-not-serve-server/page.server.js
similarity index 100%
rename from test/e2e/root-dir/app/root/should-not-serve-server.server.js
rename to test/e2e/views-dir/app/views/should-not-serve-server/page.server.js
diff --git a/test/e2e/root-dir/index.test.ts b/test/e2e/views-dir/index.test.ts
similarity index 77%
rename from test/e2e/root-dir/index.test.ts
rename to test/e2e/views-dir/index.test.ts
index 2f9b1b9dc91d..361613c99dfc 100644
--- a/test/e2e/root-dir/index.test.ts
+++ b/test/e2e/views-dir/index.test.ts
@@ -5,8 +5,11 @@ import path from 'path'
import cheerio from 'cheerio'
import webdriver from 'next-webdriver'
-// TODO: implementation
-describe.skip('root dir', () => {
+describe('views dir', () => {
+ if (process.env.NEXT_TEST_REACT_VERSION === '^17') {
+ it('should skip for react v17', () => {})
+ return
+ }
let next: NextInstance
beforeAll(async () => {
@@ -14,18 +17,11 @@ describe.skip('root dir', () => {
files: {
public: new FileRef(path.join(__dirname, 'app/public')),
pages: new FileRef(path.join(__dirname, 'app/pages')),
- root: new FileRef(path.join(__dirname, 'app/root')),
- 'root.server.js': new FileRef(
- path.join(__dirname, 'app/root.server.js')
- ),
+ views: new FileRef(path.join(__dirname, 'app/views')),
'next.config.js': new FileRef(
path.join(__dirname, 'app/next.config.js')
),
},
- dependencies: {
- react: '18.0.0-rc.2',
- 'react-dom': '18.0.0-rc.2',
- },
})
})
afterAll(() => next.destroy())
@@ -61,7 +57,7 @@ describe.skip('root dir', () => {
// TODO: why is this routable but /should-not-serve-server.server.js
it('should not include parent when not in parent directory with route in directory', async () => {
- const html = await renderViaHTTP(next.url, '/dashboard/rootonly/hello')
+ const html = await renderViaHTTP(next.url, '/dashboard/hello')
const $ = cheerio.load(html)
// Should be nested in /root.js
@@ -127,55 +123,11 @@ describe.skip('root dir', () => {
// TODO: do we want to make this only work for /root or is it allowed
// to work for /pages as well?
- it('should match partial parameters', async () => {
+ it.skip('should match partial parameters', async () => {
const html = await renderViaHTTP(next.url, '/partial-match-123')
expect(html).toContain('hello from root/partial-match-[id]. ID is: 123')
})
- describe('parallel routes', () => {
- describe('conditional routes', () => {
- it('should serve user page', async () => {
- const html = await renderViaHTTP(next.url, '/conditional/tim')
- expect(html).toContain('hello from user homepage')
- })
-
- it('should serve user teams page', async () => {
- const html = await renderViaHTTP(next.url, '/conditional/tim/teams')
- expect(html).toContain('hello from user/teams')
- })
-
- it('should not serve teams page to user', async () => {
- const html = await renderViaHTTP(next.url, '/conditional/tim/members')
- expect(html).not.toContain('hello from team/members')
- })
-
- it('should serve team page', async () => {
- const html = await renderViaHTTP(next.url, '/conditional/vercel')
- expect(html).toContain('hello from team homepage')
- })
-
- it('should serve team members page', async () => {
- const html = await renderViaHTTP(
- next.url,
- '/conditional/vercel/members'
- )
- expect(html).toContain('hello from team/members')
- })
-
- it('should provide both matches if both paths match', async () => {
- const html = await renderViaHTTP(next.url, '/conditional/both')
- expect(html).toContain('hello from team homepage')
- expect(html).toContain('hello from user homepage')
- })
-
- it('should 404 based on getServerSideProps', async () => {
- const res = await fetchViaHTTP(next.url, '/conditional/nonexistent')
- expect(res.status).toBe(404)
- expect(await res.text()).toContain('This page could not be found')
- })
- })
- })
-
describe('server components', () => {
// TODO: why is this not servable but /dashboard+rootonly/hello.server.js
// should be? Seems like they both either should be servable or not
@@ -229,7 +181,8 @@ describe.skip('root dir', () => {
expect(html).toContain('hello from root/shared-component-route')
})
- it('should serve client component', async () => {
+ // TODO: implement
+ it.skip('should serve client component', async () => {
const html = await renderViaHTTP(next.url, '/client-component-route')
expect(html).toContain('hello from root/client-component-route. count: 0')
@@ -240,7 +193,8 @@ describe.skip('root dir', () => {
)
})
- it('should include client component layout with server component route', async () => {
+ // TODO: implement
+ it.skip('should include client component layout with server component route', async () => {
const html = await renderViaHTTP(next.url, '/client-nested')
const $ = cheerio.load(html)
// Should not be nested in dashboard
diff --git a/test/integration/chunking/pages/page3.js b/test/integration/chunking/pages/page3.js
index 03d534597ca2..a49f3544a696 100644
--- a/test/integration/chunking/pages/page3.js
+++ b/test/integration/chunking/pages/page3.js
@@ -1,5 +1,7 @@
import Link from 'next/link'
-import('lodash').then((_) => console.log(_.chunk(['a', 'b', 'c', 'd'], 2)))
+import('lodash').then(({ default: _ }) =>
+ console.log(_.chunk(['a', 'b', 'c', 'd'], 2))
+)
const Page = () => {
return (
diff --git a/test/integration/custom-server/test/index.test.js b/test/integration/custom-server/test/index.test.js
index 330fe4b1c4ef..8d4e2ee70311 100644
--- a/test/integration/custom-server/test/index.test.js
+++ b/test/integration/custom-server/test/index.test.js
@@ -132,8 +132,18 @@ describe('Custom Server', () => {
try {
browser = await webdriver(context.appPort, '/test-index-hmr')
const text = await browser.elementByCss('#go-asset').text()
+ const logs = await browser.log()
expect(text).toBe('Asset')
+ // Hydrates with react 18 is correct as expected
+ expect(
+ logs.some((log) =>
+ log.message.includes(
+ 'ReactDOM.hydrate is no longer supported in React 18'
+ )
+ )
+ ).toBe(false)
+
indexPg.replace('Asset', 'Asset!!')
await check(() => browser.elementByCss('#go-asset').text(), /Asset!!/)
diff --git a/test/integration/image-component/asset-prefix/next.config.js b/test/integration/image-component/asset-prefix/next.config.js
index d6798dc737bc..f277a6b3d133 100644
--- a/test/integration/image-component/asset-prefix/next.config.js
+++ b/test/integration/image-component/asset-prefix/next.config.js
@@ -1,3 +1,4 @@
module.exports = {
assetPrefix: 'https://example.com/pre',
+ // Intentionally omit `domains` and `remotePatterns`
}
diff --git a/test/integration/image-component/unicode/next.config.js b/test/integration/image-component/unicode/next.config.js
index eb554b46c10c..e4ff44f0fe11 100644
--- a/test/integration/image-component/unicode/next.config.js
+++ b/test/integration/image-component/unicode/next.config.js
@@ -1,5 +1,7 @@
module.exports = {
- images: {
- domains: ['image-optimization-test.vercel.app'],
+ experimental: {
+ images: {
+ remotePatterns: [{ hostname: 'image-optimization-test.vercel.app' }],
+ },
},
}
diff --git a/test/integration/image-optimizer/test/index.test.js b/test/integration/image-optimizer/test/index.test.js
index bcac0f864552..bd21d869e6cc 100644
--- a/test/integration/image-optimizer/test/index.test.js
+++ b/test/integration/image-optimizer/test/index.test.js
@@ -48,6 +48,89 @@ describe('Image Optimizer', () => {
)
})
+ it('should error when remotePatterns length exceeds 50', async () => {
+ await nextConfig.replace(
+ '{ /* replaceme */ }',
+ JSON.stringify({
+ experimental: {
+ images: {
+ remotePatterns: Array.from({ length: 51 }).map((_) => ({
+ hostname: 'example.com',
+ })),
+ },
+ },
+ })
+ )
+ let stderr = ''
+
+ app = await launchApp(appDir, await findPort(), {
+ onStderr(msg) {
+ stderr += msg || ''
+ },
+ })
+ await waitFor(1000)
+ await killApp(app).catch(() => {})
+ await nextConfig.restore()
+
+ expect(stderr).toContain(
+ 'Specified images.remotePatterns exceeds length of 50, received length (51), please reduce the length of the array to continue'
+ )
+ })
+
+ it('should error when remotePatterns has invalid prop', async () => {
+ await nextConfig.replace(
+ '{ /* replaceme */ }',
+ JSON.stringify({
+ experimental: {
+ images: {
+ remotePatterns: [{ hostname: 'example.com', foo: 'bar' }],
+ },
+ },
+ })
+ )
+ let stderr = ''
+
+ app = await launchApp(appDir, await findPort(), {
+ onStderr(msg) {
+ stderr += msg || ''
+ },
+ })
+ await waitFor(1000)
+ await killApp(app).catch(() => {})
+ await nextConfig.restore()
+
+ expect(stderr).toContain(
+ 'Invalid images.remotePatterns values:\n{"hostname":"example.com","foo":"bar"}'
+ )
+ })
+
+ it('should error when remotePatterns is missing hostname', async () => {
+ await nextConfig.replace(
+ '{ /* replaceme */ }',
+ JSON.stringify({
+ experimental: {
+ images: {
+ remotePatterns: [{ protocol: 'https' }],
+ },
+ },
+ })
+ )
+ let stderr = ''
+
+ app = await launchApp(appDir, await findPort(), {
+ onStderr(msg) {
+ stderr += msg || ''
+ },
+ })
+ await waitFor(1000)
+ await killApp(app).catch(() => {})
+ await nextConfig.restore()
+
+ expect(stderr).toContain(
+ 'Invalid images.remotePatterns values:\n{"protocol":"https"}'
+ )
+ })
+
it('should error when sizes length exceeds 25', async () => {
await nextConfig.replace(
'{ /* replaceme */ }',
diff --git a/test/integration/image-optimizer/test/util.js b/test/integration/image-optimizer/test/util.js
index 1d4613dbbd5c..be4e2bdfc528 100644
--- a/test/integration/image-optimizer/test/util.js
+++ b/test/integration/image-optimizer/test/util.js
@@ -136,7 +136,7 @@ export function runTests(ctx) {
slowImageServer.stop()
})
- if (ctx.domains.includes('localhost')) {
+ if (ctx.domains?.length > 0) {
it('should normalize invalid status codes', async () => {
const url = `http://localhost:${
slowImageServer.port
@@ -583,7 +583,7 @@ export function runTests(ctx) {
})
}
- if (ctx.domains.includes('localhost')) {
+ if (ctx.domains?.length > 0) {
it('should resize absolute url from localhost', async () => {
const url = `http://localhost:${ctx.appPort}/test.png`
const query = { url, w: ctx.w, q: 80 }
@@ -762,7 +762,7 @@ export function runTests(ctx) {
)
})
- if (ctx.domains.includes('localhost')) {
+ if (ctx.domains?.length > 0) {
it('should fail when url fails to load an image', async () => {
const url = `http://localhost:${ctx.appPort}/not-an-image`
const query = { w: ctx.w, url, q: 100 }
@@ -1119,7 +1119,7 @@ export function runTests(ctx) {
expect(await res.text()).toBe("The requested resource isn't a valid image.")
})
- if (ctx.domains.length) {
+ if (ctx.domains?.length > 0) {
it('should handle concurrent requests', async () => {
await cleanImagesDir(ctx)
const delay = 500
diff --git a/test/integration/telemetry/next.config.i18n-images b/test/integration/telemetry/next.config.i18n-images
index 0af3a77e421a..4b7a8d7ce6ef 100644
--- a/test/integration/telemetry/next.config.i18n-images
+++ b/test/integration/telemetry/next.config.i18n-images
@@ -3,7 +3,12 @@ module.exports = phase => {
images: {
formats: ['image/avif', 'image/webp'],
imageSizes: [64, 128, 256, 512, 1024],
- domains: ['example.com'],
+ domains: ['example.com', 'another.com'],
+ },
+ experimental: {
+ images: {
+ remotePatterns: [{ protocol: 'https', hostname: '**.example.com' }],
+ },
},
i18n: {
locales: ['en','nl','fr'],
diff --git a/test/integration/telemetry/test/index.test.js b/test/integration/telemetry/test/index.test.js
index fbd4fa1a6732..de7e41d6790f 100644
--- a/test/integration/telemetry/test/index.test.js
+++ b/test/integration/telemetry/test/index.test.js
@@ -502,7 +502,8 @@ describe('Telemetry CLI', () => {
expect(event1).toMatch(/"locales": "en,nl,fr"/)
expect(event1).toMatch(/"localeDomainsCount": 2/)
expect(event1).toMatch(/"localeDetectionEnabled": true/)
- expect(event1).toMatch(/"imageDomainsCount": 1/)
+ expect(event1).toMatch(/"imageDomainsCount": 2/)
+ expect(event1).toMatch(/"imageRemotePatternsCount": 1/)
expect(event1).toMatch(/"imageSizes": "64,128,256,512,1024"/)
expect(event1).toMatch(/"imageFormats": "image\/avif,image\/webp"/)
expect(event1).toMatch(/"trailingSlashEnabled": false/)
@@ -538,7 +539,8 @@ describe('Telemetry CLI', () => {
expect(event2).toMatch(/"locales": "en,nl,fr"/)
expect(event2).toMatch(/"localeDomainsCount": 2/)
expect(event2).toMatch(/"localeDetectionEnabled": true/)
- expect(event2).toMatch(/"imageDomainsCount": 1/)
+ expect(event2).toMatch(/"imageDomainsCount": 2/)
+ expect(event2).toMatch(/"imageRemotePatternsCount": 1/)
expect(event2).toMatch(/"imageSizes": "64,128,256,512,1024"/)
expect(event2).toMatch(/"trailingSlashEnabled": false/)
expect(event2).toMatch(/"reactStrictMode": false/)
diff --git a/test/lib/next-modes/base.ts b/test/lib/next-modes/base.ts
index 16338e57a2d5..ebd980507770 100644
--- a/test/lib/next-modes/base.ts
+++ b/test/lib/next-modes/base.ts
@@ -128,7 +128,8 @@ export class NextInstance {
if (
process.env.NEXT_TEST_STARTER &&
!this.dependencies &&
- !this.installCommand
+ !this.installCommand &&
+ !this.packageJson
) {
await fs.copy(process.env.NEXT_TEST_STARTER, this.testDir)
} else if (!skipIsolatedNext) {
diff --git a/test/production/required-server-files-i18n.test.ts b/test/production/required-server-files-i18n.test.ts
index b74cd9bd9f0d..a1b5d0d6f8d7 100644
--- a/test/production/required-server-files-i18n.test.ts
+++ b/test/production/required-server-files-i18n.test.ts
@@ -13,6 +13,7 @@ import {
renderViaHTTP,
waitFor,
} from 'next-test-utils'
+import nodeFetch from 'node-fetch'
describe('should set-up next', () => {
let next: NextInstance
@@ -22,6 +23,22 @@ describe('should set-up next', () => {
let requiredFilesManifest
beforeAll(async () => {
+ let wasmPkgIsAvailable = false
+
+ const res = await nodeFetch(
+ `https://registry.npmjs.com/@next/swc-wasm-nodejs/-/swc-wasm-nodejs-${
+ require('next/package.json').version
+ }.tgz`,
+ {
+ method: 'HEAD',
+ }
+ )
+
+ if (res.status === 200) {
+ wasmPkgIsAvailable = true
+ console.warn(`Testing wasm fallback handling`)
+ }
+
next = await createNext({
files: {
pages: new FileRef(join(__dirname, 'required-server-files/pages')),
@@ -30,6 +47,14 @@ describe('should set-up next', () => {
join(__dirname, 'required-server-files/data.txt')
),
},
+ packageJson: {
+ scripts: {
+ build: wasmPkgIsAvailable
+ ? 'rm -rfv node_modules/@next/swc && yarn next build'
+ : 'yarn next build',
+ },
+ },
+ buildCommand: 'yarn build',
nextConfig: {
i18n: {
locales: ['en', 'fr'],
diff --git a/test/production/required-server-files.test.ts b/test/production/required-server-files.test.ts
index 1b105c739546..8f5e3dd5084c 100644
--- a/test/production/required-server-files.test.ts
+++ b/test/production/required-server-files.test.ts
@@ -56,6 +56,10 @@ describe('should set-up next', () => {
source: '/an-ssg-path',
destination: '/hello.txt',
},
+ {
+ source: '/fallback-false/:path',
+ destination: '/hello.txt',
+ },
],
afterFiles: [
{
@@ -980,6 +984,35 @@ describe('should set-up next', () => {
expect(JSON.parse($('#router').text()).asPath).toBe('/an-ssg-path')
})
+ it('should have correct asPath on dynamic SSG page fallback correctly', async () => {
+ const toCheck = [
+ {
+ pathname: '/fallback-false/first',
+ matchedPath: '/fallback-false/first',
+ },
+ {
+ pathname: '/fallback-false/first',
+ matchedPath: `/_next/data/${next.buildId}/fallback-false/first.json`,
+ },
+ ]
+ for (const check of toCheck) {
+ console.warn('checking', check)
+ const res = await fetchViaHTTP(appPort, check.pathname, undefined, {
+ headers: {
+ 'x-matched-path': check.matchedPath,
+ },
+ redirect: 'manual',
+ })
+
+ const html = await res.text()
+ const $ = cheerio.load(html)
+ expect($('#page').text()).toBe('blog slug')
+ expect($('#asPath').text()).toBe('/fallback-false/first')
+ expect($('#pathname').text()).toBe('/fallback-false/[slug]')
+ expect(JSON.parse($('#query').text())).toEqual({ slug: 'first' })
+ }
+ })
+
it('should copy and read .env file', async () => {
const res = await fetchViaHTTP(appPort, '/api/env')
diff --git a/test/production/required-server-files/pages/fallback-false/[slug].js b/test/production/required-server-files/pages/fallback-false/[slug].js
new file mode 100644
index 000000000000..8296dbc7bd93
--- /dev/null
+++ b/test/production/required-server-files/pages/fallback-false/[slug].js
@@ -0,0 +1,35 @@
+import { useRouter } from 'next/router'
+
+export default function Page(props) {
+ const router = useRouter()
+
+ return (
+ <>
+ blog slug
+ {JSON.stringify(props)}
+ {JSON.stringify(router.query)}
+ {router.pathname}
+ {router.asPath}
+ >
+ )
+}
+
+export function getStaticProps({ params }) {
+ console.log({ blogSlug: true, params })
+
+ return {
+ props: {
+ random: Math.random() + Date.now(),
+ blogSlug: true,
+ params,
+ },
+ revalidate: 1,
+ }
+}
+
+export function getStaticPaths() {
+ return {
+ paths: ['/fallback-false/first', '/fallback-false/second'],
+ fallback: false,
+ }
+}
diff --git a/test/unit/image-optimizer/match-remote-pattern.test.ts b/test/unit/image-optimizer/match-remote-pattern.test.ts
new file mode 100644
index 000000000000..cf4505507706
--- /dev/null
+++ b/test/unit/image-optimizer/match-remote-pattern.test.ts
@@ -0,0 +1,236 @@
+/* eslint-env jest */
+import {
+ matchRemotePattern as m,
+ hasMatch,
+} from 'next/dist/shared/lib/match-remote-pattern'
+
+describe('matchRemotePattern', () => {
+ it('should match literal hostname', () => {
+ const p = { hostname: 'example.com' } as const
+ expect(m(p, new URL('https://example.com'))).toBe(true)
+ expect(m(p, new URL('https://example.com.uk'))).toBe(false)
+ expect(m(p, new URL('https://example.net'))).toBe(false)
+ expect(m(p, new URL('https://sub.example.com'))).toBe(false)
+ expect(m(p, new URL('https://com'))).toBe(false)
+ expect(m(p, new URL('https://example.com/path'))).toBe(true)
+ expect(m(p, new URL('https://example.com/path/to'))).toBe(true)
+ expect(m(p, new URL('https://example.com/path/to/file'))).toBe(true)
+ expect(m(p, new URL('https://example.com:81/path/to/file'))).toBe(true)
+ expect(m(p, new URL('https://example.com:81/path/to/file?q=1'))).toBe(true)
+ expect(m(p, new URL('http://example.com:81/path/to/file'))).toBe(true)
+ })
+
+ it('should match literal protocol and hostname', () => {
+ const p = { protocol: 'https', hostname: 'example.com' } as const
+ expect(m(p, new URL('https://example.com'))).toBe(true)
+ expect(m(p, new URL('https://example.com.uk'))).toBe(false)
+ expect(m(p, new URL('https://sub.example.com'))).toBe(false)
+ expect(m(p, new URL('https://com'))).toBe(false)
+ expect(m(p, new URL('https://example.com/path/to'))).toBe(true)
+ expect(m(p, new URL('https://example.com/path/to/file'))).toBe(true)
+ expect(m(p, new URL('https://example.com/path/to/file'))).toBe(true)
+ expect(m(p, new URL('https://example.com:81/path/to/file'))).toBe(true)
+ expect(m(p, new URL('https://example.com:81/path/to/file?q=1'))).toBe(true)
+ expect(m(p, new URL('http://example.com:81/path/to/file'))).toBe(false)
+ expect(m(p, new URL('ftp://example.com:81/path/to/file'))).toBe(false)
+ })
+
+ it('should match literal protocol, hostname, no port', () => {
+ const p = { protocol: 'https', hostname: 'example.com', port: '' } as const
+ expect(m(p, new URL('https://example.com'))).toBe(true)
+ expect(m(p, new URL('https://example.com.uk'))).toBe(false)
+ expect(m(p, new URL('https://sub.example.com'))).toBe(false)
+ expect(m(p, new URL('https://com'))).toBe(false)
+ expect(m(p, new URL('https://example.com/path/to/file'))).toBe(true)
+ expect(m(p, new URL('https://example.com/path/to/file?q=1'))).toBe(true)
+ expect(m(p, new URL('http://example.com/path/to/file'))).toBe(false)
+ expect(m(p, new URL('ftp://example.com/path/to/file'))).toBe(false)
+ expect(m(p, new URL('https://example.com:81/path/to/file'))).toBe(false)
+ expect(m(p, new URL('https://example.com:81/path/to/file?q=1'))).toBe(false)
+ expect(m(p, new URL('http://example.com:81/path/to/file'))).toBe(false)
+ })
+
+ it('should match literal protocol, hostname, port 42', () => {
+ const p = {
+ protocol: 'https',
+ hostname: 'example.com',
+ port: '42',
+ } as const
+ expect(m(p, new URL('https://example.com:42'))).toBe(true)
+ expect(m(p, new URL('https://example.com.uk:42'))).toBe(false)
+ expect(m(p, new URL('https://sub.example.com:42'))).toBe(false)
+ expect(m(p, new URL('https://com:42'))).toBe(false)
+ expect(m(p, new URL('https://example.com:42/path/to/file'))).toBe(true)
+ expect(m(p, new URL('https://example.com:42/path/to/file?q=1'))).toBe(true)
+ expect(m(p, new URL('http://example.com:42/path/to/file'))).toBe(false)
+ expect(m(p, new URL('ftp://example.com:42/path/to/file'))).toBe(false)
+ expect(m(p, new URL('https://example.com'))).toBe(false)
+ expect(m(p, new URL('https://example.com.uk'))).toBe(false)
+ expect(m(p, new URL('https://sub.example.com'))).toBe(false)
+ expect(m(p, new URL('https://com'))).toBe(false)
+ expect(m(p, new URL('https://example.com/path/to/file'))).toBe(false)
+ expect(m(p, new URL('https://example.com/path/to/file?q=1'))).toBe(false)
+ expect(m(p, new URL('http://example.com/path/to/file'))).toBe(false)
+ expect(m(p, new URL('ftp://example.com/path/to/file'))).toBe(false)
+ })
+
+ it('should match literal protocol, hostname, port, pathname', () => {
+ const p = {
+ protocol: 'https',
+ hostname: 'example.com',
+ port: '42',
+ pathname: '/path/to/file',
+ } as const
+ expect(m(p, new URL('https://example.com:42'))).toBe(false)
+ expect(m(p, new URL('https://example.com.uk:42'))).toBe(false)
+ expect(m(p, new URL('https://sub.example.com:42'))).toBe(false)
+ expect(m(p, new URL('https://example.com:42/path'))).toBe(false)
+ expect(m(p, new URL('https://example.com:42/path/to'))).toBe(false)
+ expect(m(p, new URL('https://example.com:42/file'))).toBe(false)
+ expect(m(p, new URL('https://example.com:42/path/to/file'))).toBe(true)
+ expect(m(p, new URL('https://example.com:42/path/to/file?q=1'))).toBe(true)
+ expect(m(p, new URL('http://example.com:42/path/to/file'))).toBe(false)
+ expect(m(p, new URL('ftp://example.com:42/path/to/file'))).toBe(false)
+ expect(m(p, new URL('https://example.com'))).toBe(false)
+ expect(m(p, new URL('https://example.com.uk'))).toBe(false)
+ expect(m(p, new URL('https://sub.example.com'))).toBe(false)
+ expect(m(p, new URL('https://example.com/path'))).toBe(false)
+ expect(m(p, new URL('https://example.com/path/to'))).toBe(false)
+ expect(m(p, new URL('https://example.com/path/to/file'))).toBe(false)
+ expect(m(p, new URL('https://example.com/path/to/file?q=1'))).toBe(false)
+ expect(m(p, new URL('http://example.com/path/to/file'))).toBe(false)
+ expect(m(p, new URL('ftp://example.com/path/to/file'))).toBe(false)
+ })
+
+ it('should match hostname pattern with single asterisk', () => {
+ const p = { hostname: 'avatars.*.example.com' } as const
+ expect(m(p, new URL('https://com'))).toBe(false)
+ expect(m(p, new URL('https://example.com'))).toBe(false)
+ expect(m(p, new URL('https://sub.example.com'))).toBe(false)
+ expect(m(p, new URL('https://example.com.uk'))).toBe(false)
+ expect(m(p, new URL('https://sub.example.com.uk'))).toBe(false)
+ expect(m(p, new URL('https://avatars.example.com'))).toBe(false)
+ expect(m(p, new URL('https://avatars.sfo1.example.com'))).toBe(true)
+ expect(m(p, new URL('https://avatars.iad1.example.com'))).toBe(true)
+ expect(m(p, new URL('https://more.avatars.iad1.example.com'))).toBe(false)
+ })
+
+ it('should match hostname pattern with double asterisk', () => {
+ const p = { hostname: '**.example.com' } as const
+ expect(m(p, new URL('https://com'))).toBe(false)
+ expect(m(p, new URL('https://example.com'))).toBe(false)
+ expect(m(p, new URL('https://sub.example.com'))).toBe(true)
+ expect(m(p, new URL('https://deep.sub.example.com'))).toBe(true)
+ expect(m(p, new URL('https://example.com.uk'))).toBe(false)
+ expect(m(p, new URL('https://sub.example.com.uk'))).toBe(false)
+ expect(m(p, new URL('https://avatars.example.com'))).toBe(true)
+ expect(m(p, new URL('https://avatars.sfo1.example.com'))).toBe(true)
+ expect(m(p, new URL('https://avatars.iad1.example.com'))).toBe(true)
+ expect(m(p, new URL('https://more.avatars.iad1.example.com'))).toBe(true)
+ })
+
+ it('should match pathname pattern with single asterisk', () => {
+ const p = {
+ hostname: 'example.com',
+ pathname: '/act123/*/pic.jpg',
+ } as const
+ expect(m(p, new URL('https://com'))).toBe(false)
+ expect(m(p, new URL('https://example.com'))).toBe(false)
+ expect(m(p, new URL('https://sub.example.com'))).toBe(false)
+ expect(m(p, new URL('https://example.com.uk'))).toBe(false)
+ expect(m(p, new URL('https://example.com/act123'))).toBe(false)
+ expect(m(p, new URL('https://example.com/act123/usr4'))).toBe(false)
+ expect(m(p, new URL('https://example.com/act123/usr4/pic'))).toBe(false)
+ expect(m(p, new URL('https://example.com/act123/usr4/picsjpg'))).toBe(false)
+ expect(m(p, new URL('https://example.com/act123/usr4/pic.jpg'))).toBe(true)
+ expect(m(p, new URL('https://example.com/act123/usr5/pic.jpg'))).toBe(true)
+ expect(m(p, new URL('https://example.com/act123/usr6/pic.jpg'))).toBe(true)
+ expect(m(p, new URL('https://example.com/act123/team/pic.jpg'))).toBe(true)
+ expect(m(p, new URL('https://example.com/act456/team/pic.jpg'))).toBe(false)
+ expect(m(p, new URL('https://example.com/team/pic.jpg'))).toBe(false)
+ })
+
+ it('should match pathname pattern with double asterisk', () => {
+ const p = { hostname: 'example.com', pathname: '/act123/**' } as const
+ expect(m(p, new URL('https://com'))).toBe(false)
+ expect(m(p, new URL('https://example.com'))).toBe(false)
+ expect(m(p, new URL('https://sub.example.com'))).toBe(false)
+ expect(m(p, new URL('https://example.com.uk'))).toBe(false)
+ expect(m(p, new URL('https://example.com/act123'))).toBe(false)
+ expect(m(p, new URL('https://example.com/act123/usr4'))).toBe(true)
+ expect(m(p, new URL('https://example.com/act123/usr4/pic'))).toBe(true)
+ expect(m(p, new URL('https://example.com/act123/usr4/picsjpg'))).toBe(true)
+ expect(m(p, new URL('https://example.com/act123/usr4/pic.jpg'))).toBe(true)
+ expect(m(p, new URL('https://example.com/act123/usr5/pic.jpg'))).toBe(true)
+ expect(m(p, new URL('https://example.com/act123/usr6/pic.jpg'))).toBe(true)
+ expect(m(p, new URL('https://example.com/act123/team/pic.jpg'))).toBe(true)
+ expect(m(p, new URL('https://example.com/act456/team/pic.jpg'))).toBe(false)
+ expect(m(p, new URL('https://example.com/team/pic.jpg'))).toBe(false)
+ })
+
+ it('should throw when hostname is missing', () => {
+ const p = { protocol: 'https' } as const
+ // @ts-ignore testing invalid input
+ expect(() => m(p, new URL('https://example.com'))).toThrow(
+ 'Pattern should define hostname but found\n{"protocol":"https"}'
+ )
+ })
+
+ it('should throw when hostname has double asterisk in the middle', () => {
+ const p = { hostname: 'example.**.com' } as const
+ expect(() => m(p, new URL('https://example.com'))).toThrow(
+ 'Pattern can only contain ** at start of hostname but found "example.**.com"'
+ )
+ })
+
+ it('should throw when pathname has double asterisk in the middle', () => {
+ const p = { hostname: 'example.com', pathname: '/**/img' } as const
+ expect(() => m(p, new URL('https://example.com'))).toThrow(
+ 'Pattern can only contain ** at end of pathname but found "/**/img"'
+ )
+ })
+
+ it('should properly work with hasMatch', () => {
+ const url = new URL('https://example.com')
+ expect(hasMatch([], [], url)).toBe(false)
+ expect(hasMatch(['foo.com'], [], url)).toBe(false)
+ expect(hasMatch(['example.com'], [], url)).toBe(true)
+ expect(hasMatch(['**.example.com'], [], url)).toBe(false)
+ expect(hasMatch(['*.example.com'], [], url)).toBe(false)
+ expect(hasMatch(['*.example.com'], [], url)).toBe(false)
+ expect(hasMatch([], [{ hostname: 'foo.com' }], url)).toBe(false)
+ expect(
+ hasMatch([], [{ hostname: 'foo.com' }, { hostname: 'example.com' }], url)
+ ).toBe(true)
+ expect(
+ hasMatch([], [{ hostname: 'example.com', pathname: '/act123/**' }], url)
+ ).toBe(false)
+ expect(
+ hasMatch(
+ ['example.com'],
+ [{ hostname: 'example.com', pathname: '/act123/**' }],
+ url
+ )
+ ).toBe(true)
+ expect(
+ hasMatch([], [{ protocol: 'https', hostname: 'example.com' }], url)
+ ).toBe(true)
+ expect(
+ hasMatch([], [{ protocol: 'http', hostname: 'example.com' }], url)
+ ).toBe(false)
+ expect(
+ hasMatch(
+ ['example.com'],
+ [{ protocol: 'http', hostname: 'example.com' }],
+ url
+ )
+ ).toBe(true)
+ expect(
+ hasMatch(
+ ['foo.com'],
+ [{ protocol: 'http', hostname: 'example.com' }],
+ url
+ )
+ ).toBe(false)
+ })
+})
diff --git a/tsec-exemptions.json b/tsec-exemptions.json
index ec33c3b673ea..ed01f28e893e 100644
--- a/tsec-exemptions.json
+++ b/tsec-exemptions.json
@@ -1,20 +1,13 @@
{
"ban-element-innerhtml-assignments": [
"packages/next/client/head-manager.ts",
- "packages/next/client/script.tsx",
- "packages/react-dev-overlay/src/internal/components/Overlay/maintain--tab-focus.ts"
+ "packages/next/client/script.tsx"
],
"ban-element-setattribute": [
"packages/next/client/head-manager.ts",
"packages/next/client/script.tsx"
],
"ban-script-content-assignments": ["packages/next/client/script.tsx"],
- "ban-script-src-assignments": [
- "packages/next/client/route-loader.ts",
- "packages/next/client/script.tsx"
- ],
- "ban-window-stringfunctiondef": [
- "packages/next/lib/recursive-delete.ts",
- "packages/next/client/dev/fouc.ts"
- ]
+ "ban-script-src-assignments": ["packages/next/client/script.tsx"],
+ "ban-trustedtypes-createpolicy": ["packages/next/client/trusted-types.ts"]
}
diff --git a/turbo.json b/turbo.json
new file mode 100644
index 000000000000..62b903aeb59f
--- /dev/null
+++ b/turbo.json
@@ -0,0 +1,12 @@
+{
+ "pipeline": {
+ "build-native": {
+ "dependsOn": ["^build-native"],
+ "outputs": ["native/*.node"]
+ },
+ "build-wasm": {
+ "dependsOn": ["^build-wasm"],
+ "outputs": ["crates/wasm/pkg/*"]
+ }
+ }
+}
diff --git a/yarn.lock b/yarn.lock
index f09f0c2999a7..3cae0cb367a2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5407,6 +5407,11 @@
dependencies:
"@types/node" "*"
+"@types/trusted-types@2.0.2":
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756"
+ integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==
+
"@types/ua-parser-js@0.7.36":
version "0.7.36"
resolved "https://registry.yarnpkg.com/@types/ua-parser-js/-/ua-parser-js-0.7.36.tgz#9bd0b47f26b5a3151be21ba4ce9f5fa457c5f190"
@@ -20103,6 +20108,18 @@ tar@4.4.10:
safe-buffer "^5.1.2"
yallist "^3.0.3"
+tar@6.1.11, tar@^6.1.11:
+ version "6.1.11"
+ resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621"
+ integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==
+ dependencies:
+ chownr "^2.0.0"
+ fs-minipass "^2.0.0"
+ minipass "^3.0.0"
+ minizlib "^2.1.1"
+ mkdirp "^1.0.3"
+ yallist "^4.0.0"
+
tar@^4, tar@^4.4.12:
version "4.4.13"
resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525"
@@ -20140,18 +20157,6 @@ tar@^6.1.0:
mkdirp "^1.0.3"
yallist "^4.0.0"
-tar@^6.1.11:
- version "6.1.11"
- resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621"
- integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==
- dependencies:
- chownr "^2.0.0"
- fs-minipass "^2.0.0"
- minipass "^3.0.0"
- minizlib "^2.1.1"
- mkdirp "^1.0.3"
- yallist "^4.0.0"
-
taskr@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/taskr/-/taskr-1.1.0.tgz#4f29d0ace26f4deae9a478eabf9aa0432e884438"