webpack-config.ts 49.4 KB
Newer Older
1
import { codeFrameColumns } from '@babel/code-frame'
2
import ReactRefreshWebpackPlugin from '@next/react-refresh-utils/ReactRefreshWebpackPlugin'
3
import crypto from 'crypto'
4
import { readFileSync, realpathSync } from 'fs'
5
import chalk from 'next/dist/compiled/chalk'
6
import semver from 'next/dist/compiled/semver'
7
import TerserPlugin from 'next/dist/compiled/terser-webpack-plugin'
8
import path from 'path'
9
import webpack from 'webpack'
10
import type { Configuration } from 'webpack'
11
import {
12
  DOT_NEXT_ALIAS,
13 14 15 16
  NEXT_PROJECT_ROOT,
  NEXT_PROJECT_ROOT_DIST_CLIENT,
  PAGES_DIR_ALIAS,
} from '../lib/constants'
17
import { fileExists } from '../lib/file-exists'
18 19
import { getPackageVersion } from '../lib/get-package-version'
import { Rewrite } from '../lib/load-custom-routes'
20
import { resolveRequest } from '../lib/resolve-request'
21
import { getTypeScriptConfiguration } from '../lib/typescript/getTypeScriptConfiguration'
22 23
import {
  CLIENT_STATIC_FILES_RUNTIME_MAIN,
24
  CLIENT_STATIC_FILES_RUNTIME_POLYFILLS,
25 26 27
  CLIENT_STATIC_FILES_RUNTIME_WEBPACK,
  REACT_LOADABLE_MANIFEST,
  SERVERLESS_DIRECTORY,
28
  SERVER_DIRECTORY,
29
} from '../next-server/lib/constants'
30
import { execOnce } from '../next-server/lib/utils'
J
Joe Haddad 已提交
31
import { findPageFile } from '../server/lib/find-page-file'
32
import { WebpackEntrypoints } from './entries'
33
import * as Log from './output/log'
J
Joe Haddad 已提交
34 35 36 37 38
import {
  collectPlugins,
  PluginMetaData,
  VALID_MIDDLEWARE,
} from './plugins/collect-plugins'
39
import { build as buildConfiguration } from './webpack/config'
40
import { __overrideCssConfiguration } from './webpack/config/blocks/css/overrideCssConfiguration'
J
Joe Haddad 已提交
41
import { pluginLoaderOptions } from './webpack/loaders/next-plugin-loader'
42 43
import BuildManifestPlugin from './webpack/plugins/build-manifest-plugin'
import ChunkNamesPlugin from './webpack/plugins/chunk-names-plugin'
J
Joe Haddad 已提交
44
import { CssMinimizerPlugin } from './webpack/plugins/css-minimizer-plugin'
45
import { JsConfigPathsPlugin } from './webpack/plugins/jsconfig-paths-plugin'
46
import { DropClientPage } from './webpack/plugins/next-drop-client-page-plugin'
47 48 49
import NextJsSsrImportPlugin from './webpack/plugins/nextjs-ssr-import'
import NextJsSSRModuleCachePlugin from './webpack/plugins/nextjs-ssr-module-cache'
import PagesManifestPlugin from './webpack/plugins/pages-manifest-plugin'
50
import { ProfilingPlugin } from './webpack/plugins/profiling-plugin'
51 52
import { ReactLoadablePlugin } from './webpack/plugins/react-loadable-plugin'
import { ServerlessPlugin } from './webpack/plugins/serverless-plugin'
53
import WebpackConformancePlugin, {
54
  DuplicatePolyfillsConformanceCheck,
55
  GranularChunksConformanceCheck,
56
  MinificationConformanceCheck,
57
  ReactSyncScriptsConformanceCheck,
58
} from './webpack/plugins/webpack-conformance-plugin'
J
Joe Haddad 已提交
59
import { WellKnownErrorsPlugin } from './webpack/plugins/wellknown-errors-plugin'
60

61
type ExcludesFalse = <T>(x: T | false) => x is T
62

63 64
const isWebpack5 = parseInt(webpack.version!) === 5

65 66 67 68 69 70
const escapePathVariables = (value: any) => {
  return typeof value === 'string'
    ? value.replace(/\[(\\*[\w:]+\\*)\]/gi, '[\\$1\\]')
    : value
}

71 72 73 74 75 76 77 78 79
const devtoolRevertWarning = execOnce((devtool: 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' +
      'Read more: https://err.sh/next.js/improper-devtool'
  )
})

80
function parseJsonFile(filePath: string) {
G
json5  
Guy Bedford 已提交
81
  const JSON5 = require('next/dist/compiled/json5')
82
  const contents = readFileSync(filePath, 'utf8')
83 84 85 86 87 88

  // Special case an empty file
  if (contents.trim() === '') {
    return {}
  }

89 90 91 92 93 94 95 96
  try {
    return JSON5.parse(contents)
  } catch (err) {
    const codeFrame = codeFrameColumns(
      String(contents),
      { start: { line: err.lineNumber, column: err.columnNumber } },
      { message: err.message, highlightCode: true }
    )
97
    throw new Error(`Failed to parse "${filePath}":\n${codeFrame}`)
98
  }
99 100
}

101
function getOptimizedAliases(isServer: boolean): { [pkg: string]: string } {
102 103 104 105
  if (isServer) {
    return {}
  }

106
  const stubWindowFetch = path.join(__dirname, 'polyfills', 'fetch', 'index.js')
107 108 109
  const stubObjectAssign = path.join(__dirname, 'polyfills', 'object-assign.js')

  const shimAssign = path.join(__dirname, 'polyfills', 'object.assign')
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
  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'),

135 136
      // Replace: full URL polyfill with platform-based polyfill
      url: require.resolve('native-url'),
137 138
    }
  )
139 140
}

141
type ClientEntries = {
142
  [key: string]: string | string[]
143 144
}

145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
export function attachReactRefresh(
  webpackConfig: webpack.Configuration,
  targetLoader: webpack.RuleSetUseItem
) {
  let injections = 0
  const reactRefreshLoaderName = '@next/react-refresh-utils/loader'
  const reactRefreshLoader = require.resolve(reactRefreshLoaderName)
  webpackConfig.module?.rules.forEach((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]

      // inject / input: [other, babel] output: [other, refresh, babel]:
      rule.use.splice(idx, 0, reactRefreshLoader)
    }
  })

  if (injections) {
    Log.info(
      `automatically enabled Fast Refresh for ${injections} custom loader${
        injections > 1 ? 's' : ''
      }`
    )
  }
}

