webpack-config.ts 44.8 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 5 6
import { readFileSync } from 'fs'
import chalk from 'next/dist/compiled/chalk'
import TerserPlugin from 'next/dist/compiled/terser-webpack-plugin'
7
import path from 'path'
8
import webpack from 'webpack'
9
import type { Configuration } from 'webpack'
10
import {
11
  DOT_NEXT_ALIAS,
12 13 14 15
  NEXT_PROJECT_ROOT,
  NEXT_PROJECT_ROOT_DIST_CLIENT,
  PAGES_DIR_ALIAS,
} from '../lib/constants'
16
import { fileExists } from '../lib/file-exists'
17
import { resolveRequest } from '../lib/resolve-request'
18
import { getTypeScriptConfiguration } from '../lib/typescript/getTypeScriptConfiguration'
19 20
import {
  CLIENT_STATIC_FILES_RUNTIME_MAIN,
21
  CLIENT_STATIC_FILES_RUNTIME_POLYFILLS,
22 23 24
  CLIENT_STATIC_FILES_RUNTIME_WEBPACK,
  REACT_LOADABLE_MANIFEST,
  SERVERLESS_DIRECTORY,
25
  SERVER_DIRECTORY,
26
} from '../next-server/lib/constants'
27
import { execOnce } from '../next-server/lib/utils'
J
Joe Haddad 已提交
28
import { findPageFile } from '../server/lib/find-page-file'
29
import { WebpackEntrypoints } from './entries'
30
import * as Log from './output/log'
J
Joe Haddad 已提交
31 32 33 34 35
import {
  collectPlugins,
  PluginMetaData,
  VALID_MIDDLEWARE,
} from './plugins/collect-plugins'
36
import { build as buildConfiguration } from './webpack/config'
37
import { __overrideCssConfiguration } from './webpack/config/blocks/css/overrideCssConfiguration'
J
Joe Haddad 已提交
38
import { pluginLoaderOptions } from './webpack/loaders/next-plugin-loader'
39 40
import BuildManifestPlugin from './webpack/plugins/build-manifest-plugin'
import ChunkNamesPlugin from './webpack/plugins/chunk-names-plugin'
J
Joe Haddad 已提交
41
import { CssMinimizerPlugin } from './webpack/plugins/css-minimizer-plugin'
42
import { JsConfigPathsPlugin } from './webpack/plugins/jsconfig-paths-plugin'
43
import { DropClientPage } from './webpack/plugins/next-drop-client-page-plugin'
44 45 46
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'
47
import { ProfilingPlugin } from './webpack/plugins/profiling-plugin'
48 49
import { ReactLoadablePlugin } from './webpack/plugins/react-loadable-plugin'
import { ServerlessPlugin } from './webpack/plugins/serverless-plugin'
50
import WebpackConformancePlugin, {
51
  DuplicatePolyfillsConformanceCheck,
52
  GranularChunksConformanceCheck,
53
  MinificationConformanceCheck,
54
  ReactSyncScriptsConformanceCheck,
55
} from './webpack/plugins/webpack-conformance-plugin'
J
Joe Haddad 已提交
56
import { WellKnownErrorsPlugin } from './webpack/plugins/wellknown-errors-plugin'
57
type ExcludesFalse = <T>(x: T | false) => x is T
58

59 60
const isWebpack5 = parseInt(webpack.version!) === 5

61 62 63 64 65 66
const escapePathVariables = (value: any) => {
  return typeof value === 'string'
    ? value.replace(/\[(\\*[\w:]+\\*)\]/gi, '[\\$1\\]')
    : value
}

67 68 69 70 71 72 73 74 75
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'
  )
})

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

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

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

97
function getOptimizedAliases(isServer: boolean): { [pkg: string]: string } {
98 99 100 101
  if (isServer) {
    return {}
  }

102
  const stubWindowFetch = path.join(__dirname, 'polyfills', 'fetch', 'index.js')
103 104 105
  const stubObjectAssign = path.join(__dirname, 'polyfills', 'object-assign.js')

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

131 132
      // Replace: full URL polyfill with platform-based polyfill
      url: require.resolve('native-url'),
133 134
    }
  )
135 136
}

137
type ClientEntries = {
138
  [key: string]: string | string[]
139 140
}

