2022-05-31 02:05:27 +02:00
import ReactRefreshWebpackPlugin from 'next/dist/compiled/@next/react-refresh-utils/dist/ReactRefreshWebpackPlugin'
2021-12-21 16:13:45 +01:00
import chalk from 'next/dist/compiled/chalk'
2019-09-11 22:41:29 +02:00
import crypto from 'crypto'
2021-10-06 17:40:01 +02:00
import { webpack } from 'next/dist/compiled/webpack/webpack'
2022-07-02 01:57:45 +02:00
import path , { dirname , join as pathJoin , relative as relativePath } from 'path'
2022-01-03 18:41:50 +01:00
import { escapeStringRegexp } from '../shared/lib/escape-regexp'
2019-04-26 17:23:32 +02:00
import {
2019-04-26 18:12:40 +02:00
DOT_NEXT_ALIAS ,
2019-04-26 17:23:32 +02:00
PAGES_DIR_ALIAS ,
2022-05-19 17:46:21 +02:00
ROOT_DIR_ALIAS ,
2022-05-25 11:46:26 +02:00
APP_DIR_ALIAS ,
2022-06-27 03:02:24 +02:00
SERVER_RUNTIME ,
2022-08-12 15:01:19 +02:00
WEBPACK_LAYERS ,
2022-09-17 00:12:59 +02:00
RSC_MOD_REF_PROXY_ALIAS ,
2019-04-26 17:23:32 +02:00
} from '../lib/constants'
2019-07-17 08:01:21 +02:00
import { fileExists } from '../lib/file-exists'
2021-04-22 21:03:13 +02:00
import { CustomRoutes } from '../lib/load-custom-routes.js'
2019-09-11 22:41:29 +02:00
import {
2021-04-22 21:03:13 +02:00
CLIENT_STATIC_FILES_RUNTIME_AMP ,
2019-09-11 22:41:29 +02:00
CLIENT_STATIC_FILES_RUNTIME_MAIN ,
2022-08-10 21:31:01 +02:00
CLIENT_STATIC_FILES_RUNTIME_MAIN_APP ,
2021-08-14 00:40:45 +02:00
CLIENT_STATIC_FILES_RUNTIME_POLYFILLS_SYMBOL ,
2021-04-22 21:03:13 +02:00
CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH ,
2019-09-11 22:41:29 +02:00
CLIENT_STATIC_FILES_RUNTIME_WEBPACK ,
2021-10-26 18:50:56 +02:00
MIDDLEWARE_REACT_LOADABLE_MANIFEST ,
2019-09-11 22:41:29 +02:00
REACT_LOADABLE_MANIFEST ,
SERVERLESS_DIRECTORY ,
2019-12-09 21:08:15 +01:00
SERVER_DIRECTORY ,
2022-08-12 15:01:19 +02:00
COMPILER_NAMES ,
CompilerNameValues ,
2021-06-30 11:43:31 +02:00
} from '../shared/lib/constants'
import { execOnce } from '../shared/lib/utils'
2021-07-12 23:38:57 +02:00
import { NextConfigComplete } from '../server/config-shared'
2021-09-21 19:17:16 +02:00
import { finalizeEntrypoint } from './entries'
2020-08-04 23:24:56 +02:00
import * as Log from './output/log'
2019-12-09 21:08:15 +01:00
import { build as buildConfiguration } from './webpack/config'
feat: build edge functions with node.js modules and fail at runtime (#38234)
## What's in there?
The Edge runtime [does not support Node.js modules](https://edge-runtime.vercel.app/features/available-apis#unsupported-apis).
When building Next.js application, we currently fail the build when detecting node.js module imported from middleware.
This is an blocker for using code that is conditionally loading node.js modules (based on platform/env detection), as @cramforce reported.
This PR implements a new strategy where:
- we can build such middleware/Edge API route code **with a warning**
- we fail at run time, with graceful errors in dev (console & react-dev-overlay error)
- we fail at run time, with console errors in production
## How to test?
All cases are covered with integration tests.
To try them live, create a simple app with a page, a `middleware.js` file and a `pages/api/route.js`file.
Here are iconic examples:
### node.js modules
```js
// middleware.js
import { NextResponse } from 'next/server'
// static
import { basename } from 'path'
export default async function middleware() {
// dynamic
const { basename } = await import('path')
basename()
return NextResponse.next()
}
export const config = { matcher: '/' }
```
```js
// pags/api/route.js
// static
import { isAbsolute } from 'path'
export default async function handle() {
// dynamic
const { isAbsolute } = await import('path')
return Response.json({ useNodeModule: isAbsolute('/test') })
}
export const config = { runtime: 'experimental-edge' }
```
Desired error (+ source code highlight in dev):
> The edge runtime does not support Node.js 'path' module
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime
Desired warning at build time:
> A Node.js module is loaded ('path' at line 2) which is not supported in the Edge Runtime.
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime
- [x] in dev middleware, static, shows desired error on stderr
- [x] in dev route, static, shows desired error on stderr
- [x] in dev middleware, dynamic, shows desired error on stderr
- [x] in dev route, dynamic, shows desired error on stderr
- [x] in dev middleware, static, shows desired error on react error overlay
- [x] in dev route, static, shows desired error on react error overlay
- [x] in dev middleware, dynamic, shows desired error on react error overlay
- [x] in dev route, dynamic, shows desired error on react error overlay
- [x] builds middleware successfully, shows build warning, shows desired error on stderr on call
- [x] builds route successfully, shows build warning, shows desired error on stderr on call
### 3rd party modules not found
```js
// middleware.js
import { NextResponse } from 'next/server'
// static
import Unknown from 'unknown'
export default async function middleware() {
// dynamic
const Unknown = await import('unknown')
new Unknown()
return NextResponse.next()
}
```
export const config = { matcher: '/' }
```
```js
// pags/api/route.js
// static
import Unknown from 'unknown'
export default async function handle() {
// dynamic
const Unknown = await import('unknown')
return Response.json({ use3rdPartyModule: Unknown() })
}
export const config = { runtime: 'experimental-edge' }
```
Desired error (+ source code highlight in dev):
> Module not found: Can't resolve 'does-not-exist'
Learn More: https://nextjs.org/docs/messages/module-not-found
- [x] in dev middleware, static, shows desired error on stderr
- [x] in dev route, static, shows desired error on stderr
- [x] in dev middleware, dynamic, shows desired error on stderr
- [x] in dev route, dynamic, shows desired error on stderr
- [x] in dev middleware, static, shows desired error on react error overlay
- [x] in dev route, static, shows desired error on react error overlay
- [x] in dev middleware, dynamic, shows desired error on react error overlay
- [x] in dev route, dynamic, shows desired error on react error overlay
- [x] fails to build middleware, with desired error on stderr
- [x] fails to build route, with desired error on stderr
### unused node.js modules
```js
// middleware.js
import { NextResponse } from 'next/server'
export default async function middleware() {
if (process.exit) {
const { basename } = await import('path')
basename()
}
return NextResponse.next()
}
```
```js
// pags/api/route.js
export default async function handle() {
if (process.exit) {
const { basename } = await import('path')
basename()
}
return Response.json({ useNodeModule: false })
}
export const config = { runtime: 'experimental-edge' }
```
Desired warning at build time:
> A Node.js module is loaded ('path' at line 2) which is not supported in the Edge Runtime.
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime
- [x] invoke middleware in dev with no error
- [x] invoke route in dev with no error
- [x] builds successfully, shows build warning, invoke middleware with no error
- [x] builds successfully, shows build warning, invoke api-route with no error
## Notes to reviewers
The strategy to implement this feature is to leverages webpack [externals](https://webpack.js.org/configuration/externals/#externals) and run a global `__unsupported_module()` function when using a node.js module from edge function's code.
For the record, I tried using [webpack resolve.fallback](https://webpack.js.org/configuration/resolve/#resolvefallback) and [Webpack.IgnorePlugin](https://webpack.js.org/plugins/ignore-plugin/) but they do not allow throwing proper errors at runtime that would contain the loaded module name for reporting.
`__unsupported_module()` is defined in `EdgeRuntime`, and returns a proxy that's throw on use (whether it's property access, function call, new operator... synchronous & promise-based styles).
However there's an issue with error reporting: webpack does not includes the import lines in the generated sourcemaps, preventing from displaying useful errors.
I extended our middleware-plugin to supplement the sourcemaps (when analyzing edge function code, it saves which module is imported from which file, together with line/column/source)
The react-dev-overlay was adapted to look for this additional information when the caught error relates to modules, instead of looking at sourcemaps.
I removed the previous mechanism (built by @nkzawa ) which caught webpack errors at built time to change the displayed error message (files `next/build/index.js`, `next/build/utils.ts` and `wellknown-errors-plugin`)
2022-07-06 22:54:44 +02:00
import MiddlewarePlugin , {
handleWebpackExtenalForEdgeRuntime ,
} from './webpack/plugins/middleware-plugin'
2019-04-26 18:12:40 +02:00
import BuildManifestPlugin from './webpack/plugins/build-manifest-plugin'
2020-04-15 20:20:25 +02:00
import { JsConfigPathsPlugin } from './webpack/plugins/jsconfig-paths-plugin'
2019-05-08 03:11:56 +02:00
import { DropClientPage } from './webpack/plugins/next-drop-client-page-plugin'
2019-04-26 18:12:40 +02:00
import PagesManifestPlugin from './webpack/plugins/pages-manifest-plugin'
2019-09-11 22:41:29 +02:00
import { ProfilingPlugin } from './webpack/plugins/profiling-plugin'
2019-04-26 18:12:40 +02:00
import { ReactLoadablePlugin } from './webpack/plugins/react-loadable-plugin'
import { ServerlessPlugin } from './webpack/plugins/serverless-plugin'
2020-05-13 17:43:41 +02:00
import { WellKnownErrorsPlugin } from './webpack/plugins/wellknown-errors-plugin'
2021-06-07 14:43:14 +02:00
import { regexLikeCss } from './webpack/config/blocks/css'
2021-08-14 00:40:45 +02:00
import { CopyFilePlugin } from './webpack/plugins/copy-file-plugin'
2021-10-26 18:50:56 +02:00
import { FlightManifestPlugin } from './webpack/plugins/flight-manifest-plugin'
2022-07-29 00:35:52 +02:00
import { FlightClientEntryPlugin } from './webpack/plugins/flight-client-entry-plugin'
2022-06-03 20:47:16 +02:00
import type {
2022-03-22 01:09:03 +01:00
Feature ,
SWC_TARGET_TRIPLE ,
} from './webpack/plugins/telemetry-plugin'
2021-09-13 15:49:29 +02:00
import type { Span } from '../trace'
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
import type { MiddlewareMatcher } from './analysis/get-page-static-info'
2021-11-11 01:16:15 +01:00
import loadJsConfig from './load-jsconfig'
2022-05-12 11:15:27 +02:00
import { loadBindings } from './swc'
2022-08-10 21:31:01 +02:00
import { AppBuildManifestPlugin } from './webpack/plugins/app-build-manifest-plugin'
2022-09-09 00:17:15 +02:00
import { SubresourceIntegrityPlugin } from './webpack/plugins/subresource-integrity-plugin'
2022-09-22 07:12:59 +02:00
import { FontLoaderManifestPlugin } from './webpack/plugins/font-loader-manifest-plugin'
2022-09-27 12:03:57 +02:00
import { getSupportedBrowsers } from './utils'
2021-11-03 19:38:04 +01:00
2022-08-12 17:25:47 +02:00
const NEXT_PROJECT_ROOT = pathJoin ( __dirname , '..' , '..' )
const NEXT_PROJECT_ROOT_DIST = pathJoin ( NEXT_PROJECT_ROOT , 'dist' )
const NEXT_PROJECT_ROOT_DIST_CLIENT = pathJoin ( NEXT_PROJECT_ROOT_DIST , 'client' )
2022-09-27 12:03:57 +02:00
const babelIncludeRegexes : RegExp [ ] = [
/next[\\/]dist[\\/](esm[\\/])?shared[\\/]lib/ ,
/next[\\/]dist[\\/](esm[\\/])?client/ ,
/next[\\/]dist[\\/](esm[\\/])?pages/ ,
/[\\/](strip-ansi|ansi-regex)[\\/]/ ,
/styled-jsx[\\/]/ ,
]
const BABEL_CONFIG_FILES = [
'.babelrc' ,
'.babelrc.json' ,
'.babelrc.js' ,
'.babelrc.mjs' ,
'.babelrc.cjs' ,
'babel.config.js' ,
'babel.config.json' ,
'babel.config.mjs' ,
'babel.config.cjs' ,
]
const rscSharedRegex =
2022-10-03 15:43:35 +02:00
/(node_modules[\\/]react\/|[\\/]shared[\\/]lib[\\/](head-manager-context|router-context|server-inserted-html)\.js|node_modules[\\/]styled-jsx[\\/])/
2022-09-27 12:03:57 +02:00
// Support for NODE_PATH
const nodePathList = ( process . env . NODE_PATH || '' )
. split ( process . platform === 'win32' ? ';' : ':' )
. filter ( ( p ) = > ! ! p )
const reactDir = dirname ( require . resolve ( 'react/package.json' ) )
const reactDomDir = dirname ( require . resolve ( 'react-dom/package.json' ) )
const watchOptions = Object . freeze ( {
aggregateTimeout : 5 ,
ignored : [ '**/.git/**' , '**/.next/**' ] ,
} )
function isModuleCSS ( module : { type : string } ) {
return (
// mini-css-extract-plugin
module . type === ` css/mini-extract ` ||
// extract-css-chunks-webpack-plugin (old)
module . type === ` css/extract-chunks ` ||
// extract-css-chunks-webpack-plugin (new)
module . type === ` css/extract-css-chunks `
)
}
2022-08-15 16:29:51 +02:00
function errorIfEnvConflicted ( config : NextConfigComplete , key : string ) {
const isPrivateKey = /^(?:NODE_.+)|^(?:__.+)$/i . test ( key )
const hasNextRuntimeKey = key === 'NEXT_RUNTIME'
if ( isPrivateKey || hasNextRuntimeKey ) {
throw new Error (
` The key " ${ key } " under "env" in ${ config . configFileName } is not allowed. https://nextjs.org/docs/messages/env-key-not-allowed `
)
}
}
2022-09-30 15:15:56 +02:00
function isResourceInPackages ( resource : string , packageNames? : string [ ] ) {
return packageNames ? . some ( ( p : string ) = >
new RegExp ( '[/\\\\]node_modules[/\\\\]' + p + '[/\\\\]' ) . test ( resource )
)
}
2022-08-13 18:55:55 +02:00
export function getDefineEnv ( {
dev ,
config ,
distDir ,
isClient ,
hasRewrites ,
hasReactRoot ,
isNodeServer ,
isEdgeServer ,
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
middlewareMatchers ,
2022-08-13 18:55:55 +02:00
} : {
dev? : boolean
distDir : string
isClient? : boolean
hasRewrites? : boolean
hasReactRoot? : boolean
isNodeServer? : boolean
isEdgeServer? : boolean
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
middlewareMatchers? : MiddlewareMatcher [ ]
2022-08-13 18:55:55 +02:00
config : NextConfigComplete
} ) {
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' ) ,
2022-09-28 12:29:22 +02:00
'process.env.NEXT_RUNTIME' : JSON . stringify (
isEdgeServer ? 'edge' : isNodeServer ? 'nodejs' : undefined
) ,
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
'process.env.__NEXT_MIDDLEWARE_MATCHERS' : JSON . stringify (
middlewareMatchers || [ ]
2022-08-13 18:55:55 +02:00
) ,
'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_OPTIMIZE_FONTS' : JSON . stringify (
2022-09-16 23:13:21 +02:00
! dev && config . optimizeFonts
2022-08-13 18:55:55 +02:00
) ,
'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 ,
2022-09-01 00:44:17 +02:00
unoptimized : config?.images?.unoptimized ,
2022-08-13 18:55:55 +02:00
. . . ( dev
? {
// pass domains in development to allow validating on the client
domains : config.images.domains ,
2022-09-01 00:44:17 +02:00
remotePatterns : config.images?.remotePatterns ,
2022-08-13 18:55:55 +02:00
}
: { } ) ,
} ) ,
'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 ) ,
2022-10-04 19:08:17 +02:00
'process.env.__NEXT_NO_MIDDLEWARE_URL_NORMALIZE' : JSON . stringify (
config . experimental . skipMiddlewareUrlNormalize
) ,
add attribution to web vitals (#39368)
This commit implements the main proposal presented in
https://github.com/vercel/next.js/issues/39241
to add attribution to web vitals.
Attribution adds more specific debugging info to web vitals,
for example in the case of Cumulative Layout Shift (CLS),
we might want to know
> What's the first element that shifted when the single largest layout shift occurred?
on in the case of Largest Contentful Paint (LCP),
> What's the element corresponding to the LCP for the page?
> If it is an image, what's the URL of the image resource?
Attribution is *disabled* by default because it could potentially
generate a lot data and overwhelm the RUM backend.
It is enabled *per metric* (LCP, FCP, CLS, etc)
As part of this change, `web-vitals` has been upgraded to v3.0.0
This version contains minor bug fixes, please see changelog at
https://github.com/GoogleChrome/web-vitals/commit/9fe3cc02c875cb70ac0f1803f5e11b428e7a4014
Fixes #39241
## Bug
- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Errors have helpful link attached, see `contributing.md`
## Feature
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [x] Related issues linked using `fixes #number`
- [x] Integration tests added
- [x] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
## Documentation / Examples
- [ ] Make sure the linting passes by running `pnpm lint`
- [x] The examples guidelines are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing.md#adding-examples)
Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com>
2022-10-04 02:17:30 +02:00
'process.env.__NEXT_HAS_WEB_VITALS_ATTRIBUTION' : JSON . stringify (
config . experimental . webVitalsAttribution &&
config . experimental . webVitalsAttribution . length > 0
) ,
'process.env.__NEXT_WEB_VITALS_ATTRIBUTION' : JSON . stringify (
config . experimental . webVitalsAttribution
) ,
2022-08-13 18:55:55 +02:00
. . . ( 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 ]
}
} )
` ,
}
: { } ) ,
}
}
2019-02-20 20:24:02 +01:00
type ExcludesFalse = < T > ( x : T | false ) = > x is T
2016-12-28 00:28:19 +01:00
2021-01-14 02:59:08 +01:00
const devtoolRevertWarning = execOnce (
( devtool : webpack.Configuration [ 'devtool' ] ) = > {
console . warn (
chalk . yellow . bold ( 'Warning: ' ) +
chalk . bold ( ` Reverting webpack devtool to ' ${ devtool } '. \ n ` ) +
'Changing the webpack devtool in development mode will cause severe performance regressions.\n' +
2021-03-29 10:25:00 +02:00
'Read more: https://nextjs.org/docs/messages/improper-devtool'
2021-01-14 02:59:08 +01:00
)
}
)
2020-06-24 06:15:57 +02:00
2021-10-23 10:21:44 +02:00
let loggedSwcDisabled = false
2022-02-10 02:54:28 +01:00
let loggedIgnoredCompilerOptions = false
2021-10-23 10:21:44 +02:00
2021-10-26 18:50:56 +02:00
function getOptimizedAliases ( ) : { [ pkg : string ] : string } {
2019-11-15 07:47:08 +01:00
const stubWindowFetch = path . join ( __dirname , 'polyfills' , 'fetch' , 'index.js' )
2019-11-11 05:48:11 +01:00
const stubObjectAssign = path . join ( __dirname , 'polyfills' , 'object-assign.js' )
const shimAssign = path . join ( __dirname , 'polyfills' , 'object.assign' )
2020-01-30 07:04:29 +01:00
return Object . assign (
{ } ,
{
unfetch$ : stubWindowFetch ,
'isomorphic-unfetch$' : stubWindowFetch ,
'whatwg-fetch$' : path . join (
__dirname ,
'polyfills' ,
'fetch' ,
'whatwg-fetch.js'
) ,
} ,
{
'object-assign$' : stubObjectAssign ,
// Stub Package: object.assign
'object.assign/auto' : path . join ( shimAssign , 'auto.js' ) ,
'object.assign/implementation' : path . join (
shimAssign ,
'implementation.js'
) ,
'object.assign$' : path . join ( shimAssign , 'index.js' ) ,
'object.assign/polyfill' : path . join ( shimAssign , 'polyfill.js' ) ,
'object.assign/shim' : path . join ( shimAssign , 'shim.js' ) ,
2020-02-28 04:08:58 +01:00
// Replace: full URL polyfill with platform-based polyfill
2021-10-18 17:46:39 +02:00
url : require.resolve ( 'next/dist/compiled/native-url' ) ,
2020-01-30 07:04:29 +01:00
}
)
2019-11-02 02:00:56 +01:00
}
2020-02-17 22:16:19 +01:00
type ClientEntries = {
2020-07-17 10:38:06 +02:00
[ key : string ] : string | string [ ]
2020-02-17 22:16:19 +01:00
}
2020-08-04 23:24:56 +02:00
export function attachReactRefresh (
webpackConfig : webpack.Configuration ,
targetLoader : webpack.RuleSetUseItem
) {
let injections = 0
2022-01-17 16:17:22 +01:00
const reactRefreshLoaderName =
2022-05-31 02:05:27 +02:00
'next/dist/compiled/@next/react-refresh-utils/dist/loader'
2020-08-04 23:24:56 +02:00
const reactRefreshLoader = require . resolve ( reactRefreshLoaderName )
2022-08-16 11:55:37 +02:00
webpackConfig . module ? . rules ? . forEach ( ( rule ) = > {
if ( rule && typeof rule === 'object' && 'use' in rule ) {
const curr = rule . use
// When the user has configured `defaultLoaders.babel` for a input file:
if ( curr === targetLoader ) {
++ injections
rule . use = [ reactRefreshLoader , curr as webpack . RuleSetUseItem ]
} else if (
Array . isArray ( curr ) &&
curr . some ( ( r ) = > r === targetLoader ) &&
// Check if loader already exists:
! curr . some (
( r ) = > r === reactRefreshLoader || r === reactRefreshLoaderName
)
) {
++ injections
const idx = curr . findIndex ( ( r ) = > r === targetLoader )
// Clone to not mutate user input
rule . use = [ . . . curr ]
2020-08-04 23:24:56 +02:00
2022-08-16 11:55:37 +02:00
// inject / input: [other, babel] output: [other, refresh, babel]:
rule . use . splice ( idx , 0 , reactRefreshLoader )
}
2020-08-04 23:24:56 +02:00
}
} )
if ( injections ) {
Log . info (
` automatically enabled Fast Refresh for ${ injections } custom loader ${
injections > 1 ? 's' : ''
} `
)
}
}
2021-09-24 15:39:48 +02:00
export const NODE_RESOLVE_OPTIONS = {
2021-05-07 18:32:33 +02:00
dependencyType : 'commonjs' ,
modules : [ 'node_modules' ] ,
fallback : false ,
exportsFields : [ 'exports' ] ,
importsFields : [ 'imports' ] ,
2021-08-31 10:32:33 +02:00
conditionNames : [ 'node' , 'require' ] ,
2021-05-07 18:32:33 +02:00
descriptionFiles : [ 'package.json' ] ,
extensions : [ '.js' , '.json' , '.node' ] ,
enforceExtensions : false ,
symlinks : true ,
mainFields : [ 'main' ] ,
mainFiles : [ 'index' ] ,
roots : [ ] ,
fullySpecified : false ,
preferRelative : false ,
preferAbsolute : false ,
restrictions : [ ] ,
}
2021-10-05 01:57:27 +02:00
export const NODE_BASE_RESOLVE_OPTIONS = {
. . . NODE_RESOLVE_OPTIONS ,
alias : false ,
}
2021-09-24 15:39:48 +02:00
export const NODE_ESM_RESOLVE_OPTIONS = {
2021-07-10 18:49:02 +02:00
. . . NODE_RESOLVE_OPTIONS ,
2021-10-05 01:57:27 +02:00
alias : false ,
2021-07-10 18:49:02 +02:00
dependencyType : 'esm' ,
2021-08-31 10:32:33 +02:00
conditionNames : [ 'node' , 'import' ] ,
2021-07-10 18:49:02 +02:00
fullySpecified : true ,
}
2021-10-05 01:57:27 +02:00
export const NODE_BASE_ESM_RESOLVE_OPTIONS = {
. . . NODE_ESM_RESOLVE_OPTIONS ,
alias : false ,
}
2021-10-18 19:01:02 +02:00
export const nextImageLoaderRegex =
/\.(png|jpg|jpeg|gif|webp|avif|ico|bmp|svg)$/i
2021-10-28 10:14:09 +02:00
export async function resolveExternal (
appDir : string ,
esmExternalsConfig : NextConfigComplete [ 'experimental' ] [ 'esmExternals' ] ,
context : string ,
request : string ,
isEsmRequested : boolean ,
getResolve : (
options : any
) = > (
resolveContext : string ,
resolveRequest : string
) = > Promise < [ string | null , boolean ] > ,
isLocalCallback ? : ( res : string ) = > any ,
baseResolveCheck = true ,
esmResolveOptions : any = NODE_ESM_RESOLVE_OPTIONS ,
nodeResolveOptions : any = NODE_RESOLVE_OPTIONS ,
baseEsmResolveOptions : any = NODE_BASE_ESM_RESOLVE_OPTIONS ,
baseResolveOptions : any = NODE_BASE_RESOLVE_OPTIONS
) {
const esmExternals = ! ! esmExternalsConfig
const looseEsmExternals = esmExternalsConfig === 'loose'
let res : string | null = null
let isEsm : boolean = false
let preferEsmOptions =
esmExternals && isEsmRequested ? [ true , false ] : [ false ]
for ( const preferEsm of preferEsmOptions ) {
const resolve = getResolve (
preferEsm ? esmResolveOptions : nodeResolveOptions
)
// Resolve the import with the webpack provided context, this
// ensures we're resolving the correct version when multiple
// exist.
try {
; [ res , isEsm ] = await resolve ( context , request )
} catch ( err ) {
res = null
}
if ( ! res ) {
continue
}
// ESM externals can only be imported (and not required).
// Make an exception in loose mode.
if ( ! isEsmRequested && isEsm && ! looseEsmExternals ) {
continue
}
if ( isLocalCallback ) {
return { localRes : isLocalCallback ( res ) }
}
// Bundled Node.js code is relocated without its node_modules tree.
// This means we need to make sure its request resolves to the same
// package that'll be available at runtime. If it's not identical,
// we need to bundle the code (even if it _should_ be external).
if ( baseResolveCheck ) {
let baseRes : string | null
let baseIsEsm : boolean
try {
const baseResolve = getResolve (
isEsm ? baseEsmResolveOptions : baseResolveOptions
)
; [ baseRes , baseIsEsm ] = await baseResolve ( appDir , request )
} catch ( err ) {
baseRes = null
baseIsEsm = false
}
// Same as above: if the package, when required from the root,
// would be different from what the real resolution would use, we
// cannot externalize it.
// if request is pointing to a symlink it could point to the the same file,
// the resolver will resolve symlinks so this is handled
if ( baseRes !== res || isEsm !== baseIsEsm ) {
res = null
continue
}
}
break
}
return { res , isEsm }
}
2019-04-26 17:23:32 +02:00
export default async function getBaseWebpackConfig (
dir : string ,
{
buildId ,
config ,
2022-04-27 11:50:29 +02:00
compilerType ,
2019-09-24 17:15:14 +02:00
dev = false ,
2022-04-27 11:50:29 +02:00
entrypoints ,
hasReactRoot ,
isDevFallback = false ,
2019-09-24 17:15:14 +02:00
pagesDir ,
2020-07-09 13:39:12 +02:00
reactProductionProfiling = false ,
2020-08-13 14:39:36 +02:00
rewrites ,
2021-08-17 09:18:47 +02:00
runWebpackSpan ,
2022-08-12 15:01:19 +02:00
target = COMPILER_NAMES . server ,
2022-05-25 11:46:26 +02:00
appDir ,
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
middlewareMatchers ,
2019-04-26 17:23:32 +02:00
} : {
buildId : string
2021-07-12 23:38:57 +02:00
config : NextConfigComplete
2022-08-12 15:01:19 +02:00
compilerType : CompilerNameValues
2019-09-24 17:15:14 +02:00
dev? : boolean
2022-08-16 11:55:37 +02:00
entrypoints : webpack.EntryObject
2022-04-27 11:50:29 +02:00
hasReactRoot : boolean
isDevFallback? : boolean
2022-09-03 02:13:47 +02:00
pagesDir? : string
2020-07-09 13:39:12 +02:00
reactProductionProfiling? : boolean
2021-03-26 16:19:48 +01:00
rewrites : CustomRoutes [ 'rewrites' ]
2021-08-25 10:47:16 +02:00
runWebpackSpan : Span
2022-04-27 11:50:29 +02:00
target? : string
2022-05-25 11:46:26 +02:00
appDir? : string
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
middlewareMatchers? : MiddlewareMatcher [ ]
2019-04-26 17:23:32 +02:00
}
) : Promise < webpack.Configuration > {
2022-08-12 15:01:19 +02:00
const isClient = compilerType === COMPILER_NAMES . client
const isEdgeServer = compilerType === COMPILER_NAMES . edgeServer
const isNodeServer = compilerType === COMPILER_NAMES . server
2022-06-03 20:47:16 +02:00
2022-09-27 12:03:57 +02:00
const { jsConfig , resolvedBaseUrl } = await loadJsConfig ( dir , config )
2022-05-17 17:09:34 +02:00
const supportedBrowsers = await getSupportedBrowsers ( dir , dev , config )
2021-03-26 16:19:48 +01:00
const hasRewrites =
rewrites . beforeFiles . length > 0 ||
rewrites . afterFiles . length > 0 ||
rewrites . fallback . length > 0
2022-02-08 14:16:46 +01:00
2022-09-12 15:15:18 +02:00
// Only error in first one compiler (client) once
if ( isClient ) {
if ( ! hasReactRoot ) {
if ( config . experimental . runtime ) {
throw new Error (
'`experimental.runtime` requires React 18 to be installed.'
)
}
2022-09-21 21:30:46 +02:00
if ( config . experimental . appDir ) {
2022-09-12 15:15:18 +02:00
throw new Error (
2022-09-21 21:30:46 +02:00
'`experimental.appDir` requires React 18 to be installed.'
2022-09-12 15:15:18 +02:00
)
}
2022-06-23 18:33:16 +02:00
}
2021-10-26 18:50:56 +02:00
}
2022-02-08 14:16:46 +01:00
2022-03-26 00:05:35 +01:00
const hasConcurrentFeatures = hasReactRoot
2022-09-21 21:30:46 +02:00
const hasServerComponents = ! ! config . experimental . appDir
2022-02-08 14:16:46 +01:00
const disableOptimizedLoading = hasConcurrentFeatures
? true
: config . experimental . disableOptimizedLoading
2021-10-26 18:50:56 +02:00
2022-04-27 11:50:29 +02:00
if ( isClient ) {
2022-06-27 03:02:24 +02:00
if ( config . experimental . runtime === SERVER_RUNTIME . edge ) {
2022-02-08 14:16:46 +01:00
Log . warn (
'You are using the experimental Edge Runtime with `experimental.runtime`.'
)
}
2022-04-27 11:50:29 +02:00
if ( config . experimental . runtime === 'nodejs' ) {
2022-02-08 14:16:46 +01:00
Log . warn (
'You are using the experimental Node.js Runtime with `experimental.runtime`.'
)
}
2021-10-26 18:50:56 +02:00
}
2022-09-27 12:03:57 +02:00
const babelConfigFile = await BABEL_CONFIG_FILES . reduce (
async ( memo : Promise < string | undefined > , filename ) = > {
const configFilePath = path . join ( dir , filename )
return (
( await memo ) ||
( ( await fileExists ( configFilePath ) ) ? configFilePath : undefined )
)
} ,
Promise . resolve ( undefined )
)
2021-04-20 10:45:57 +02:00
2019-04-22 18:16:42 +02:00
const distDir = path . join ( dir , config . distDir )
2021-04-08 14:03:02 +02:00
2022-05-10 12:33:31 +02:00
let useSWCLoader = ! babelConfigFile || config . experimental . forceSwcTransforms
2022-03-22 01:09:03 +01:00
let SWCBinaryTarget : [ Feature , boolean ] | undefined = undefined
if ( useSWCLoader ) {
// TODO: we do not collect wasm target yet
const binaryTarget = require ( './swc' ) ? . getBinaryMetadata ? . ( )
? . target as SWC_TARGET_TRIPLE
SWCBinaryTarget = binaryTarget
? [ ` swc/target/ ${ binaryTarget } ` as const , true ]
: undefined
}
2021-10-26 18:50:56 +02:00
2021-10-23 10:21:44 +02:00
if ( ! loggedSwcDisabled && ! useSWCLoader && babelConfigFile ) {
2021-11-06 19:37:24 +01:00
Log . info (
2021-10-26 19:59:37 +02:00
` Disabled SWC as replacement for Babel because of custom Babel configuration " ${ path . relative (
2021-10-23 10:21:44 +02:00
dir ,
babelConfigFile
) } " https : //nextjs.org/docs/messages/swc-disabled`
2021-08-06 16:07:36 +02:00
)
2021-10-23 10:21:44 +02:00
loggedSwcDisabled = true
2021-08-06 16:07:36 +02:00
}
2021-10-24 15:26:36 +02:00
2022-05-12 11:15:27 +02:00
// eagerly load swc bindings instead of waiting for transform calls
if ( ! babelConfigFile && isClient ) {
await loadBindings ( )
}
2022-02-10 02:54:28 +01:00
if ( ! loggedIgnoredCompilerOptions && ! useSWCLoader && config . compiler ) {
Log . info (
2022-03-20 23:50:33 +01:00
'`compiler` options in `next.config.js` will be ignored while using Babel https://nextjs.org/docs/messages/ignored-compiler-options'
2022-02-10 02:54:28 +01:00
)
loggedIgnoredCompilerOptions = true
}
2022-09-27 11:31:25 +02:00
const getBabelLoader = ( ) = > {
return {
loader : require.resolve ( './babel/loader/index' ) ,
options : {
configFile : babelConfigFile ,
isServer : isNodeServer || isEdgeServer ,
distDir ,
pagesDir ,
cwd : dir ,
development : dev ,
hasReactRefresh : dev && isClient ,
hasJsxRuntime : true ,
} ,
}
}
let swcTraceProfilingInitialized = false
const getSwcLoader = ( extraOptions? : any ) = > {
if (
config ? . experimental ? . swcTraceProfiling &&
! swcTraceProfilingInitialized
) {
2022-05-03 00:20:59 +02:00
// 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.
2022-09-27 11:31:25 +02:00
swcTraceProfilingInitialized = true
2022-05-03 00:20:59 +02:00
require ( './swc' ) ? . initCustomTraceSubscriber ? . (
path . join ( distDir , ` swc-trace-profile- ${ Date . now ( ) } .json ` )
)
}
2022-09-27 11:31:25 +02:00
return {
loader : 'next-swc-loader' ,
options : {
isServer : isNodeServer || isEdgeServer ,
rootDir : dir ,
pagesDir ,
hasReactRefresh : dev && isClient ,
fileReading : config.experimental.swcFileReading ,
nextConfig : config ,
jsConfig ,
supportedBrowsers : config.experimental.browsersListForSwc
? supportedBrowsers
: undefined ,
swcCacheDir : path.join ( dir , config ? . distDir ? ? '.next' , 'cache' , 'swc' ) ,
. . . extraOptions ,
} ,
}
}
const getBabelOrSwcLoader = ( ) = > {
return useSWCLoader ? getSwcLoader ( ) : getBabelLoader ( )
2021-10-24 15:26:36 +02:00
}
const defaultLoaders = {
2022-04-27 11:50:29 +02:00
babel : getBabelOrSwcLoader ( ) ,
2018-01-30 16:40:52 +01:00
}
2022-09-18 02:00:16 +02:00
const pageExtensions = config . pageExtensions
2021-10-26 18:50:56 +02:00
2019-08-06 00:26:20 +02:00
// Intentionally not using isTargetLikeServerless helper
2022-04-27 11:50:29 +02:00
const isLikeServerless =
target === 'serverless' || target === 'experimental-serverless-trace'
const outputPath =
isNodeServer || isEdgeServer
? path . join (
distDir ,
isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY
)
: distDir
2019-08-06 00:26:20 +02:00
2022-04-27 11:50:29 +02:00
const clientEntries = isClient
2020-02-17 22:16:19 +01:00
? ( {
2019-04-26 17:23:32 +02:00
// Backwards compatibility
'main.js' : [ ] ,
2021-02-19 11:10:19 +01:00
. . . ( dev
? {
[ CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH ] : require . resolve (
2022-05-31 02:05:27 +02:00
` next/dist/compiled/@next/react-refresh-utils/dist/runtime `
2021-02-19 11:10:19 +01:00
) ,
[ CLIENT_STATIC_FILES_RUNTIME_AMP ] :
` ./ ` +
relativePath (
dir ,
pathJoin ( NEXT_PROJECT_ROOT_DIST_CLIENT , 'dev' , 'amp-dev' )
) . replace ( /\\/g , '/' ) ,
}
: { } ) ,
2019-04-26 17:23:32 +02:00
[ CLIENT_STATIC_FILES_RUNTIME_MAIN ] :
2020-07-09 14:31:06 +02:00
` ./ ` +
path
. relative (
dir ,
path . join (
NEXT_PROJECT_ROOT_DIST_CLIENT ,
dev ? ` next-dev.js ` : 'next.js'
)
2019-04-26 17:23:32 +02:00
)
2020-07-09 14:31:06 +02:00
. replace ( /\\/g , '/' ) ,
2022-05-25 11:46:26 +02:00
. . . ( config . experimental . appDir
2022-05-03 12:37:23 +02:00
? {
2022-08-10 21:31:01 +02:00
[ CLIENT_STATIC_FILES_RUNTIME_MAIN_APP ] : dev
2022-07-10 19:18:48 +02:00
? [
require . resolve (
` next/dist/compiled/@next/react-refresh-utils/dist/runtime `
) ,
` ./ ` +
path
. relative (
dir ,
path . join (
NEXT_PROJECT_ROOT_DIST_CLIENT ,
'app-next-dev.js'
)
)
. replace ( /\\/g , '/' ) ,
]
: ` ./ ` +
path
. relative (
dir ,
path . join ( NEXT_PROJECT_ROOT_DIST_CLIENT , 'app-next.js' )
)
. replace ( /\\/g , '/' ) ,
2022-05-03 12:37:23 +02:00
}
: { } ) ,
2020-02-17 22:16:19 +01:00
} as ClientEntries )
2019-04-26 17:23:32 +02:00
: undefined
2016-10-19 14:41:45 +02:00
2020-06-28 13:23:29 +02:00
function getReactProfilingInProduction() {
2020-07-09 13:39:12 +02:00
if ( reactProductionProfiling ) {
2020-06-28 13:23:29 +02:00
return {
'react-dom$' : 'react-dom/profiling' ,
'scheduler/tracing' : 'scheduler/tracing-profiling' ,
}
}
}
2021-08-19 10:12:12 +02:00
// tell webpack where to look for _app and _document
// using aliases to allow falling back to the default
// version when removed or not present
2020-08-13 14:39:36 +02:00
const clientResolveRewrites = require . resolve (
2021-06-30 11:43:31 +02:00
'../shared/lib/router/utils/resolve-rewrites'
2020-08-13 14:39:36 +02:00
)
2021-08-19 10:12:12 +02:00
const customAppAliases : { [ key : string ] : string [ ] } = { }
const customErrorAlias : { [ key : string ] : string [ ] } = { }
const customDocumentAliases : { [ key : string ] : string [ ] } = { }
2022-05-03 12:37:23 +02:00
const customRootAliases : { [ key : string ] : string [ ] } = { }
2021-08-19 10:12:12 +02:00
2021-10-06 17:40:01 +02:00
if ( dev ) {
2021-08-19 10:12:12 +02:00
customAppAliases [ ` ${ PAGES_DIR_ALIAS } /_app ` ] = [
2022-09-03 02:13:47 +02:00
. . . ( pagesDir
2022-09-18 02:00:16 +02:00
? pageExtensions . reduce ( ( prev , ext ) = > {
2022-09-03 02:13:47 +02:00
prev . push ( path . join ( pagesDir , ` _app. ${ ext } ` ) )
return prev
} , [ ] as string [ ] )
: [ ] ) ,
2022-09-29 23:24:04 +02:00
'next/dist/pages/_app.js' ,
2021-08-19 10:12:12 +02:00
]
customAppAliases [ ` ${ PAGES_DIR_ALIAS } /_error ` ] = [
2022-09-03 02:13:47 +02:00
. . . ( pagesDir
2022-09-18 02:00:16 +02:00
? pageExtensions . reduce ( ( prev , ext ) = > {
2022-09-03 02:13:47 +02:00
prev . push ( path . join ( pagesDir , ` _error. ${ ext } ` ) )
return prev
} , [ ] as string [ ] )
: [ ] ) ,
2022-09-29 23:24:04 +02:00
'next/dist/pages/_error.js' ,
2021-08-19 10:12:12 +02:00
]
customDocumentAliases [ ` ${ PAGES_DIR_ALIAS } /_document ` ] = [
2022-09-03 02:13:47 +02:00
. . . ( pagesDir
2022-09-18 02:00:16 +02:00
? pageExtensions . reduce ( ( prev , ext ) = > {
2022-09-03 02:13:47 +02:00
prev . push ( path . join ( pagesDir , ` _document. ${ ext } ` ) )
return prev
} , [ ] as string [ ] )
: [ ] ) ,
2022-09-29 23:24:04 +02:00
'next/dist/pages/_document.js' ,
2021-08-19 10:12:12 +02:00
]
}
2022-07-05 11:16:14 +02:00
const mainFieldsPerCompiler : Record < typeof compilerType , string [ ] > = {
2022-08-12 15:01:19 +02:00
[ COMPILER_NAMES . server ] : [ 'main' , 'module' ] ,
[ COMPILER_NAMES . client ] : [ 'browser' , 'module' , 'main' ] ,
[ COMPILER_NAMES . edgeServer ] : [ 'browser' , 'module' , 'main' ] ,
2022-07-05 11:16:14 +02:00
}
2018-08-14 00:09:05 +02:00
const resolveConfig = {
2018-11-20 12:13:31 +01:00
// Disable .mjs for node_modules bundling
2022-04-27 11:50:29 +02:00
extensions : isNodeServer
2022-08-23 20:16:47 +02:00
? [ '.js' , '.mjs' , '.tsx' , '.ts' , '.jsx' , '.json' , '.wasm' ]
: [ '.mjs' , '.js' , '.tsx' , '.ts' , '.jsx' , '.json' , '.wasm' ] ,
2018-08-14 00:09:05 +02:00
modules : [
'node_modules' ,
2019-04-26 17:23:32 +02:00
. . . nodePathList , // Support for NODE_PATH environment variable
2018-08-14 00:09:05 +02:00
] ,
alias : {
2022-09-29 23:24:04 +02:00
// Alias next/dist imports to next/dist/esm assets,
// let this alias hit before `next` alias.
. . . ( isEdgeServer
? {
'next/dist/client' : 'next/dist/esm/client' ,
'next/dist/shared' : 'next/dist/esm/shared' ,
'next/dist/pages' : 'next/dist/esm/pages' ,
}
: undefined ) ,
2020-08-21 20:50:24 +02:00
next : NEXT_PROJECT_ROOT ,
2021-08-18 13:01:02 +02:00
2022-09-22 16:39:19 +02:00
react : reactDir ,
'react-dom$' : reactDomDir ,
'react-dom/server$' : ` ${ reactDomDir } /server ` ,
'react-dom/server.browser$' : ` ${ reactDomDir } /server.browser ` ,
'react-dom/client$' : ` ${ reactDomDir } /client ` ,
2022-09-21 20:45:33 +02:00
2022-08-12 19:08:38 +02:00
'styled-jsx/style$' : require . resolve ( ` styled-jsx/style ` ) ,
'styled-jsx$' : require . resolve ( ` styled-jsx ` ) ,
2022-07-02 01:57:45 +02:00
2021-08-19 10:12:12 +02:00
. . . customAppAliases ,
. . . customErrorAlias ,
. . . customDocumentAliases ,
2022-05-03 12:37:23 +02:00
. . . customRootAliases ,
2021-08-18 13:01:02 +02:00
2022-09-03 02:13:47 +02:00
. . . ( pagesDir ? { [ PAGES_DIR_ALIAS ] : pagesDir } : { } ) ,
2022-05-25 11:46:26 +02:00
. . . ( appDir
2022-05-03 12:37:23 +02:00
? {
2022-05-25 11:46:26 +02:00
[ APP_DIR_ALIAS ] : appDir ,
2022-05-03 12:37:23 +02:00
}
: { } ) ,
2022-05-19 17:46:21 +02:00
[ ROOT_DIR_ALIAS ] : dir ,
2019-04-15 11:26:23 +02:00
[ DOT_NEXT_ALIAS ] : distDir ,
2022-04-27 11:50:29 +02:00
. . . ( isClient || isEdgeServer ? getOptimizedAliases ( ) : { } ) ,
2020-06-28 13:23:29 +02:00
. . . getReactProfilingInProduction ( ) ,
2021-10-06 19:16:01 +02:00
2022-09-17 00:12:59 +02:00
[ RSC_MOD_REF_PROXY_ALIAS ] :
2022-09-18 02:00:16 +02:00
'next/dist/build/webpack/loaders/next-flight-loader/module-proxy' ,
2022-09-17 00:12:59 +02:00
2022-04-27 11:50:29 +02:00
. . . ( isClient || isEdgeServer
2021-10-06 19:16:01 +02:00
? {
[ clientResolveRewrites ] : hasRewrites
? clientResolveRewrites
: // With webpack 5 an alias can be pointed to false to noop
false ,
}
: { } ) ,
2021-11-05 22:51:10 +01:00
2022-06-30 00:47:44 +02:00
'@swc/helpers' : path . dirname (
require . resolve ( '@swc/helpers/package.json' )
) ,
2022-01-03 20:31:23 +01:00
setimmediate : 'next/dist/compiled/setimmediate' ,
2018-12-04 10:59:12 +01:00
} ,
2022-04-27 11:50:29 +02:00
. . . ( isClient || isEdgeServer
2021-02-24 17:25:57 +01:00
? {
fallback : {
2022-01-10 19:45:00 +01:00
process : require.resolve ( './polyfills/process' ) ,
2021-02-24 17:25:57 +01:00
} ,
}
: undefined ) ,
2022-07-05 11:16:14 +02:00
mainFields : mainFieldsPerCompiler [ compilerType ] ,
2021-10-06 17:40:01 +02:00
plugins : [ ] ,
2018-08-14 00:09:05 +02:00
}
2020-04-26 21:16:43 +02:00
const terserOptions : any = {
2019-06-11 19:34:45 +02:00
parse : {
ecma : 8 ,
} ,
compress : {
ecma : 5 ,
warnings : false ,
// The following two options are known to break valid JavaScript code
comparisons : false ,
2020-05-27 23:51:11 +02:00
inline : 2 , // https://github.com/vercel/next.js/issues/7178#issuecomment-493048965
2019-06-11 19:34:45 +02:00
} ,
2022-09-28 15:10:59 +02:00
mangle : {
safari10 : true ,
. . . ( process . env . __NEXT_MANGLING_DEBUG
? {
toplevel : true ,
module : true ,
keep_classnames : true ,
keep_fnames : true ,
}
: { } ) ,
} ,
2019-06-11 19:34:45 +02:00
output : {
ecma : 5 ,
safari10 : true ,
comments : false ,
// Fixes usage of Emoji and certain Regex
ascii_only : true ,
2022-09-28 15:10:59 +02:00
. . . ( process . env . __NEXT_MANGLING_DEBUG
? {
beautify : true ,
}
: { } ) ,
2019-06-11 19:34:45 +02:00
} ,
}
2021-11-09 14:42:23 +01:00
// Packages which will be split into the 'framework' chunk.
// Only top-level packages are included, e.g. nested copies like
// 'node_modules/meow/node_modules/object-assign' are not included.
2022-02-06 23:38:42 +01:00
const topLevelFrameworkPaths : string [ ] = [ ]
const visitedFrameworkPackages = new Set < string > ( )
// Adds package-paths of dependencies recursively
const addPackagePath = ( packageName : string , relativeToPath : string ) = > {
try {
if ( visitedFrameworkPackages . has ( packageName ) ) {
return
}
visitedFrameworkPackages . add ( packageName )
const packageJsonPath = require . resolve ( ` ${ packageName } /package.json ` , {
paths : [ relativeToPath ] ,
} )
// Include a trailing slash so that a `.startsWith(packagePath)` check avoids false positives
// when one package name starts with the full name of a different package.
// For example:
// "node_modules/react-slider".startsWith("node_modules/react") // true
// "node_modules/react-slider".startsWith("node_modules/react/") // false
const directory = path . join ( packageJsonPath , '../' )
// Returning from the function in case the directory has already been added and traversed
if ( topLevelFrameworkPaths . includes ( directory ) ) return
topLevelFrameworkPaths . push ( directory )
const dependencies = require ( packageJsonPath ) . dependencies || { }
for ( const name of Object . keys ( dependencies ) ) {
addPackagePath ( name , directory )
}
} catch ( _ ) {
// don't error on failing to resolve framework packages
}
}
for ( const packageName of [ 'react' , 'react-dom' ] ) {
addPackagePath ( packageName , dir )
}
2021-11-09 14:42:23 +01:00
2020-11-18 19:30:00 +01:00
const crossOrigin = config . crossOrigin
2021-07-10 18:49:02 +02:00
const looseEsmExternals = config . experimental ? . esmExternals === 'loose'
2021-01-17 20:02:20 +01:00
async function handleExternals (
context : string ,
request : string ,
2021-07-10 18:49:02 +02:00
dependencyType : string ,
2022-09-21 20:45:33 +02:00
layer : string | null ,
2021-05-07 18:32:33 +02:00
getResolve : (
options : any
2021-07-10 18:49:02 +02:00
) = > (
resolveContext : string ,
resolveRequest : string
) = > Promise < [ string | null , boolean ] >
2021-01-17 20:02:20 +01:00
) {
2020-08-03 14:26:23 +02:00
// We need to externalize internal requests for files intended to
// not be bundled.
const isLocal : boolean =
request . startsWith ( '.' ) ||
// Always check for unix-style path, as webpack sometimes
// normalizes as posix.
path . posix . isAbsolute ( request ) ||
// When on Windows, we also want to check for Windows-specific
// absolute paths.
( process . platform === 'win32' && path . win32 . isAbsolute ( request ) )
2022-04-05 17:57:45 +02:00
// make sure import "next" shows a warning when imported
// in pages/components
if ( request === 'next' ) {
return ` commonjs next/dist/lib/import-next-warning `
}
2022-09-27 10:18:06 +02:00
// Special internal modules that must be bundled for Server Components.
2022-09-21 20:45:33 +02:00
if ( layer === WEBPACK_LAYERS . server ) {
if (
2022-09-27 12:03:57 +02:00
request === 'react' ||
2022-09-28 16:19:40 +02:00
request === 'react/jsx-runtime' ||
2022-09-21 20:45:33 +02:00
request ===
2022-09-27 12:03:57 +02:00
'next/dist/compiled/react-server-dom-webpack/writer.browser.server'
2022-09-21 20:45:33 +02:00
) {
return
}
}
2020-08-03 14:26:23 +02:00
// Relative requires don't need custom resolution, because they
// are relative to requests we've already resolved here.
// Absolute requires (require('/foo')) are extremely uncommon, but
// also have no need for customization as they're already resolved.
2021-06-30 11:43:31 +02:00
if ( ! isLocal ) {
2022-09-21 20:45:33 +02:00
if ( /^(?:next$|react(?:$|\/))/ . test ( request ) ) {
2021-05-07 18:32:33 +02:00
return ` commonjs ${ request } `
}
2021-08-17 09:18:08 +02:00
const notExternalModules =
2022-09-17 00:12:59 +02:00
/^(?:private-next-pages\/|next\/(?:dist\/pages\/|(?:app|document|link|image|future\/image|constants|dynamic|script)$)|string-hash|private-next-rsc-mod-ref-proxy$)/
2021-05-07 18:32:33 +02:00
if ( notExternalModules . test ( request ) ) {
return
}
2020-08-03 14:26:23 +02:00
}
2019-03-07 17:40:08 +01:00
2022-06-30 00:47:44 +02:00
// @swc/helpers should not be external as it would
// require hoisting the package which we can't rely on
if ( request . includes ( '@swc/helpers' ) ) {
return
}
2021-07-10 18:49:02 +02:00
// When in esm externals mode, and using import, we resolve with
// ESM resolving options.
const isEsmRequested = dependencyType === 'esm'
2021-10-28 10:14:09 +02:00
const isLocalCallback = ( localRes : string ) = > {
2022-08-12 19:08:38 +02:00
// Makes sure dist/shared and dist/server are not bundled
2021-10-28 10:14:09 +02:00
// we need to process shared `router/router` and `dynamic`,
// so that the DefinePlugin can inject process.env values
const isNextExternal =
2022-08-12 19:08:38 +02:00
/next[/\\]dist[/\\](shared|server)[/\\](?!lib[/\\](router[/\\]router|dynamic))/ . test (
2021-10-28 10:14:09 +02:00
localRes
)
2021-10-27 15:53:44 +02:00
2021-10-28 10:14:09 +02:00
if ( isNextExternal ) {
// Generate Next.js external import
const externalRequest = path . posix . join (
'next' ,
'dist' ,
path
. relative (
// Root of Next.js package:
path . join ( __dirname , '..' ) ,
localRes
)
// Windows path normalization
. replace ( /\\/g , '/' )
2021-10-27 15:53:44 +02:00
)
2021-10-28 10:14:09 +02:00
return ` commonjs ${ externalRequest } `
} else {
// We don't want to retry local requests
// with other preferEsm options
return
2021-10-27 15:53:44 +02:00
}
2021-10-28 10:14:09 +02:00
}
2021-10-27 15:53:44 +02:00
2021-10-28 10:14:09 +02:00
const resolveResult = await resolveExternal (
dir ,
config . experimental . esmExternals ,
context ,
request ,
isEsmRequested ,
getResolve ,
isLocal ? isLocalCallback : undefined
)
2021-10-27 15:53:44 +02:00
2021-10-28 10:14:09 +02:00
if ( 'localRes' in resolveResult ) {
return resolveResult . localRes
2021-07-10 18:49:02 +02:00
}
2022-08-12 19:08:38 +02:00
// Forcedly resolve the styled-jsx installed by next.js,
// since `resolveExternal` cannot find the styled-jsx dep with pnpm
if ( request === 'styled-jsx/style' ) {
resolveResult . res = require . resolve ( request )
}
2021-10-28 10:14:09 +02:00
const { res , isEsm } = resolveResult
2021-07-10 18:49:02 +02:00
// If the request cannot be resolved we need to have
2020-08-03 14:26:23 +02:00
// webpack "bundle" it so it surfaces the not found error.
if ( ! res ) {
2021-01-17 20:02:20 +01:00
return
2020-08-03 14:26:23 +02:00
}
2020-04-14 09:50:39 +02:00
2021-07-10 18:49:02 +02:00
// ESM externals can only be imported (and not required).
// Make an exception in loose mode.
if ( ! isEsmRequested && isEsm && ! looseEsmExternals ) {
throw new Error (
` ESM packages ( ${ request } ) need to be imported. Use 'import' to reference the package instead. https://nextjs.org/docs/messages/import-esm-externals `
)
}
const externalType = isEsm ? 'module' : 'commonjs'
2021-05-28 13:17:08 +02:00
if (
2022-09-27 12:03:57 +02:00
/next[/\\]dist[/\\]shared[/\\](?!lib[/\\]router[/\\]router)/ . test ( res ) ||
/next[/\\]dist[/\\]compiled[/\\].*\.[mc]?js$/ . test ( res )
2021-05-28 13:17:08 +02:00
) {
2021-07-10 18:49:02 +02:00
return ` ${ externalType } ${ request } `
2021-05-28 13:17:08 +02:00
}
2020-08-03 14:26:23 +02:00
// Default pages have to be transpiled
if (
2022-09-27 12:03:57 +02:00
/[/\\]next[/\\]dist[/\\]/ . test ( res ) ||
2021-05-07 18:32:33 +02:00
// This is the @babel/plugin-transform-runtime "helpers: true" option
2022-09-27 12:03:57 +02:00
/node_modules[/\\]@babel[/\\]runtime[/\\]/ . test ( res )
2020-08-03 14:26:23 +02:00
) {
2021-01-17 20:02:20 +01:00
return
2020-08-03 14:26:23 +02:00
}
2019-03-07 17:40:08 +01:00
2020-08-03 14:26:23 +02:00
// Webpack itself has to be compiled because it doesn't always use module relative paths
if (
2022-09-27 12:03:57 +02:00
/node_modules[/\\]webpack/ . test ( res ) ||
/node_modules[/\\]css-loader/ . test ( res )
2020-08-03 14:26:23 +02:00
) {
2021-01-17 20:02:20 +01:00
return
2020-08-03 14:26:23 +02:00
}
2020-04-11 12:36:06 +02:00
2021-10-27 15:53:44 +02:00
if ( /node_modules[/\\].*\.[mc]?js$/ . test ( res ) ) {
2022-09-27 10:18:06 +02:00
if ( layer === WEBPACK_LAYERS . server ) {
// All packages should be bundled for the server layer if they're not opted out.
if (
2022-09-30 15:15:56 +02:00
isResourceInPackages (
res ,
config . experimental . serverComponentsExternalPackages
2022-09-21 20:45:33 +02:00
)
2022-09-27 10:18:06 +02:00
) {
return ` ${ externalType } ${ request } `
2022-09-21 20:45:33 +02:00
}
2022-09-27 10:18:06 +02:00
return
2022-09-21 20:45:33 +02:00
}
// Anything else that is standard JavaScript within `node_modules`
// can be externalized.
2021-07-10 18:49:02 +02:00
return ` ${ externalType } ${ request } `
2020-08-03 14:26:23 +02:00
}
// Default behavior: bundle the code!
}
2021-09-17 21:20:09 +02:00
const codeCondition = {
test : /\.(tsx|ts|js|cjs|mjs|jsx)$/ ,
. . . ( config . experimental . externalDir
? // Allowing importing TS/TSX files from outside of the root dir.
{ }
: { include : [ dir , . . . babelIncludeRegexes ] } ) ,
exclude : ( excludePath : string ) = > {
if ( babelIncludeRegexes . some ( ( r ) = > r . test ( excludePath ) ) ) {
return false
}
2022-09-27 12:03:57 +02:00
return excludePath . includes ( 'node_modules' )
2021-09-17 21:20:09 +02:00
} ,
}
2022-09-27 20:10:05 +02:00
const fontLoaderTargets =
config . experimental . fontLoaders &&
Object . keys ( config . experimental . fontLoaders ) . map ( ( fontLoader ) = > {
const resolved = require . resolve ( fontLoader )
return path . join ( resolved , '../target.css' )
} )
2020-08-03 14:26:23 +02:00
let webpackConfig : webpack.Configuration = {
2021-08-18 14:22:53 +02:00
parallelism : Number ( process . env . NEXT_WEBPACK_PARALLELISM ) || undefined ,
2022-05-13 19:48:53 +02:00
// @ts-ignore
2022-04-27 11:50:29 +02:00
externals :
isClient || isEdgeServer
? // make sure importing "next" is handled gracefully for client
// bundles in case a user imported types and it wasn't removed
// TODO: should we warn/error for this instead?
[
'next' ,
. . . ( isEdgeServer
? [
{
'@builder.io/partytown' : '{}' ,
'next/dist/compiled/etag' : '{}' ,
'next/dist/compiled/chalk' : '{}' ,
2022-09-29 10:56:28 +02:00
'./cjs/react-dom-server-legacy.browser.production.min.js' :
'{}' ,
'./cjs/react-dom-server-legacy.browser.development.js' :
'{}' ,
2022-04-27 11:50:29 +02:00
'react-dom' : '{}' ,
} ,
feat: build edge functions with node.js modules and fail at runtime (#38234)
## What's in there?
The Edge runtime [does not support Node.js modules](https://edge-runtime.vercel.app/features/available-apis#unsupported-apis).
When building Next.js application, we currently fail the build when detecting node.js module imported from middleware.
This is an blocker for using code that is conditionally loading node.js modules (based on platform/env detection), as @cramforce reported.
This PR implements a new strategy where:
- we can build such middleware/Edge API route code **with a warning**
- we fail at run time, with graceful errors in dev (console & react-dev-overlay error)
- we fail at run time, with console errors in production
## How to test?
All cases are covered with integration tests.
To try them live, create a simple app with a page, a `middleware.js` file and a `pages/api/route.js`file.
Here are iconic examples:
### node.js modules
```js
// middleware.js
import { NextResponse } from 'next/server'
// static
import { basename } from 'path'
export default async function middleware() {
// dynamic
const { basename } = await import('path')
basename()
return NextResponse.next()
}
export const config = { matcher: '/' }
```
```js
// pags/api/route.js
// static
import { isAbsolute } from 'path'
export default async function handle() {
// dynamic
const { isAbsolute } = await import('path')
return Response.json({ useNodeModule: isAbsolute('/test') })
}
export const config = { runtime: 'experimental-edge' }
```
Desired error (+ source code highlight in dev):
> The edge runtime does not support Node.js 'path' module
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime
Desired warning at build time:
> A Node.js module is loaded ('path' at line 2) which is not supported in the Edge Runtime.
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime
- [x] in dev middleware, static, shows desired error on stderr
- [x] in dev route, static, shows desired error on stderr
- [x] in dev middleware, dynamic, shows desired error on stderr
- [x] in dev route, dynamic, shows desired error on stderr
- [x] in dev middleware, static, shows desired error on react error overlay
- [x] in dev route, static, shows desired error on react error overlay
- [x] in dev middleware, dynamic, shows desired error on react error overlay
- [x] in dev route, dynamic, shows desired error on react error overlay
- [x] builds middleware successfully, shows build warning, shows desired error on stderr on call
- [x] builds route successfully, shows build warning, shows desired error on stderr on call
### 3rd party modules not found
```js
// middleware.js
import { NextResponse } from 'next/server'
// static
import Unknown from 'unknown'
export default async function middleware() {
// dynamic
const Unknown = await import('unknown')
new Unknown()
return NextResponse.next()
}
```
export const config = { matcher: '/' }
```
```js
// pags/api/route.js
// static
import Unknown from 'unknown'
export default async function handle() {
// dynamic
const Unknown = await import('unknown')
return Response.json({ use3rdPartyModule: Unknown() })
}
export const config = { runtime: 'experimental-edge' }
```
Desired error (+ source code highlight in dev):
> Module not found: Can't resolve 'does-not-exist'
Learn More: https://nextjs.org/docs/messages/module-not-found
- [x] in dev middleware, static, shows desired error on stderr
- [x] in dev route, static, shows desired error on stderr
- [x] in dev middleware, dynamic, shows desired error on stderr
- [x] in dev route, dynamic, shows desired error on stderr
- [x] in dev middleware, static, shows desired error on react error overlay
- [x] in dev route, static, shows desired error on react error overlay
- [x] in dev middleware, dynamic, shows desired error on react error overlay
- [x] in dev route, dynamic, shows desired error on react error overlay
- [x] fails to build middleware, with desired error on stderr
- [x] fails to build route, with desired error on stderr
### unused node.js modules
```js
// middleware.js
import { NextResponse } from 'next/server'
export default async function middleware() {
if (process.exit) {
const { basename } = await import('path')
basename()
}
return NextResponse.next()
}
```
```js
// pags/api/route.js
export default async function handle() {
if (process.exit) {
const { basename } = await import('path')
basename()
}
return Response.json({ useNodeModule: false })
}
export const config = { runtime: 'experimental-edge' }
```
Desired warning at build time:
> A Node.js module is loaded ('path' at line 2) which is not supported in the Edge Runtime.
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime
- [x] invoke middleware in dev with no error
- [x] invoke route in dev with no error
- [x] builds successfully, shows build warning, invoke middleware with no error
- [x] builds successfully, shows build warning, invoke api-route with no error
## Notes to reviewers
The strategy to implement this feature is to leverages webpack [externals](https://webpack.js.org/configuration/externals/#externals) and run a global `__unsupported_module()` function when using a node.js module from edge function's code.
For the record, I tried using [webpack resolve.fallback](https://webpack.js.org/configuration/resolve/#resolvefallback) and [Webpack.IgnorePlugin](https://webpack.js.org/plugins/ignore-plugin/) but they do not allow throwing proper errors at runtime that would contain the loaded module name for reporting.
`__unsupported_module()` is defined in `EdgeRuntime`, and returns a proxy that's throw on use (whether it's property access, function call, new operator... synchronous & promise-based styles).
However there's an issue with error reporting: webpack does not includes the import lines in the generated sourcemaps, preventing from displaying useful errors.
I extended our middleware-plugin to supplement the sourcemaps (when analyzing edge function code, it saves which module is imported from which file, together with line/column/source)
The react-dev-overlay was adapted to look for this additional information when the caught error relates to modules, instead of looking at sourcemaps.
I removed the previous mechanism (built by @nkzawa ) which caught webpack errors at built time to change the displayed error message (files `next/build/index.js`, `next/build/utils.ts` and `wellknown-errors-plugin`)
2022-07-06 22:54:44 +02:00
handleWebpackExtenalForEdgeRuntime ,
2022-04-27 11:50:29 +02:00
]
: [ ] ) ,
]
: target !== 'serverless'
? [
( {
context ,
request ,
dependencyType ,
2022-09-21 20:45:33 +02:00
contextInfo ,
2022-04-27 11:50:29 +02:00
getResolve ,
} : {
context : string
request : string
dependencyType : string
2022-09-21 20:45:33 +02:00
contextInfo : {
issuer : string
issuerLayer : string | null
compiler : string
}
2022-04-27 11:50:29 +02:00
getResolve : (
options : any
) = > (
resolveContext : string ,
resolveRequest : string ,
callback : (
err? : Error ,
result? : string ,
resolveData ? : { descriptionFileData ? : { type ? : any } }
) = > void
2021-10-06 17:40:01 +02:00
) = > void
2022-04-27 11:50:29 +02:00
} ) = >
2022-09-21 20:45:33 +02:00
handleExternals (
context ,
request ,
dependencyType ,
contextInfo . issuerLayer ,
( options ) = > {
const resolveFunction = getResolve ( options )
return ( resolveContext : string , requestToResolve : string ) = >
new Promise ( ( resolve , reject ) = > {
resolveFunction (
resolveContext ,
requestToResolve ,
( err , result , resolveData ) = > {
if ( err ) return reject ( err )
if ( ! result ) return resolve ( [ null , false ] )
const isEsm = /\.js$/i . test ( result )
? resolveData ? . descriptionFileData ? . type ===
'module'
: /\.mjs$/i . test ( result )
resolve ( [ result , isEsm ] )
}
)
} )
}
) ,
2022-04-27 11:50:29 +02:00
]
: [
// When the 'serverless' target is used all node_modules will be compiled into the output bundles
// So that the 'serverless' bundles have 0 runtime dependencies
'next/dist/compiled/@ampproject/toolbox-optimizer' , // except this one
// Mark this as external if not enabled so it doesn't cause a
// webpack error from being missing
. . . ( config . experimental . optimizeCss ? [ ] : [ 'critters' ] ) ,
] ,
2019-08-20 17:07:57 +02:00
optimization : {
2021-10-06 17:40:01 +02:00
// @ts-ignore: TODO remove ts-ignore when webpack 4 is removed
emitOnErrors : ! dev ,
2019-08-20 17:07:57 +02:00
checkWasmTypes : false ,
nodeEnv : false ,
2022-08-16 11:55:37 +02:00
splitChunks : ( ( ) :
| Required < webpack.Configuration > [ 'optimization' ] [ 'splitChunks' ]
| false = > {
2022-05-13 19:48:53 +02:00
// For the edge runtime, we have to bundle all dependencies inside without dynamic `require`s.
// To make some dependencies like `react` to be shared between entrypoints, we use a special
// cache group here even under dev mode.
const edgeRSCCacheGroups = hasServerComponents
? {
rscDeps : {
enforce : true ,
name : 'rsc-runtime-deps' ,
filename : 'rsc-runtime-deps.js' ,
2022-05-30 21:32:26 +02:00
test : rscSharedRegex ,
2022-05-13 19:48:53 +02:00
} ,
}
: undefined
if ( isEdgeServer && edgeRSCCacheGroups ) {
return {
cacheGroups : edgeRSCCacheGroups ,
}
}
2022-04-27 11:50:29 +02:00
if ( dev ) {
return false
}
if ( isNodeServer ) {
return {
// @ts-ignore
filename : '[name].js' ,
chunks : 'all' ,
minSize : 1000 ,
}
}
if ( isEdgeServer ) {
return {
// @ts-ignore
filename : 'edge-chunks/[name].js' ,
chunks : 'all' ,
minChunks : 2 ,
2022-05-13 19:48:53 +02:00
cacheGroups : edgeRSCCacheGroups ,
2022-04-27 11:50:29 +02:00
}
}
return {
// Keep main and _app chunks unsplitted in webpack 5
// as we don't need a separate vendor chunk from that
// and all other chunk depend on them so there is no
// duplication that need to be pulled out.
2022-08-16 11:55:37 +02:00
chunks : ( chunk : any ) = >
! /^(polyfills|main|pages\/_app)$/ . test ( chunk . name ) ,
2022-04-27 11:50:29 +02:00
cacheGroups : {
framework : {
chunks : 'all' ,
name : 'framework' ,
2022-08-16 11:55:37 +02:00
test ( module : any ) {
2022-04-27 11:50:29 +02:00
const resource = module . nameForCondition ? . ( )
return resource
? topLevelFrameworkPaths . some ( ( pkgPath ) = >
resource . startsWith ( pkgPath )
)
: false
} ,
priority : 40 ,
// Don't let webpack eliminate this chunk (prevents this chunk from
// becoming a part of the commons chunk)
enforce : true ,
} ,
lib : {
test ( module : {
size : Function
nameForCondition : Function
} ) : boolean {
return (
module . size ( ) > 160000 &&
/node_modules[/\\]/ . test ( module . nameForCondition ( ) || '' )
)
} ,
name ( module : {
type : string
libIdent? : Function
updateHash : ( hash : crypto.Hash ) = > void
} ) : string {
const hash = crypto . createHash ( 'sha1' )
if ( isModuleCSS ( module ) ) {
module . updateHash ( hash )
} else {
if ( ! module . libIdent ) {
throw new Error (
` Encountered unknown module type: ${ module . type } . Please open an issue. `
)
}
hash . update ( module . libIdent ( { context : dir } ) )
}
return hash . digest ( 'hex' ) . substring ( 0 , 8 )
} ,
priority : 30 ,
minChunks : 1 ,
reuseExistingChunk : true ,
} ,
} ,
maxInitialRequests : 25 ,
minSize : 20000 ,
}
} ) ( ) ,
runtimeChunk : isClient
? { name : CLIENT_STATIC_FILES_RUNTIME_WEBPACK }
: undefined ,
2022-09-22 02:18:18 +02:00
minimize : ! dev && ( isClient || isEdgeServer ) ,
2019-08-20 17:07:57 +02:00
minimizer : [
2019-09-17 22:05:20 +02:00
// Minify JavaScript
2021-04-18 12:28:09 +02:00
( compiler : webpack.Compiler ) = > {
// @ts-ignore No typings yet
const {
TerserPlugin ,
} = require ( './webpack/plugins/terser-webpack-plugin/src/index.js' )
new TerserPlugin ( {
cacheDir : path.join ( distDir , 'cache' , 'next-minifier' ) ,
parallel : config.experimental.cpus ,
2021-10-25 13:49:11 +02:00
swcMinify : config.swcMinify ,
2022-05-29 05:53:57 +02:00
terserOptions : {
. . . terserOptions ,
compress : {
. . . terserOptions . compress ,
. . . ( config . experimental . swcMinifyDebugOptions ? . compress ? ? { } ) ,
} ,
mangle : {
. . . terserOptions . mangle ,
. . . ( config . experimental . swcMinifyDebugOptions ? . mangle ? ? { } ) ,
} ,
} ,
2021-04-18 12:28:09 +02:00
} ) . apply ( compiler )
} ,
2019-09-17 22:05:20 +02:00
// Minify CSS
2021-04-18 12:28:09 +02:00
( compiler : webpack.Compiler ) = > {
const {
CssMinimizerPlugin ,
} = require ( './webpack/plugins/css-minimizer-plugin' )
new CssMinimizerPlugin ( {
postcssOptions : {
map : {
// `inline: false` generates the source map in a separate file.
// Otherwise, the CSS file is needlessly large.
inline : false ,
// `annotation: false` skips appending the `sourceMappingURL`
// to the end of the CSS file. Webpack already handles this.
annotation : false ,
} ,
2019-09-17 22:05:20 +02:00
} ,
2021-04-18 12:28:09 +02:00
} ) . apply ( compiler )
} ,
2020-04-25 21:00:41 +02:00
] ,
2019-08-20 17:07:57 +02:00
} ,
2016-10-14 17:05:08 +02:00
context : dir ,
2018-03-31 14:00:56 +02:00
// Kept as function to be backwards compatible
2021-05-03 12:25:44 +02:00
// @ts-ignore TODO webpack 5 typings needed
2018-01-30 16:40:52 +01:00
entry : async ( ) = > {
return {
2019-04-26 17:23:32 +02:00
. . . ( clientEntries ? clientEntries : { } ) ,
. . . entrypoints ,
2018-01-30 16:40:52 +01:00
}
} ,
2021-11-25 09:31:20 +01:00
watchOptions ,
2016-10-14 17:05:08 +02:00
output : {
2021-05-29 14:15:26 +02:00
// we must set publicPath to an empty value to override the default of
// auto which doesn't work in IE11
2021-06-18 19:12:20 +02:00
publicPath : ` ${ config . assetPrefix || '' } /_next/ ` ,
2022-04-27 11:50:29 +02:00
path : ! dev && isNodeServer ? path . join ( outputPath , 'chunks' ) : outputPath ,
2021-05-12 18:33:51 +02:00
// On the server we don't use hashes
2022-04-27 11:50:29 +02:00
filename :
isNodeServer || isEdgeServer
? dev || isEdgeServer
? ` [name].js `
: ` ../[name].js `
: ` static/chunks/ ${ isDevFallback ? 'fallback/' : '' } [name] ${
2022-05-25 11:46:26 +02:00
dev ? '' : appDir ? '-[chunkhash]' : '-[contenthash]'
2022-04-27 11:50:29 +02:00
} . js ` ,
library : isClient || isEdgeServer ? '_N_E' : undefined ,
libraryTarget : isClient || isEdgeServer ? 'assign' : 'commonjs2' ,
2021-10-06 17:40:01 +02:00
hotUpdateChunkFilename : 'static/webpack/[id].[fullhash].hot-update.js' ,
hotUpdateMainFilename :
'static/webpack/[fullhash].[runtime].hot-update.json' ,
2018-07-24 11:24:40 +02:00
// This saves chunks with the name given via `import()`
2022-04-27 11:50:29 +02:00
chunkFilename :
isNodeServer || isEdgeServer
? '[name].js'
: ` static/chunks/ ${ isDevFallback ? 'fallback/' : '' } ${
dev ? '[name]' : '[name].[contenthash]'
} . js ` ,
2018-10-02 13:10:07 +02:00
strictModuleExceptionHandling : true ,
2019-07-25 04:16:32 +02:00
crossOriginLoading : crossOrigin ,
2019-04-26 17:23:32 +02:00
webassemblyModuleFilename : 'static/wasm/[modulehash].wasm' ,
2021-10-20 23:39:55 +02:00
hashFunction : 'xxhash64' ,
hashDigestLength : 16 ,
2016-10-14 17:05:08 +02:00
} ,
2019-04-09 19:43:24 +02:00
performance : false ,
2018-08-14 00:09:05 +02:00
resolve : resolveConfig ,
2016-10-14 17:05:08 +02:00
resolveLoader : {
2019-08-27 22:37:47 +02:00
// The loaders Next.js provides
alias : [
2019-10-04 17:55:18 +02:00
'error-loader' ,
2021-08-06 16:07:36 +02:00
'next-swc-loader' ,
2019-08-27 22:37:47 +02:00
'next-client-pages-loader' ,
2021-06-04 10:06:00 +02:00
'next-image-loader' ,
2019-08-27 22:37:47 +02:00
'next-serverless-loader' ,
2021-01-20 06:25:46 +01:00
'next-style-loader' ,
2022-09-18 02:00:16 +02:00
'next-flight-loader' ,
2022-05-13 19:48:53 +02:00
'next-flight-client-entry-loader' ,
2021-10-07 01:46:46 +02:00
'noop-loader' ,
2021-10-20 19:52:11 +02:00
'next-middleware-loader' ,
2022-06-13 20:17:44 +02:00
'next-edge-function-loader' ,
2022-06-26 23:01:26 +02:00
'next-edge-ssr-loader' ,
2022-07-19 19:27:15 +02:00
'next-middleware-asset-loader' ,
2022-03-02 16:09:36 +01:00
'next-middleware-wasm-loader' ,
2022-05-25 11:46:26 +02:00
'next-app-loader' ,
2022-09-22 07:12:59 +02:00
'next-font-loader' ,
2019-11-11 04:24:53 +01:00
] . reduce ( ( alias , loader ) = > {
// using multiple aliases to replace `resolveLoader.modules`
alias [ loader ] = path . join ( __dirname , 'webpack' , 'loaders' , loader )
2019-08-27 22:37:47 +02:00
2019-11-11 04:24:53 +01:00
return alias
} , { } as Record < string , string > ) ,
2016-12-28 19:16:52 +01:00
modules : [
2019-04-08 14:57:21 +02:00
'node_modules' ,
2019-04-26 17:23:32 +02:00
. . . nodePathList , // Support for NODE_PATH environment variable
] ,
2021-10-06 17:40:01 +02:00
plugins : [ ] ,
2016-10-14 17:05:08 +02:00
} ,
module : {
2018-01-03 13:43:48 +01:00
rules : [
2022-09-27 10:18:06 +02:00
. . . ( config . experimental . appDir && ! isClient && ! isEdgeServer
? [
{
issuerLayer : WEBPACK_LAYERS.server ,
test : ( req : string ) = > {
2022-09-30 15:15:56 +02:00
// If it's not a source code file, or has been opted out of
// bundling, don't resolve it.
2022-09-27 10:18:06 +02:00
if (
2022-09-28 16:19:40 +02:00
! codeCondition . test . test ( req ) ||
2022-09-30 15:15:56 +02:00
isResourceInPackages (
req ,
config . experimental . serverComponentsExternalPackages
2022-09-27 10:18:06 +02:00
)
) {
return false
}
2022-09-30 15:15:56 +02:00
2022-09-27 10:18:06 +02:00
return true
} ,
resolve : process.env.__NEXT_REACT_CHANNEL
? {
conditionNames : [ 'react-server' , 'node' , 'require' ] ,
alias : {
react : ` react- ${ process . env . __NEXT_REACT_CHANNEL } ` ,
'react-dom' : ` react-dom- ${ process . env . __NEXT_REACT_CHANNEL } ` ,
} ,
}
: {
conditionNames : [ 'react-server' , 'node' , 'require' ] ,
alias : {
// If missing the alias override here, the default alias will be used which aliases
// react to the direct file path, not the package name. In that case the condition
// will be ignored completely.
react : 'react' ,
'react-dom' : 'react-dom' ,
} ,
} ,
} ,
]
: [ ] ) ,
2021-10-06 17:40:01 +02:00
// TODO: FIXME: do NOT webpack 5 support with this
// x-ref: https://github.com/webpack/webpack/issues/11467
2021-10-13 17:23:50 +02:00
. . . ( ! config . experimental . fullySpecified
? [
{
test : /\.m?js/ ,
resolve : {
fullySpecified : false ,
} ,
} as any ,
]
: [ ] ) ,
2022-09-19 15:50:03 +02:00
. . . ( hasServerComponents && ( isNodeServer || isEdgeServer )
? [
// RSC server compilation loaders
{
test : codeCondition.test ,
include : [
dir ,
// To let the internal client components passing through flight loader
/next[\\/]dist/ ,
] ,
issuerLayer : WEBPACK_LAYERS.server ,
use : {
loader : 'next-flight-loader' ,
2021-10-26 18:50:56 +02:00
} ,
2022-09-19 15:50:03 +02:00
} ,
]
2022-05-13 19:48:53 +02:00
: [ ] ) ,
. . . ( hasServerComponents && isEdgeServer
? [
// Move shared dependencies from sc_server and sc_client into the
// same layer.
{
2022-05-30 21:32:26 +02:00
test : rscSharedRegex ,
2022-08-12 15:01:19 +02:00
layer : WEBPACK_LAYERS.rscShared ,
2022-05-13 19:48:53 +02:00
} ,
]
2021-10-26 18:50:56 +02:00
: [ ] ) ,
2021-10-06 17:40:01 +02:00
{
test : /\.(js|cjs|mjs)$/ ,
2022-08-12 15:01:19 +02:00
issuerLayer : WEBPACK_LAYERS.api ,
2021-10-06 17:40:01 +02:00
parser : {
// Switch back to normal URL handling
url : true ,
} ,
} ,
{
oneOf : [
{
. . . codeCondition ,
2022-08-12 15:01:19 +02:00
issuerLayer : WEBPACK_LAYERS.api ,
2021-10-06 17:40:01 +02:00
parser : {
// Switch back to normal URL handling
url : true ,
} ,
use : defaultLoaders.babel ,
} ,
2021-10-20 19:52:11 +02:00
{
. . . codeCondition ,
2022-08-12 15:01:19 +02:00
issuerLayer : WEBPACK_LAYERS.middleware ,
2022-04-27 11:50:29 +02:00
use : getBabelOrSwcLoader ( ) ,
2021-10-20 19:52:11 +02:00
} ,
2022-09-17 00:12:59 +02:00
. . . ( hasServerComponents
? [
{
test : codeCondition.test ,
issuerLayer : WEBPACK_LAYERS.server ,
2022-09-27 11:31:25 +02:00
use : useSWCLoader
? getSwcLoader ( { isServerLayer : true } )
: // When using Babel, we will have to add the SWC loader
// as an additional pass to handle RSC correctly.
// This will cause some performance overhead but
// acceptable as Babel will not be recommended.
[
getSwcLoader ( { isServerLayer : true } ) ,
getBabelLoader ( ) ,
] ,
2022-09-17 00:12:59 +02:00
} ,
]
: [ ] ) ,
2021-10-06 17:40:01 +02:00
{
. . . codeCondition ,
2022-04-27 11:50:29 +02:00
use :
dev && isClient
? [
require . resolve (
2022-05-31 02:05:27 +02:00
'next/dist/compiled/@next/react-refresh-utils/dist/loader'
2022-04-27 11:50:29 +02:00
) ,
defaultLoaders . babel ,
]
: defaultLoaders . babel ,
2021-10-06 17:40:01 +02:00
} ,
] ,
2019-04-21 23:25:17 +02:00
} ,
2021-10-06 17:40:01 +02:00
. . . ( ! config . images . disableStaticImages
2021-06-24 00:56:18 +02:00
? [
{
2021-10-18 19:01:02 +02:00
test : nextImageLoaderRegex ,
2021-06-24 00:56:18 +02:00
loader : 'next-image-loader' ,
issuer : { not : regexLikeCss } ,
dependency : { not : [ 'url' ] } ,
2021-07-02 13:27:32 +02:00
options : {
2022-04-27 11:50:29 +02:00
isServer : isNodeServer || isEdgeServer ,
2021-07-10 22:27:14 +02:00
isDev : dev ,
2021-09-27 23:13:23 +02:00
basePath : config.basePath ,
2021-07-10 22:27:14 +02:00
assetPrefix : config.assetPrefix ,
2021-07-02 13:27:32 +02:00
} ,
2021-06-24 00:56:18 +02:00
} ,
]
: [ ] ) ,
2022-04-27 11:50:29 +02:00
. . . ( isEdgeServer || isClient
2022-04-25 11:25:27 +02:00
? [
{
oneOf : [
{
2022-08-12 15:01:19 +02:00
issuerLayer : WEBPACK_LAYERS.middleware ,
2022-04-25 11:25:27 +02:00
resolve : {
fallback : {
process : require.resolve ( './polyfills/process' ) ,
} ,
} ,
} ,
{
resolve : {
feat: add `experimental.fallbackNodePolyfills` flag (#39248)
For historical reasons, Next.js has been falling back to polyfill certain Node.js APIs in the browser. `webpack` itself stopped doing it, and so should Next.js. This might unexpectedly break some packages in the ecosystem though, so it is being introduced as an experimental flag. These imports will now throw a `Module not found` error and the package maintainer should make sure that the library isn't relying on these Node.js APIs when the package is meant for browser usage.
Let's take a look at a common example, the `crypto` API, which can be imported as `import crypto from "crypto"` but [should already be available in browsers](https://caniuse.com/cryptography). Until now, Next.js has fallen back to use a polyfilled version for the import, which resulted in a bundle-size increase.
```js
import crypto from 'crypto'
import { useEffect } from 'react'
export default function Page() {
useEffect(() => {
console.log(crypto)
}, [])
}
```
it imports `crypto`, which currently resolves to [`crypto-browserify`](https://www.npmjs.com/package/crypto-browserify).
So the bundle will include `crypto-browserify` as well:
```sh
Page Size First Load JS
┌ ○ / 131 kB 213 kB # <--
└ ○ /404 194 B 82.2 kB
+ First Load JS shared by all 82 kB
├ chunks/framework-bcc2dc0ea27ab0c6.js 45.1 kB
├ chunks/main-dc2421aef72299b4.js 35.4 kB
├ chunks/pages/_app-a85935458980c5c2.js 708 B
└ chunks/webpack-9b312e20a4e32339.js 836 B
```
Here, we can just remove the import, as we are [safely accessing](https://nextjs.org/docs/migrating/from-create-react-app#safely-accessing-web-apis) the [Crypto Web API](https://caniuse.com/cryptography):
```diff
- import crypto from 'crypto'
import { useEffect } from 'react'
export default function Page() {
useEffect(() => {
console.log(crypto)
}, [])
}
```
Which will reduce the bundle size:
```sh
Page Size First Load JS
┌ ○ / 269 B 82.2 kB # <--
└ ○ /404 194 B 82.1 kB
+ First Load JS shared by all 81.9 kB
├ chunks/framework-bcc2dc0ea27ab0c6.js 45.1 kB
├ chunks/main-dc2421aef72299b4.js 35.4 kB
├ chunks/pages/_app-a85935458980c5c2.js 708 B
└ chunks/webpack-fd82975a6094609f.js 727 B
```
This is harder to detect if the `crypto` import is in a third-party package though. By setting `experimental: { fallbackNodePolyfills: false }`, Next.js will now fail at build-time and should show where the unnecessary import comes from, so the developer can reach out to the package maintainer to fix this issue.
Note: There might be differences between the living standard and some of these older polyfills, so you have to make sure your code works well without the polyfilled version.
Related feedback: https://twitter.com/lfredolo/status/1539608666026000384
2022-08-27 01:11:57 +02:00
fallback :
config . experimental . fallbackNodePolyfills === false
2022-09-16 22:23:56 +02:00
? {
assert : false ,
buffer : false ,
constants : false ,
crypto : false ,
domain : false ,
http : false ,
https : false ,
os : false ,
path : false ,
punycode : false ,
process : false ,
querystring : false ,
stream : false ,
string_decoder : false ,
sys : false ,
timers : false ,
tty : false ,
util : false ,
vm : false ,
zlib : false ,
events : false ,
setImmediate : false ,
}
feat: add `experimental.fallbackNodePolyfills` flag (#39248)
For historical reasons, Next.js has been falling back to polyfill certain Node.js APIs in the browser. `webpack` itself stopped doing it, and so should Next.js. This might unexpectedly break some packages in the ecosystem though, so it is being introduced as an experimental flag. These imports will now throw a `Module not found` error and the package maintainer should make sure that the library isn't relying on these Node.js APIs when the package is meant for browser usage.
Let's take a look at a common example, the `crypto` API, which can be imported as `import crypto from "crypto"` but [should already be available in browsers](https://caniuse.com/cryptography). Until now, Next.js has fallen back to use a polyfilled version for the import, which resulted in a bundle-size increase.
```js
import crypto from 'crypto'
import { useEffect } from 'react'
export default function Page() {
useEffect(() => {
console.log(crypto)
}, [])
}
```
it imports `crypto`, which currently resolves to [`crypto-browserify`](https://www.npmjs.com/package/crypto-browserify).
So the bundle will include `crypto-browserify` as well:
```sh
Page Size First Load JS
┌ ○ / 131 kB 213 kB # <--
└ ○ /404 194 B 82.2 kB
+ First Load JS shared by all 82 kB
├ chunks/framework-bcc2dc0ea27ab0c6.js 45.1 kB
├ chunks/main-dc2421aef72299b4.js 35.4 kB
├ chunks/pages/_app-a85935458980c5c2.js 708 B
└ chunks/webpack-9b312e20a4e32339.js 836 B
```
Here, we can just remove the import, as we are [safely accessing](https://nextjs.org/docs/migrating/from-create-react-app#safely-accessing-web-apis) the [Crypto Web API](https://caniuse.com/cryptography):
```diff
- import crypto from 'crypto'
import { useEffect } from 'react'
export default function Page() {
useEffect(() => {
console.log(crypto)
}, [])
}
```
Which will reduce the bundle size:
```sh
Page Size First Load JS
┌ ○ / 269 B 82.2 kB # <--
└ ○ /404 194 B 82.1 kB
+ First Load JS shared by all 81.9 kB
├ chunks/framework-bcc2dc0ea27ab0c6.js 45.1 kB
├ chunks/main-dc2421aef72299b4.js 35.4 kB
├ chunks/pages/_app-a85935458980c5c2.js 708 B
└ chunks/webpack-fd82975a6094609f.js 727 B
```
This is harder to detect if the `crypto` import is in a third-party package though. By setting `experimental: { fallbackNodePolyfills: false }`, Next.js will now fail at build-time and should show where the unnecessary import comes from, so the developer can reach out to the package maintainer to fix this issue.
Note: There might be differences between the living standard and some of these older polyfills, so you have to make sure your code works well without the polyfilled version.
Related feedback: https://twitter.com/lfredolo/status/1539608666026000384
2022-08-27 01:11:57 +02:00
: {
assert : require.resolve (
'next/dist/compiled/assert'
) ,
buffer : require.resolve (
'next/dist/compiled/buffer/'
) ,
constants : require.resolve (
'next/dist/compiled/constants-browserify'
) ,
crypto : require.resolve (
'next/dist/compiled/crypto-browserify'
) ,
domain : require.resolve (
'next/dist/compiled/domain-browser'
) ,
http : require.resolve (
'next/dist/compiled/stream-http'
) ,
https : require.resolve (
'next/dist/compiled/https-browserify'
) ,
os : require.resolve (
'next/dist/compiled/os-browserify'
) ,
path : require.resolve (
'next/dist/compiled/path-browserify'
) ,
punycode : require.resolve (
'next/dist/compiled/punycode'
) ,
process : require.resolve ( './polyfills/process' ) ,
// Handled in separate alias
querystring : require.resolve (
'next/dist/compiled/querystring-es3'
) ,
stream : require.resolve (
'next/dist/compiled/stream-browserify'
) ,
string_decoder : require.resolve (
'next/dist/compiled/string_decoder'
) ,
sys : require.resolve ( 'next/dist/compiled/util/' ) ,
timers : require.resolve (
'next/dist/compiled/timers-browserify'
) ,
tty : require.resolve (
'next/dist/compiled/tty-browserify'
) ,
// Handled in separate alias
// url: require.resolve('url/'),
util : require.resolve ( 'next/dist/compiled/util/' ) ,
vm : require.resolve (
'next/dist/compiled/vm-browserify'
) ,
zlib : require.resolve (
'next/dist/compiled/browserify-zlib'
) ,
events : require.resolve (
'next/dist/compiled/events/'
) ,
setImmediate : require.resolve (
'next/dist/compiled/setimmediate'
) ,
} ,
2022-04-25 11:25:27 +02:00
} ,
} ,
] ,
} ,
]
: [ ] ) ,
2019-04-26 17:23:32 +02:00
] . filter ( Boolean ) ,
2016-10-16 06:01:17 +02:00
} ,
2018-01-30 16:40:52 +01:00
plugins : [
2022-04-27 11:50:29 +02:00
dev && isClient && new ReactRefreshWebpackPlugin ( webpack ) ,
2021-10-26 18:50:56 +02:00
// Makes sure `Buffer` and `process` are polyfilled in client and flight bundles (same behavior as webpack 4)
2022-04-27 11:50:29 +02:00
( isClient || isEdgeServer ) &&
2021-01-11 13:31:31 +01:00
new webpack . ProvidePlugin ( {
2021-11-10 19:57:27 +01:00
// Buffer is used by getInlineScriptSource
2021-01-11 13:31:31 +01:00
Buffer : [ require . resolve ( 'buffer' ) , 'Buffer' ] ,
2021-11-10 19:57:27 +01:00
// Avoid process being overridden when in web run time
2022-04-27 11:50:29 +02:00
. . . ( isClient && { process : [ require . resolve ( 'process' ) ] } ) ,
2021-01-11 13:31:31 +01:00
} ) ,
2022-08-13 18:55:55 +02:00
new webpack . DefinePlugin (
getDefineEnv ( {
dev ,
config ,
distDir ,
isClient ,
hasRewrites ,
hasReactRoot ,
isNodeServer ,
isEdgeServer ,
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
middlewareMatchers ,
2022-08-13 18:55:55 +02:00
} )
) ,
2022-04-27 11:50:29 +02:00
isClient &&
2019-04-26 17:23:32 +02:00
new ReactLoadablePlugin ( {
filename : REACT_LOADABLE_MANIFEST ,
2021-04-21 13:18:05 +02:00
pagesDir ,
2021-10-26 18:50:56 +02:00
runtimeAsset : hasConcurrentFeatures
? ` server/ ${ MIDDLEWARE_REACT_LOADABLE_MANIFEST } .js `
: undefined ,
2021-12-14 11:33:04 +01:00
dev ,
2019-04-26 17:23:32 +02:00
} ) ,
2022-04-27 11:50:29 +02:00
( isClient || isEdgeServer ) && new DropClientPage ( ) ,
2021-10-26 06:22:45 +02:00
config . outputFileTracing &&
2021-08-16 21:29:11 +02:00
! isLikeServerless &&
2022-04-27 11:50:29 +02:00
( isNodeServer || isEdgeServer ) &&
2021-08-16 21:29:11 +02:00
! dev &&
2022-06-03 20:47:16 +02:00
new ( require ( './webpack/plugins/next-trace-entrypoints-plugin' ) . TraceEntryPointsPlugin ) (
{
appDir : dir ,
esmExternals : config.experimental.esmExternals ,
outputFileTracingRoot : config.experimental.outputFileTracingRoot ,
2022-10-04 00:53:28 +02:00
appDirEnabled : ! ! config . experimental . appDir ,
2022-06-03 20:47:16 +02:00
}
) ,
2019-08-20 21:19:45 +02:00
// Moment.js is an extremely popular library that bundles large locale files
// by default due to how Webpack interprets its code. This is a practical
// solution that requires the user to opt into importing specific locales.
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
2021-06-14 16:20:34 +02:00
config . excludeDefaultMomentLocales &&
2020-06-17 17:00:29 +02:00
new webpack . IgnorePlugin ( {
resourceRegExp : /^\.\/locale$/ ,
contextRegExp : /moment$/ ,
} ) ,
2019-04-26 17:23:32 +02:00
. . . ( dev
? ( ( ) = > {
// Even though require.cache is server only we have to clear assets from both compilations
// This is because the client compilation generates the build manifest that's used on the server side
const {
NextJsRequireCacheHotReloader ,
} = require ( './webpack/plugins/nextjs-require-cache-hot-reloader' )
2022-06-02 16:51:41 +02:00
const devPlugins = [
new NextJsRequireCacheHotReloader ( {
2022-09-21 21:30:46 +02:00
hasServerComponents ,
2022-06-02 16:51:41 +02:00
} ) ,
]
2019-03-10 05:27:33 +01:00
2022-04-27 11:50:29 +02:00
if ( isClient || isEdgeServer ) {
2019-04-26 17:23:32 +02:00
devPlugins . push ( new webpack . HotModuleReplacementPlugin ( ) )
}
2019-03-10 05:27:33 +01:00
2019-04-26 17:23:32 +02:00
return devPlugins
} ) ( )
: [ ] ) ,
! dev &&
new webpack . IgnorePlugin ( {
2020-04-15 20:25:02 +02:00
resourceRegExp : /react-is/ ,
2021-06-30 11:43:31 +02:00
contextRegExp : /next[\\/]dist[\\/]/ ,
2019-04-26 17:23:32 +02:00
} ) ,
2022-04-27 11:50:29 +02:00
target === 'serverless' &&
( isNodeServer || isEdgeServer ) &&
new ServerlessPlugin ( ) ,
( isNodeServer || isEdgeServer ) &&
2022-03-11 14:33:40 +01:00
new PagesManifestPlugin ( {
serverless : isLikeServerless ,
dev ,
2022-04-27 11:50:29 +02:00
isEdgeRuntime : isEdgeServer ,
2022-05-25 11:46:26 +02:00
appDirEnabled : ! ! config . experimental . appDir ,
2022-03-11 14:33:40 +01:00
} ) ,
2021-10-20 19:52:11 +02:00
// MiddlewarePlugin should be after DefinePlugin so NEXT_PUBLIC_*
// replacement is done before its process.env.* handling
2022-09-09 00:17:15 +02:00
isEdgeServer &&
new MiddlewarePlugin ( {
dev ,
sriEnabled : ! dev && ! ! config . experimental . sri ? . algorithm ,
2022-09-22 07:12:59 +02:00
hasFontLoaders : ! ! config . experimental . fontLoaders ,
2022-09-09 00:17:15 +02:00
} ) ,
2022-04-27 11:50:29 +02:00
isClient &&
2019-08-08 19:14:33 +02:00
new BuildManifestPlugin ( {
buildId ,
2020-08-13 14:39:36 +02:00
rewrites ,
2021-04-22 13:08:47 +02:00
isDevFallback ,
2021-10-27 01:17:55 +02:00
exportRuntime : hasConcurrentFeatures ,
2022-05-25 11:46:26 +02:00
appDirEnabled : ! ! config . experimental . appDir ,
2019-08-08 19:14:33 +02:00
} ) ,
2021-08-17 09:18:47 +02:00
new ProfilingPlugin ( { runWebpackSpan } ) ,
2021-04-05 19:47:03 +02:00
config . optimizeFonts &&
2020-12-21 20:26:00 +01:00
! dev &&
2022-04-27 11:50:29 +02:00
isNodeServer &&
2020-07-30 01:19:32 +02:00
( function ( ) {
2021-08-17 09:18:08 +02:00
const { FontStylesheetGatheringPlugin } =
require ( './webpack/plugins/font-stylesheet-gathering-plugin' ) as {
FontStylesheetGatheringPlugin : typeof import ( './webpack/plugins/font-stylesheet-gathering-plugin' ) . FontStylesheetGatheringPlugin
}
2021-03-03 10:37:24 +01:00
return new FontStylesheetGatheringPlugin ( {
isLikeServerless ,
2022-09-16 23:13:21 +02:00
adjustFontFallbacks : config.experimental.adjustFontFallbacks ,
2022-09-30 02:35:50 +02:00
adjustFontFallbacksWithSizeAdjust :
config . experimental . adjustFontFallbacksWithSizeAdjust ,
2021-03-03 10:37:24 +01:00
} )
2020-07-30 01:19:32 +02:00
} ) ( ) ,
feat: build edge functions with node.js modules and fail at runtime (#38234)
## What's in there?
The Edge runtime [does not support Node.js modules](https://edge-runtime.vercel.app/features/available-apis#unsupported-apis).
When building Next.js application, we currently fail the build when detecting node.js module imported from middleware.
This is an blocker for using code that is conditionally loading node.js modules (based on platform/env detection), as @cramforce reported.
This PR implements a new strategy where:
- we can build such middleware/Edge API route code **with a warning**
- we fail at run time, with graceful errors in dev (console & react-dev-overlay error)
- we fail at run time, with console errors in production
## How to test?
All cases are covered with integration tests.
To try them live, create a simple app with a page, a `middleware.js` file and a `pages/api/route.js`file.
Here are iconic examples:
### node.js modules
```js
// middleware.js
import { NextResponse } from 'next/server'
// static
import { basename } from 'path'
export default async function middleware() {
// dynamic
const { basename } = await import('path')
basename()
return NextResponse.next()
}
export const config = { matcher: '/' }
```
```js
// pags/api/route.js
// static
import { isAbsolute } from 'path'
export default async function handle() {
// dynamic
const { isAbsolute } = await import('path')
return Response.json({ useNodeModule: isAbsolute('/test') })
}
export const config = { runtime: 'experimental-edge' }
```
Desired error (+ source code highlight in dev):
> The edge runtime does not support Node.js 'path' module
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime
Desired warning at build time:
> A Node.js module is loaded ('path' at line 2) which is not supported in the Edge Runtime.
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime
- [x] in dev middleware, static, shows desired error on stderr
- [x] in dev route, static, shows desired error on stderr
- [x] in dev middleware, dynamic, shows desired error on stderr
- [x] in dev route, dynamic, shows desired error on stderr
- [x] in dev middleware, static, shows desired error on react error overlay
- [x] in dev route, static, shows desired error on react error overlay
- [x] in dev middleware, dynamic, shows desired error on react error overlay
- [x] in dev route, dynamic, shows desired error on react error overlay
- [x] builds middleware successfully, shows build warning, shows desired error on stderr on call
- [x] builds route successfully, shows build warning, shows desired error on stderr on call
### 3rd party modules not found
```js
// middleware.js
import { NextResponse } from 'next/server'
// static
import Unknown from 'unknown'
export default async function middleware() {
// dynamic
const Unknown = await import('unknown')
new Unknown()
return NextResponse.next()
}
```
export const config = { matcher: '/' }
```
```js
// pags/api/route.js
// static
import Unknown from 'unknown'
export default async function handle() {
// dynamic
const Unknown = await import('unknown')
return Response.json({ use3rdPartyModule: Unknown() })
}
export const config = { runtime: 'experimental-edge' }
```
Desired error (+ source code highlight in dev):
> Module not found: Can't resolve 'does-not-exist'
Learn More: https://nextjs.org/docs/messages/module-not-found
- [x] in dev middleware, static, shows desired error on stderr
- [x] in dev route, static, shows desired error on stderr
- [x] in dev middleware, dynamic, shows desired error on stderr
- [x] in dev route, dynamic, shows desired error on stderr
- [x] in dev middleware, static, shows desired error on react error overlay
- [x] in dev route, static, shows desired error on react error overlay
- [x] in dev middleware, dynamic, shows desired error on react error overlay
- [x] in dev route, dynamic, shows desired error on react error overlay
- [x] fails to build middleware, with desired error on stderr
- [x] fails to build route, with desired error on stderr
### unused node.js modules
```js
// middleware.js
import { NextResponse } from 'next/server'
export default async function middleware() {
if (process.exit) {
const { basename } = await import('path')
basename()
}
return NextResponse.next()
}
```
```js
// pags/api/route.js
export default async function handle() {
if (process.exit) {
const { basename } = await import('path')
basename()
}
return Response.json({ useNodeModule: false })
}
export const config = { runtime: 'experimental-edge' }
```
Desired warning at build time:
> A Node.js module is loaded ('path' at line 2) which is not supported in the Edge Runtime.
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime
- [x] invoke middleware in dev with no error
- [x] invoke route in dev with no error
- [x] builds successfully, shows build warning, invoke middleware with no error
- [x] builds successfully, shows build warning, invoke api-route with no error
## Notes to reviewers
The strategy to implement this feature is to leverages webpack [externals](https://webpack.js.org/configuration/externals/#externals) and run a global `__unsupported_module()` function when using a node.js module from edge function's code.
For the record, I tried using [webpack resolve.fallback](https://webpack.js.org/configuration/resolve/#resolvefallback) and [Webpack.IgnorePlugin](https://webpack.js.org/plugins/ignore-plugin/) but they do not allow throwing proper errors at runtime that would contain the loaded module name for reporting.
`__unsupported_module()` is defined in `EdgeRuntime`, and returns a proxy that's throw on use (whether it's property access, function call, new operator... synchronous & promise-based styles).
However there's an issue with error reporting: webpack does not includes the import lines in the generated sourcemaps, preventing from displaying useful errors.
I extended our middleware-plugin to supplement the sourcemaps (when analyzing edge function code, it saves which module is imported from which file, together with line/column/source)
The react-dev-overlay was adapted to look for this additional information when the caught error relates to modules, instead of looking at sourcemaps.
I removed the previous mechanism (built by @nkzawa ) which caught webpack errors at built time to change the displayed error message (files `next/build/index.js`, `next/build/utils.ts` and `wellknown-errors-plugin`)
2022-07-06 22:54:44 +02:00
new WellKnownErrorsPlugin ( ) ,
2022-04-27 11:50:29 +02:00
isClient &&
2021-08-14 00:40:45 +02:00
new CopyFilePlugin ( {
filePath : require.resolve ( './polyfills/polyfill-nomodule' ) ,
cacheKey : process.env.__NEXT_VERSION as string ,
name : ` static/chunks/polyfills ${ dev ? '' : '-[hash]' } .js ` ,
minimize : false ,
info : {
[ CLIENT_STATIC_FILES_RUNTIME_POLYFILLS_SYMBOL ] : 1 ,
// This file is already minified
minimized : true ,
} ,
} ) ,
2022-08-10 21:31:01 +02:00
! ! config . experimental . appDir &&
isClient &&
new AppBuildManifestPlugin ( { dev } ) ,
2021-10-26 18:50:56 +02:00
hasServerComponents &&
2022-05-24 16:54:26 +02:00
( isClient
? new FlightManifestPlugin ( {
dev ,
2022-09-27 20:10:05 +02:00
fontLoaderTargets ,
2022-05-24 16:54:26 +02:00
} )
2022-07-29 00:35:52 +02:00
: new FlightClientEntryPlugin ( {
2022-05-24 16:54:26 +02:00
dev ,
isEdgeServer ,
2022-09-27 20:10:05 +02:00
fontLoaderTargets ,
2022-05-24 16:54:26 +02:00
} ) ) ,
2022-09-09 00:17:15 +02:00
! dev &&
isClient &&
! ! config . experimental . sri ? . algorithm &&
new SubresourceIntegrityPlugin ( config . experimental . sri . algorithm ) ,
2022-09-22 07:12:59 +02:00
isClient &&
config . experimental . fontLoaders &&
new FontLoaderManifestPlugin ( ) ,
2021-10-26 09:37:38 +02:00
! dev &&
2022-04-27 11:50:29 +02:00
isClient &&
2022-06-03 20:47:16 +02:00
new ( require ( './webpack/plugins/telemetry-plugin' ) . TelemetryPlugin ) (
2022-03-22 01:09:03 +01:00
new Map (
2022-02-10 02:54:28 +01:00
[
2022-03-22 01:09:03 +01:00
[ 'swcLoader' , useSWCLoader ] ,
[ 'swcMinify' , config . swcMinify ] ,
[ 'swcRelay' , ! ! config . compiler ? . relay ] ,
[ 'swcStyledComponents' , ! ! config . compiler ? . styledComponents ] ,
[
'swcReactRemoveProperties' ,
! ! config . compiler ? . reactRemoveProperties ,
] ,
[
'swcExperimentalDecorators' ,
! ! jsConfig ? . compilerOptions ? . experimentalDecorators ,
] ,
[ 'swcRemoveConsole' , ! ! config . compiler ? . removeConsole ] ,
[ 'swcImportSource' , ! ! jsConfig ? . compilerOptions ? . jsxImportSource ] ,
2022-05-21 06:09:30 +02:00
[ 'swcEmotion' , ! ! config . compiler ? . emotion ] ,
2022-03-22 01:09:03 +01:00
SWCBinaryTarget ,
] . filter < [ Feature , boolean ] > ( Boolean as any )
)
2021-10-26 09:37:38 +02:00
) ,
2021-08-17 09:18:08 +02:00
] . filter ( Boolean as any as ExcludesFalse ) ,
2016-12-17 19:38:11 +01:00
}
2016-12-22 02:36:00 +01:00
2020-03-19 17:34:24 +01:00
// Support tsconfig and jsconfig baseUrl
if ( resolvedBaseUrl ) {
webpackConfig . resolve ? . modules ? . push ( resolvedBaseUrl )
}
2022-08-23 20:16:47 +02:00
// allows add JsConfigPathsPlugin to allow hot-reloading
// if the config is added/removed
webpackConfig . resolve ? . plugins ? . unshift (
new JsConfigPathsPlugin (
jsConfig ? . compilerOptions ? . paths || { } ,
resolvedBaseUrl || dir
2020-03-23 15:45:51 +01:00
)
2022-08-23 20:16:47 +02:00
)
2020-03-23 15:45:51 +01:00
2022-08-16 11:55:37 +02:00
const webpack5Config = webpackConfig as webpack . Configuration
2021-02-19 11:10:19 +01:00
2022-06-20 15:08:40 +02:00
if ( isEdgeServer ) {
webpack5Config . module ? . rules ? . unshift ( {
test : /\.wasm$/ ,
loader : 'next-middleware-wasm-loader' ,
type : 'javascript/auto' ,
resourceQuery : /module/i ,
} )
2022-07-19 19:27:15 +02:00
webpack5Config . module ? . rules ? . unshift ( {
dependency : 'url' ,
loader : 'next-middleware-asset-loader' ,
type : 'javascript/auto' ,
2022-08-12 15:01:19 +02:00
layer : WEBPACK_LAYERS.edgeAsset ,
2022-07-19 19:27:15 +02:00
} )
webpack5Config . module ? . rules ? . unshift ( {
2022-08-12 15:01:19 +02:00
issuerLayer : WEBPACK_LAYERS.edgeAsset ,
2022-07-19 19:27:15 +02:00
type : 'asset/source' ,
} )
2022-06-20 15:08:40 +02:00
}
2022-03-02 16:09:36 +01:00
2021-10-06 17:40:01 +02:00
webpack5Config . experiments = {
layers : true ,
cacheUnaffected : true ,
2021-10-21 13:14:57 +02:00
buildHttp : Array.isArray ( config . experimental . urlImports )
? {
allowedUris : config.experimental.urlImports ,
cacheLocation : path.join ( dir , 'next.lock/data' ) ,
lockfileLocation : path.join ( dir , 'next.lock/lock.json' ) ,
}
: config . experimental . urlImports
? {
cacheLocation : path.join ( dir , 'next.lock/data' ) ,
lockfileLocation : path.join ( dir , 'next.lock/lock.json' ) ,
. . . config . experimental . urlImports ,
}
: undefined ,
2021-10-06 17:40:01 +02:00
}
2021-09-21 19:17:16 +02:00
2021-10-06 17:40:01 +02:00
webpack5Config . module ! . parser = {
javascript : {
url : 'relative' ,
} ,
}
webpack5Config . module ! . generator = {
asset : {
filename : 'static/media/[name].[hash:8][ext]' ,
} ,
}
2021-09-21 19:17:16 +02:00
2022-05-22 06:25:26 +02:00
if ( ! webpack5Config . output ) {
webpack5Config . output = { }
}
if ( isClient ) {
webpack5Config . output . trustedTypes = 'nextjs#bundler'
}
2022-04-27 11:50:29 +02:00
if ( isClient || isEdgeServer ) {
2022-05-22 06:25:26 +02:00
webpack5Config . output . enabledLibraryTypes = [ 'assign' ]
2021-10-20 19:52:11 +02:00
}
2021-10-06 17:40:01 +02:00
if ( dev ) {
// @ts-ignore unsafeCache exists
webpack5Config . module . unsafeCache = ( module ) = >
! /[\\/]pages[\\/][^\\/]+(?:$|\?|#)/ . test ( module . resource )
}
2021-09-17 21:20:09 +02:00
2021-10-21 03:25:45 +02:00
// This enables managedPaths for all node_modules
// and also for the unplugged folder when using yarn pnp
// It also add the yarn cache to the immutable paths
2021-10-06 17:40:01 +02:00
webpack5Config . snapshot = { }
if ( process . versions . pnp === '3' ) {
2021-10-21 03:25:45 +02:00
webpack5Config . snapshot . managedPaths = [
/^(.+?(?:[\\/]\.yarn[\\/]unplugged[\\/][^\\/]+)?[\\/]node_modules[\\/])/ ,
]
2021-10-06 17:40:01 +02:00
} else {
2021-10-21 03:25:45 +02:00
webpack5Config . snapshot . managedPaths = [ /^(.+?[\\/]node_modules[\\/])/ ]
2021-10-06 17:40:01 +02:00
}
2021-10-21 03:25:45 +02:00
if ( process . versions . pnp === '3' ) {
webpack5Config . snapshot . immutablePaths = [
/^(.+?[\\/]cache[\\/][^\\/]+\.zip[\\/]node_modules[\\/])/ ,
]
2021-10-06 17:40:01 +02:00
}
2021-05-19 21:43:16 +02:00
2021-10-06 17:40:01 +02:00
if ( dev ) {
if ( ! webpack5Config . optimization ) {
webpack5Config . optimization = { }
2020-07-09 14:31:06 +02:00
}
2022-03-02 21:57:50 +01:00
// For Server Components, it's necessary to have provided exports collected
// to generate the correct flight manifest.
if ( ! hasServerComponents ) {
webpack5Config . optimization . providedExports = false
}
2021-10-06 17:40:01 +02:00
webpack5Config . optimization . usedExports = false
}
2020-08-18 15:46:55 +02:00
2021-10-06 17:40:01 +02:00
const configVars = JSON . stringify ( {
crossOrigin : config.crossOrigin ,
2022-09-18 02:00:16 +02:00
pageExtensions : pageExtensions ,
2021-10-06 17:40:01 +02:00
trailingSlash : config.trailingSlash ,
buildActivity : config.devIndicators.buildActivity ,
2021-11-03 22:17:36 +01:00
buildActivityPosition : config.devIndicators.buildActivityPosition ,
2021-10-06 17:40:01 +02:00
productionBrowserSourceMaps : ! ! config . productionBrowserSourceMaps ,
reactStrictMode : config.reactStrictMode ,
optimizeFonts : config.optimizeFonts ,
optimizeCss : config.experimental.optimizeCss ,
2022-03-11 23:26:46 +01:00
nextScriptWorkers : config.experimental.nextScriptWorkers ,
2021-10-06 17:40:01 +02:00
scrollRestoration : config.experimental.scrollRestoration ,
basePath : config.basePath ,
pageEnv : config.experimental.pageEnv ,
excludeDefaultMomentLocales : config.excludeDefaultMomentLocales ,
assetPrefix : config.assetPrefix ,
2022-02-08 14:16:46 +01:00
disableOptimizedLoading ,
2021-10-06 17:40:01 +02:00
target ,
2022-04-27 11:50:29 +02:00
isEdgeRuntime : isEdgeServer ,
2021-10-06 17:40:01 +02:00
reactProductionProfiling ,
webpack : ! ! config . webpack ,
hasRewrites ,
2022-04-27 11:50:29 +02:00
runtime : config.experimental.runtime ,
2021-10-25 13:49:11 +02:00
swcMinify : config.swcMinify ,
swcLoader : useSWCLoader ,
2022-02-10 02:54:28 +01:00
removeConsole : config.compiler?.removeConsole ,
reactRemoveProperties : config.compiler?.reactRemoveProperties ,
styledComponents : config.compiler?.styledComponents ,
relay : config.compiler?.relay ,
2022-05-21 06:09:30 +02:00
emotion : config.compiler?.emotion ,
2022-03-22 14:20:57 +01:00
modularizeImports : config.experimental?.modularizeImports ,
2022-05-17 17:09:34 +02:00
legacyBrowsers : config.experimental?.legacyBrowsers ,
2021-10-06 17:40:01 +02:00
} )
2020-08-25 00:37:52 +02:00
2021-10-06 17:40:01 +02:00
const cache : any = {
type : 'filesystem' ,
// Includes:
// - Next.js version
// - next.config.js keys that affect compilation
version : ` ${ process . env . __NEXT_VERSION } | ${ configVars } ` ,
cacheDirectory : path.join ( distDir , 'cache' , 'webpack' ) ,
}
2020-08-24 03:37:48 +02:00
2021-10-06 17:40:01 +02:00
// Adds `next.config.js` as a buildDependency when custom webpack config is provided
if ( config . webpack && config . configFile ) {
cache . buildDependencies = {
config : [ config . configFile ] ,
2020-08-25 00:37:52 +02:00
}
2021-10-06 17:40:01 +02:00
}
2020-08-24 03:37:48 +02:00
2021-10-06 17:40:01 +02:00
webpack5Config . cache = cache
if ( process . env . NEXT_WEBPACK_LOGGING ) {
2021-10-11 19:31:12 +02:00
const infra = process . env . NEXT_WEBPACK_LOGGING . includes ( 'infrastructure' )
const profileClient =
2021-10-06 17:40:01 +02:00
process . env . NEXT_WEBPACK_LOGGING . includes ( 'profile-client' )
2021-10-11 19:31:12 +02:00
const profileServer =
2021-10-06 17:40:01 +02:00
process . env . NEXT_WEBPACK_LOGGING . includes ( 'profile-server' )
2021-10-11 19:31:12 +02:00
const summaryClient =
process . env . NEXT_WEBPACK_LOGGING . includes ( 'summary-client' )
const summaryServer =
process . env . NEXT_WEBPACK_LOGGING . includes ( 'summary-server' )
2021-10-06 17:40:01 +02:00
2022-04-27 11:50:29 +02:00
const profile =
( profileClient && isClient ) ||
( profileServer && ( isNodeServer || isEdgeServer ) )
const summary =
( summaryClient && isClient ) ||
( summaryServer && ( isNodeServer || isEdgeServer ) )
2021-10-11 19:31:12 +02:00
const logDefault = ! infra && ! profile && ! summary
if ( logDefault || infra ) {
2021-10-06 17:40:01 +02:00
webpack5Config . infrastructureLogging = {
level : 'verbose' ,
debug : /FileSystemInfo/ ,
2021-03-18 10:46:07 +01:00
}
2021-10-06 17:40:01 +02:00
}
2021-03-18 10:46:07 +01:00
2021-10-11 19:31:12 +02:00
if ( logDefault || profile ) {
2022-08-16 11:55:37 +02:00
webpack5Config . plugins ! . push ( ( compiler : webpack.Compiler ) = > {
2021-10-06 17:40:01 +02:00
compiler . hooks . done . tap ( 'next-webpack-logging' , ( stats ) = > {
console . log (
stats . toString ( {
colors : true ,
logging : logDefault ? 'log' : 'verbose' ,
} )
)
2021-03-18 10:46:07 +01:00
} )
2021-10-06 17:40:01 +02:00
} )
2021-10-11 19:31:12 +02:00
} else if ( summary ) {
2022-08-16 11:55:37 +02:00
webpack5Config . plugins ! . push ( ( compiler : webpack.Compiler ) = > {
2021-10-11 19:31:12 +02:00
compiler . hooks . done . tap ( 'next-webpack-logging' , ( stats ) = > {
console . log (
stats . toString ( {
preset : 'summary' ,
colors : true ,
timings : true ,
} )
)
} )
} )
2021-10-06 17:40:01 +02:00
}
2021-03-18 10:46:07 +01:00
2021-10-11 19:31:12 +02:00
if ( profile ) {
2021-10-06 17:40:01 +02:00
const ProgressPlugin =
2022-08-16 11:55:37 +02:00
webpack . ProgressPlugin as unknown as typeof webpack . ProgressPlugin
2021-10-06 17:40:01 +02:00
webpack5Config . plugins ! . push (
new ProgressPlugin ( {
profile : true ,
} )
)
webpack5Config . profile = true
2021-03-18 10:46:07 +01:00
}
2020-04-15 20:25:02 +02:00
}
2019-12-09 21:08:15 +01:00
webpackConfig = await buildConfiguration ( webpackConfig , {
2021-11-03 19:38:04 +01:00
supportedBrowsers ,
2019-12-09 21:08:15 +01:00
rootDirectory : dir ,
2022-09-03 02:13:47 +02:00
customAppFile : pagesDir
? new RegExp ( escapeStringRegexp ( path . join ( pagesDir , ` _app ` ) ) )
: undefined ,
2019-12-09 21:08:15 +01:00
isDevelopment : dev ,
2022-04-27 11:50:29 +02:00
isServer : isNodeServer || isEdgeServer ,
isEdgeRuntime : isEdgeServer ,
targetWeb : isClient || isEdgeServer ,
2020-01-03 18:45:04 +01:00
assetPrefix : config.assetPrefix || '' ,
2020-05-11 04:11:48 +02:00
sassOptions : config.sassOptions ,
2021-01-01 21:30:50 +01:00
productionBrowserSourceMaps : config.productionBrowserSourceMaps ,
2021-01-11 18:19:56 +01:00
future : config.future ,
2021-10-25 01:54:16 +02:00
experimental : config.experimental ,
2021-11-02 21:25:12 +01:00
disableStaticImages : config.images.disableStaticImages ,
2019-12-09 21:08:15 +01:00
} )
2021-10-24 15:25:02 +02:00
// @ts-ignore Cache exists
webpackConfig . cache . name = ` ${ webpackConfig . name } - ${ webpackConfig . mode } ${
isDevFallback ? '-fallback' : ''
} `
2020-06-24 06:15:57 +02:00
let originalDevtool = webpackConfig . devtool
2018-01-30 16:40:52 +01:00
if ( typeof config . webpack === 'function' ) {
2019-04-26 17:23:32 +02:00
webpackConfig = config . webpack ( webpackConfig , {
dir ,
dev ,
2022-04-27 11:50:29 +02:00
isServer : isNodeServer || isEdgeServer ,
2019-04-26 17:23:32 +02:00
buildId ,
config ,
defaultLoaders ,
2022-04-27 11:50:29 +02:00
totalPages : Object.keys ( entrypoints ) . length ,
2019-04-26 17:23:32 +02:00
webpack ,
2022-04-27 11:50:29 +02:00
. . . ( isNodeServer || isEdgeServer
2022-04-26 23:05:40 +02:00
? {
2022-04-27 11:50:29 +02:00
nextRuntime : isEdgeServer ? 'edge' : 'nodejs' ,
2022-04-26 23:05:40 +02:00
}
: { } ) ,
2019-04-26 17:23:32 +02:00
} )
2019-02-28 14:39:51 +01:00
2020-12-01 22:58:52 +01:00
if ( ! webpackConfig ) {
throw new Error (
2021-10-22 01:04:40 +02:00
` Webpack config is undefined. You may have forgot to return properly from within the "webpack" method of your ${ config . configFileName } . \ n ` +
2021-03-29 10:25:00 +02:00
'See more info here https://nextjs.org/docs/messages/undefined-webpack-config'
2020-12-01 22:58:52 +01:00
)
}
2020-06-24 06:15:57 +02:00
if ( dev && originalDevtool !== webpackConfig . devtool ) {
webpackConfig . devtool = originalDevtool
devtoolRevertWarning ( originalDevtool )
}
2021-12-14 11:33:04 +01:00
// eslint-disable-next-line no-shadow
2022-08-16 11:55:37 +02:00
const webpack5Config = webpackConfig as webpack . Configuration
2021-12-14 11:33:04 +01:00
// disable lazy compilation of entries as next.js has it's own method here
if ( webpack5Config . experiments ? . lazyCompilation === true ) {
webpack5Config . experiments . lazyCompilation = {
entries : false ,
}
} else if (
typeof webpack5Config . experiments ? . lazyCompilation === 'object' &&
webpack5Config . experiments . lazyCompilation . entries !== false
) {
webpack5Config . experiments . lazyCompilation . entries = false
}
2020-02-17 22:16:19 +01:00
if ( typeof ( webpackConfig as any ) . then === 'function' ) {
2019-04-26 17:23:32 +02:00
console . warn (
2021-03-29 10:25:00 +02:00
'> Promise returned in next config. https://nextjs.org/docs/messages/promise-in-next-config'
2019-04-26 17:23:32 +02:00
)
2019-02-28 14:39:51 +01:00
}
2016-12-17 19:38:11 +01:00
}
2018-01-30 16:40:52 +01:00
2021-10-06 17:40:01 +02:00
if ( ! config . images . disableStaticImages ) {
2021-06-24 00:56:18 +02:00
const rules = webpackConfig . module ? . rules || [ ]
const hasCustomSvg = rules . some (
2021-06-18 02:40:22 +02:00
( rule ) = >
2022-08-16 11:55:37 +02:00
rule &&
typeof rule === 'object' &&
2021-06-24 00:56:18 +02:00
rule . loader !== 'next-image-loader' &&
'test' in rule &&
rule . test instanceof RegExp &&
rule . test . test ( '.svg' )
2021-06-18 02:40:22 +02:00
)
2021-06-24 00:56:18 +02:00
const nextImageRule = rules . find (
2022-08-16 11:55:37 +02:00
( rule ) = >
rule && typeof rule === 'object' && rule . loader === 'next-image-loader'
2021-06-24 00:56:18 +02:00
)
2022-08-16 11:55:37 +02:00
if (
hasCustomSvg &&
nextImageRule &&
nextImageRule &&
typeof nextImageRule === 'object'
) {
2021-06-24 00:56:18 +02:00
// Exclude svg if the user already defined it in custom
// webpack config such as `@svgr/webpack` plugin or
// the `babel-plugin-inline-react-svg` plugin.
2021-10-12 01:17:47 +02:00
nextImageRule . test = /\.(png|jpg|jpeg|gif|webp|avif|ico|bmp)$/i
2021-06-24 00:56:18 +02:00
}
2021-06-18 02:40:22 +02:00
}
2021-06-09 16:51:56 +02:00
if (
config . experimental . craCompat &&
webpackConfig . module ? . rules &&
webpackConfig . plugins
) {
// CRA allows importing non-webpack handled files with file-loader
// these need to be the last rule to prevent catching other items
// https://github.com/facebook/create-react-app/blob/fddce8a9e21bf68f37054586deb0c8636a45f50b/packages/react-scripts/config/webpack.config.js#L594
const fileLoaderExclude = [ /\.(js|mjs|jsx|ts|tsx|json)$/ ]
2021-10-06 17:40:01 +02:00
const fileLoader = {
exclude : fileLoaderExclude ,
issuer : fileLoaderExclude ,
type : 'asset/resource' ,
}
2021-06-09 16:51:56 +02:00
const topRules = [ ]
const innerRules = [ ]
for ( const rule of webpackConfig . module . rules ) {
2022-08-16 11:55:37 +02:00
if ( ! rule || typeof rule !== 'object' ) continue
2021-06-09 16:51:56 +02:00
if ( rule . resolve ) {
topRules . push ( rule )
} else {
if (
rule . oneOf &&
! ( rule . test || rule . exclude || rule . resource || rule . issuer )
) {
rule . oneOf . forEach ( ( r ) = > innerRules . push ( r ) )
} else {
innerRules . push ( rule )
}
}
}
webpackConfig . module . rules = [
. . . ( topRules as any ) ,
{
oneOf : [ . . . innerRules , fileLoader ] ,
} ,
]
}
2020-06-26 06:26:09 +02:00
// Backwards compat with webpack-dev-middleware options object
if ( typeof config . webpackDevMiddleware === 'function' ) {
const options = config . webpackDevMiddleware ( {
watchOptions : webpackConfig.watchOptions ,
} )
if ( options . watchOptions ) {
webpackConfig . watchOptions = options . watchOptions
}
}
2019-12-13 21:23:28 +01:00
function canMatchCss ( rule : webpack.RuleSetCondition | undefined ) : boolean {
if ( ! rule ) {
return false
}
2020-01-10 22:07:06 +01:00
const fileNames = [
2022-02-23 13:28:17 +01:00
'/tmp/NEXTJS_CSS_DETECTION_FILE.css' ,
'/tmp/NEXTJS_CSS_DETECTION_FILE.scss' ,
'/tmp/NEXTJS_CSS_DETECTION_FILE.sass' ,
'/tmp/NEXTJS_CSS_DETECTION_FILE.less' ,
'/tmp/NEXTJS_CSS_DETECTION_FILE.styl' ,
2020-01-10 22:07:06 +01:00
]
2020-05-18 21:24:37 +02:00
if ( rule instanceof RegExp && fileNames . some ( ( input ) = > rule . test ( input ) ) ) {
2019-12-13 21:23:28 +01:00
return true
}
if ( typeof rule === 'function' ) {
2020-01-10 22:07:06 +01:00
if (
2020-05-18 21:24:37 +02:00
fileNames . some ( ( input ) = > {
2020-01-10 22:07:06 +01:00
try {
if ( rule ( input ) ) {
return true
}
} catch ( _ ) { }
return false
} )
) {
return true
}
2019-12-13 21:23:28 +01:00
}
if ( Array . isArray ( rule ) && rule . some ( canMatchCss ) ) {
return true
}
return false
}
2020-04-25 21:00:41 +02:00
const hasUserCssConfig =
2022-08-16 11:55:37 +02:00
webpackConfig . module ? . rules ? . some (
( rule : any ) = > canMatchCss ( rule . test ) || canMatchCss ( rule . include )
2020-04-25 21:00:41 +02:00
) ? ? false
2020-03-10 19:29:40 +01:00
2020-04-25 21:00:41 +02:00
if ( hasUserCssConfig ) {
// only show warning for one build
2022-04-27 11:50:29 +02:00
if ( isNodeServer || isEdgeServer ) {
2020-04-25 21:00:41 +02:00
console . warn (
chalk . yellow . bold ( 'Warning: ' ) +
chalk . bold (
'Built-in CSS support is being disabled due to custom CSS configuration being detected.\n'
) +
2021-03-29 10:25:00 +02:00
'See here for more info: https://nextjs.org/docs/messages/built-in-css-disabled\n'
2020-04-25 21:00:41 +02:00
)
}
2022-08-16 11:55:37 +02:00
if ( webpackConfig . module ? . rules ? . length ) {
2021-11-29 15:19:39 +01:00
// Remove default CSS Loaders
webpackConfig . module . rules . forEach ( ( r ) = > {
2022-08-16 11:55:37 +02:00
if ( ! r || typeof r !== 'object' ) return
2021-11-29 15:19:39 +01:00
if ( Array . isArray ( r . oneOf ) ) {
r . oneOf = r . oneOf . filter (
( o ) = > ( o as any ) [ Symbol . for ( '__next_css_remove' ) ] !== true
2020-04-25 21:00:41 +02:00
)
2021-11-29 15:19:39 +01:00
}
} )
2019-12-13 21:23:28 +01:00
}
2020-04-25 21:00:41 +02:00
if ( webpackConfig . plugins ? . length ) {
// Disable CSS Extraction Plugin
webpackConfig . plugins = webpackConfig . plugins . filter (
2020-05-18 21:24:37 +02:00
( p ) = > ( p as any ) . __next_css_remove !== true
2020-04-25 21:00:41 +02:00
)
}
if ( webpackConfig . optimization ? . minimizer ? . length ) {
// Disable CSS Minifier
2021-08-17 09:18:08 +02:00
webpackConfig . optimization . minimizer =
webpackConfig . optimization . minimizer . filter (
( e ) = > ( e as any ) . __next_css_remove !== true
)
2020-04-25 21:00:41 +02:00
}
2019-12-13 21:23:28 +01:00
}
2020-08-04 23:24:56 +02:00
// Inject missing React Refresh loaders so that development mode is fast:
2022-04-27 11:50:29 +02:00
if ( dev && isClient ) {
2020-08-04 23:24:56 +02:00
attachReactRefresh ( webpackConfig , defaultLoaders . babel )
}
2019-04-25 22:49:39 +02:00
// check if using @zeit/next-typescript and show warning
2019-04-26 17:23:32 +02:00
if (
2022-04-27 11:50:29 +02:00
( isNodeServer || isEdgeServer ) &&
2019-04-26 17:23:32 +02:00
webpackConfig . module &&
2019-04-25 22:49:39 +02:00
Array . isArray ( webpackConfig . module . rules )
) {
let foundTsRule = false
2019-04-26 17:23:32 +02:00
webpackConfig . module . rules = webpackConfig . module . rules . filter (
( rule ) : boolean = > {
2022-08-16 11:55:37 +02:00
if ( ! rule || typeof rule !== 'object' ) return true
2019-04-25 22:49:39 +02:00
if ( ! ( rule . test instanceof RegExp ) ) return true
2022-09-27 12:03:57 +02:00
if ( rule . test . test ( 'noop.ts' ) && ! rule . test . test ( 'noop.js' ) ) {
2019-04-25 22:49:39 +02:00
// remove if it matches @zeit/next-typescript
foundTsRule = rule . use === defaultLoaders . babel
return ! foundTsRule
}
return true
2019-04-26 17:23:32 +02:00
}
)
2019-04-25 22:49:39 +02:00
if ( foundTsRule ) {
2019-04-26 17:23:32 +02:00
console . warn (
2021-10-22 01:04:40 +02:00
` \ n@zeit/next-typescript is no longer needed since Next.js has built-in support for TypeScript now. Please remove it from your ${ config . configFileName } and your .babelrc \ n `
2019-04-26 17:23:32 +02:00
)
2019-04-25 22:49:39 +02:00
}
}
2019-11-11 04:56:29 +01:00
// Patch `@zeit/next-sass`, `@zeit/next-less`, `@zeit/next-stylus` for compatibility
2019-10-05 04:53:14 +02:00
if ( webpackConfig . module && Array . isArray ( webpackConfig . module . rules ) ) {
2021-08-17 09:18:08 +02:00
; [ ] . forEach . call (
webpackConfig . module . rules ,
function ( rule : webpack.RuleSetRule ) {
if ( ! ( rule . test instanceof RegExp && Array . isArray ( rule . use ) ) ) {
return
}
2019-10-03 19:10:42 +02:00
2021-08-17 09:18:08 +02:00
const isSass =
rule . test . source === '\\.scss$' || rule . test . source === '\\.sass$'
const isLess = rule . test . source === '\\.less$'
const isCss = rule . test . source === '\\.css$'
const isStylus = rule . test . source === '\\.styl$'
2019-10-03 16:08:49 +02:00
2021-08-17 09:18:08 +02:00
// Check if the rule we're iterating over applies to Sass, Less, or CSS
if ( ! ( isSass || isLess || isCss || isStylus ) ) {
2019-10-03 16:08:49 +02:00
return
}
2021-08-17 09:18:08 +02:00
; [ ] . forEach . call ( rule . use , function ( use : webpack.RuleSetUseItem ) {
if (
! (
use &&
typeof use === 'object' &&
// Identify use statements only pertaining to `css-loader`
( use . loader === 'css-loader' ||
use . loader === 'css-loader/locals' ) &&
use . options &&
typeof use . options === 'object' &&
// The `minimize` property is a good heuristic that we need to
// perform this hack. The `minimize` property was only valid on
// old `css-loader` versions. Custom setups (that aren't next-sass,
// next-less or next-stylus) likely have the newer version.
// We still handle this gracefully below.
( Object . prototype . hasOwnProperty . call ( use . options , 'minimize' ) ||
Object . prototype . hasOwnProperty . call (
use . options ,
'exportOnlyLocals'
) )
)
) {
return
}
2019-10-03 16:08:49 +02:00
2021-08-17 09:18:08 +02:00
// Try to monkey patch within a try-catch. We shouldn't fail the build
// if we cannot pull this off.
// The user may not even be using the `next-sass` or `next-less` or
// `next-stylus` plugins.
// If it does work, great!
try {
// Resolve the version of `@zeit/next-css` as depended on by the Sass,
// Less or Stylus plugin.
const correctNextCss = require . resolve ( '@zeit/next-css' , {
paths : [
isCss
? // Resolve `@zeit/next-css` from the base directory
dir
: // Else, resolve it from the specific plugins
require . resolve (
isSass
? '@zeit/next-sass'
: isLess
? '@zeit/next-less'
: isStylus
? '@zeit/next-stylus'
: 'next'
) ,
] ,
2021-01-11 15:43:08 +01:00
} )
2021-08-17 09:18:08 +02:00
// If we found `@zeit/next-css` ...
if ( correctNextCss ) {
// ... resolve the version of `css-loader` shipped with that
// package instead of whichever was hoisted highest in your
// `node_modules` tree.
const correctCssLoader = require . resolve ( use . loader , {
paths : [ correctNextCss ] ,
} )
if ( correctCssLoader ) {
// We saved the user from a failed build!
use . loader = correctCssLoader
}
2019-10-03 16:08:49 +02:00
}
2021-08-17 09:18:08 +02:00
} catch ( _ ) {
// The error is not required to be handled.
2019-10-03 16:08:49 +02:00
}
2021-08-17 09:18:08 +02:00
} )
}
)
2019-10-03 16:08:49 +02:00
}
2018-07-24 11:24:40 +02:00
// Backwards compat for `main.js` entry key
2021-05-03 12:25:44 +02:00
// and setup of dependencies between entries
// we can't do that in the initial entry for
// backward-compat reasons
2019-03-24 14:33:10 +01:00
const originalEntry : any = webpackConfig . entry
2019-02-20 20:24:02 +01:00
if ( typeof originalEntry !== 'undefined' ) {
2021-05-03 12:25:44 +02:00
const updatedEntry = async ( ) = > {
2022-08-16 11:55:37 +02:00
const entry : webpack.EntryObject =
2019-04-26 17:23:32 +02:00
typeof originalEntry === 'function'
? await originalEntry ( )
: originalEntry
2019-03-25 15:08:02 +01:00
// Server compilation doesn't have main.js
2021-05-03 12:25:44 +02:00
if (
clientEntries &&
Array . isArray ( entry [ 'main.js' ] ) &&
entry [ 'main.js' ] . length > 0
) {
2020-07-17 10:38:06 +02:00
const originalFile = clientEntries [
CLIENT_STATIC_FILES_RUNTIME_MAIN
] as string
2020-07-20 04:16:50 +02:00
entry [ CLIENT_STATIC_FILES_RUNTIME_MAIN ] = [
. . . entry [ 'main.js' ] ,
originalFile ,
]
2019-02-20 20:24:02 +01:00
}
2019-03-25 15:08:02 +01:00
delete entry [ 'main.js' ]
2018-07-24 11:24:40 +02:00
2022-04-27 11:50:29 +02:00
for ( const name of Object . keys ( entry ) ) {
entry [ name ] = finalizeEntrypoint ( {
value : entry [ name ] ,
compilerType ,
name ,
2022-06-01 13:52:57 +02:00
appDir : config.experimental.appDir ,
2022-04-27 11:50:29 +02:00
} )
2021-05-03 12:25:44 +02:00
}
2019-02-20 20:24:02 +01:00
return entry
2018-07-24 11:24:40 +02:00
}
2021-05-03 12:25:44 +02:00
// @ts-ignore webpack 5 typings needed
webpackConfig . entry = updatedEntry
2018-07-24 11:24:40 +02:00
}
2022-08-16 11:55:37 +02:00
if ( ! dev && typeof webpackConfig . entry === 'function' ) {
2020-02-17 22:16:19 +01:00
// entry is always a function
2022-08-16 11:55:37 +02:00
webpackConfig . entry = await webpackConfig . entry ( )
2019-04-22 20:57:02 +02:00
}
2018-01-30 16:40:52 +01:00
return webpackConfig
2016-10-14 17:05:08 +02:00
}