185 186 187 188 189
export default async function getBaseWebpackConfig(
  dir: string,
  {
    buildId,
    config,
J
JJ Kasper 已提交
190 191 192 193
    dev = false,
    isServer = false,
    pagesDir,
    tracer,
194
    target = 'server',
195
    reactProductionProfiling = false,
196
    entrypoints,
197
    rewrites,
198 199 200
  }: {
    buildId: string
    config: any
J
JJ Kasper 已提交
201 202 203
    dev?: boolean
    isServer?: boolean
    pagesDir: string
204
    target?: string
J
JJ Kasper 已提交
205
    tracer?: any
206
    reactProductionProfiling?: boolean
207
    entrypoints: WebpackEntrypoints
208
    rewrites: Rewrite[]
209 210
  }
): Promise<webpack.Configuration> {
211 212
  const productionBrowserSourceMaps =
    config.experimental.productionBrowserSourceMaps && !isServer
213 214 215
  let plugins: PluginMetaData[] = []
  let babelPresetPlugins: { dir: string; config: any }[] = []

216 217
  const hasRewrites = rewrites.length > 0 || dev

218 219 220 221 222 223 224 225 226 227 228 229 230
  if (config.experimental.plugins) {
    plugins = await collectPlugins(dir, config.env, config.plugins)
    pluginLoaderOptions.plugins = plugins

    for (const plugin of plugins) {
      if (plugin.middleware.includes('babel-preset-build')) {
        babelPresetPlugins.push({
          dir: plugin.directory,
          config: plugin.config,
        })
      }
    }
  }
231

232 233 234 235
  // Normalize defined image host to end in slash
  if (config.images?.path) {
    if (config.images.path[config.images.path.length - 1] !== '/') {
      config.images.path += '/'
A
Alex Castle 已提交
236 237
    }
  }
238

239 240 241 242 243 244 245
  const reactVersion = await getPackageVersion({ cwd: dir, name: 'react' })
  const hasReactRefresh: boolean = dev && !isServer
  const hasJsxRuntime: boolean =
    Boolean(reactVersion) &&
    // 17.0.0-rc.0 had a breaking change not compatible with Next.js, but was
    // fixed in rc.1.
    semver.gte(reactVersion!, '17.0.0-rc.1')
246

247
  const distDir = path.join(dir, config.distDir)
T
Tim Neutkens 已提交
248 249
  const defaultLoaders = {
    babel: {
250
      loader: 'next-babel-loader',
251 252 253
      options: {
        isServer,
        distDir,
J
JJ Kasper 已提交
254
        pagesDir,
255
        cwd: dir,
256
        // Webpack 5 has a built-in loader cache
257
        cache: !isWebpack5,
258 259
        babelPresetPlugins,
        hasModern: !!config.experimental.modern,
260
        development: dev,
261
        hasReactRefresh,
262
        hasJsxRuntime,
263
      },
264
    },
265
    // Backwards compat
266
    hotSelfAccept: {
267 268
      loader: 'noop-loader',
    },
T
Tim Neutkens 已提交
269 270
  }

271 272 273 274 275 276
  const babelIncludeRegexes: RegExp[] = [
    /next[\\/]dist[\\/]next-server[\\/]lib/,
    /next[\\/]dist[\\/]client/,
    /next[\\/]dist[\\/]pages/,
    /[\\/](strip-ansi|ansi-regex)[\\/]/,
    ...(config.experimental.plugins
J
Joe Haddad 已提交
277
      ? VALID_MIDDLEWARE.map((name) => new RegExp(`src(\\\\|/)${name}`))
278 279 280
      : []),
  ]

T
Tim Neutkens 已提交
281 282 283
  // Support for NODE_PATH
  const nodePathList = (process.env.NODE_PATH || '')
    .split(process.platform === 'win32' ? ';' : ':')
J
Joe Haddad 已提交
284
    .filter((p) => !!p)
T
Tim Neutkens 已提交
285

286 287 288 289 290 291
  const isServerless = target === 'serverless'
  const isServerlessTrace = target === 'experimental-serverless-trace'
  // Intentionally not using isTargetLikeServerless helper
  const isLikeServerless = isServerless || isServerlessTrace

  const outputDir = isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY
T
Tim Neutkens 已提交
292
  const outputPath = path.join(distDir, isServer ? outputDir : '')
293
  const totalPages = Object.keys(entrypoints).length
294
  const clientEntries = !isServer
295
    ? ({
296 297 298
        // Backwards compatibility
        'main.js': [],
        [CLIENT_STATIC_FILES_RUNTIME_MAIN]:
299 300 301 302 303 304 305 306
          `./` +
          path
            .relative(
              dir,
              path.join(
                NEXT_PROJECT_ROOT_DIST_CLIENT,
                dev ? `next-dev.js` : 'next.js'
              )
307
            )
308
            .replace(/\\/g, '/'),
309 310
        [CLIENT_STATIC_FILES_RUNTIME_POLYFILLS]: path.join(
          NEXT_PROJECT_ROOT_DIST_CLIENT,
311
          'polyfills.js'
312
        ),
313
      } as ClientEntries)
314
    : undefined
N
nkzawa 已提交
315

316
  let typeScriptPath: string | undefined
317
  try {
318
    typeScriptPath = resolveRequest('typescript', `${dir}/`)
319
  } catch (_) {}
320
  const tsConfigPath = path.join(dir, 'tsconfig.json')
321 322 323
  const useTypeScript = Boolean(
    typeScriptPath && (await fileExists(tsConfigPath))
  )
324

325 326 327
  let jsConfig
  // jsconfig is a subset of tsconfig
  if (useTypeScript) {
328
    const ts = (await import(typeScriptPath!)) as typeof import('typescript')
329 330
    const tsConfig = await getTypeScriptConfiguration(ts, tsConfigPath)
    jsConfig = { compilerOptions: tsConfig.options }
331 332 333 334
  }

  const jsConfigPath = path.join(dir, 'jsconfig.json')
  if (!useTypeScript && (await fileExists(jsConfigPath))) {
335
    jsConfig = parseJsonFile(jsConfigPath)
336 337 338 339 340 341 342
  }

  let resolvedBaseUrl
  if (jsConfig?.compilerOptions?.baseUrl) {
    resolvedBaseUrl = path.resolve(dir, jsConfig.compilerOptions.baseUrl)
  }

343
  function getReactProfilingInProduction() {
344
    if (reactProductionProfiling) {
345 346 347 348 349 350 351
      return {
        'react-dom$': 'react-dom/profiling',
        'scheduler/tracing': 'scheduler/tracing-profiling',
      }
    }
  }

352 353 354 355
  const clientResolveRewrites = require.resolve(
    'next/dist/next-server/lib/router/utils/resolve-rewrites'
  )

T
Tim Neutkens 已提交
356
  const resolveConfig = {
357
    // Disable .mjs for node_modules bundling
358
    extensions: isServer
359 360 361
      ? [
          '.js',
          '.mjs',
362
          ...(useTypeScript ? ['.tsx', '.ts'] : []),
363 364 365 366 367 368 369
          '.jsx',
          '.json',
          '.wasm',
        ]
      : [
          '.mjs',
          '.js',
370
          ...(useTypeScript ? ['.tsx', '.ts'] : []),
371 372 373 374
          '.jsx',
          '.json',
          '.wasm',
        ],
T
Tim Neutkens 已提交
375 376
    modules: [
      'node_modules',
377
      ...nodePathList, // Support for NODE_PATH environment variable
T
Tim Neutkens 已提交
378 379
    ],
    alias: {
380 381
      // These aliases make sure the wrapper module is not included in the bundles
      // Which makes bundles slightly smaller, but also skips parsing a module that we know will result in this alias
382
      'next/head': 'next/dist/next-server/lib/head.js',
383
      'next/router': 'next/dist/client/router.js',
384 385
      'next/config': 'next/dist/next-server/lib/runtime-config.js',
      'next/dynamic': 'next/dist/next-server/lib/dynamic.js',
386 387 388
      next: NEXT_PROJECT_ROOT,
      ...(isWebpack5 && !isServer
        ? {
T
Tim Neutkens 已提交
389 390 391 392 393
            stream: require.resolve('stream-browserify'),
            path: require.resolve('path-browserify'),
            crypto: require.resolve('crypto-browserify'),
            buffer: require.resolve('buffer'),
            vm: require.resolve('vm-browserify'),
394 395
          }
        : undefined),
J
JJ Kasper 已提交
396
      [PAGES_DIR_ALIAS]: pagesDir,
397
      [DOT_NEXT_ALIAS]: distDir,
398
      ...getOptimizedAliases(isServer),
399
      ...getReactProfilingInProduction(),
400 401 402
      [clientResolveRewrites]: hasRewrites
        ? clientResolveRewrites
        : require.resolve('next/dist/client/dev/noop.js'),
403
    },
404
    mainFields: isServer ? ['main', 'module'] : ['browser', 'module', 'main'],
405 406 407
    plugins: isWebpack5
      ? // webpack 5+ has the PnP resolver built-in by default:
        []
408
      : [require('pnp-webpack-plugin')],
T
Tim Neutkens 已提交
409 410
  }

T
Tim Neutkens 已提交
411 412
  const webpackMode = dev ? 'development' : 'production'

413
  const terserOptions: any = {
414 415 416 417 418 419 420 421
    parse: {
      ecma: 8,
    },
    compress: {
      ecma: 5,
      warnings: false,
      // The following two options are known to break valid JavaScript code
      comparisons: false,
422
      inline: 2, // https://github.com/vercel/next.js/issues/7178#issuecomment-493048965
423 424 425 426 427 428 429 430 431 432 433
    },
    mangle: { safari10: true },
    output: {
      ecma: 5,
      safari10: true,
      comments: false,
      // Fixes usage of Emoji and certain Regex
      ascii_only: true,
    },
  }

434 435 436 437 438 439 440 441 442 443 444
  const isModuleCSS = (module: { type: string }): boolean => {
    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`
    )
  }

445 446 447 448 449 450 451 452
  // Contains various versions of the Webpack SplitChunksPlugin used in different build types
  const splitChunksConfigs: {
    [propName: string]: webpack.Options.SplitChunksOptions
  } = {
    dev: {
      cacheGroups: {
        default: false,
        vendors: false,
453 454
        // In webpack 5 vendors was renamed to defaultVendors
        defaultVendors: false,
455 456
      },
    },
457
    prodGranular: {
458
      chunks: 'all',
459 460 461
      cacheGroups: {
        default: false,
        vendors: false,
462 463
        // In webpack 5 vendors was renamed to defaultVendors
        defaultVendors: false,
464
        framework: {
465
          chunks: 'all',
466
          name: 'framework',
A
Alex Castle 已提交
467
          // This regex ignores nested copies of framework libraries so they're
468
          // bundled with their issuer.
469
          // https://github.com/vercel/next.js/pull/9012
470
          test: /(?<!node_modules.*)[\\/]node_modules[\\/](react|react-dom|scheduler|prop-types|use-subscription)[\\/]/,
471
          priority: 40,
J
Joe Haddad 已提交
472 473 474
          // Don't let webpack eliminate this chunk (prevents this chunk from
          // becoming a part of the commons chunk)
          enforce: true,
475 476 477 478 479 480 481 482
        },
        lib: {
          test(module: { size: Function; identifier: Function }): boolean {
            return (
              module.size() > 160000 &&
              /node_modules[/\\]/.test(module.identifier())
            )
          },
J
Joe Haddad 已提交
483 484 485 486 487 488
          name(module: {
            type: string
            libIdent?: Function
            updateHash: (hash: crypto.Hash) => void
          }): string {
            const hash = crypto.createHash('sha1')
489
            if (isModuleCSS(module)) {
J
Joe Haddad 已提交
490 491 492 493 494 495 496 497 498 499 500
              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 }))
            }

501
            return hash.digest('hex').substring(0, 8)
502 503 504 505 506 507
          },
          priority: 30,
          minChunks: 1,
          reuseExistingChunk: true,
        },
        commons: {
508
          name: 'commons',
509 510 511 512 513
          minChunks: totalPages,
          priority: 20,
        },
        shared: {
          name(module, chunks) {
514
            return (
515
              crypto
516 517 518 519 520 521 522 523
                .createHash('sha1')
                .update(
                  chunks.reduce(
                    (acc: string, chunk: webpack.compilation.Chunk) => {
                      return acc + chunk.name
                    },
                    ''
                  )
524
                )
525
                .digest('hex') + (isModuleCSS(module) ? '_CSS' : '')
526
            )
527 528 529 530 531 532
          },
          priority: 10,
          minChunks: 2,
          reuseExistingChunk: true,
        },
      },
533 534
      maxInitialRequests: 25,
      minSize: 20000,
535
    },
536 537 538 539 540 541 542
  }

  // Select appropriate SplitChunksPlugin config for this build
  let splitChunksConfig: webpack.Options.SplitChunksOptions
  if (dev) {
    splitChunksConfig = splitChunksConfigs.dev
  } else {
543
    splitChunksConfig = splitChunksConfigs.prodGranular
544 545
  }

546 547 548 549 550
  const crossOrigin =
    !config.crossOrigin && config.experimental.modern
      ? 'anonymous'
      : config.crossOrigin

551 552 553 554 555
  let customAppFile: string | null = await findPageFile(
    pagesDir,
    '/_app',
    config.pageExtensions
  )
J
Joe Haddad 已提交
556
  if (customAppFile) {
557
    customAppFile = path.resolve(path.join(pagesDir, customAppFile))
J
Joe Haddad 已提交
558 559
  }

560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576
  const conformanceConfig = Object.assign(
    {
      ReactSyncScriptsConformanceCheck: {
        enabled: true,
      },
      MinificationConformanceCheck: {
        enabled: true,
      },
      DuplicatePolyfillsConformanceCheck: {
        enabled: true,
        BlockedAPIToBePolyfilled: Object.assign(
          [],
          ['fetch'],
          config.conformance?.DuplicatePolyfillsConformanceCheck
            ?.BlockedAPIToBePolyfilled || []
        ),
      },
577 578 579
      GranularChunksConformanceCheck: {
        enabled: true,
      },
580 581 582
    },
    config.conformance
  )
583

584 585 586 587
  function handleExternals(context: any, request: any, callback: any) {
    if (request === 'next') {
      return callback(undefined, `commonjs ${request}`)
    }
K
k-kawakami 已提交
588

589 590 591 592
    const notExternalModules = [
      'next/app',
      'next/document',
      'next/link',
A
Alex Castle 已提交
593
      'next/image',
594 595 596 597 598 599 600 601
      'next/error',
      'string-hash',
      'next/constants',
    ]

    if (notExternalModules.indexOf(request) !== -1) {
      return callback()
    }
602

603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623
    // 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))
    const isLikelyNextExternal =
      isLocal && /[/\\]next-server[/\\]/.test(request)

    // 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.
    if (isLocal && !isLikelyNextExternal) {
      return callback()
    }
K
k-kawakami 已提交
624

625 626 627 628 629 630 631 632 633 634 635 636
    // Resolve the import with the webpack provided context, this
    // ensures we're resolving the correct version when multiple
    // exist.
    let res: string
    try {
      res = resolveRequest(request, `${context}/`)
    } catch (err) {
      // If the request cannot be resolved, we need to tell webpack to
      // "bundle" it so that webpack shows an error (that it cannot be
      // resolved).
      return callback()
    }
K
k-kawakami 已提交
637

638 639 640 641 642
    // Same as above, if the request cannot be resolved we need to have
    // webpack "bundle" it so it surfaces the not found error.
    if (!res) {
      return callback()
    }
643

644 645 646 647 648 649 650
    let isNextExternal: boolean = false
    if (isLocal) {
      // we need to process next-server/lib/router/router so that
      // the DefinePlugin can inject process.env values
      isNextExternal = /next[/\\]dist[/\\]next-server[/\\](?!lib[/\\]router[/\\]router)/.test(
        res
      )
651

652 653 654 655
      if (!isNextExternal) {
        return callback()
      }
    }
656

657 658 659 660 661 662 663 664 665 666 667 668 669 670 671
    // `isNextExternal` special cases Next.js' internal requires that
    // should not be bundled. We need to skip the base resolve routine
    // to prevent it from being bundled (assumes Next.js version cannot
    // mismatch).
    if (!isNextExternal) {
      // 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).
      let baseRes: string | null
      try {
        baseRes = resolveRequest(request, `${dir}/`)
      } catch (err) {
        baseRes = null
      }
672

673 674 675
      // 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.
676 677 678 679 680 681
      if (
        !baseRes ||
        (baseRes !== res &&
          // if res and baseRes are symlinks they could point to the the same file
          realpathSync(baseRes) !== realpathSync(res))
      ) {
682 683 684
        return callback()
      }
    }
K
k-kawakami 已提交
685

686 687 688 689 690 691 692 693 694
    // Default pages have to be transpiled
    if (
      !res.match(/next[/\\]dist[/\\]next-server[/\\]/) &&
      (res.match(/[/\\]next[/\\]dist[/\\]/) ||
        // This is the @babel/plugin-transform-runtime "helpers: true" option
        res.match(/node_modules[/\\]@babel[/\\]runtime[/\\]/))
    ) {
      return callback()
    }
K
k-kawakami 已提交
695

696 697 698 699 700 701 702
    // Webpack itself has to be compiled because it doesn't always use module relative paths
    if (
      res.match(/node_modules[/\\]webpack/) ||
      res.match(/node_modules[/\\]css-loader/)
    ) {
      return callback()
    }
703

704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721
    // Anything else that is standard JavaScript within `node_modules`
    // can be externalized.
    if (isNextExternal || res.match(/node_modules[/\\].*\.js$/)) {
      const externalRequest = isNextExternal
        ? // Generate Next.js external import
          path.posix.join(
            'next',
            'dist',
            path
              .relative(
                // Root of Next.js package:
                path.join(__dirname, '..'),
                res
              )
              // Windows path normalization
              .replace(/\\/g, '/')
          )
        : request
K
k-kawakami 已提交
722

723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742
      return callback(undefined, `commonjs ${externalRequest}`)
    }

    // Default behavior: bundle the code!
    callback()
  }

  let webpackConfig: webpack.Configuration = {
    externals: !isServer
      ? // 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']
      : !isServerless
      ? [
          isWebpack5
            ? ({ context, request }, callback) =>
                handleExternals(context, request, callback)
            : (context, request, callback) =>
                handleExternals(context, request, callback),
743 744
        ]
      : [
745 746
          // 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
747
          '@ampproject/toolbox-optimizer', // except this one
748
        ],
749
    optimization: {
750 751
      // Webpack 5 uses a new property for the same functionality
      ...(isWebpack5 ? { emitOnErrors: !dev } : { noEmitOnErrors: dev }),
752 753 754 755
      checkWasmTypes: false,
      nodeEnv: false,
      splitChunks: isServer ? false : splitChunksConfig,
      runtimeChunk: isServer
756 757 758
        ? isWebpack5 && !isLikeServerless
          ? { name: 'webpack-runtime' }
          : undefined
759 760 761
        : { name: CLIENT_STATIC_FILES_RUNTIME_WEBPACK },
      minimize: !(dev || isServer),
      minimizer: [
J
Joe Haddad 已提交
762
        // Minify JavaScript
763
        new TerserPlugin({
J
Joe Haddad 已提交
764
          extractComments: false,
765 766
          cache: path.join(distDir, 'cache', 'next-minifier'),
          parallel: config.experimental.cpus || true,
767 768
          terserOptions,
        }),
J
Joe Haddad 已提交
769
        // Minify CSS
770 771 772 773 774 775 776 777 778
        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,
J
Joe Haddad 已提交
779
            },
780 781 782
          },
        }),
      ],
783
    },
N
nkzawa 已提交
784
    context: dir,
785 786 787
    node: {
      setImmediate: false,
    },
788
    // Kept as function to be backwards compatible
T
Tim Neutkens 已提交
789 790
    entry: async () => {
      return {
791 792
        ...(clientEntries ? clientEntries : {}),
        ...entrypoints,
793 794 795 796 797 798 799
        ...(isServer
          ? {
              'init-server.js': 'next-plugin-loader?middleware=on-init-server!',
              'on-error-server.js':
                'next-plugin-loader?middleware=on-error-server!',
            }
          : {}),
T
Tim Neutkens 已提交
800 801
      }
    },
802
    watchOptions: {
803
      ignored: ['**/.git/**', '**/node_modules/**', '**/.next/**'],
804
    },
N
nkzawa 已提交
805
    output: {
806 807 808 809 810 811 812 813 814 815 816 817 818
      ...(isWebpack5
        ? {
            environment: {
              arrowFunction: false,
              bigIntLiteral: false,
              const: false,
              destructuring: false,
              dynamicImport: false,
              forOf: false,
              module: false,
            },
          }
        : {}),
819
      path: outputPath,
820
      // On the server we don't use the chunkhash
821 822 823
      filename: isServer
        ? '[name].js'
        : `static/chunks/[name]${dev ? '' : '-[chunkhash]'}.js`,
824 825
      library: isServer ? undefined : '_N_E',
      libraryTarget: isServer ? 'commonjs2' : 'assign',
826 827 828 829 830 831
      hotUpdateChunkFilename: isWebpack5
        ? 'static/webpack/[id].[fullhash].hot-update.js'
        : 'static/webpack/[id].[hash].hot-update.js',
      hotUpdateMainFilename: isWebpack5
        ? 'static/webpack/[fullhash].hot-update.json'
        : 'static/webpack/[hash].hot-update.json',
832
      // This saves chunks with the name given via `import()`
833 834 835
      chunkFilename: isServer
        ? `${dev ? '[name]' : '[name].[contenthash]'}.js`
        : `static/chunks/${dev ? '[name]' : '[name].[contenthash]'}.js`,
A
Andy 已提交
836
      strictModuleExceptionHandling: true,
837
      crossOriginLoading: crossOrigin,
838
      futureEmitAssets: !dev,
839
      webassemblyModuleFilename: 'static/wasm/[modulehash].wasm',
N
nkzawa 已提交
840
    },
841
    performance: false,
T
Tim Neutkens 已提交
842
    resolve: resolveConfig,
N
nkzawa 已提交
843
    resolveLoader: {
844 845 846
      // The loaders Next.js provides
      alias: [
        'emit-file-loader',
847
        'error-loader',
848 849 850 851 852
        'next-babel-loader',
        'next-client-pages-loader',
        'next-data-loader',
        'next-serverless-loader',
        'noop-loader',
853
        'next-plugin-loader',
854 855 856
      ].reduce((alias, loader) => {
        // using multiple aliases to replace `resolveLoader.modules`
        alias[loader] = path.join(__dirname, 'webpack', 'loaders', loader)
857

858 859
        return alias
      }, {} as Record<string, string>),
N
Naoyuki Kanezawa 已提交
860
      modules: [
861
        'node_modules',
862 863
        ...nodePathList, // Support for NODE_PATH environment variable
      ],
864
      plugins: isWebpack5 ? [] : [require('pnp-webpack-plugin')],
N
nkzawa 已提交
865 866
    },
    module: {
867
      rules: [
J
JJ Kasper 已提交
868 869 870 871 872 873 874 875 876 877 878 879
        ...(isWebpack5
          ? [
              // TODO: FIXME: do NOT webpack 5 support with this
              // x-ref: https://github.com/webpack/webpack/issues/11467
              {
                test: /\.m?js/,
                resolve: {
                  fullySpecified: false,
                },
              } as any,
            ]
          : []),
T
Tim Neutkens 已提交
880
        {
881
          test: /\.(tsx|ts|js|mjs|jsx)$/,
882
          include: [dir, ...babelIncludeRegexes],
883 884
          exclude: (excludePath: string) => {
            if (babelIncludeRegexes.some((r) => r.test(excludePath))) {
885 886
              return false
            }
887
            return /node_modules/.test(excludePath)
888
          },
889 890 891 892 893
          use: config.experimental.babelMultiThread
            ? [
                // Move Babel transpilation into a thread pool (2 workers, unlimited batch size).
                // Applying a cache to the off-thread work avoids paying transfer costs for unchanged modules.
                {
G
Guy Bedford 已提交
894
                  loader: 'next/dist/compiled/cache-loader',
895 896 897 898 899 900 901 902 903
                  options: {
                    cacheContext: dir,
                    cacheDirectory: path.join(dir, '.next', 'cache', 'webpack'),
                    cacheIdentifier: `webpack${isServer ? '-server' : ''}${
                      config.experimental.modern ? '-hasmodern' : ''
                    }`,
                  },
                },
                {
G
Guy Bedford 已提交
904
                  loader: require.resolve('next/dist/compiled/thread-loader'),
905 906 907 908 909
                  options: {
                    workers: 2,
                    workerParallelJobs: Infinity,
                  },
                },
910 911 912
                hasReactRefresh
                  ? require.resolve('@next/react-refresh-utils/loader')
                  : '',
913
                defaultLoaders.babel,
914 915 916 917
              ].filter(Boolean)
            : hasReactRefresh
            ? [
                require.resolve('@next/react-refresh-utils/loader'),
918
                defaultLoaders.babel,
919 920
              ]
            : defaultLoaders.babel,
921
        },
922
      ].filter(Boolean),
N
nkzawa 已提交
923
    },
T
Tim Neutkens 已提交
924
    plugins: [
925
      hasReactRefresh && new ReactRefreshWebpackPlugin(),
926 927 928 929 930 931 932 933
      // Makes sure `Buffer` is polyfilled in client-side bundles (same behavior as webpack 4)
      isWebpack5 &&
        !isServer &&
        new webpack.ProvidePlugin({ Buffer: ['buffer', 'Buffer'] }),
      // Makes sure `process` is polyfilled in client-side bundles (same behavior as webpack 4)
      isWebpack5 &&
        !isServer &&
        new webpack.ProvidePlugin({ process: ['process'] }),
934
      // This plugin makes sure `output.filename` is used for entry chunks
T
Tim Neutkens 已提交
935
      !isWebpack5 && new ChunkNamesPlugin(),
936
      new webpack.DefinePlugin({
937 938 939 940 941 942 943 944 945
        ...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
          },
          {}
        ),
946
        ...Object.keys(config.env).reduce((acc, key) => {
947
          if (/^(?:NODE_.+)|^(?:__.+)$/i.test(key)) {
948
            throw new Error(
949
              `The key "${key}" under "env" in next.config.js is not allowed. https://err.sh/vercel/next.js/env-key-not-allowed`
950
            )
951 952 953
          }

          return {
954
            ...acc,
955
            [`process.env.${key}`]: JSON.stringify(config.env[key]),
956
          }