141 142 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
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' : ''
      }`
    )
  }
}

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

  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,
        })
      }
    }
  }
223

224
  const hasReactRefresh = dev && !isServer
225

226
  const distDir = path.join(dir, config.distDir)
T
Tim Neutkens 已提交
227 228
  const defaultLoaders = {
    babel: {
229
      loader: 'next-babel-loader',
230 231 232
      options: {
        isServer,
        distDir,
J
JJ Kasper 已提交
233
        pagesDir,
234
        cwd: dir,
235
        cache: true,
236 237
        babelPresetPlugins,
        hasModern: !!config.experimental.modern,
238
        development: dev,
239
        hasReactRefresh,
240
      },
241
    },
242
    // Backwards compat
243
    hotSelfAccept: {
244 245
      loader: 'noop-loader',
    },
T
Tim Neutkens 已提交
246 247
  }

248 249 250 251 252 253
  const babelIncludeRegexes: RegExp[] = [
    /next[\\/]dist[\\/]next-server[\\/]lib/,
    /next[\\/]dist[\\/]client/,
    /next[\\/]dist[\\/]pages/,
    /[\\/](strip-ansi|ansi-regex)[\\/]/,
    ...(config.experimental.plugins
J
Joe Haddad 已提交
254
      ? VALID_MIDDLEWARE.map((name) => new RegExp(`src(\\\\|/)${name}`))
255 256 257
      : []),
  ]

T
Tim Neutkens 已提交
258 259 260
  // Support for NODE_PATH
  const nodePathList = (process.env.NODE_PATH || '')
    .split(process.platform === 'win32' ? ';' : ':')
J
Joe Haddad 已提交
261
    .filter((p) => !!p)
T
Tim Neutkens 已提交
262

263 264 265 266 267 268
  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 已提交
269
  const outputPath = path.join(distDir, isServer ? outputDir : '')
270
  const totalPages = Object.keys(entrypoints).length
271
  const clientEntries = !isServer
272
    ? ({
273 274 275
        // Backwards compatibility
        'main.js': [],
        [CLIENT_STATIC_FILES_RUNTIME_MAIN]:
276 277 278 279 280 281 282 283
          `./` +
          path
            .relative(
              dir,
              path.join(
                NEXT_PROJECT_ROOT_DIST_CLIENT,
                dev ? `next-dev.js` : 'next.js'
              )
284
            )
285
            .replace(/\\/g, '/'),
286 287
        [CLIENT_STATIC_FILES_RUNTIME_POLYFILLS]: path.join(
          NEXT_PROJECT_ROOT_DIST_CLIENT,
288
          'polyfills.js'
289
        ),
290
      } as ClientEntries)
291
    : undefined
N
nkzawa 已提交
292

293
  let typeScriptPath: string | undefined
294
  try {
295
    typeScriptPath = resolveRequest('typescript', `${dir}/`)
296
  } catch (_) {}
297
  const tsConfigPath = path.join(dir, 'tsconfig.json')
298 299 300
  const useTypeScript = Boolean(
    typeScriptPath && (await fileExists(tsConfigPath))
  )
301

302 303 304
  let jsConfig
  // jsconfig is a subset of tsconfig
  if (useTypeScript) {
305
    const ts = (await import(typeScriptPath!)) as typeof import('typescript')
306 307
    const tsConfig = await getTypeScriptConfiguration(ts, tsConfigPath)
    jsConfig = { compilerOptions: tsConfig.options }
308 309 310 311
  }

  const jsConfigPath = path.join(dir, 'jsconfig.json')
  if (!useTypeScript && (await fileExists(jsConfigPath))) {
312
    jsConfig = parseJsonFile(jsConfigPath)
313 314 315 316 317 318 319
  }

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

320
  function getReactProfilingInProduction() {
321
    if (reactProductionProfiling) {
322 323 324 325 326 327 328
      return {
        'react-dom$': 'react-dom/profiling',
        'scheduler/tracing': 'scheduler/tracing-profiling',
      }
    }
  }

T
Tim Neutkens 已提交
329
  const resolveConfig = {
330
    // Disable .mjs for node_modules bundling
331
    extensions: isServer
332 333 334
      ? [
          '.js',
          '.mjs',
335
          ...(useTypeScript ? ['.tsx', '.ts'] : []),
336 337 338 339 340 341 342
          '.jsx',
          '.json',
          '.wasm',
        ]
      : [
          '.mjs',
          '.js',
343
          ...(useTypeScript ? ['.tsx', '.ts'] : []),
344 345 346 347
          '.jsx',
          '.json',
          '.wasm',
        ],
T
Tim Neutkens 已提交
348 349
    modules: [
      'node_modules',
350
      ...nodePathList, // Support for NODE_PATH environment variable
T
Tim Neutkens 已提交
351 352
    ],
    alias: {
353 354
      // 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
355
      'next/head': 'next/dist/next-server/lib/head.js',
356
      'next/router': 'next/dist/client/router.js',
357 358
      'next/config': 'next/dist/next-server/lib/runtime-config.js',
      'next/dynamic': 'next/dist/next-server/lib/dynamic.js',
359 360 361 362 363 364 365 366 367 368
      ...(isServer
        ? {}
        : {
            stream: 'stream-browserify',
            path: 'path-browserify',
            crypto: 'crypto-browserify',
            buffer: 'buffer',
            vm: 'vm-browserify',
            next: NEXT_PROJECT_ROOT,
          }),
J
JJ Kasper 已提交
369
      [PAGES_DIR_ALIAS]: pagesDir,
370
      [DOT_NEXT_ALIAS]: distDir,
371
      ...getOptimizedAliases(isServer),
372
      ...getReactProfilingInProduction(),
373
    },
374
    mainFields: isServer ? ['main', 'module'] : ['browser', 'module', 'main'],
375 376 377
    plugins: isWebpack5
      ? // webpack 5+ has the PnP resolver built-in by default:
        []
378
      : [require('pnp-webpack-plugin')],
T
Tim Neutkens 已提交
379 380
  }

T
Tim Neutkens 已提交
381 382
  const webpackMode = dev ? 'development' : 'production'

383
  const terserOptions: any = {
384 385 386 387 388 389 390 391
    parse: {
      ecma: 8,
    },
    compress: {
      ecma: 5,
      warnings: false,
      // The following two options are known to break valid JavaScript code
      comparisons: false,
392
      inline: 2, // https://github.com/vercel/next.js/issues/7178#issuecomment-493048965
393 394 395 396 397 398 399 400 401 402 403
    },
    mangle: { safari10: true },
    output: {
      ecma: 5,
      safari10: true,
      comments: false,
      // Fixes usage of Emoji and certain Regex
      ascii_only: true,
    },
  }

404 405 406 407 408 409 410 411 412 413 414
  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`
    )
  }

