为什么 .env.development 在我的 Next.js 应用程序中不起作用?

Why isn't .env.development working in my Next.js app?

我正在编写 Jest/testing 库测试。

假设我们有一个名为 BenchmarksPage.

的组件

请查看其return语句的第一行。

import {
  Box,
  capitalize,
  Container,
  FormControl,
  InputLabel,
  MenuItem,
  Select,
} from '@material-ui/core';
import { NextPage } from 'next';
import React, { useCallback, useEffect, useState } from 'react';
import { Helmet } from 'react-helmet-async';
import RequireScope from 'src/components/authentication/RequireScope';
import BenchmarkTable from 'src/components/benchmark/BenchmarkTable';
import DashboardLayout from 'src/components/dashboard/DashboardLayout';
import Heading from 'src/components/Heading';
import useSettings from 'src/hooks/useSettings';
import gtm from 'src/lib/gtm';
import { useDispatch, useSelector } from 'src/store';
import { getAllRollingTwelveCalcs } from 'src/store/rolling-twelve/rolling-twelve.thunk';
import { Timeframe, timeframeMap } from 'src/types/benchmark';

const BenchmarksPage: NextPage = () => {
  const { settings } = useSettings();
  const dispatch = useDispatch();

  const [selectedTimeframe, setSelectedTimeframe] = useState<Timeframe>(
    Timeframe.Monthly,
  );

  const company = useSelector((state) => state.company.current);

  useEffect(() => {
    gtm.push({ event: 'page_view' });
  }, []);

  useEffect(() => {
    dispatch(getAllRollingTwelveCalcs());
  }, [company]);

  const handleTimeframeChange = useCallback(
    (
      event: React.ChangeEvent<{
        name?: string;
        value: Timeframe;
        event: Event | React.SyntheticEvent<Element, Event>;
      }>,
    ) => {
      setSelectedTimeframe(event.target.value);
    },
    [],
  );

  return (
    <RequireScope scopes={['query:benchmark-calcs']}>
      <DashboardLayout>
        <Helmet>
          <title>Benchmarks</title>
        </Helmet>
        <Container maxWidth={settings.compact ? 'xl' : false}>
          <Box
            sx={{
              display: 'flex',
              alignItems: 'flex-end',
              justifyContent: 'space-between',
              mb: 4,
            }}
          >
            <Heading>Benchmarks</Heading>
            <FormControl sx={{ width: 300 }}>
              <InputLabel>Timeframe</InputLabel>
              <Select
                sx={{ background: '#ffffff', maxWidth: 400 }}
                value={selectedTimeframe}
                label="Timeframe"
                onChange={handleTimeframeChange}
              >
                {[...timeframeMap.keys()].map((timeframe) => (
                  <MenuItem key={timeframe} value={timeframe}>
                    {capitalize(timeframe)}
                  </MenuItem>
                ))}
              </Select>
            </FormControl>
          </Box>
          <BenchmarkTable timeframe={selectedTimeframe} />
        </Container>
      </DashboardLayout>
    </RequireScope>
  );
};

export default BenchmarksPage;

注意它的 return 值是用 RequireScope

包裹的

RequireScope 只会在用户通过身份验证时呈现其子项

RequireScope:

import React, { useEffect, useState } from 'react';
import useAuth from 'src/hooks/useAuth';

export interface RequireScopeProps {
  scopes: string[];
}

const RequireScope: React.FC<RequireScopeProps> = React.memo((props) => {
  const { children, scopes } = props;
  const { isInitialized, isAuthenticated, permissions } = useAuth();
  const [isPermitted, setIsPermitted] = useState(false);

  useEffect(() => {
    if (process.env.NEXT_PUBLIC_IS_LOCAL) {
      setIsPermitted(true);
    }
  }, []);

  useEffect(() => {
    if (isAuthenticated && isInitialized) {
      (async () => {
        const hasPermissions = scopes
          .map((s) => {
            return permissions.includes(s);
          })
          .filter(Boolean);

        if (hasPermissions.length === scopes.length) {
          setIsPermitted(true);
        }
      })();
    }
  }, [isAuthenticated, isInitialized, scopes, permissions]);

  if (isPermitted) {
    return <>{children}</>;
  }

  return null;
});

export default RequireScope;

我们只需要将isPermitted设为true

(useAuth 使用 JWT 登录用户顺便说一下)

现在,当我使用测试库的 render 方法渲染 BenchmarksPage 时,

它不会在 jsdom 上呈现,因为

'isPermitted'RequireScope 中仍然是错误的。

所以,为了isPermitted = true

我设置了

NEXT_PUBLIC_IS_LOCAL=true

.env.development

根据 RequireScope 中的第一个 useEffectisPermitted 现在应该是 true

然而,组件仍然没有渲染,returning

<body></div></body> 

这仍然意味着 isPermitted 是错误的。

我尝试了什么:

我也试过用.env.local

我可以向您保证我的设置都是正确的(jest.config,使用 MockProvider 包装 BenchmarksPage 等)

为什么 .env.development 不工作,而使 isPermitted = true

根据我的理解,一切在逻辑上都是正确的。

编辑:我试过写

NEXT_PUBLIC_IS_LOCAL=真

也在 .env.test 文件中

编辑 - 答案:

必须设置

import { loadEnvConfig } from '@next/env';

export default async (): Promise<void> => {
  loadEnvConfig(process.env.PWD);
};

在 setupEnv.ts 文件中。

然后在 package.json 中添加 globalSetup(因为我不使用 jest.config.js -> eslint 与 tsconfig 一起搞乱了它)

"jest": {
    "moduleNameMapper": {
      "^src/(.*)$": "<rootDir>/src/"
      },
    "testEnvironment": "jsdom",
    "globalSetup": "<rootDir>/test/setupEnv.ts"
  },

如果您的环境变量在 运行 开发服务器时有效,但在测试时不起作用,这是因为 Next 在单元测试时没有为 Jest 设置变量。

首先,创建一个 .env.test,设置仅用于测试的环境变量。

然后,为了在测试环境中设置 envs,您必须将其添加到您的测试入口点:

// jest.config.js
module.exports = {
  globalSetup: '<rootDir>/__test__/setupEnv.js'
}
// __test__/setupEnv.js
import { loadEnvConfig } from '@next/env'

export default async () => {
  const projectDir = process.cwd()
  loadEnvConfig(projectDir)
}

您可以将 __test__/ 目录更改为您的测试目录。

请注意 .env.local 在测试期间未加载