957
        }, {}),
958
        'process.env.NODE_ENV': JSON.stringify(webpackMode),
959
        'process.env.__NEXT_CROSS_ORIGIN': JSON.stringify(crossOrigin),
960
        'process.browser': JSON.stringify(!isServer),
J
JJ Kasper 已提交
961 962 963
        'process.env.__NEXT_TEST_MODE': JSON.stringify(
          process.env.__NEXT_TEST_MODE
        ),
964
        // This is used in client/dev-error-overlay/hot-dev-client.js to replace the dist directory
965 966 967 968 969
        ...(dev && !isServer
          ? {
              'process.env.__NEXT_DIST_DIR': JSON.stringify(distDir),
            }
          : {}),
J
Jan Potoms 已提交
970
        'process.env.__NEXT_TRAILING_SLASH': JSON.stringify(
971
          config.trailingSlash
J
Jan Potoms 已提交
972
        ),
973 974 975 976 977 978 979 980 981
        'process.env.__NEXT_MODERN_BUILD': JSON.stringify(
          config.experimental.modern && !dev
        ),
        'process.env.__NEXT_BUILD_INDICATOR': JSON.stringify(
          config.devIndicators.buildActivity
        ),
        'process.env.__NEXT_PRERENDER_INDICATOR': JSON.stringify(
          config.devIndicators.autoPrerender
        ),