415 416 417 418 419 420 421 422
  // 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,
423 424
        // In webpack 5 vendors was renamed to defaultVendors
        defaultVendors: false,
425 426
      },
    },
427
    prodGranular: {
428
      chunks: 'all',
429 430 431
      cacheGroups: {
        default: false,
        vendors: false,
432 433
        // In webpack 5 vendors was renamed to defaultVendors
        defaultVendors: false,
434
        framework: {
435
          chunks: 'all',
436
          name: 'framework',
A
Alex Castle 已提交
437
          // This regex ignores nested copies of framework libraries so they're
438
          // bundled with their issuer.
439
          // https://github.com/vercel/next.js/pull/9012
440
          test: /(?<!node_modules.*)[\\/]node_modules[\\/](react|react-dom|scheduler|prop-types|use-subscription)[\\/]/,
441
          priority: 40,
J
Joe Haddad 已提交
442 443 444
          // Don't let webpack eliminate this chunk (prevents this chunk from
          // becoming a part of the commons chunk)
          enforce: true,
445 446 447 448 449 450 451 452
        },
        lib: {
          test(module: { size: Function; identifier: Function }): boolean {
            return (
              module.size() > 160000 &&
              /node_modules[/\\]/.test(module.identifier())
            )
          },
J
Joe Haddad 已提交
453 454 455 456 457 458
          name(module: {
            type: string
            libIdent?: Function
            updateHash: (hash: crypto.Hash) => void
          }): string {
            const hash = crypto.createHash('sha1')
459
            if (isModuleCSS(module)) {
J
Joe Haddad 已提交
460 461 462 463 464 465 466 467 468 469 470
              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 }))
            }

471
            return hash.digest('hex').substring(0, 8)
472 473 474 475 476 477
          },
          priority: 30,
          minChunks: 1,
          reuseExistingChunk: true,
        },
        commons: {
478
          name: 'commons',
479 480 481 482 483
          minChunks: totalPages,
          priority: 20,
        },
        shared: {
          name(module, chunks) {
484
            return (
485
              crypto
486 487 488 489 490 491 492 493
                .createHash('sha1')
                .update(
                  chunks.reduce(
                    (acc: string, chunk: webpack.compilation.Chunk) => {
                      return acc + chunk.name
                    },
                    ''
                  )
494
                )
495
                .digest('hex') + (isModuleCSS(module) ? '_CSS' : '')
496
            )
497 498 499 500 501 502
          },
          priority: 10,
          minChunks: 2,
          reuseExistingChunk: true,
        },
      },
503 504
      maxInitialRequests: 25,
      minSize: 20000,
505
    },
506 507 508 509 510 511 512
  }

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

516 517 518 519 520
  const crossOrigin =
    !config.crossOrigin && config.experimental.modern
      ? 'anonymous'
      : config.crossOrigin

521 522 523 524 525
  let customAppFile: string | null = await findPageFile(
    pagesDir,
    '/_app',
    config.pageExtensions
  )
J
Joe Haddad 已提交
526
  if (customAppFile) {
527
    customAppFile = path.resolve(path.join(pagesDir, customAppFile))
J
Joe Haddad 已提交
528 529
  }

