Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update .env HMR handling #39566

Merged
merged 1 commit into from Aug 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 9 additions & 1 deletion packages/next-env/index.ts
Expand Up @@ -13,6 +13,7 @@ export type LoadedEnvFiles = Array<{
let initialEnv: Env | undefined = undefined
let combinedEnv: Env | undefined = undefined
let cachedLoadedEnvFiles: LoadedEnvFiles = []
let previousLoadedEnvFiles: LoadedEnvFiles = []

type Log = {
info: (...args: any[]) => void
Expand Down Expand Up @@ -49,7 +50,13 @@ export function processEnv(

result = dotenvExpand(result)

if (result.parsed) {
if (
result.parsed &&
!previousLoadedEnvFiles.some(
(item) =>
item.contents === envFile.contents && item.path === envFile.path
)
) {
log.info(`Loaded env from ${path.join(dir || '', envFile.path)}`)
}

Expand Down Expand Up @@ -88,6 +95,7 @@ export function loadEnvConfig(
return { combinedEnv, loadedEnvFiles: cachedLoadedEnvFiles }
}
process.env = Object.assign({}, initialEnv)
previousLoadedEnvFiles = cachedLoadedEnvFiles
cachedLoadedEnvFiles = []

const isTest = process.env.NODE_ENV === 'test'
Expand Down
306 changes: 171 additions & 135 deletions packages/next/build/webpack-config.ts
Expand Up @@ -71,6 +71,163 @@ const watchOptions = Object.freeze({
ignored: ['**/.git/**', '**/.next/**'],
})

export function getDefineEnv({
dev,
config,
distDir,
isClient,
hasRewrites,
hasReactRoot,
isNodeServer,
isEdgeServer,
middlewareRegex,
hasServerComponents,
}: {
dev?: boolean
distDir: string
isClient?: boolean
hasRewrites?: boolean
hasReactRoot?: boolean
isNodeServer?: boolean
isEdgeServer?: boolean
middlewareRegex?: string
config: NextConfigComplete
hasServerComponents?: boolean
}) {
return {
// internal field to identify the plugin config
__NEXT_DEFINE_ENV: 'true',

...Object.keys(process.env).reduce(
(prev: { [key: string]: string }, key: string) => {
if (key.startsWith('NEXT_PUBLIC_')) {
prev[`process.env.${key}`] = JSON.stringify(process.env[key]!)
}
return prev
},
{}
),
...Object.keys(config.env).reduce((acc, key) => {
errorIfEnvConflicted(config, key)

return {
...acc,
[`process.env.${key}`]: JSON.stringify(config.env[key]),
}
}, {}),
...(!isEdgeServer
? {}
: {
EdgeRuntime: JSON.stringify(
/**
* Cloud providers can set this environment variable to allow users
* and library authors to have different implementations based on
* the runtime they are running with, if it's not using `edge-runtime`
*/
process.env.NEXT_EDGE_RUNTIME_PROVIDER || 'edge-runtime'
),
}),
// TODO: enforce `NODE_ENV` on `process.env`, and add a test:
'process.env.NODE_ENV': JSON.stringify(dev ? 'development' : 'production'),
...((isNodeServer || isEdgeServer) && {
'process.env.NEXT_RUNTIME': JSON.stringify(
isEdgeServer ? 'edge' : 'nodejs'
),
}),
'process.env.__NEXT_MIDDLEWARE_REGEX': JSON.stringify(
middlewareRegex || ''
),
'process.env.__NEXT_MANUAL_CLIENT_BASE_PATH': JSON.stringify(
config.experimental.manualClientBasePath
),
'process.env.__NEXT_NEW_LINK_BEHAVIOR': JSON.stringify(
config.experimental.newNextLinkBehavior
),
'process.env.__NEXT_OPTIMISTIC_CLIENT_CACHE': JSON.stringify(
config.experimental.optimisticClientCache
),
'process.env.__NEXT_CROSS_ORIGIN': JSON.stringify(config.crossOrigin),
'process.browser': JSON.stringify(isClient),
'process.env.__NEXT_TEST_MODE': JSON.stringify(
process.env.__NEXT_TEST_MODE
),
// This is used in client/dev-error-overlay/hot-dev-client.js to replace the dist directory
...(dev && (isClient || isEdgeServer)
? {
'process.env.__NEXT_DIST_DIR': JSON.stringify(distDir),
}
: {}),
'process.env.__NEXT_TRAILING_SLASH': JSON.stringify(config.trailingSlash),
'process.env.__NEXT_BUILD_INDICATOR': JSON.stringify(
config.devIndicators.buildActivity
),
'process.env.__NEXT_BUILD_INDICATOR_POSITION': JSON.stringify(
config.devIndicators.buildActivityPosition
),
'process.env.__NEXT_STRICT_MODE': JSON.stringify(config.reactStrictMode),
'process.env.__NEXT_REACT_ROOT': JSON.stringify(hasReactRoot),
'process.env.__NEXT_RSC': JSON.stringify(hasServerComponents),
'process.env.__NEXT_OPTIMIZE_FONTS': JSON.stringify(
config.optimizeFonts && !dev
),
'process.env.__NEXT_OPTIMIZE_CSS': JSON.stringify(
config.experimental.optimizeCss && !dev
),
'process.env.__NEXT_SCRIPT_WORKERS': JSON.stringify(
config.experimental.nextScriptWorkers && !dev
),
'process.env.__NEXT_SCROLL_RESTORATION': JSON.stringify(
config.experimental.scrollRestoration
),
'process.env.__NEXT_IMAGE_OPTS': JSON.stringify({
deviceSizes: config.images.deviceSizes,
imageSizes: config.images.imageSizes,
path: config.images.path,
loader: config.images.loader,
dangerouslyAllowSVG: config.images.dangerouslyAllowSVG,
experimentalUnoptimized: config?.experimental?.images?.unoptimized,
experimentalFuture: config.experimental?.images?.allowFutureImage,
...(dev
? {
// pass domains in development to allow validating on the client
domains: config.images.domains,
experimentalRemotePatterns:
config.experimental?.images?.remotePatterns,
}
: {}),
}),
'process.env.__NEXT_ROUTER_BASEPATH': JSON.stringify(config.basePath),
'process.env.__NEXT_HAS_REWRITES': JSON.stringify(hasRewrites),
'process.env.__NEXT_I18N_SUPPORT': JSON.stringify(!!config.i18n),
'process.env.__NEXT_I18N_DOMAINS': JSON.stringify(config.i18n?.domains),
'process.env.__NEXT_ANALYTICS_ID': JSON.stringify(config.analyticsId),
...(isNodeServer || isEdgeServer
? {
// Fix bad-actors in the npm ecosystem (e.g. `node-formidable`)
// This is typically found in unmaintained modules from the
// pre-webpack era (common in server-side code)
'global.GENTLY': JSON.stringify(false),
}
: undefined),
// stub process.env with proxy to warn a missing value is
// being accessed in development mode
...(config.experimental.pageEnv && dev
? {
'process.env': `
new Proxy(${isNodeServer ? 'process.env' : '{}'}, {
get(target, prop) {
if (typeof target[prop] === 'undefined') {
console.warn(\`An environment variable (\${prop}) that was not provided in the environment was accessed.\nSee more info here: https://nextjs.org/docs/messages/missing-env-value\`)
}
return target[prop]
}
})
`,
}
: {}),
}
}

function getSupportedBrowsers(
dir: string,
isDevelopment: boolean,
Expand Down Expand Up @@ -1495,141 +1652,20 @@ export default async function getBaseWebpackConfig(
// Avoid process being overridden when in web run time
...(isClient && { process: [require.resolve('process')] }),
}),
new webpack.DefinePlugin({
...Object.keys(process.env).reduce(
(prev: { [key: string]: string }, key: string) => {
if (key.startsWith('NEXT_PUBLIC_')) {
prev[`process.env.${key}`] = JSON.stringify(process.env[key]!)
}
return prev
},
{}
),
...Object.keys(config.env).reduce((acc, key) => {
errorIfEnvConflicted(config, key)

return {
...acc,
[`process.env.${key}`]: JSON.stringify(config.env[key]),
}
}, {}),
...(compilerType !== COMPILER_NAMES.edgeServer
? {}
: {
EdgeRuntime: JSON.stringify(
/**
* Cloud providers can set this environment variable to allow users
* and library authors to have different implementations based on
* the runtime they are running with, if it's not using `edge-runtime`
*/
process.env.NEXT_EDGE_RUNTIME_PROVIDER || 'edge-runtime'
),
}),
// TODO: enforce `NODE_ENV` on `process.env`, and add a test:
'process.env.NODE_ENV': JSON.stringify(
dev ? 'development' : 'production'
),
...((isNodeServer || isEdgeServer) && {
'process.env.NEXT_RUNTIME': JSON.stringify(
isEdgeServer ? 'edge' : 'nodejs'
),
}),
'process.env.__NEXT_MIDDLEWARE_REGEX': JSON.stringify(
middlewareRegex || ''
),
'process.env.__NEXT_MANUAL_CLIENT_BASE_PATH': JSON.stringify(
config.experimental.manualClientBasePath
),
'process.env.__NEXT_NEW_LINK_BEHAVIOR': JSON.stringify(
config.experimental.newNextLinkBehavior
),
'process.env.__NEXT_OPTIMISTIC_CLIENT_CACHE': JSON.stringify(
config.experimental.optimisticClientCache
),
'process.env.__NEXT_CROSS_ORIGIN': JSON.stringify(crossOrigin),
'process.browser': JSON.stringify(isClient),
'process.env.__NEXT_TEST_MODE': JSON.stringify(
process.env.__NEXT_TEST_MODE
),
// This is used in client/dev-error-overlay/hot-dev-client.js to replace the dist directory
...(dev && (isClient || isEdgeServer)
? {
'process.env.__NEXT_DIST_DIR': JSON.stringify(distDir),
}
: {}),
'process.env.__NEXT_TRAILING_SLASH': JSON.stringify(
config.trailingSlash
),
'process.env.__NEXT_BUILD_INDICATOR': JSON.stringify(
config.devIndicators.buildActivity
),
'process.env.__NEXT_BUILD_INDICATOR_POSITION': JSON.stringify(
config.devIndicators.buildActivityPosition
),
'process.env.__NEXT_STRICT_MODE': JSON.stringify(
config.reactStrictMode
),
'process.env.__NEXT_REACT_ROOT': JSON.stringify(hasReactRoot),
'process.env.__NEXT_RSC': JSON.stringify(hasServerComponents),
'process.env.__NEXT_OPTIMIZE_FONTS': JSON.stringify(
config.optimizeFonts && !dev
),
'process.env.__NEXT_OPTIMIZE_CSS': JSON.stringify(
config.experimental.optimizeCss && !dev
),
'process.env.__NEXT_SCRIPT_WORKERS': JSON.stringify(
config.experimental.nextScriptWorkers && !dev
),
'process.env.__NEXT_SCROLL_RESTORATION': JSON.stringify(
config.experimental.scrollRestoration
),
'process.env.__NEXT_IMAGE_OPTS': JSON.stringify({
deviceSizes: config.images.deviceSizes,
imageSizes: config.images.imageSizes,
path: config.images.path,
loader: config.images.loader,
dangerouslyAllowSVG: config.images.dangerouslyAllowSVG,
experimentalUnoptimized: config?.experimental?.images?.unoptimized,
experimentalFuture: config.experimental?.images?.allowFutureImage,
...(dev
? {
// pass domains in development to allow validating on the client
domains: config.images.domains,
experimentalRemotePatterns:
config.experimental?.images?.remotePatterns,
}
: {}),
}),
'process.env.__NEXT_ROUTER_BASEPATH': JSON.stringify(config.basePath),
'process.env.__NEXT_HAS_REWRITES': JSON.stringify(hasRewrites),
'process.env.__NEXT_I18N_SUPPORT': JSON.stringify(!!config.i18n),
'process.env.__NEXT_I18N_DOMAINS': JSON.stringify(config.i18n?.domains),
'process.env.__NEXT_ANALYTICS_ID': JSON.stringify(config.analyticsId),
...(isNodeServer || isEdgeServer
? {
// Fix bad-actors in the npm ecosystem (e.g. `node-formidable`)
// This is typically found in unmaintained modules from the
// pre-webpack era (common in server-side code)
'global.GENTLY': JSON.stringify(false),
}
: undefined),
// stub process.env with proxy to warn a missing value is
// being accessed in development mode
...(config.experimental.pageEnv && dev
? {
'process.env': `
new Proxy(${isNodeServer ? 'process.env' : '{}'}, {
get(target, prop) {
if (typeof target[prop] === 'undefined') {
console.warn(\`An environment variable (\${prop}) that was not provided in the environment was accessed.\nSee more info here: https://nextjs.org/docs/messages/missing-env-value\`)
}
return target[prop]
}
})
`,
}
: {}),
}),
new webpack.DefinePlugin(
getDefineEnv({
dev,
config,
distDir,
isClient,
hasRewrites,
hasReactRoot,
isNodeServer,
isEdgeServer,
middlewareRegex,
hasServerComponents,
})
),
isClient &&
new ReactLoadablePlugin({
filename: REACT_LOADABLE_MANIFEST,
Expand Down