982 983 984
        'process.env.__NEXT_PLUGINS': JSON.stringify(
          config.experimental.plugins
        ),
G
Gerald Monaco 已提交
985 986 987
        'process.env.__NEXT_STRICT_MODE': JSON.stringify(
          config.reactStrictMode
        ),
988 989 990
        'process.env.__NEXT_REACT_MODE': JSON.stringify(
          config.experimental.reactMode
        ),
P
Prateek Bhatnagar 已提交
991
        'process.env.__NEXT_OPTIMIZE_FONTS': JSON.stringify(
992
          config.experimental.optimizeFonts && !dev
P
Prateek Bhatnagar 已提交
993
        ),
994 995 996
        'process.env.__NEXT_OPTIMIZE_IMAGES': JSON.stringify(
          config.experimental.optimizeImages
        ),
997 998 999
        'process.env.__NEXT_SCROLL_RESTORATION': JSON.stringify(
          config.experimental.scrollRestoration
        ),
A
Alex Castle 已提交
1000
        'process.env.__NEXT_IMAGE_OPTS': JSON.stringify(config.images),
1001
        'process.env.__NEXT_ROUTER_BASEPATH': JSON.stringify(config.basePath),
1002
        'process.env.__NEXT_HAS_REWRITES': JSON.stringify(hasRewrites),