530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546
  const conformanceConfig = Object.assign(
    {
      ReactSyncScriptsConformanceCheck: {
        enabled: true,
      },
      MinificationConformanceCheck: {
        enabled: true,
      },
      DuplicatePolyfillsConformanceCheck: {
        enabled: true,
        BlockedAPIToBePolyfilled: Object.assign(
          [],
          ['fetch'],
          config.conformance?.DuplicatePolyfillsConformanceCheck
            ?.BlockedAPIToBePolyfilled || []
        ),
      },
547 548 549
      GranularChunksConformanceCheck: {
        enabled: true,
      },
550 551 552
    },
    config.conformance
  )
553

554 555 556 557
  function handleExternals(context: any, request: any, callback: any) {
    if (request === 'next') {
      return callback(undefined, `commonjs ${request}`)
    }
K
k-kawakami 已提交
558

559 560 561 562 563 564 565 566 567 568 569 570
    const notExternalModules = [
      'next/app',
      'next/document',
      'next/link',
      'next/error',
      'string-hash',
      'next/constants',
    ]

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

572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592
    // 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 已提交
593

594 595 596 597 598 599 600 601 602 603 604 605
    // 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 已提交
606

607 608 609 610 611
    // 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()
    }
612

613 614 615 616 617 618 619
    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
      )
620

621 622 623 624
      if (!isNextExternal) {
        return callback()
      }
    }
625

626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
    // `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
      }
641

642 643 644 645 646 647 648
      // 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 (baseRes !== res) {
        return callback()
      }
    }
K
k-kawakami 已提交
649

650 651 652 653 654 655 656 657 658
    // 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 已提交
659

660 661 662 663 664 665 666
    // 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()
    }
667

668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685
    // 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 已提交
686

687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706
      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),
707 708
        ]
      : [
709 710
          // 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
711
          '@ampproject/toolbox-optimizer', // except this one
712
        ],
713
    optimization: {
714 715
      // Webpack 5 uses a new property for the same functionality
      ...(isWebpack5 ? { emitOnErrors: !dev } : { noEmitOnErrors: dev }),
716 717 718 719
      checkWasmTypes: false,
      nodeEnv: false,
      splitChunks: isServer ? false : splitChunksConfig,
      runtimeChunk: isServer
720 721 722
        ? isWebpack5 && !isLikeServerless
          ? { name: 'webpack-runtime' }
          : undefined
723 724 725
        : { name: CLIENT_STATIC_FILES_RUNTIME_WEBPACK },
      minimize: !(dev || isServer),
      minimizer: [
J
Joe Haddad 已提交
726
        // Minify JavaScript
727
        new TerserPlugin({
J
Joe Haddad 已提交
728
          extractComments: false,
729 730
          cache: path.join(distDir, 'cache', 'next-minifier'),
          parallel: config.experimental.cpus || true,
731 732
          terserOptions,
        }),
J
Joe Haddad 已提交
733
        // Minify CSS
734 735 736 737 738 739 740 741 742
        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 已提交
743
            },
744 745 746
          },
        }),
      ],
747
    },
N
nkzawa 已提交
748
    context: dir,
749 750 751
    node: {
      setImmediate: false,
    },
752
    // Kept as function to be backwards compatible
