From 19ad91709e72b6e8d57af5b4cf702036ad09d223 Mon Sep 17 00:00:00 2001 From: "Blitz.js Bot" <79382586+blitzjs-bot@users.noreply.github.com> Date: Fri, 3 Dec 2021 08:23:32 -0500 Subject: [PATCH 1/3] (meta) added @divpreet as contributor --- .all-contributorsrc | 9 +++++++++ README.md | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 779d7a9c83..04f12499db 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -3499,6 +3499,15 @@ "contributions": [ "doc" ] + }, + { + "login": "divpreet", + "name": "Div", + "avatar_url": "https://avatars.githubusercontent.com/u/2805650?v=4", + "profile": "https://github.com/divpreet", + "contributions": [ + "doc" + ] } ], "contributorsPerLine": 7, diff --git a/README.md b/README.md index f31a112a24..90d355d140 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ - + @@ -732,6 +732,9 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e
David Christie

📖
Ajanth

📖 + +
Div

📖 + From 4aba0d31f6cf990810f34ac56820ca7dd2942e8f Mon Sep 17 00:00:00 2001 From: Aleksandra Sikora Date: Fri, 3 Dec 2021 16:05:37 +0100 Subject: [PATCH 2/3] Load env variables based on `APP_ENV` or `-e` flag (#2878) (minor) --- .github/workflows/compressed.yml | 3 + .github/workflows/main.yml | 37 ++-- CONTRIBUTING.md | 6 + examples/cypress/cypress/plugins/index.ts | 4 +- examples/fauna/test/setup.ts | 4 +- nextjs/packages/next-env/README.md | 2 +- nextjs/packages/next-env/index.ts | 9 +- nextjs/packages/next-env/package.json | 7 +- nextjs/packages/next/bin/next.ts | 7 + nextjs/packages/next/build/entries.ts | 2 +- nextjs/packages/next/build/index.ts | 2 +- .../loaders/next-serverless-loader/index.ts | 2 +- nextjs/packages/next/export/index.ts | 2 +- nextjs/packages/next/package.json | 2 +- .../next/server/config-utils-worker.ts | 2 +- nextjs/packages/next/server/config.ts | 2 +- nextjs/packages/next/server/next-server.ts | 2 +- nextjs/packages/next/shared/lib/utils.ts | 2 +- .../integration/chunking/test/index.test.js | 2 +- .../integration/env-config-app-env/app/.env | 5 + .../env-config-app-env/app/.env.production | 1 + .../env-config-app-env/app/.env.staging | 4 + .../env-config-app-env/app/.env.staging.local | 1 + .../env-config-app-env/app/next.config.js | 17 ++ .../env-config-app-env/app/package.json | 4 + .../env-config-app-env/app/pages/api/all.js | 24 +++ .../env-config-app-env/app/pages/global.js | 1 + .../env-config-app-env/app/pages/index.js | 35 ++++ .../env-config-app-env/app/pages/some-ssg.js | 35 ++++ .../env-config-app-env/app/pages/some-ssp.js | 34 ++++ .../env-config-app-env/test/index.test.js | 163 ++++++++++++++++++ .../package-managers/pnpm/app/package.json | 2 +- .../package-managers/pnpm/test/index.test.js | 2 +- package.json | 8 +- packages/blitz/jest-preset/global-setup.js | 4 +- packages/blitz/src/cli.ts | 4 + packages/cli/package.json | 1 - packages/cli/src/commands/build.ts | 4 + packages/cli/src/commands/codegen.ts | 4 + packages/cli/src/commands/console.ts | 4 + packages/cli/src/commands/db.ts | 4 + packages/cli/src/commands/dev.ts | 4 + packages/cli/src/commands/export.ts | 4 + packages/cli/src/commands/generate.ts | 4 + packages/cli/src/commands/install.ts | 4 + packages/cli/src/commands/new.ts | 4 +- packages/cli/src/commands/prisma.ts | 9 +- packages/cli/src/commands/start.ts | 4 + packages/cli/src/index.ts | 8 +- yarn.lock | 32 ++-- 50 files changed, 476 insertions(+), 57 deletions(-) create mode 100644 nextjs/test/integration/env-config-app-env/app/.env create mode 100644 nextjs/test/integration/env-config-app-env/app/.env.production create mode 100644 nextjs/test/integration/env-config-app-env/app/.env.staging create mode 100644 nextjs/test/integration/env-config-app-env/app/.env.staging.local create mode 100644 nextjs/test/integration/env-config-app-env/app/next.config.js create mode 100644 nextjs/test/integration/env-config-app-env/app/package.json create mode 100644 nextjs/test/integration/env-config-app-env/app/pages/api/all.js create mode 100644 nextjs/test/integration/env-config-app-env/app/pages/global.js create mode 100644 nextjs/test/integration/env-config-app-env/app/pages/index.js create mode 100644 nextjs/test/integration/env-config-app-env/app/pages/some-ssg.js create mode 100644 nextjs/test/integration/env-config-app-env/app/pages/some-ssp.js create mode 100644 nextjs/test/integration/env-config-app-env/test/index.test.js diff --git a/.github/workflows/compressed.yml b/.github/workflows/compressed.yml index ce7fc6f009..175069598a 100644 --- a/.github/workflows/compressed.yml +++ b/.github/workflows/compressed.yml @@ -15,6 +15,9 @@ jobs: steps: - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: "14" - name: Count size uses: preactjs/compressed-size-action@v2 with: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2ff8fa5ac1..17483f8fee 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,9 +30,9 @@ jobs: path: | ${{ steps.yarn-cache-dir-path.outputs.dir }} **/node_modules - key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v13-${{ hashFiles('yarn.lock') }} + key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v14-${{ hashFiles('yarn.lock') }} restore-keys: | - ${{ runner.os }}-${{ runner.node_version}}-yarn-v13- + ${{ runner.os }}-${{ runner.node_version}}-yarn-v14- - name: Install dependencies run: yarn install --frozen-lockfile --silent env: @@ -74,9 +74,9 @@ jobs: path: | ${{ steps.yarn-cache-dir-path.outputs.dir }} **/node_modules - key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v13-${{ hashFiles('yarn.lock') }} + key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v14-${{ hashFiles('yarn.lock') }} restore-keys: | - ${{ runner.os }}-${{ runner.node_version}}-yarn-v13- + ${{ runner.os }}-${{ runner.node_version}}-yarn-v14- - run: yarn install --frozen-lockfile --check-files - name: Build Packages run: yarn build @@ -98,6 +98,10 @@ jobs: with: path: ./* key: ${{ runner.os }}-${{ github.sha }} + - name: Use Node.js + uses: actions/setup-node@v2 + with: + node-version: "14" - name: Setup kernel to increase watchers if: runner.os == 'Linux' run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p @@ -120,6 +124,10 @@ jobs: with: path: ./* key: ${{ runner.os }}-${{ github.sha }} + - name: Use Node.js + uses: actions/setup-node@v2 + with: + node-version: "14" # Needed to get cypress binary - run: yarn cypress install - name: Install sass @@ -157,9 +165,9 @@ jobs: # path: | # ${{ steps.yarn-cache-dir-path.outputs.dir }} # **/node_modules - # key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v13-${{ hashFiles('yarn.lock') }} + # key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v14-${{ hashFiles('yarn.lock') }} # restore-keys: | - # ${{ runner.os }}-${{ runner.node_version}}-yarn-v13- + # ${{ runner.os }}-${{ runner.node_version}}-yarn-v14- - run: yarn install --frozen-lockfile --check-files - name: Build Packages run: yarn build @@ -222,6 +230,10 @@ jobs: with: path: ./* key: ${{ runner.os }}-${{ github.sha }} + - name: Use Node.js + uses: actions/setup-node@v2 + with: + node-version: "14" # TODO: remove after we fix watchpack watching too much - name: Setup kernel to increase watchers @@ -256,9 +268,9 @@ jobs: # path: | # ${{ steps.yarn-cache-dir-path.outputs.dir }} # **/node_modules - # key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v13-${{ hashFiles('yarn.lock') }} + # key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v14-${{ hashFiles('yarn.lock') }} # restore-keys: | - # ${{ runner.os }}-${{ runner.node_version}}-yarn-v13- + # ${{ runner.os }}-${{ runner.node_version}}-yarn-v14- - run: yarn install --frozen-lockfile --check-files - name: Build Packages run: yarn build @@ -285,7 +297,10 @@ jobs: with: path: ./* key: ${{ runner.os }}-${{ github.sha }} - + - name: Use Node.js + uses: actions/setup-node@v2 + with: + node-version: "14" # TODO: remove after we fix watchpack watching too much - name: Setup kernel to increase watchers if: runner.os == 'Linux' @@ -318,9 +333,9 @@ jobs: # path: | # ${{ steps.yarn-cache-dir-path.outputs.dir }} # **/node_modules - # key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v13-${{ hashFiles('yarn.lock') }} + # key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v14-${{ hashFiles('yarn.lock') }} # restore-keys: | - # ${{ runner.os }}-${{ runner.node_version}}-yarn-v13- + # ${{ runner.os }}-${{ runner.node_version}}-yarn-v14- - run: yarn install --frozen-lockfile --check-files - name: Build Packages run: yarn build diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 48672c9edb..3b5ed7fcf3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,6 +4,12 @@ ## Notes For Core Team +### To Publish a new NPM Package under `@blitzjs/` namespace + +1. cd into the package directory +2. Run `npm publish --tag danger --access public` + - `--access public` is required because scoped packages are set to private by default + ### Syncing Next.js Fork 1. Run `yarn push-nextjs` diff --git a/examples/cypress/cypress/plugins/index.ts b/examples/cypress/cypress/plugins/index.ts index 5e198bd0b8..b7967503bf 100644 --- a/examples/cypress/cypress/plugins/index.ts +++ b/examples/cypress/cypress/plugins/index.ts @@ -1,5 +1,7 @@ process.env.NODE_ENV = "test" -require("dotenv-flow").config({ silent: true }) + +import { loadEnvConfig } from "@blitzjs/env" +loadEnvConfig() import "./register-ts-paths" import db from "db" diff --git a/examples/fauna/test/setup.ts b/examples/fauna/test/setup.ts index 8f777a3189..ecbde24ef3 100644 --- a/examples/fauna/test/setup.ts +++ b/examples/fauna/test/setup.ts @@ -3,4 +3,6 @@ // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom import "@testing-library/jest-dom/extend-expect" -require("dotenv-flow").config({ silent: true }) +import { loadEnvConfig } from "@blitzjs/env" + +loadEnvConfig() diff --git a/nextjs/packages/next-env/README.md b/nextjs/packages/next-env/README.md index cc08c112ee..af60128ea7 100644 --- a/nextjs/packages/next-env/README.md +++ b/nextjs/packages/next-env/README.md @@ -1,3 +1,3 @@ -# `@next/env` +# `@blitzjs/env` Next.js' util for loading dotenv files in with the proper priorities diff --git a/nextjs/packages/next-env/index.ts b/nextjs/packages/next-env/index.ts index a33636c702..2ab59f2288 100644 --- a/nextjs/packages/next-env/index.ts +++ b/nextjs/packages/next-env/index.ts @@ -66,7 +66,7 @@ export function processEnv( } export function loadEnvConfig( - dir: string, + dir: string = process.cwd(), dev?: boolean, log: Log = console ): { @@ -79,8 +79,11 @@ export function loadEnvConfig( const isTest = process.env.NODE_ENV === 'test' const mode = isTest ? 'test' : dev ? 'development' : 'production' - const dotenvFiles = [ + const appEnv = process.env.APP_ENV + let dotenvFiles = [ `.env.${mode}.local`, + appEnv && `.env.${appEnv}.local`, + appEnv && `.env.${appEnv}`, // Don't include `.env.local` for `test` environment // since normally you expect tests to produce the same // results for everyone @@ -106,7 +109,7 @@ export function loadEnvConfig( path: envFile, contents, }) - } catch (err) { + } catch (err: any) { if (err.code !== 'ENOENT') { log.error(`Failed to load env from ${envFile}`, err) } diff --git a/nextjs/packages/next-env/package.json b/nextjs/packages/next-env/package.json index b2f6a2b635..b71c45e0e6 100644 --- a/nextjs/packages/next-env/package.json +++ b/nextjs/packages/next-env/package.json @@ -1,7 +1,6 @@ { - "private": true, - "name": "@next/env", - "version": "11.1.0", + "name": "@blitzjs/env", + "version": "0.43.0", "keywords": [ "react", "next", @@ -14,7 +13,7 @@ "url": "https://github.com/vercel/next.js", "directory": "packages/next-env" }, - "author": "Next.js Team ", + "author": "Blitz.js", "license": "MIT", "main": "dist/index.js", "types": "types/index.d.ts", diff --git a/nextjs/packages/next/bin/next.ts b/nextjs/packages/next/bin/next.ts index 9e34173180..3bf38eb68c 100755 --- a/nextjs/packages/next/bin/next.ts +++ b/nextjs/packages/next/bin/next.ts @@ -30,10 +30,12 @@ const args = arg( '--version': Boolean, '--help': Boolean, '--inspect': Boolean, + '--env': String, // Aliases '-v': '--version', '-h': '--help', + '-e': '--env' }, { permissive: true, @@ -61,6 +63,7 @@ if (!foundCommand && args['--help']) { ${Object.keys(commands).join(', ')} Options + --env, -e App environment name --version, -v Version number --help, -h Displays this message @@ -83,6 +86,10 @@ if (args['--help']) { forwardedArgs.push('--help') } +if (args['--env']) { + process.env.APP_ENV = args['--env'] +} + const defaultEnv = command === 'dev' ? 'development' : 'production' const standardEnv = ['production', 'development', 'test'] diff --git a/nextjs/packages/next/build/entries.ts b/nextjs/packages/next/build/entries.ts index 59a3bcc7c7..2798c14360 100644 --- a/nextjs/packages/next/build/entries.ts +++ b/nextjs/packages/next/build/entries.ts @@ -8,7 +8,7 @@ import { normalizePagePath } from '../server/normalize-page-path' import { warn } from './output/log' import { ClientPagesLoaderOptions } from './webpack/loaders/next-client-pages-loader' import { ServerlessLoaderQuery } from './webpack/loaders/next-serverless-loader' -import { LoadedEnvFiles } from '@next/env' +import { LoadedEnvFiles } from '@blitzjs/env' import { convertPageFilePathToRoutePath } from './utils' import { NextConfigComplete } from '../server/config-shared' diff --git a/nextjs/packages/next/build/index.ts b/nextjs/packages/next/build/index.ts index cce32b9926..cf678d9477 100644 --- a/nextjs/packages/next/build/index.ts +++ b/nextjs/packages/next/build/index.ts @@ -1,4 +1,4 @@ -import { loadEnvConfig } from '@next/env' +import { loadEnvConfig } from '@blitzjs/env' import chalk from 'chalk' import crypto from 'crypto' import { promises, writeFileSync } from 'fs' diff --git a/nextjs/packages/next/build/webpack/loaders/next-serverless-loader/index.ts b/nextjs/packages/next/build/webpack/loaders/next-serverless-loader/index.ts index 7db9b8832d..6196232978 100644 --- a/nextjs/packages/next/build/webpack/loaders/next-serverless-loader/index.ts +++ b/nextjs/packages/next/build/webpack/loaders/next-serverless-loader/index.ts @@ -90,7 +90,7 @@ const nextServerlessLoader: webpack.loader.Loader = function () { ) const envLoading = ` - const { processEnv } = require('@next/env') + const { processEnv } = require('@blitzjs/env') processEnv(${Buffer.from(loadedEnvFiles, 'base64').toString()}) ` diff --git a/nextjs/packages/next/export/index.ts b/nextjs/packages/next/export/index.ts index 646e2eaf23..294bae2665 100644 --- a/nextjs/packages/next/export/index.ts +++ b/nextjs/packages/next/export/index.ts @@ -37,7 +37,7 @@ import { normalizePagePath, denormalizePagePath, } from '../server/normalize-page-path' -import { loadEnvConfig } from '@next/env' +import { loadEnvConfig } from '@blitzjs/env' import { PrerenderManifest } from '../build' import { PagesManifest } from '../build/webpack/plugins/pages-manifest-plugin' import { getPagePath } from '../server/require' diff --git a/nextjs/packages/next/package.json b/nextjs/packages/next/package.json index 88fe41bbd3..476b93ddcf 100644 --- a/nextjs/packages/next/package.json +++ b/nextjs/packages/next/package.json @@ -73,8 +73,8 @@ "dependencies": { "@babel/helper-module-imports": "^7.0.0", "@babel/runtime": "7.12.5", + "@blitzjs/env": "0.43.0", "@hapi/accept": "5.0.2", - "@next/env": "11.1.0", "@next/polyfill-module": "11.1.0", "@next/react-dev-overlay": "11.1.0", "@next/react-refresh-utils": "11.1.0", diff --git a/nextjs/packages/next/server/config-utils-worker.ts b/nextjs/packages/next/server/config-utils-worker.ts index 3fa34c399f..957c38584f 100644 --- a/nextjs/packages/next/server/config-utils-worker.ts +++ b/nextjs/packages/next/server/config-utils-worker.ts @@ -1,4 +1,4 @@ -import { loadEnvConfig } from '@next/env' +import { loadEnvConfig } from '@blitzjs/env' import findUp from 'next/dist/compiled/find-up' import { init as initWebpack } from 'next/dist/compiled/webpack/webpack' import { CONFIG_FILE, PHASE_DEVELOPMENT_SERVER } from '../shared/lib/constants' diff --git a/nextjs/packages/next/server/config.ts b/nextjs/packages/next/server/config.ts index ecf7e3230c..49ecb170f3 100644 --- a/nextjs/packages/next/server/config.ts +++ b/nextjs/packages/next/server/config.ts @@ -14,7 +14,7 @@ import { } from './config-shared' import { loadWebpackHook } from './config-utils' import { ImageConfig, imageConfigDefault, VALID_LOADERS } from './image-config' -import { loadEnvConfig } from '@next/env' +import { loadEnvConfig } from '@blitzjs/env' import { hasNextSupport } from '../telemetry/ci-info' const debug = require('debug')('blitz:config') diff --git a/nextjs/packages/next/server/next-server.ts b/nextjs/packages/next/server/next-server.ts index 3580e16a8d..8d991f63f3 100644 --- a/nextjs/packages/next/server/next-server.ts +++ b/nextjs/packages/next/server/next-server.ts @@ -82,7 +82,7 @@ import { resultFromChunks, resultToChunks, } from './utils' -import { loadEnvConfig } from '@next/env' +import { loadEnvConfig } from '@blitzjs/env' import './node-polyfill-fetch' import { PagesManifest } from '../build/webpack/plugins/pages-manifest-plugin' import { removePathTrailingSlash } from '../client/normalize-trailing-slash' diff --git a/nextjs/packages/next/shared/lib/utils.ts b/nextjs/packages/next/shared/lib/utils.ts index ea031e843d..c9f5f5d6ba 100644 --- a/nextjs/packages/next/shared/lib/utils.ts +++ b/nextjs/packages/next/shared/lib/utils.ts @@ -2,7 +2,7 @@ import { formatUrl } from './router/utils/format-url' import type { BuildManifest } from '../../server/get-page-files' import type { ComponentType } from 'react' import type { DomainLocale } from '../../server/config' -import type { Env } from '@next/env' +import type { Env } from '@blitzjs/env' import type { IncomingMessage, ServerResponse } from 'http' import type { NextRouter } from './router/router' import type { ParsedUrlQuery } from 'querystring' diff --git a/nextjs/test/integration/chunking/test/index.test.js b/nextjs/test/integration/chunking/test/index.test.js index 24bcfd92d2..5b0d74a807 100644 --- a/nextjs/test/integration/chunking/test/index.test.js +++ b/nextjs/test/integration/chunking/test/index.test.js @@ -12,7 +12,7 @@ import { import webdriver from 'next-webdriver' import { join } from 'path' -jest.setTimeout(1000 * 60 * 1) +jest.setTimeout(1000 * 60 * 3) const appDir = join(__dirname, '../') diff --git a/nextjs/test/integration/env-config-app-env/app/.env b/nextjs/test/integration/env-config-app-env/app/.env new file mode 100644 index 0000000000..3ae85099f0 --- /dev/null +++ b/nextjs/test/integration/env-config-app-env/app/.env @@ -0,0 +1,5 @@ +PROCESS_ENV_KEY="env" +ENV_FILE_KEY=env +NEXT_PUBLIC_TEST_DEST=another +ENV_KEY_IN_NEXT_CONFIG="hello from next.config.js" +NEXT_PUBLIC_ENV_KEY_IN_NEXT_CONFIG="hello again from next.config.js" diff --git a/nextjs/test/integration/env-config-app-env/app/.env.production b/nextjs/test/integration/env-config-app-env/app/.env.production new file mode 100644 index 0000000000..04bfde25bb --- /dev/null +++ b/nextjs/test/integration/env-config-app-env/app/.env.production @@ -0,0 +1 @@ +PRODUCTION_ENV_FILE_KEY=production diff --git a/nextjs/test/integration/env-config-app-env/app/.env.staging b/nextjs/test/integration/env-config-app-env/app/.env.staging new file mode 100644 index 0000000000..1d105ed833 --- /dev/null +++ b/nextjs/test/integration/env-config-app-env/app/.env.staging @@ -0,0 +1,4 @@ +NEXT_PUBLIC_TEST_DEST=staging-route +ENV_KEY_IN_NEXT_CONFIG="hello from staging app" +NEXT_PUBLIC_ENV_KEY_IN_NEXT_CONFIG="hello again from staging app" +ENV_FILE_DEVELOPMENT_OVERRIDE_TEST=staging diff --git a/nextjs/test/integration/env-config-app-env/app/.env.staging.local b/nextjs/test/integration/env-config-app-env/app/.env.staging.local new file mode 100644 index 0000000000..bf2d4e5ae4 --- /dev/null +++ b/nextjs/test/integration/env-config-app-env/app/.env.staging.local @@ -0,0 +1 @@ +ENV_FILE_DEVELOPMENT_OVERRIDE_TEST=staginglocal diff --git a/nextjs/test/integration/env-config-app-env/app/next.config.js b/nextjs/test/integration/env-config-app-env/app/next.config.js new file mode 100644 index 0000000000..df889543a5 --- /dev/null +++ b/nextjs/test/integration/env-config-app-env/app/next.config.js @@ -0,0 +1,17 @@ +module.exports = { + cleanDistDir: false, + // update me + env: { + nextConfigEnv: process.env.ENV_KEY_IN_NEXT_CONFIG, + nextConfigPublicEnv: process.env.NEXT_PUBLIC_ENV_KEY_IN_NEXT_CONFIG, + }, + async redirects() { + return [ + { + source: '/hello', + permanent: false, + destination: `/${process.env.NEXT_PUBLIC_TEST_DEST}`, + }, + ] + }, +} diff --git a/nextjs/test/integration/env-config-app-env/app/package.json b/nextjs/test/integration/env-config-app-env/app/package.json new file mode 100644 index 0000000000..2904b4924b --- /dev/null +++ b/nextjs/test/integration/env-config-app-env/app/package.json @@ -0,0 +1,4 @@ +{ + "name": "env-config-app-env", + "dependencies": {} +} diff --git a/nextjs/test/integration/env-config-app-env/app/pages/api/all.js b/nextjs/test/integration/env-config-app-env/app/pages/api/all.js new file mode 100644 index 0000000000..4099fa68fd --- /dev/null +++ b/nextjs/test/integration/env-config-app-env/app/pages/api/all.js @@ -0,0 +1,24 @@ +const variables = [ + 'PROCESS_ENV_KEY', + 'ENV_FILE_KEY', + 'ENV_FILE_EMPTY_FIRST', + 'PRODUCTION_ENV_FILE_KEY', + 'LOCAL_PRODUCTION_ENV_FILE_KEY', + 'ENV_FILE_DEVELOPMENT_OVERRIDE_TEST', + 'ENV_FILE_PRODUCTION_OVERRIDEOVERRIDE_TEST', + 'ENV_FILE_PRODUCTION_LOCAL_OVERRIDEOVERRIDE_TEST', +] + +const items = { + nextConfigEnv: process.env.nextConfigEnv, + nextConfigPublicEnv: process.env.nextConfigPublicEnv, +} + +variables.forEach((variable) => { + items[variable] = process.env[variable] +}) + +export default async (req, res) => { + // Only for testing, don't do this... + res.json(items) +} diff --git a/nextjs/test/integration/env-config-app-env/app/pages/global.js b/nextjs/test/integration/env-config-app-env/app/pages/global.js new file mode 100644 index 0000000000..ded4746e31 --- /dev/null +++ b/nextjs/test/integration/env-config-app-env/app/pages/global.js @@ -0,0 +1 @@ +export default () =>

{process.env.NEXT_PUBLIC_TEST_DEST}

diff --git a/nextjs/test/integration/env-config-app-env/app/pages/index.js b/nextjs/test/integration/env-config-app-env/app/pages/index.js new file mode 100644 index 0000000000..36d51b372e --- /dev/null +++ b/nextjs/test/integration/env-config-app-env/app/pages/index.js @@ -0,0 +1,35 @@ +const variables = [ + 'PROCESS_ENV_KEY', + 'ENV_FILE_KEY', + 'ENV_FILE_EMPTY_FIRST', + 'PRODUCTION_ENV_FILE_KEY', + 'LOCAL_PRODUCTION_ENV_FILE_KEY', + 'ENV_FILE_DEVELOPMENT_OVERRIDE_TEST', + 'ENV_FILE_PRODUCTION_OVERRIDEOVERRIDE_TEST', + 'ENV_FILE_PRODUCTION_LOCAL_OVERRIDEOVERRIDE_TEST', +] + +export async function getStaticProps() { + const items = {} + + variables.forEach((variable) => { + if (process.env[variable]) { + items[variable] = process.env[variable] + } + }) + + return { + // Do not pass any sensitive values here as they will + // be made PUBLICLY available in `pageProps` + props: { env: items }, + revalidate: 1, + } +} + +export default ({ env }) => ( + <> +

{JSON.stringify(env)}

+
{process.env.nextConfigEnv}
+
{process.env.nextConfigPublicEnv}
+ +) diff --git a/nextjs/test/integration/env-config-app-env/app/pages/some-ssg.js b/nextjs/test/integration/env-config-app-env/app/pages/some-ssg.js new file mode 100644 index 0000000000..0fc7a3b6dc --- /dev/null +++ b/nextjs/test/integration/env-config-app-env/app/pages/some-ssg.js @@ -0,0 +1,35 @@ +const variables = [ + 'PROCESS_ENV_KEY', + 'ENV_FILE_KEY', + 'ENV_FILE_EMPTY_FIRST', + 'PRODUCTION_ENV_FILE_KEY', + 'LOCAL_PRODUCTION_ENV_FILE_KEY', + 'ENV_FILE_DEVELOPMENT_OVERRIDE_TEST', + 'ENV_FILE_PRODUCTION_OVERRIDEOVERRIDE_TEST', + 'ENV_FILE_PRODUCTION_LOCAL_OVERRIDEOVERRIDE_TEST', +] + +export async function getStaticProps() { + const items = {} + + variables.forEach((variable) => { + if (typeof process.env[variable] !== 'undefined') { + items[variable] = process.env[variable] + } + }) + + return { + // Do not pass any sensitive values here as they will + // be made PUBLICLY available in `pageProps` + props: { env: items }, + revalidate: 1, + } +} + +export default ({ env }) => ( + <> +

{JSON.stringify(env)}

+
{process.env.nextConfigEnv}
+
{process.env.nextConfigPublicEnv}
+ +) diff --git a/nextjs/test/integration/env-config-app-env/app/pages/some-ssp.js b/nextjs/test/integration/env-config-app-env/app/pages/some-ssp.js new file mode 100644 index 0000000000..e9a908e955 --- /dev/null +++ b/nextjs/test/integration/env-config-app-env/app/pages/some-ssp.js @@ -0,0 +1,34 @@ +const variables = [ + 'PROCESS_ENV_KEY', + 'ENV_FILE_KEY', + 'ENV_FILE_EMPTY_FIRST', + 'PRODUCTION_ENV_FILE_KEY', + 'LOCAL_PRODUCTION_ENV_FILE_KEY', + 'ENV_FILE_DEVELOPMENT_OVERRIDE_TEST', + 'ENV_FILE_PRODUCTION_OVERRIDEOVERRIDE_TEST', + 'ENV_FILE_PRODUCTION_LOCAL_OVERRIDEOVERRIDE_TEST', +] + +export async function getServerSideProps() { + const items = {} + + variables.forEach((variable) => { + if (typeof process.env[variable] !== 'undefined') { + items[variable] = process.env[variable] + } + }) + + return { + // Do not pass any sensitive values here as they will + // be made PUBLICLY available in `pageProps` + props: { env: items }, + } +} + +export default ({ env }) => ( + <> +

{JSON.stringify(env)}

+
{process.env.nextConfigEnv}
+
{process.env.nextConfigPublicEnv}
+ +) diff --git a/nextjs/test/integration/env-config-app-env/test/index.test.js b/nextjs/test/integration/env-config-app-env/test/index.test.js new file mode 100644 index 0000000000..653e17a5f2 --- /dev/null +++ b/nextjs/test/integration/env-config-app-env/test/index.test.js @@ -0,0 +1,163 @@ +/* eslint-env jest */ + +import url from 'url' +import fs from 'fs-extra' +import { join } from 'path' +import cheerio from 'cheerio' +import { + nextBuild, + nextStart, + renderViaHTTP, + findPort, + launchApp, + killApp, + fetchViaHTTP, +} from 'next-test-utils' + +jest.setTimeout(1000 * 60 * 2) + +let app +let appPort +const appDir = join(__dirname, '../app') + +const getEnvFromHtml = async (path) => { + const html = await renderViaHTTP(appPort, path) + const $ = cheerio.load(html) + const env = JSON.parse($('p').text()) + env.nextConfigEnv = $('#nextConfigEnv').text() + env.nextConfigPublicEnv = $('#nextConfigPublicEnv').text() + return env +} + +const runTests = (mode = 'dev') => { + const isDevOnly = mode === 'dev' + + const checkEnvData = (data) => { + expect(data.ENV_FILE_KEY).toBe('env') + expect(data.PRODUCTION_ENV_FILE_KEY).toBe( + isDevOnly ? undefined : 'production' + ) + expect(data.ENV_FILE_DEVELOPMENT_OVERRIDE_TEST).toEqual('staginglocal') + + expect(data.nextConfigEnv).toBe('hello from staging app') + expect(data.nextConfigPublicEnv).toBe('hello again from staging app') + } + + it('should pass staging env to next.config.js', async () => { + const res = await fetchViaHTTP(appPort, '/hello', undefined, { + redirect: 'manual', + }) + const { pathname } = url.parse(res.headers.get('location')) + + expect(res.status).toBe(307) + expect(pathname).toBe('/staging-route') + }) + + it('should provide env for SSG', async () => { + const data = await getEnvFromHtml('/some-ssg') + checkEnvData(data) + }) + + it('should provide env correctly for SSR', async () => { + const data = await getEnvFromHtml('/some-ssp') + checkEnvData(data) + }) + + it('should provide env correctly for API routes', async () => { + const data = JSON.parse(await renderViaHTTP(appPort, '/api/all')) + checkEnvData(data) + }) + + it('should load env from .env', async () => { + const data = await getEnvFromHtml('/') + expect(data.ENV_FILE_KEY).toEqual('env') + }) +} + +describe('Env Config', () => { + describe('dev mode', () => { + beforeAll(async () => { + appPort = await findPort() + app = await launchApp(appDir, appPort, { + env: { + APP_ENV: 'staging', + }, + }) + }) + afterAll(() => killApp(app)) + + runTests('dev') + }) + + describe('server mode', () => { + beforeAll(async () => { + const { code } = await nextBuild(appDir, [], { + env: { + APP_ENV: 'staging', + }, + }) + if (code !== 0) throw new Error(`Build failed with exit code ${code}`) + + appPort = await findPort() + app = await nextStart(appDir, appPort, { + env: { + APP_ENV: 'staging', + }, + }) + }) + afterAll(() => killApp(app)) + + runTests('server') + }) + + describe('serverless mode', () => { + let nextConfigContent = '' + const nextConfigPath = join(appDir, 'next.config.js') + const envFiles = [ + '.env', + '.env.staging', + '.env.staging.local', + '.env.production', + ].map((file) => join(appDir, file)) + + beforeAll(async () => { + nextConfigContent = await fs.readFile(nextConfigPath, 'utf8') + await fs.writeFile( + nextConfigPath, + nextConfigContent.replace( + '// update me', + `target: 'experimental-serverless-trace',` + ) + ) + const { code } = await nextBuild(appDir, [], { + env: { + APP_ENV: 'staging', + }, + }) + + if (code !== 0) throw new Error(`Build failed with exit code ${code}`) + appPort = await findPort() + + // rename the files so they aren't loaded by `next start` + // to test that they were bundled into the serverless files + for (const file of envFiles) { + await fs.rename(file, `${file}.bak`) + } + + app = await nextStart(appDir, appPort, { + env: { + APP_ENV: 'staging', + }, + }) + }) + afterAll(async () => { + for (const file of envFiles) { + await fs.rename(`${file}.bak`, file) + } + await fs.writeFile(nextConfigPath, nextConfigContent) + await killApp(app) + }) + + runTests('serverless') + }) +}) diff --git a/nextjs/test/package-managers/pnpm/app/package.json b/nextjs/test/package-managers/pnpm/app/package.json index 2c323a6a50..14bc0660a6 100644 --- a/nextjs/test/package-managers/pnpm/app/package.json +++ b/nextjs/test/package-managers/pnpm/app/package.json @@ -10,7 +10,7 @@ "dependencies": { "react": "^16.7.0", "react-dom": "^16.7.0", - "@next/env": "^10.0.9", + "@blitzjs/env": "0.43.0", "@next/polyfill-module": "^10.0.9", "@next/react-dev-overlay": "^10.0.9", "@next/react-refresh-utils": "^10.0.9" diff --git a/nextjs/test/package-managers/pnpm/test/index.test.js b/nextjs/test/package-managers/pnpm/test/index.test.js index 8e9a4a1d40..9198e5da34 100644 --- a/nextjs/test/package-managers/pnpm/test/index.test.js +++ b/nextjs/test/package-managers/pnpm/test/index.test.js @@ -79,7 +79,7 @@ describe('pnpm support', () => { await usingTempDir(async (tempDir) => { const nextTarballPath = await pack(tempDir, 'next') const dependencyTarballPaths = { - // '@next/env': await pack(tempDir, 'next-env'), + '@blitzjs/env': await pack(tempDir, 'next-env'), // '@next/polyfill-module': await pack(tempDir, 'next-polyfill-module'), // '@next/react-dev-overlay': await pack(tempDir, 'react-dev-overlay'), // '@next/react-refresh-utils': await pack(tempDir, 'react-refresh-utils'), diff --git a/package.json b/package.json index 03bf164dc5..2de6efb052 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "nextjs/packages/next-mdx", "nextjs/packages/eslint-config-next", "nextjs/packages/eslint-plugin-next", + "nextjs/packages/next-env", "examples/*", "recipes/*" ], @@ -28,14 +29,16 @@ "wait:nextjs": "wait-on -d 1000 nextjs/packages/next/dist/build/index.js", "wait:nextjs-types": "wait-on -d 1000 nextjs/packages/next/dist/build/index.d.ts", "dev:nextjs": "yarn workspace next dev", + "dev:next-env": "yarn workspace @blitzjs/env dev", "dev:nextjs-types": "yarn wait:nextjs && yarn workspace next types && echo 'Finished building nextjs types'", "dev:blitz": "cross-env BLITZ_PROD_BUILD=true preconstruct watch", "dev:tsc": "yarn dev:nextjs-types && tsc --watch --pretty --preserveWatchOutput", "dev:cli": "yarn wait:nextjs-types && yarn workspace @blitzjs/cli dev", "dev:templates": "yarn workspace @blitzjs/generator dev", - "dev": "concurrently --names \"nextjs,blitz,typecheck,cli,templates\" -c \"magenta,cyan,green,yellow,black\" -p \"{name}\" \"npm:dev:nextjs\" \"npm:dev:blitz\" \"npm:dev:tsc\" \"npm:dev:cli\" \"npm:dev:templates\"", + "dev": "concurrently --names \"nextjs,blitz,typecheck,cli,templates,next-env\" -c \"magenta,cyan,green,yellow,black,blue\" -p \"{name}\" \"npm:dev:nextjs\" \"npm:dev:blitz\" \"npm:dev:tsc\" \"npm:dev:cli\" \"npm:dev:templates\" \"npm:dev:next-env\"", "build:nextjs": "yarn workspace next prepublish", - "build": "yarn build:nextjs && cross-env BLITZ_PROD_BUILD=true preconstruct build && ultra -r --filter \"packages/*\" buildpkg && tsc", + "build:next-env": "yarn workspace @blitzjs/env prepublish", + "build": "yarn build:nextjs && yarn build:next-env && cross-env BLITZ_PROD_BUILD=true preconstruct build && ultra -r --filter \"packages/*\" buildpkg && tsc", "lint": "eslint --ext \".js,.ts,.tsx\" .", "link-cli": "yarn workspace blitz link", "unlink-cli": "yarn workspace blitz unlink", @@ -103,7 +106,6 @@ "@types/debug": "4.1.5", "@types/detect-port": "1.3.0", "@types/diff": "5.0.0", - "@types/dotenv-flow": "3.1.0", "@types/flush-write-stream": "1.0.0", "@types/from2": "2.3.0", "@types/fs-extra": "8.1.0", diff --git a/packages/blitz/jest-preset/global-setup.js b/packages/blitz/jest-preset/global-setup.js index 66196efa38..9b0b882711 100644 --- a/packages/blitz/jest-preset/global-setup.js +++ b/packages/blitz/jest-preset/global-setup.js @@ -1,4 +1,6 @@ // eslint-disable-next-line import/no-default-export +import {loadEnvConfig} from "@blitzjs/env" module.exports = function globalSetup(globalConfig) { - require("dotenv-flow").config({silent: true}) + const projectDir = process.cwd() + loadEnvConfig(projectDir) } diff --git a/packages/blitz/src/cli.ts b/packages/blitz/src/cli.ts index efef22dcaf..86fbf72080 100755 --- a/packages/blitz/src/cli.ts +++ b/packages/blitz/src/cli.ts @@ -33,6 +33,10 @@ async function main() { const cli = require(cliPkgPath) + if (options.e || options.env) { + process.env.APP_ENV = options.e || options.env + } + const hasVersionFlag = options._.length === 0 && (options.v || options.version) const hasVerboseFlag = options._.length === 0 && (options.V || options.verbose) diff --git a/packages/cli/package.json b/packages/cli/package.json index 9ccccfe087..e63b7ad478 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -37,7 +37,6 @@ "chalk": "^4.1.0", "cross-spawn": "7.0.3", "dotenv-expand": "^5.1.0", - "dotenv-flow": "^3.2.0", "enquirer": "2.3.6", "esm": "3.2.25", "fs-extra": "^9.1.0", diff --git a/packages/cli/src/commands/build.ts b/packages/cli/src/commands/build.ts index 2fc4e3e8a5..39f701a724 100644 --- a/packages/cli/src/commands/build.ts +++ b/packages/cli/src/commands/build.ts @@ -7,6 +7,10 @@ export class Build extends Command { static flags = { help: flags.help({char: "h"}), + env: flags.string({ + char: "e", + description: "Set app environment name", + }), } async run() { diff --git a/packages/cli/src/commands/codegen.ts b/packages/cli/src/commands/codegen.ts index 6072c0ac97..c509e53546 100644 --- a/packages/cli/src/commands/codegen.ts +++ b/packages/cli/src/commands/codegen.ts @@ -9,6 +9,10 @@ export class CodeGen extends Command { static flags = { help: flags.help({char: "h"}), + env: flags.string({ + char: "e", + description: "Set app environment name", + }), } async run() { diff --git a/packages/cli/src/commands/console.ts b/packages/cli/src/commands/console.ts index 10b322de6d..d9549e167f 100644 --- a/packages/cli/src/commands/console.ts +++ b/packages/cli/src/commands/console.ts @@ -14,6 +14,10 @@ export class Console extends Command { static flags = { help: flags.help({char: "h"}), + env: flags.string({ + char: "e", + description: "Set app environment name", + }), } async run() { diff --git a/packages/cli/src/commands/db.ts b/packages/cli/src/commands/db.ts index e02da6ccf3..2e1fcc6b0d 100644 --- a/packages/cli/src/commands/db.ts +++ b/packages/cli/src/commands/db.ts @@ -69,6 +69,10 @@ ${require("chalk").bold("seed")} Generates seeded data in database via Prisma. char: "f", description: `Path to the seeds file, relative to the project root folder. Examples: db/seeds, db/seeds.ts, db/seeds/index.ts, db/my-seeds`, }), + env: flags.string({ + char: "e", + description: "Set app environment name", + }), } static strict = false diff --git a/packages/cli/src/commands/dev.ts b/packages/cli/src/commands/dev.ts index 8ce762db67..96833586d2 100644 --- a/packages/cli/src/commands/dev.ts +++ b/packages/cli/src/commands/dev.ts @@ -21,6 +21,10 @@ export class Dev extends Command { "no-incremental-build": flags.boolean({ description: "Disable incremental build and start from a fresh cache", }), + env: flags.string({ + char: "e", + description: "Set app environment name", + }), } async run() { diff --git a/packages/cli/src/commands/export.ts b/packages/cli/src/commands/export.ts index da8590dc3f..57c9780e11 100644 --- a/packages/cli/src/commands/export.ts +++ b/packages/cli/src/commands/export.ts @@ -11,6 +11,10 @@ export class Export extends Command { char: "o", description: "set the output dir (defaults to 'out')", }), + env: flags.string({ + char: "e", + description: "Set app environment name", + }), } async run() { diff --git a/packages/cli/src/commands/generate.ts b/packages/cli/src/commands/generate.ts index 9f04f5416f..e766e7169d 100644 --- a/packages/cli/src/commands/generate.ts +++ b/packages/cli/src/commands/generate.ts @@ -116,6 +116,10 @@ export class Generate extends Command { char: "d", description: "Show what files will be created without writing them to disk", }), + env: flags.string({ + char: "e", + description: "Set app environment name", + }), } static examples = [ diff --git a/packages/cli/src/commands/install.ts b/packages/cli/src/commands/install.ts index 09aa9c178a..82c3af0066 100644 --- a/packages/cli/src/commands/install.ts +++ b/packages/cli/src/commands/install.ts @@ -94,6 +94,10 @@ export class Install extends Command { default: false, description: "Install the recipe automatically without user confirmation", }), + env: flags.string({ + char: "e", + description: "Set app environment name", + }), } static args = [ diff --git a/packages/cli/src/commands/new.ts b/packages/cli/src/commands/new.ts index 43620d2b97..652fa45807 100644 --- a/packages/cli/src/commands/new.ts +++ b/packages/cli/src/commands/new.ts @@ -1,4 +1,5 @@ import {log} from "@blitzjs/display" +import {loadEnvConfig} from "@blitzjs/env" import type {AppGeneratorOptions} from "@blitzjs/generator" import {getLatestVersion} from "@blitzjs/generator" import {flags} from "@oclif/command" @@ -167,7 +168,8 @@ export class New extends Command { const spinner = log.spinner(log.withBrand("Initializing SQLite database")).start() try { // Required in order for DATABASE_URL to be available - require("dotenv-expand")(require("dotenv-flow").config({silent: true})) + const projectDir = process.cwd() + loadEnvConfig(projectDir) const result = await runPrisma(["migrate", "dev", "--name", "Initial migration"], true) if (!result.success) throw new Error() diff --git a/packages/cli/src/commands/prisma.ts b/packages/cli/src/commands/prisma.ts index 4a42a2e26c..dd59668a55 100644 --- a/packages/cli/src/commands/prisma.ts +++ b/packages/cli/src/commands/prisma.ts @@ -1,4 +1,4 @@ -import {Command} from "@oclif/command" +import {Command, flags} from "@oclif/command" import {Readable} from "stream" const getPrismaBin = async () => { @@ -55,6 +55,13 @@ export class PrismaCommand extends Command { static description = "Loads env variables then proxies all args to Prisma CLI" static aliases = ["p"] + static flags = { + env: flags.string({ + char: "e", + description: "Set app environment name", + }), + } + static strict = false async run() { diff --git a/packages/cli/src/commands/start.ts b/packages/cli/src/commands/start.ts index 1626968b2e..97dffebf1e 100644 --- a/packages/cli/src/commands/start.ts +++ b/packages/cli/src/commands/start.ts @@ -18,6 +18,10 @@ export class Start extends Command { inspect: flags.boolean({ description: "Enable the Node.js inspector", }), + env: flags.string({ + char: "e", + description: "Set app environment name", + }), } async run() { diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 9b528da2d7..8cdc8b0708 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -2,12 +2,18 @@ require("v8-compile-cache") const cacheFile = require("path").join(__dirname, ".blitzjs-cli-cache") const lazyLoad = require("@salesforce/lazy-require").default.create(cacheFile) lazyLoad.start() +import {loadEnvConfig} from "@blitzjs/env" import {run as oclifRun} from "@oclif/command" import {compileConfig} from "next/dist/server/config-shared" import {getProjectRoot} from "next/dist/server/lib/utils" +const options = require("minimist")(process.argv.slice(2)) +if (options.e || options.env) { + process.env.APP_ENV = options.e || options.env +} + // Load the .env environment variable so it's available for all commands -require("dotenv-expand")(require("dotenv-flow").config({silent: true})) +loadEnvConfig() async function buildConfigIfNeeded() { if (["help", "-h", "autocomplete", "new", "s", "start"].includes(process.argv[2])) { diff --git a/yarn.lock b/yarn.lock index 21ad809cd8..5d32ce6970 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3197,11 +3197,6 @@ dependencies: webpack-bundle-analyzer "4.3.0" -"@next/env@11.1.0": - version "11.1.0" - resolved "https://registry.yarnpkg.com/@next/env/-/env-11.1.0.tgz#cae83d8e0a65aa9f2af3368f8269ffd9d911746a" - integrity sha512-zPJkMFRenSf7BLlVee8987G0qQXAhxy7k+Lb/5hLAGkPVHAHm+oFFeL+2ipbI2KTEFlazdmGY0M+AlLQn7pWaw== - "@next/polyfill-module@11.1.0": version "11.1.0" resolved "https://registry.yarnpkg.com/@next/polyfill-module/-/polyfill-module-11.1.0.tgz#ee6b9117a1f9bb137479dfa51d5a9e38e066a62f" @@ -4629,10 +4624,12 @@ resolved "https://registry.yarnpkg.com/@types/diff/-/diff-5.0.0.tgz#eb71e94feae62548282c4889308a3dfb57e36020" integrity sha512-jrm2K65CokCCX4NmowtA+MfXyuprZC13jbRuwprs6/04z/EcFg/MCwYdsHn+zgV4CQBiATiI7AEq7y1sZCtWKA== -"@types/dotenv-flow@3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@types/dotenv-flow/-/dotenv-flow-3.1.0.tgz#e9ba53f95f3d40bbc0df99b206f649b2acfca0c6" - integrity sha512-qaWT42KDePdAGZFryYoV7EZnuuYZAO4KPVDWUV9OBOyJx7xCgKKERtVB7jBCM2mtKVI+OMMDK2ef11PWcHJz3g== +"@types/dotenv@8.2.0": + version "8.2.0" + resolved "https://registry.yarnpkg.com/@types/dotenv/-/dotenv-8.2.0.tgz#5cd64710c3c98e82d9d15844375a33bf1b45d053" + integrity sha512-ylSC9GhfRH7m1EUXBXofhgx4lUWmFeQDINW5oLuS+gxWdfUeW4zJdeVTYVkexEW+e2VUvlZR2kGnGGipAWR7kw== + dependencies: + dotenv "*" "@types/duplexify@*": version "3.6.0" @@ -5866,6 +5863,11 @@ async-retry "1.2.3" lru-cache "5.1.1" +"@zeit/ncc@0.20.4": + version "0.20.4" + resolved "https://registry.yarnpkg.com/@zeit/ncc/-/ncc-0.20.4.tgz#00f0a25a88cac3712af4ba66561d9e281c6f05c9" + integrity sha512-fmq+F/QxPec+k/zvT7HiVpk7oiGFseS6brfT/AYqmCUp6QFRK7vZf2Ref46MImsg/g2W3g5X6SRvGRmOAvEfdA== + "@zeit/next-css@1.0.2-canary.2": version "1.0.2-canary.2" resolved "https://registry.yarnpkg.com/@zeit/next-css/-/next-css-1.0.2-canary.2.tgz#0eeb877e7469892b65471c1ec7c14346b8f240df" @@ -9924,14 +9926,12 @@ dotenv-expand@^5.1.0: resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== -dotenv-flow@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/dotenv-flow/-/dotenv-flow-3.2.0.tgz#a5d79dd60ddb6843d457a4874aaf122cf659a8b7" - integrity sha512-GEB6RrR4AbqDJvNSFrYHqZ33IKKbzkvLYiD5eo4+9aFXr4Y4G+QaFrB/fNp0y6McWBmvaPn3ZNjIufnj8irCtg== - dependencies: - dotenv "^8.0.0" +dotenv@*: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== -dotenv@^8.0.0, dotenv@^8.2.0: +dotenv@8.2.0, dotenv@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== From ad71e15290b4133a5431236c3d5ce0666486a61f Mon Sep 17 00:00:00 2001 From: Aleksandra Sikora Date: Fri, 3 Dec 2021 20:54:40 +0100 Subject: [PATCH 3/3] Make prefetching work with `usePaginatedQuery` and `useInfiniteQuery` (#3014) (patch) --- .github/workflows/compressed.yml | 3 +- .../build/babel/plugins/rewrite-imports.ts | 1 + nextjs/packages/next/data-client/index.ts | 1 + .../next/data-client/react-query-utils.ts | 17 +++++ .../packages/next/data-client/react-query.tsx | 54 ++++++++++++---- .../queries/getIncrementedWithPagination.ts | 33 ++++++++++ .../dehydrated-state-use-infinite-query.tsx | 47 ++++++++++++++ .../dehydrated-state-use-paginated-query.tsx | 32 ++++++++++ ...ate.tsx => dehydrated-state-use-query.tsx} | 7 +-- .../pages/invalidate-use-infinite-query.tsx | 34 ++++++++++ ...nvalidate.tsx => invalidate-use-query.tsx} | 0 test/integration/queries/test/index.test.ts | 62 ++++++++++++++++++- 12 files changed, 269 insertions(+), 22 deletions(-) create mode 100644 test/integration/queries/app/queries/getIncrementedWithPagination.ts create mode 100644 test/integration/queries/pages/dehydrated-state-use-infinite-query.tsx create mode 100644 test/integration/queries/pages/dehydrated-state-use-paginated-query.tsx rename test/integration/queries/pages/{dehydrated-state.tsx => dehydrated-state-use-query.tsx} (85%) create mode 100644 test/integration/queries/pages/invalidate-use-infinite-query.tsx rename test/integration/queries/pages/{invalidate.tsx => invalidate-use-query.tsx} (100%) diff --git a/.github/workflows/compressed.yml b/.github/workflows/compressed.yml index 175069598a..18a78bafb9 100644 --- a/.github/workflows/compressed.yml +++ b/.github/workflows/compressed.yml @@ -15,7 +15,8 @@ jobs: steps: - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - name: Use Node.js + uses: actions/setup-node@v2 with: node-version: "14" - name: Count size diff --git a/nextjs/packages/next/build/babel/plugins/rewrite-imports.ts b/nextjs/packages/next/build/babel/plugins/rewrite-imports.ts index fdffcf84de..8953ec89f3 100644 --- a/nextjs/packages/next/build/babel/plugins/rewrite-imports.ts +++ b/nextjs/packages/next/build/babel/plugins/rewrite-imports.ts @@ -56,6 +56,7 @@ const specialImports: Record = { useMutation: 'next/data-client', queryClient: 'next/data-client', getQueryKey: 'next/data-client', + getInfiniteQueryKey: 'next/data-client', invalidateQuery: 'next/data-client', setQueryData: 'next/data-client', useQueryErrorResetBoundary: 'next/data-client', diff --git a/nextjs/packages/next/data-client/index.ts b/nextjs/packages/next/data-client/index.ts index 2b3e688513..c6c19c9754 100644 --- a/nextjs/packages/next/data-client/index.ts +++ b/nextjs/packages/next/data-client/index.ts @@ -11,6 +11,7 @@ export { export { queryClient, getQueryKey, + getInfiniteQueryKey, invalidateQuery, setQueryData, } from './react-query-utils' diff --git a/nextjs/packages/next/data-client/react-query-utils.ts b/nextjs/packages/next/data-client/react-query-utils.ts index 2b4be6192b..d1fab760c8 100644 --- a/nextjs/packages/next/data-client/react-query-utils.ts +++ b/nextjs/packages/next/data-client/react-query-utils.ts @@ -125,6 +125,23 @@ export function getQueryKey( return getQueryKeyFromUrlAndParams(sanitizeQuery(resolver)._routePath, params) } +export function getInfiniteQueryKey( + resolver: T | Resolver | RpcClient, + params?: TInput +) { + if (typeof resolver === 'undefined') { + throw new Error( + 'getInfiniteQueryKey is missing the first argument - it must be a resolver function' + ) + } + + const queryKey = getQueryKeyFromUrlAndParams( + sanitizeQuery(resolver)._routePath, + params + ) + return [...queryKey, 'infinite'] +} + export function invalidateQuery( resolver: T | Resolver | RpcClient, params?: TInput diff --git a/nextjs/packages/next/data-client/react-query.tsx b/nextjs/packages/next/data-client/react-query.tsx index 50651143ad..5a738af000 100644 --- a/nextjs/packages/next/data-client/react-query.tsx +++ b/nextjs/packages/next/data-client/react-query.tsx @@ -20,6 +20,7 @@ import { QueryCacheFunctions, sanitizeQuery, sanitizeMutation, + getInfiniteQueryKey, } from './react-query-utils' import { useRouter } from '../client/router' @@ -166,16 +167,19 @@ export function usePaginatedQuery< ) } - const suspense = - options?.enabled === false || options?.enabled === null + const suspenseEnabled = Boolean(process.env.__BLITZ_SUSPENSE_ENABLED) + let enabled = + isServer && suspenseEnabled ? false - : options?.suspense + : options?.enabled ?? options?.enabled !== null + const suspense = enabled === false ? false : options?.suspense + const session = useSession({ suspense }) if (session.isLoading) { - options.enabled = false + enabled = false } - const routerIsReady = useRouter().isReady + const routerIsReady = useRouter().isReady || (isServer && suspenseEnabled) const enhancedResolverRpcClient = sanitizeQuery(queryFn) const queryKey = getQueryKey(queryFn, params) @@ -186,8 +190,20 @@ export function usePaginatedQuery< : (emptyQueryFn as any), ...options, keepPreviousData: true, + enabled, }) + if ( + queryRest.isIdle && + isServer && + suspenseEnabled !== false && + !data && + (!options || !('suspense' in options) || options.suspense) && + (!options || !('enabled' in options) || options.enabled) + ) { + throw new Promise(() => {}) + } + const rest = { ...queryRest, ...getQueryCacheFunctions, TResult, T>(queryFn, params), @@ -250,24 +266,26 @@ export function useInfiniteQuery< ) } - const suspense = - options?.enabled === false || options?.enabled === null + const suspenseEnabled = Boolean(process.env.__BLITZ_SUSPENSE_ENABLED) + let enabled = + isServer && suspenseEnabled ? false - : options?.suspense + : options?.enabled ?? options?.enabled !== null + const suspense = enabled === false ? false : options?.suspense const session = useSession({ suspense }) if (session.isLoading) { - options.enabled = false + enabled = false } - const routerIsReady = useRouter().isReady + const routerIsReady = useRouter().isReady || (isServer && suspenseEnabled) const enhancedResolverRpcClient = sanitizeQuery(queryFn) - const queryKey = getQueryKey(queryFn, getQueryParams) + const queryKey = getInfiniteQueryKey(queryFn, getQueryParams) const { data, ...queryRest } = useInfiniteReactQuery({ // we need an extra cache key for infinite loading so that the cache for // for this query is stored separately since the hook result is an array of results. // Without this cache for usePaginatedQuery and this will conflict and break. - queryKey: routerIsReady ? [...queryKey, 'infinite'] : ['_routerNotReady_'], + queryKey: routerIsReady ? queryKey : ['_routerNotReady_'], queryFn: routerIsReady ? ({ pageParam }) => enhancedResolverRpcClient(getQueryParams(pageParam), { @@ -275,8 +293,20 @@ export function useInfiniteQuery< }) : (emptyQueryFn as any), ...options, + enabled, }) + if ( + queryRest.isIdle && + isServer && + suspenseEnabled !== false && + !data && + (!options || !('suspense' in options) || options.suspense) && + (!options || !('enabled' in options) || options.enabled) + ) { + throw new Promise(() => {}) + } + const rest = { ...queryRest, ...getQueryCacheFunctions, TResult, T>( diff --git a/test/integration/queries/app/queries/getIncrementedWithPagination.ts b/test/integration/queries/app/queries/getIncrementedWithPagination.ts new file mode 100644 index 0000000000..f852bf0393 --- /dev/null +++ b/test/integration/queries/app/queries/getIncrementedWithPagination.ts @@ -0,0 +1,33 @@ +import {paginate, resolver} from "blitz" + +const dataset = Array.from(Array(100).keys()) + +type Args = { + skip: number + take: number + where?: {value: {gte: number}} +} + +let counter = 0 +export default resolver.pipe(async ({skip = 0, take = 100, where}: Args) => { + counter++ + const {items, hasMore, nextPage, count} = await paginate({ + skip, + take, + count: async () => dataset.length, + query: async (paginateArgs) => + dataset + .filter((i) => { + if (!where) return true + return i >= where.value.gte + }) + .slice(paginateArgs.skip, paginateArgs.skip + paginateArgs.take), + }) + return { + counter, + items, + hasMore, + nextPage, + count, + } +}) diff --git a/test/integration/queries/pages/dehydrated-state-use-infinite-query.tsx b/test/integration/queries/pages/dehydrated-state-use-infinite-query.tsx new file mode 100644 index 0000000000..e75d508da1 --- /dev/null +++ b/test/integration/queries/pages/dehydrated-state-use-infinite-query.tsx @@ -0,0 +1,47 @@ +import getPaginated from "app/queries/getPaginated" +import { + dehydrate, + getInfiniteQueryKey, + GetServerSideProps, + invokeWithMiddleware, + QueryClient, + useInfiniteQuery, +} from "blitz" +import {useState} from "react" + +export const getServerSideProps: GetServerSideProps = async (ctx) => { + const queryClient = new QueryClient() + const queryKey = getInfiniteQueryKey(getPaginated, {where: {value: {gte: 10}}, take: 5, skip: 0}) + + await queryClient.prefetchInfiniteQuery(queryKey, () => + invokeWithMiddleware(getPaginated, {where: {value: {gte: 10}}, take: 5, skip: 0}, ctx), + ) + + return { + props: { + dehydratedState: dehydrate(queryClient), + }, + } +} + +function Content() { + const [value] = useState(10) + + const [groups] = useInfiniteQuery( + getPaginated, + (page = {take: 5, skip: 0}) => ({ + where: {value: {gte: value}}, + ...page, + }), + { + getNextPageParam: (lastGroup) => lastGroup.nextPage, + }, + ) + return
{JSON.stringify(groups)}
+} + +function InfiniteQueryDehydratedState() { + return +} + +export default InfiniteQueryDehydratedState diff --git a/test/integration/queries/pages/dehydrated-state-use-paginated-query.tsx b/test/integration/queries/pages/dehydrated-state-use-paginated-query.tsx new file mode 100644 index 0000000000..80c3197b20 --- /dev/null +++ b/test/integration/queries/pages/dehydrated-state-use-paginated-query.tsx @@ -0,0 +1,32 @@ +import getMap from "app/queries/getMap" +import { + dehydrate, + getQueryKey, + GetServerSideProps, + invokeWithMiddleware, + QueryClient, + usePaginatedQuery, +} from "blitz" + +export const getServerSideProps: GetServerSideProps = async (ctx) => { + const queryClient = new QueryClient() + const queryKey = getQueryKey(getMap, undefined) + await queryClient.prefetchQuery(queryKey, () => invokeWithMiddleware(getMap, undefined, ctx)) + + return { + props: { + dehydratedState: dehydrate(queryClient), + }, + } +} + +function Content() { + const [map] = usePaginatedQuery(getMap, undefined) + return

map is Map: {"" + (map instanceof Map)}

+} + +function DehydratedStateWithPagination() { + return +} + +export default DehydratedStateWithPagination diff --git a/test/integration/queries/pages/dehydrated-state.tsx b/test/integration/queries/pages/dehydrated-state-use-query.tsx similarity index 85% rename from test/integration/queries/pages/dehydrated-state.tsx rename to test/integration/queries/pages/dehydrated-state-use-query.tsx index df6ed362d3..b4d8a40f9b 100644 --- a/test/integration/queries/pages/dehydrated-state.tsx +++ b/test/integration/queries/pages/dehydrated-state-use-query.tsx @@ -7,7 +7,6 @@ import { QueryClient, useQuery, } from "blitz" -import {Suspense} from "react" export const getServerSideProps: GetServerSideProps = async (ctx) => { const queryClient = new QueryClient() @@ -27,11 +26,7 @@ function Content() { } function DehydratedState() { - return ( - - - - ) + return } export default DehydratedState diff --git a/test/integration/queries/pages/invalidate-use-infinite-query.tsx b/test/integration/queries/pages/invalidate-use-infinite-query.tsx new file mode 100644 index 0000000000..15b5b88b9b --- /dev/null +++ b/test/integration/queries/pages/invalidate-use-infinite-query.tsx @@ -0,0 +1,34 @@ +import getIncremented from "app/queries/getIncrementedWithPagination" +import {invalidateQuery, useInfiniteQuery} from "blitz" +import {Suspense} from "react" + +function Content() { + const [groups] = useInfiniteQuery( + getIncremented, + (page = {take: 5, skip: 0}) => ({ + where: {value: {gte: 10}}, + ...page, + }), + { + getNextPageParam: (lastGroup) => lastGroup.nextPage, + }, + ) + return ( + <> + +
{JSON.stringify(groups)}
+ + ) +} + +function InvalidateInfiniteQuery() { + return ( +
+ + + +
+ ) +} + +export default InvalidateInfiniteQuery diff --git a/test/integration/queries/pages/invalidate.tsx b/test/integration/queries/pages/invalidate-use-query.tsx similarity index 100% rename from test/integration/queries/pages/invalidate.tsx rename to test/integration/queries/pages/invalidate-use-query.tsx diff --git a/test/integration/queries/test/index.test.ts b/test/integration/queries/test/index.test.ts index ffcc3a2df3..b694bb7191 100644 --- a/test/integration/queries/test/index.test.ts +++ b/test/integration/queries/test/index.test.ts @@ -13,7 +13,7 @@ describe("Queries", () => { env: {__NEXT_TEST_WITH_DEVTOOL: 1}, }) - const prerender = ["/use-query", "/invalidate"] + const prerender = ["/use-query", "/invalidate-use-query", "/invalidate-use-infinite-query"] await Promise.all(prerender.map((route) => renderViaHTTP(context.appPort, route))) }) afterAll(() => killApp(context.server)) @@ -32,7 +32,7 @@ describe("Queries", () => { describe("invalidateQuery", () => { it("should invalidate the query", async () => { - const browser = await webdriver(context.appPort, "/invalidate") + const browser = await webdriver(context.appPort, "/invalidate-use-query") await browser.waitForElementByCss("#content") let text = await browser.elementByCss("#content").text() expect(text).toMatch(/0/) @@ -75,10 +75,66 @@ describe("Queries", () => { describe("DehydratedState", () => { it("should work", async () => { - const browser = await webdriver(context.appPort, "/dehydrated-state") + const browser = await webdriver(context.appPort, "/dehydrated-state-use-query") let text = await browser.elementByCss("#content").text() expect(text).toMatch(/map is Map: true/) if (browser) await browser.close() }) }) + + describe("DehydratedState with usePaginatedQuery", () => { + it("should work", async () => { + const browser = await webdriver(context.appPort, "/dehydrated-state-use-paginated-query") + let text = await browser.elementByCss("#content").text() + expect(text).toMatch(/map is Map: true/) + if (browser) await browser.close() + }) + }) + + describe("DehydratedState with useInfiniteQuery", () => { + it("should work", async () => { + const browser = await webdriver(context.appPort, "/dehydrated-state-use-infinite-query") + await browser.waitForElementByCss("#content") + let text = await browser.elementByCss("#content").text() + expect(JSON.parse(text)).toEqual([ + { + items: [10, 11, 12, 13, 14], + hasMore: true, + nextPage: {take: 5, skip: 5}, + count: 100, + }, + ]) + }) + }) + + describe("invalidateQuery with useInfiniteQuery", () => { + it("should invalidate the query", async () => { + const browser = await webdriver(context.appPort, "/invalidate-use-infinite-query") + await browser.waitForElementByCss("#content") + let text = await browser.elementByCss("#content").text() + expect(JSON.parse(text)).toEqual([ + { + counter: 1, + items: [10, 11, 12, 13, 14], + hasMore: true, + nextPage: {take: 5, skip: 5}, + count: 100, + }, + ]) + await browser.elementByCss("button").click() + waitFor(500) + text = await browser.elementByCss("#content").text() + expect(JSON.parse(text)).toEqual([ + { + counter: 2, + items: [10, 11, 12, 13, 14], + hasMore: true, + nextPage: {take: 5, skip: 5}, + count: 100, + }, + ]) + + if (browser) await browser.close() + }) + }) })