1003
        'process.env.__NEXT_I18N_SUPPORT': JSON.stringify(
1004 1005
          !!config.experimental.i18n
        ),
1006 1007 1008
        'process.env.__NEXT_I18N_DOMAINS': JSON.stringify(
          config.experimental.i18n.domains
        ),
J
Joe Haddad 已提交
1009 1010 1011
        'process.env.__NEXT_ANALYTICS_ID': JSON.stringify(
          config.experimental.analyticsId
        ),
1012
        ...(isServer
1013 1014 1015 1016 1017 1018
          ? {
              // 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),
            }
1019
          : undefined),
1020
        // stub process.env with proxy to warn a missing value is
1021 1022
        // being accessed in development mode
        ...(config.experimental.pageEnv && process.env.NODE_ENV !== 'production'
1023
          ? {
1024
              'process.env': `
1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035
            new Proxy(${isServer ? '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://err.sh/next.js/missing-env-value\`)
                }
                return target[prop]
              }
            })
          `,
            }
          : {}),
1036
      }),
1037 1038 1039 1040
      !isServer &&
        new ReactLoadablePlugin({
          filename: REACT_LOADABLE_MANIFEST,
        }),
1041
      !isServer && new DropClientPage(),
1042 1043 1044 1045 1046
      // 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
      config.future.excludeDefaultMomentLocales &&