T
Tim Neutkens 已提交
753 754
    entry: async () => {
      return {
755 756
        ...(clientEntries ? clientEntries : {}),
        ...entrypoints,
757 758 759 760 761 762 763
        ...(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 已提交
764 765
      }
    },
766
    watchOptions: {
767
      ignored: ['**/.git/**', '**/node_modules/**', '**/.next/**'],
768
    },
N
nkzawa 已提交
769
    output: {
R
Rafau 已提交
770
      ...(isWebpack5 ? { ecmaVersion: 5 } : {}),
771
      path: outputPath,
772
      // On the server we don't use the chunkhash
773 774 775
      filename: isServer
        ? '[name].js'
        : `static/chunks/[name]${dev ? '' : '-[chunkhash]'}.js`,
776 777
      library: isServer ? undefined : '_N_E',
      libraryTarget: isServer ? 'commonjs2' : 'assign',
778 779 780 781 782 783
      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',
784
      // This saves chunks with the name given via `import()`
785 786 787
      chunkFilename: isServer
        ? `${dev ? '[name]' : '[name].[contenthash]'}.js`
        : `static/chunks/${dev ? '[name]' : '[name].[contenthash]'}.js`,
A
Andy 已提交
788
      strictModuleExceptionHandling: true,
789
      crossOriginLoading: crossOrigin,
790
      futureEmitAssets: !dev,
791
      webassemblyModuleFilename: 'static/wasm/[modulehash].wasm',
N
nkzawa 已提交
792
    },
793
    performance: false,
T
Tim Neutkens 已提交
794
    resolve: resolveConfig,
N
nkzawa 已提交
795
    resolveLoader: {
796 797 798
      // The loaders Next.js provides
      alias: [
        'emit-file-loader',
799
        'error-loader',
800 801 802 803 804
        'next-babel-loader',
        'next-client-pages-loader',
        'next-data-loader',
        'next-serverless-loader',
        'noop-loader',
805
        'next-plugin-loader',
806 807 808
      ].reduce((alias, loader) => {
        // using multiple aliases to replace `resolveLoader.modules`
        alias[loader] = path.join(__dirname, 'webpack', 'loaders', loader)
809

810 811
        return alias
      }, {} as Record<string, string>),
N
Naoyuki Kanezawa 已提交
812
      modules: [
813
        'node_modules',
814 815
        ...nodePathList, // Support for NODE_PATH environment variable
      ],
816
      plugins: isWebpack5 ? [] : [require('pnp-webpack-plugin')],
N
nkzawa 已提交
817 818
    },
    module: {
819
      rules: [
T
Tim Neutkens 已提交
820
        {
821
          test: /\.(tsx|ts|js|mjs|jsx)$/,
822
          include: [dir, ...babelIncludeRegexes],
823 824
          exclude: (excludePath: string) => {
            if (babelIncludeRegexes.some((r) => r.test(excludePath))) {
825 826
              return false
            }
827
            return /node_modules/.test(excludePath)
828
          },
829 830 831 832 833
          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 已提交
834
                  loader: 'next/dist/compiled/cache-loader',
835 836 837 838 839 840 841 842 843
                  options: {
                    cacheContext: dir,
                    cacheDirectory: path.join(dir, '.next', 'cache', 'webpack'),
                    cacheIdentifier: `webpack${isServer ? '-server' : ''}${
                      config.experimental.modern ? '-hasmodern' : ''
                    }`,
                  },
                },
                {
G
Guy Bedford 已提交
844
                  loader: require.resolve('next/dist/compiled/thread-loader'),
845 846 847 848 849
                  options: {
                    workers: 2,
                    workerParallelJobs: Infinity,
                  },
                },
850 851 852
                hasReactRefresh
                  ? require.resolve('@next/react-refresh-utils/loader')
                  : '',
853
                defaultLoaders.babel,
854 855 856 857
              ].filter(Boolean)
            : hasReactRefresh
            ? [
                require.resolve('@next/react-refresh-utils/loader'),
858
                defaultLoaders.babel,
859 860
              ]
            : defaultLoaders.babel,
861
        },
862
      ].filter(Boolean),
N
nkzawa 已提交
863
    },
T
Tim Neutkens 已提交
864
    plugins: [
865
      hasReactRefresh && new ReactRefreshWebpackPlugin(),
866 867 868 869 870 871 872 873
      // 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'] }),
874
      // This plugin makes sure `output.filename` is used for entry chunks
T
Tim Neutkens 已提交
875
      !isWebpack5 && new ChunkNamesPlugin(),
876
      new webpack.DefinePlugin({
877 878 879 880 881 882 883 884 885
        ...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
          },
          {}
        ),
886
        ...Object.keys(config.env).reduce((acc, key) => {
887
          if (/^(?:NODE_.+)|^(?:__.+)$/i.test(key)) {
888
            throw new Error(
889
              `The key "${key}" under "env" in next.config.js is not allowed. https://err.sh/vercel/next.js/env-key-not-allowed`
890
            )
891 892 893
          }

          return {
894
            ...acc,
895
            [`process.env.${key}`]: JSON.stringify(config.env[key]),
896
          }
897
        }, {}),
898
        'process.env.NODE_ENV': JSON.stringify(webpackMode),
899
        'process.env.__NEXT_CROSS_ORIGIN': JSON.stringify(crossOrigin),
900
        'process.browser': JSON.stringify(!isServer),
J
JJ Kasper 已提交
901 902 903
        'process.env.__NEXT_TEST_MODE': JSON.stringify(
          process.env.__NEXT_TEST_MODE
        ),
904
        // This is used in client/dev-error-overlay/hot-dev-client.js to replace the dist directory
905 906 907 908 909
        ...(dev && !isServer
          ? {
              'process.env.__NEXT_DIST_DIR': JSON.stringify(distDir),
            }
          : {}),
J
Jan Potoms 已提交
910
        'process.env.__NEXT_TRAILING_SLASH': JSON.stringify(
911
          config.trailingSlash
J
Jan Potoms 已提交
912
        ),
913 914 915 916 917 918 919 920 921
        '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
        ),
922 923 924
        'process.env.__NEXT_PLUGINS': JSON.stringify(
          config.experimental.plugins
        ),