1047 1048 1049 1050
        new webpack.IgnorePlugin({
          resourceRegExp: /^\.\/locale$/,
          contextRegExp: /moment$/,
        }),
1051 1052 1053 1054 1055 1056 1057
      ...(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')
1058
            const devPlugins = [new NextJsRequireCacheHotReloader()]
1059

1060
            if (!isServer) {
1061 1062
              devPlugins.push(new webpack.HotModuleReplacementPlugin())
            }
1063

1064 1065 1066
            return devPlugins
          })()
        : []),
1067 1068
      // Webpack 5 no longer requires this plugin in production:
      !isWebpack5 && !dev && new webpack.HashedModuleIdsPlugin(),
1069 1070
      !dev &&
        new webpack.IgnorePlugin({
1071 1072
          resourceRegExp: /react-is/,
          contextRegExp: /(next-server|next)[\\/]dist[\\/]/,
1073
        }),
J
Joe Haddad 已提交
1074
      isServerless && isServer && new ServerlessPlugin(),
1075
      isServer && new PagesManifestPlugin(isLikeServerless),
1076 1077
      !isWebpack5 &&
        target === 'server' &&
1078 1079
        isServer &&
        new NextJsSSRModuleCachePlugin({ outputPath }),
1080
      isServer && new NextJsSsrImportPlugin(),
1081 1082 1083
      !isServer &&
        new BuildManifestPlugin({
          buildId,
1084
          rewrites,
1085 1086
          modern: config.experimental.modern,
        }),
1087 1088 1089
      tracer &&
        new ProfilingPlugin({
          tracer,
1090
        }),
1091 1092
      !isWebpack5 &&
        config.experimental.modern &&
1093 1094
        !isServer &&
        !dev &&
1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113
        (() => {
          const { NextEsmPlugin } = require('./webpack/plugins/next-esm-plugin')
          return new NextEsmPlugin({
            filename: (getFileName: Function | string) => (...args: any[]) => {
              const name =
                typeof getFileName === 'function'
                  ? getFileName(...args)
                  : getFileName

              return name.includes('.js')
                ? name.replace(/\.js$/, '.module.js')
                : escapePathVariables(
                    args[0].chunk.name.replace(/\.js$/, '.module.js')
                  )
            },
            chunkFilename: (inputChunkName: string) =>
              inputChunkName.replace(/\.js$/, '.module.js'),
          })
        })(),
P
Prateek Bhatnagar 已提交
1114 1115 1116
      config.experimental.optimizeFonts &&
        !dev &&
        isServer &&
J
Joe Haddad 已提交
1117 1118 1119 1120 1121 1122
        (function () {
          const {
            FontStylesheetGatheringPlugin,
          } = require('./webpack/plugins/font-stylesheet-gathering-plugin')
          return new FontStylesheetGatheringPlugin()
        })(),
1123
      config.experimental.conformance &&
1124
        !isWebpack5 &&
1125 1126
        !dev &&
        new WebpackConformancePlugin({
1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143
          tests: [
            !isServer &&
              conformanceConfig.MinificationConformanceCheck.enabled &&
              new MinificationConformanceCheck(),
            conformanceConfig.ReactSyncScriptsConformanceCheck.enabled &&
              new ReactSyncScriptsConformanceCheck({
                AllowedSources:
                  conformanceConfig.ReactSyncScriptsConformanceCheck
                    .allowedSources || [],
              }),
            !isServer &&
              conformanceConfig.DuplicatePolyfillsConformanceCheck.enabled &&
              new DuplicatePolyfillsConformanceCheck({
                BlockedAPIToBePolyfilled:
                  conformanceConfig.DuplicatePolyfillsConformanceCheck
                    .BlockedAPIToBePolyfilled,
              }),
1144 1145 1146 1147 1148
            !isServer &&
              conformanceConfig.GranularChunksConformanceCheck.enabled &&
              new GranularChunksConformanceCheck(
                splitChunksConfigs.prodGranular
              ),
1149
          ].filter(Boolean),
1150
        }),