G
Gerald Monaco 已提交
925 926 927
        'process.env.__NEXT_STRICT_MODE': JSON.stringify(
          config.reactStrictMode
        ),
928 929 930
        'process.env.__NEXT_REACT_MODE': JSON.stringify(
          config.experimental.reactMode
        ),
P
Prateek Bhatnagar 已提交
931 932 933
        'process.env.__NEXT_OPTIMIZE_FONTS': JSON.stringify(
          config.experimental.optimizeFonts
        ),
934 935 936
        'process.env.__NEXT_OPTIMIZE_IMAGES': JSON.stringify(
          config.experimental.optimizeImages
        ),
937 938 939
        'process.env.__NEXT_SCROLL_RESTORATION': JSON.stringify(
          config.experimental.scrollRestoration
        ),
940
        'process.env.__NEXT_ROUTER_BASEPATH': JSON.stringify(config.basePath),
941
        ...(isServer
942 943 944 945 946 947
          ? {
              // 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),
            }
948
          : undefined),
949
        // stub process.env with proxy to warn a missing value is
950 951
        // being accessed in development mode
        ...(config.experimental.pageEnv && process.env.NODE_ENV !== 'production'
952
          ? {
953
              'process.env': `
954 955 956 957 958 959 960 961 962 963 964
            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]
              }
            })
          `,
            }
          : {}),
965
      }),
966 967 968 969
      !isServer &&
        new ReactLoadablePlugin({
          filename: REACT_LOADABLE_MANIFEST,
        }),
970
      !isServer && new DropClientPage(),
971 972 973 974 975
      // 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 &&
976 977 978 979
        new webpack.IgnorePlugin({
          resourceRegExp: /^\.\/locale$/,
          contextRegExp: /moment$/,
        }),
980 981 982 983 984 985 986
      ...(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')
987
            const devPlugins = [new NextJsRequireCacheHotReloader()]
988

989
            if (!isServer) {
990 991
              devPlugins.push(new webpack.HotModuleReplacementPlugin())
            }
992

993 994 995
            return devPlugins
          })()
        : []),
996 997
      // Webpack 5 no longer requires this plugin in production:
      !isWebpack5 && !dev && new webpack.HashedModuleIdsPlugin(),
998 999
      !dev &&
        new webpack.IgnorePlugin({
1000 1001
          resourceRegExp: /react-is/,
          contextRegExp: /(next-server|next)[\\/]dist[\\/]/,
1002
        }),
J
Joe Haddad 已提交
1003
      isServerless && isServer && new ServerlessPlugin(),
1004
      isServer && new PagesManifestPlugin(isLikeServerless),
1005 1006
      !isWebpack5 &&
        target === 'server' &&
1007 1008
        isServer &&
        new NextJsSSRModuleCachePlugin({ outputPath }),
1009
      isServer && new NextJsSsrImportPlugin(),
1010 1011 1012 1013 1014
      !isServer &&
        new BuildManifestPlugin({
          buildId,
          modern: config.experimental.modern,
        }),
1015 1016 1017
      tracer &&
        new ProfilingPlugin({
          tracer,
1018
        }),
1019 1020
      !isWebpack5 &&
        config.experimental.modern &&
1021 1022
        !isServer &&
        !dev &&
1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041
        (() => {
          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 已提交
1042 1043 1044
      config.experimental.optimizeFonts &&
        !dev &&
        isServer &&
J
Joe Haddad 已提交
1045 1046 1047 1048 1049 1050
        (function () {
          const {
            FontStylesheetGatheringPlugin,
          } = require('./webpack/plugins/font-stylesheet-gathering-plugin')
          return new FontStylesheetGatheringPlugin()
        })(),
1051
      config.experimental.conformance &&
1052
        !isWebpack5 &&
1053 1054
        !dev &&
        new WebpackConformancePlugin({
1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071
          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,
              }),
1072 1073 1074 1075 1076
            !isServer &&
              conformanceConfig.GranularChunksConformanceCheck.enabled &&
              new GranularChunksConformanceCheck(
                splitChunksConfigs.prodGranular
              ),
1077
          ].filter(Boolean),
1078
        }),
J
Joe Haddad 已提交
1079
      new WellKnownErrorsPlugin(),
1080
    ].filter((Boolean as any) as ExcludesFalse),
1081
  }
1082

1083 1084 1085 1086 1087
  // Support tsconfig and jsconfig baseUrl
  if (resolvedBaseUrl) {
    webpackConfig.resolve?.modules?.push(resolvedBaseUrl)
  }

1088
  if (jsConfig?.compilerOptions?.paths && resolvedBaseUrl) {
1089
    webpackConfig.resolve?.plugins?.unshift(
1090 1091 1092 1093
      new JsConfigPathsPlugin(jsConfig.compilerOptions.paths, resolvedBaseUrl)
    )
  }

1094 1095 1096 1097 1098
  if (isWebpack5) {
    // On by default:
    delete webpackConfig.output?.futureEmitAssets
    // No longer polyfills Node.js modules:
    if (webpackConfig.node) delete webpackConfig.node.setImmediate
1099 1100 1101 1102 1103 1104 1105

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

1108 1109 1110 1111 1112
  webpackConfig = await buildConfiguration(webpackConfig, {
    rootDirectory: dir,
    customAppFile,
    isDevelopment: dev,
    isServer,
1113
    assetPrefix: config.assetPrefix || '',
1114
    sassOptions: config.sassOptions,
1115
    productionBrowserSourceMaps,
1116 1117
  })

1118
  let originalDevtool = webpackConfig.devtool
T
Tim Neutkens 已提交
1119
  if (typeof config.webpack === 'function') {
1120 1121 1122 1123 1124 1125 1126 1127 1128 1129
    webpackConfig = config.webpack(webpackConfig, {
      dir,
      dev,
      isServer,
      buildId,
      config,
      defaultLoaders,
      totalPages,
      webpack,
    })
1130

1131 1132 1133 1134 1135
    if (dev && originalDevtool !== webpackConfig.devtool) {
      webpackConfig.devtool = originalDevtool
      devtoolRevertWarning(originalDevtool)
    }

1136
    if (typeof (webpackConfig as any).then === 'function') {
1137
      console.warn(
1138
        '> Promise returned in next config. https://err.sh/vercel/next.js/promise-in-next-config'
1139
      )
1140
    }
1141
  }
T
Tim Neutkens 已提交
1142

1143 1144 1145 1146 1147 1148 1149 1150 1151 1152
  // 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
    }
  }

1153 1154 1155 1156 1157
  function canMatchCss(rule: webpack.RuleSetCondition | undefined): boolean {
    if (!rule) {
      return false
    }

1158 1159 1160 1161 1162 1163 1164 1165
    const fileNames = [
      '/tmp/test.css',
      '/tmp/test.scss',
      '/tmp/test.sass',
      '/tmp/test.less',
      '/tmp/test.styl',
    ]

J
Joe Haddad 已提交
1166
    if (rule instanceof RegExp && fileNames.some((input) => rule.test(input))) {
1167 1168 1169 1170
      return true
    }

    if (typeof rule === 'function') {
1171
      if (
J
Joe Haddad 已提交
1172
        fileNames.some((input) => {
1173 1174 1175 1176 1177 1178 1179 1180 1181 1182
          try {
            if (rule(input)) {
              return true
            }
          } catch (_) {}
          return false
        })
      ) {
        return true
      }
1183 1184 1185 1186 1187 1188 1189 1190 1191
    }

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

    return false
  }

1192 1193
  const hasUserCssConfig =
    webpackConfig.module?.rules.some(
J
Joe Haddad 已提交
1194
      (rule) => canMatchCss(rule.test) || canMatchCss(rule.include)
1195
    ) ?? false
1196

1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211
  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 已提交
1212
        (r) =>
1213 1214 1215 1216 1217
          !(
            typeof r.oneOf?.[0]?.options === 'object' &&
            r.oneOf[0].options.__next_css_remove === true
          )
      )
1218
    }
1219 1220 1221
    if (webpackConfig.plugins?.length) {
      // Disable CSS Extraction Plugin
      webpackConfig.plugins = webpackConfig.plugins.filter(
J
Joe Haddad 已提交
1222
        (p) => (p as any).__next_css_remove !== true
1223 1224 1225 1226 1227
      )
    }
    if (webpackConfig.optimization?.minimizer?.length) {
      // Disable CSS Minifier
      webpackConfig.optimization.minimizer = webpackConfig.optimization.minimizer.filter(
J
Joe Haddad 已提交
1228
        (e) => (e as any).__next_css_remove !== true
1229 1230 1231 1232
      )
    }
  } else {
    await __overrideCssConfiguration(dir, !dev, webpackConfig)
1233 1234
  }

1235 1236 1237 1238 1239
  // Inject missing React Refresh loaders so that development mode is fast:
  if (hasReactRefresh) {
    attachReactRefresh(webpackConfig, defaultLoaders.babel)
  }

1240
  // check if using @zeit/next-typescript and show warning