J
Joe Haddad 已提交
1151
      new WellKnownErrorsPlugin(),
1152
    ].filter((Boolean as any) as ExcludesFalse),
1153
  }
1154

1155 1156 1157 1158 1159
  // Support tsconfig and jsconfig baseUrl
  if (resolvedBaseUrl) {
    webpackConfig.resolve?.modules?.push(resolvedBaseUrl)
  }

1160
  if (jsConfig?.compilerOptions?.paths && resolvedBaseUrl) {
1161
    webpackConfig.resolve?.plugins?.unshift(
1162 1163 1164 1165
      new JsConfigPathsPlugin(jsConfig.compilerOptions.paths, resolvedBaseUrl)
    )
  }

1166
  if (isWebpack5) {
1167
    // futureEmitAssets is on by default in webpack 5
1168
    delete webpackConfig.output?.futureEmitAssets
1169
    // webpack 5 no longer polyfills Node.js modules:
1170
    if (webpackConfig.node) delete webpackConfig.node.setImmediate
1171 1172 1173 1174 1175 1176 1177

    if (dev) {
      if (!webpackConfig.optimization) {
        webpackConfig.optimization = {}
      }
      webpackConfig.optimization.usedExports = false
    }
1178

1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193
    const nextPublicVariables = Object.keys(process.env).reduce(
      (prev: string, key: string) => {
        if (key.startsWith('NEXT_PUBLIC_')) {
          return `${prev}|${key}=${process.env[key]}`
        }
        return prev
      },
      ''
    )
    const nextEnvVariables = Object.keys(config.env).reduce(
      (prev: string, key: string) => {
        return `${prev}|${key}=${config.env[key]}`
      },
      ''
    )
1194

1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225
    const configVars = JSON.stringify({
      crossOrigin: config.crossOrigin,
      pageExtensions: config.pageExtensions,
      trailingSlash: config.trailingSlash,
      modern: config.experimental.modern,
      buildActivity: config.devIndicators.buildActivity,
      autoPrerender: config.devIndicators.autoPrerender,
      plugins: config.experimental.plugins,
      reactStrictMode: config.reactStrictMode,
      reactMode: config.experimental.reactMode,
      optimizeFonts: config.experimental.optimizeFonts,
      optimizeImages: config.experimental.optimizeImages,
      scrollRestoration: config.experimental.scrollRestoration,
      basePath: config.basePath,
      pageEnv: config.experimental.pageEnv,
      excludeDefaultMomentLocales: config.future.excludeDefaultMomentLocales,
      assetPrefix: config.assetPrefix,
      target,
      reactProductionProfiling,
    })

    const cache: any = {
      type: 'filesystem',
      // Includes:
      //  - Next.js version
      //  - NEXT_PUBLIC_ variable values (they affect caching) TODO: make this module usage only
      //  - next.config.js `env` key
      //  - next.config.js keys that affect compilation
      version: `${process.env.__NEXT_VERSION}|${nextPublicVariables}|${nextEnvVariables}|${configVars}`,
      cacheDirectory: path.join(dir, '.next', 'cache', 'webpack'),
    }
1226

1227 1228 1229 1230
    // Adds `next.config.js` as a buildDependency when custom webpack config is provided
    if (config.webpack && config.configFile) {
      cache.buildDependencies = {
        config: [config.configFile],
1231
      }
1232
    }
1233

1234
    webpackConfig.cache = cache
1235

1236 1237
    // @ts-ignore TODO: remove ignore when webpack 5 is stable
    webpackConfig.optimization.realContentHash = false
1238 1239
  }

1240 1241 1242 1243 1244
  webpackConfig = await buildConfiguration(webpackConfig, {
    rootDirectory: dir,
    customAppFile,
    isDevelopment: dev,
    isServer,
1245
    assetPrefix: config.assetPrefix || '',
1246
    sassOptions: config.sassOptions,
1247
    productionBrowserSourceMaps,
1248 1249
  })

1250
  let originalDevtool = webpackConfig.devtool
T
Tim Neutkens 已提交
1251
  if (typeof config.webpack === 'function') {
1252 1253 1254 1255 1256 1257 1258 1259 1260 1261
    webpackConfig = config.webpack(webpackConfig, {
      dir,
      dev,
      isServer,
      buildId,
      config,
      defaultLoaders,
      totalPages,
      webpack,
    })
1262

1263 1264 1265 1266 1267
    if (dev && originalDevtool !== webpackConfig.devtool) {
      webpackConfig.devtool = originalDevtool
      devtoolRevertWarning(originalDevtool)
    }

1268
    if (typeof (webpackConfig as any).then === 'function') {
1269
      console.warn(
1270
        '> Promise returned in next config. https://err.sh/vercel/next.js/promise-in-next-config'
1271
      )
1272
    }
1273
  }
T
Tim Neutkens 已提交
1274

1275 1276 1277 1278 1279 1280 1281 1282 1283 1284
  // 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
    }
  }

1285 1286 1287 1288 1289
  function canMatchCss(rule: webpack.RuleSetCondition | undefined): boolean {
    if (!rule) {
      return false
    }

1290 1291 1292 1293 1294 1295 1296 1297
    const fileNames = [
      '/tmp/test.css',
      '/tmp/test.scss',
      '/tmp/test.sass',
      '/tmp/test.less',
      '/tmp/test.styl',
    ]

J
Joe Haddad 已提交
1298
    if (rule instanceof RegExp && fileNames.some((input) => rule.test(input))) {
1299 1300 1301 1302
      return true
    }

    if (typeof rule === 'function') {
1303
      if (
J
Joe Haddad 已提交
1304
        fileNames.some((input) => {
1305 1306 1307 1308 1309 1310 1311 1312 1313 1314
          try {
            if (rule(input)) {
              return true
            }
          } catch (_) {}
          return false
        })
      ) {
        return true
      }
1315 1316 1317 1318 1319 1320 1321 1322 1323
    }

    if (Array.isArray(rule) && rule.some(canMatchCss)) {
      return true
    }

    return false
  }

1324 1325
  const hasUserCssConfig =
    webpackConfig.module?.rules.some(
J
Joe Haddad 已提交
1326
      (rule) => canMatchCss(rule.test) || canMatchCss(rule.include)
1327
    ) ?? false
1328