1241 1242 1243
  if (
    isServer &&
    webpackConfig.module &&
1244 1245 1246 1247
    Array.isArray(webpackConfig.module.rules)
  ) {
    let foundTsRule = false

1248 1249
    webpackConfig.module.rules = webpackConfig.module.rules.filter(
      (rule): boolean => {
1250
        if (!(rule.test instanceof RegExp)) return true
1251
        if ('noop.ts'.match(rule.test) && !'noop.js'.match(rule.test)) {
1252 1253 1254 1255 1256
          // remove if it matches @zeit/next-typescript
          foundTsRule = rule.use === defaultLoaders.babel
          return !foundTsRule
        }
        return true
1257 1258
      }
    )
1259 1260

    if (foundTsRule) {
1261
      console.warn(
1262
        '\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'
1263
      )
1264 1265 1266
    }
  }

1267
  // Patch `@zeit/next-sass`, `@zeit/next-less`, `@zeit/next-stylus` for compatibility
1268
  if (webpackConfig.module && Array.isArray(webpackConfig.module.rules)) {
J
Joe Haddad 已提交
1269
    ;[].forEach.call(webpackConfig.module.rules, function (
1270 1271 1272 1273 1274 1275
      rule: webpack.RuleSetRule
    ) {
      if (!(rule.test instanceof RegExp && Array.isArray(rule.use))) {
        return
      }

1276 1277 1278 1279
      const isSass =
        rule.test.source === '\\.scss$' || rule.test.source === '\\.sass$'
      const isLess = rule.test.source === '\\.less$'
      const isCss = rule.test.source === '\\.css$'
1280
      const isStylus = rule.test.source === '\\.styl$'
1281 1282

      // Check if the rule we're iterating over applies to Sass, Less, or CSS
1283
      if (!(isSass || isLess || isCss || isStylus)) {
1284 1285 1286
        return
      }

J
Joe Haddad 已提交
1287
      ;[].forEach.call(rule.use, function (use: webpack.RuleSetUseItem) {
1288 1289 1290 1291 1292
        if (
          !(
            use &&
            typeof use === 'object' &&
            // Identify use statements only pertaining to `css-loader`
1293 1294
            (use.loader === 'css-loader' ||
              use.loader === 'css-loader/locals') &&
1295 1296 1297 1298
            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
1299 1300
            // old `css-loader` versions. Custom setups (that aren't next-sass,
            // next-less or next-stylus) likely have the newer version.
1301
            // We still handle this gracefully below.
1302 1303 1304 1305 1306
            (Object.prototype.hasOwnProperty.call(use.options, 'minimize') ||
              Object.prototype.hasOwnProperty.call(
                use.options,
                'exportOnlyLocals'
              ))
1307 1308 1309 1310 1311 1312 1313
          )
        ) {
          return
        }

        // Try to monkey patch within a try-catch. We shouldn't fail the build
        // if we cannot pull this off.
1314 1315
        // The user may not even be using the `next-sass` or `next-less` or
        // `next-stylus` plugins.
1316 1317
        // If it does work, great!
        try {
1318 1319
          // Resolve the version of `@zeit/next-css` as depended on by the Sass,
          // Less or Stylus plugin.
1320 1321
          const correctNextCss = resolveRequest(
            '@zeit/next-css',
1322 1323 1324 1325 1326 1327 1328 1329 1330
            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'
1331 1332
                    : isStylus
                    ? '@zeit/next-stylus'
1333 1334
                    : 'next'
                )
1335 1336 1337 1338 1339 1340 1341
          )

          // 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.
1342
            const correctCssLoader = resolveRequest(use.loader, correctNextCss)
1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354
            if (correctCssLoader) {
              // We saved the user from a failed build!
              use.loader = correctCssLoader
            }
          }
        } catch (_) {
          // The error is not required to be handled.
        }
      })
    })
  }

1355
  // Backwards compat for `main.js` entry key
1356
  const originalEntry: any = webpackConfig.entry
1357 1358
  if (typeof originalEntry !== 'undefined') {
    webpackConfig.entry = async () => {
1359 1360 1361 1362
      const entry: WebpackEntrypoints =
        typeof originalEntry === 'function'
          ? await originalEntry()
          : originalEntry
1363 1364
      // Server compilation doesn't have main.js
      if (clientEntries && entry['main.js'] && entry['main.js'].length > 0) {
1365 1366 1367
        const originalFile = clientEntries[
          CLIENT_STATIC_FILES_RUNTIME_MAIN
        ] as string
1368 1369 1370 1371
        entry[CLIENT_STATIC_FILES_RUNTIME_MAIN] = [
          ...entry['main.js'],
          originalFile,
        ]
1372
      }
1373
      delete entry['main.js']
1374

1375
      return entry
1376 1377 1378
    }
  }

1379
  if (!dev) {
1380 1381
    // entry is always a function
    webpackConfig.entry = await (webpackConfig.entry as webpack.EntryFunc)()
1382 1383
  }

T
Tim Neutkens 已提交
1384
  return webpackConfig
N
nkzawa 已提交
1385
}