1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343
  if (hasUserCssConfig) {
    // only show warning for one build
    if (isServer) {
      console.warn(
        chalk.yellow.bold('Warning: ') +
          chalk.bold(
            'Built-in CSS support is being disabled due to custom CSS configuration being detected.\n'
          ) +
          'See here for more info: https://err.sh/next.js/built-in-css-disabled\n'
      )
    }

    if (webpackConfig.module?.rules.length) {
      // Remove default CSS Loader
      webpackConfig.module.rules = webpackConfig.module.rules.filter(
J
Joe Haddad 已提交
1344
        (r) =>
1345 1346 1347 1348 1349
          !(
            typeof r.oneOf?.[0]?.options === 'object' &&
            r.oneOf[0].options.__next_css_remove === true
          )
      )
1350
    }
1351 1352 1353
    if (webpackConfig.plugins?.length) {
      // Disable CSS Extraction Plugin
      webpackConfig.plugins = webpackConfig.plugins.filter(
J
Joe Haddad 已提交
1354
        (p) => (p as any).__next_css_remove !== true
1355 1356 1357 1358 1359
      )
    }
    if (webpackConfig.optimization?.minimizer?.length) {
      // Disable CSS Minifier
      webpackConfig.optimization.minimizer = webpackConfig.optimization.minimizer.filter(
J
Joe Haddad 已提交
1360
        (e) => (e as any).__next_css_remove !== true
1361 1362 1363 1364
      )
    }
  } else {
    await __overrideCssConfiguration(dir, !dev, webpackConfig)
1365 1366
  }

1367 1368 1369 1370 1371
  // Inject missing React Refresh loaders so that development mode is fast:
  if (hasReactRefresh) {
    attachReactRefresh(webpackConfig, defaultLoaders.babel)
  }

1372
  // check if using @zeit/next-typescript and show warning
1373 1374 1375
  if (
    isServer &&
    webpackConfig.module &&
1376 1377 1378 1379
    Array.isArray(webpackConfig.module.rules)
  ) {
    let foundTsRule = false

1380 1381
    webpackConfig.module.rules = webpackConfig.module.rules.filter(
      (rule): boolean => {
1382
        if (!(rule.test instanceof RegExp)) return true
1383
        if ('noop.ts'.match(rule.test) && !'noop.js'.match(rule.test)) {
1384 1385 1386 1387 1388
          // remove if it matches @zeit/next-typescript
          foundTsRule = rule.use === defaultLoaders.babel
          return !foundTsRule
        }
        return true
1389 1390
      }
    )
1391 1392

    if (foundTsRule) {
1393
      console.warn(
1394
        '\n@zeit/next-typescript is no longer needed since Next.js has built-in support for TypeScript now. Please remove it from your next.config.js and your .babelrc\n'
1395
      )
1396 1397 1398
    }
  }

1399
  // Patch `@zeit/next-sass`, `@zeit/next-less`, `@zeit/next-stylus` for compatibility
1400
  if (webpackConfig.module && Array.isArray(webpackConfig.module.rules)) {
J
Joe Haddad 已提交
1401
    ;[].forEach.call(webpackConfig.module.rules, function (
1402 1403 1404 1405 1406 1407
      rule: webpack.RuleSetRule
    ) {
      if (!(rule.test instanceof RegExp && Array.isArray(rule.use))) {
        return
      }

1408 1409 1410 1411
      const isSass =
        rule.test.source === '\\.scss$' || rule.test.source === '\\.sass$'
      const isLess = rule.test.source === '\\.less$'
      const isCss = rule.test.source === '\\.css$'
1412
      const isStylus = rule.test.source === '\\.styl$'
1413 1414

      // Check if the rule we're iterating over applies to Sass, Less, or CSS
1415
      if (!(isSass || isLess || isCss || isStylus)) {
1416 1417 1418
        return
      }

J
Joe Haddad 已提交
1419
      ;[].forEach.call(rule.use, function (use: webpack.RuleSetUseItem) {
1420 1421 1422 1423 1424
        if (
          !(
            use &&
            typeof use === 'object' &&
            // Identify use statements only pertaining to `css-loader`
1425 1426
            (use.loader === 'css-loader' ||
              use.loader === 'css-loader/locals') &&
1427 1428 1429 1430
            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
1431 1432
            // old `css-loader` versions. Custom setups (that aren't next-sass,
            // next-less or next-stylus) likely have the newer version.
1433
            // We still handle this gracefully below.
1434 1435 1436 1437 1438
            (Object.prototype.hasOwnProperty.call(use.options, 'minimize') ||
              Object.prototype.hasOwnProperty.call(
                use.options,
                'exportOnlyLocals'
              ))
1439 1440 1441 1442 1443 1444 1445
          )
        ) {
          return
        }

        // Try to monkey patch within a try-catch. We shouldn't fail the build
        // if we cannot pull this off.
1446 1447
        // The user may not even be using the `next-sass` or `next-less` or
        // `next-stylus` plugins.
1448 1449
        // If it does work, great!
        try {
1450 1451
          // Resolve the version of `@zeit/next-css` as depended on by the Sass,
          // Less or Stylus plugin.
1452 1453
          const correctNextCss = resolveRequest(
            '@zeit/next-css',
1454 1455 1456 1457 1458 1459 1460 1461 1462
            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'
1463 1464
                    : isStylus
                    ? '@zeit/next-stylus'
1465 1466
                    : 'next'
                )
1467 1468 1469 1470 1471 1472 1473
          )

          // 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.
1474
            const correctCssLoader = resolveRequest(use.loader, correctNextCss)
1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486
            if (correctCssLoader) {
              // We saved the user from a failed build!
              use.loader = correctCssLoader
            }
          }
        } catch (_) {
          // The error is not required to be handled.
        }
      })
    })
  }

1487
  // Backwards compat for `main.js` entry key
1488
  const originalEntry: any = webpackConfig.entry
1489 1490
  if (typeof originalEntry !== 'undefined') {
    webpackConfig.entry = async () => {
1491 1492 1493 1494
      const entry: WebpackEntrypoints =
        typeof originalEntry === 'function'
          ? await originalEntry()
          : originalEntry
1495 1496
      // Server compilation doesn't have main.js
      if (clientEntries && entry['main.js'] && entry['main.js'].length > 0) {
1497 1498 1499
        const originalFile = clientEntries[
          CLIENT_STATIC_FILES_RUNTIME_MAIN
        ] as string
1500 1501 1502 1503
        entry[CLIENT_STATIC_FILES_RUNTIME_MAIN] = [
          ...entry['main.js'],
          originalFile,
        ]
1504
      }
1505
      delete entry['main.js']
1506

1507
      return entry
1508 1509 1510
    }
  }

1511
  if (!dev) {
1512 1513
    // entry is always a function
    webpackConfig.entry = await (webpackConfig.entry as webpack.EntryFunc)()
1514 1515
  }

T
Tim Neutkens 已提交
1516
  return webpackConfig
N
nkzawa 已提交
1517
}