webpack-config.ts 35.8 KB
Newer Older
1
import chalk from 'chalk'
2
import crypto from 'crypto'
3
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'
J
Joe Haddad 已提交
4
import MiniCssExtractPlugin from 'mini-css-extract-plugin'
5
import path from 'path'
6 7
// @ts-ignore: Currently missing types
import PnpWebpackPlugin from 'pnp-webpack-plugin'
8
import webpack from 'webpack'
9

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 18 19 20 21 22 23 24
import { resolveRequest } from '../lib/resolve-request'
import {
  CLIENT_STATIC_FILES_RUNTIME_MAIN,
  CLIENT_STATIC_FILES_RUNTIME_WEBPACK,
  REACT_LOADABLE_MANIFEST,
  SERVER_DIRECTORY,
  SERVERLESS_DIRECTORY,
} from '../next-server/lib/constants'
J
Joe Haddad 已提交
25
import { findPageFile } from '../server/lib/find-page-file'
26 27
import { WebpackEntrypoints } from './entries'
import BuildManifestPlugin from './webpack/plugins/build-manifest-plugin'
28
import { ChunkGraphPlugin } from './webpack/plugins/chunk-graph-plugin'
29
import ChunkNamesPlugin from './webpack/plugins/chunk-names-plugin'
J
Joe Haddad 已提交
30
import { CssMinimizerPlugin } from './webpack/plugins/css-minimizer-plugin'
31
import { importAutoDllPlugin } from './webpack/plugins/dll-import'
32
import { DropClientPage } from './webpack/plugins/next-drop-client-page-plugin'
33
import NextEsmPlugin from './webpack/plugins/next-esm-plugin'
34 35 36
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'
37 38
// @ts-ignore: JS file
import { ProfilingPlugin } from './webpack/plugins/profiling-plugin'
39 40 41 42
import { ReactLoadablePlugin } from './webpack/plugins/react-loadable-plugin'
import { ServerlessPlugin } from './webpack/plugins/serverless-plugin'
import { TerserPlugin } from './webpack/plugins/terser-webpack-plugin/src/index'

43
type ExcludesFalse = <T>(x: T | false) => x is T
44

45 46 47 48 49 50
const escapePathVariables = (value: any) => {
  return typeof value === 'string'
    ? value.replace(/\[(\\*[\w:]+\\*)\]/gi, '[\\$1\\]')
    : value
}

51 52 53 54 55
export default async function getBaseWebpackConfig(
  dir: string,
  {
    buildId,
    config,
J
JJ Kasper 已提交
56 57 58 59
    dev = false,
    isServer = false,
    pagesDir,
    tracer,
60 61 62 63 64
    target = 'server',
    entrypoints,
  }: {
    buildId: string
    config: any
J
JJ Kasper 已提交
65 66 67
    dev?: boolean
    isServer?: boolean
    pagesDir: string
68
    target?: string
J
JJ Kasper 已提交
69
    tracer?: any
70 71 72
    entrypoints: WebpackEntrypoints
  }
): Promise<webpack.Configuration> {
73
  const distDir = path.join(dir, config.distDir)
T
Tim Neutkens 已提交
74 75
  const defaultLoaders = {
    babel: {
76
      loader: 'next-babel-loader',
77 78
      options: {
        isServer,
79
        hasModern: !!config.experimental.modern,
80
        distDir,
J
JJ Kasper 已提交
81
        pagesDir,
82
        cwd: dir,
83
        cache: true,
84
      },
85
    },
86
    // Backwards compat
87
    hotSelfAccept: {
88 89
      loader: 'noop-loader',
    },
T
Tim Neutkens 已提交
90 91
  }

T
Tim Neutkens 已提交
92 93 94
  // Support for NODE_PATH
  const nodePathList = (process.env.NODE_PATH || '')
    .split(process.platform === 'win32' ? ';' : ':')
95
    .filter(p => !!p)
T
Tim Neutkens 已提交
96

97 98 99 100 101 102
  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 已提交
103
  const outputPath = path.join(distDir, isServer ? outputDir : '')
104
  const totalPages = Object.keys(entrypoints).length
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
  const clientEntries = !isServer
    ? {
        // Backwards compatibility
        'main.js': [],
        [CLIENT_STATIC_FILES_RUNTIME_MAIN]:
          `.${path.sep}` +
          path.relative(
            dir,
            path.join(
              NEXT_PROJECT_ROOT_DIST_CLIENT,
              dev ? `next-dev.js` : 'next.js'
            )
          ),
      }
    : undefined
N
nkzawa 已提交
120

121 122
  let typeScriptPath
  try {
123
    typeScriptPath = resolveRequest('typescript', `${dir}/`)
124
  } catch (_) {}
125
  const tsConfigPath = path.join(dir, 'tsconfig.json')
126 127 128
  const useTypeScript = Boolean(
    typeScriptPath && (await fileExists(tsConfigPath))
  )
129

T
Tim Neutkens 已提交
130
  const resolveConfig = {
131
    // Disable .mjs for node_modules bundling
132
    extensions: isServer
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
      ? [
          ...(useTypeScript ? ['.tsx', '.ts'] : []),
          '.js',
          '.mjs',
          '.jsx',
          '.json',
          '.wasm',
        ]
      : [
          ...(useTypeScript ? ['.tsx', '.ts'] : []),
          '.mjs',
          '.js',
          '.jsx',
          '.json',
          '.wasm',
        ],
T
Tim Neutkens 已提交
149 150
    modules: [
      'node_modules',
151
      ...nodePathList, // Support for NODE_PATH environment variable
T
Tim Neutkens 已提交
152 153
    ],
    alias: {
154 155
      // 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
156
      'next/head': 'next/dist/next-server/lib/head.js',
157
      'next/router': 'next/dist/client/router.js',
158 159
      'next/config': 'next/dist/next-server/lib/runtime-config.js',
      'next/dynamic': 'next/dist/next-server/lib/dynamic.js',
T
Tim Neutkens 已提交
160
      next: NEXT_PROJECT_ROOT,
J
JJ Kasper 已提交
161
      [PAGES_DIR_ALIAS]: pagesDir,
162
      [DOT_NEXT_ALIAS]: distDir,
163
    },
164
    mainFields: isServer ? ['main', 'module'] : ['browser', 'module', 'main'],
165
    plugins: [PnpWebpackPlugin],
T
Tim Neutkens 已提交
166 167
  }

T
Tim Neutkens 已提交
168 169
  const webpackMode = dev ? 'development' : 'production'

170 171 172
  const terserPluginConfig = {
    parallel: true,
    sourceMap: false,
173
    cache: true,
174
    cpus: config.experimental.cpus,
175
    distDir: distDir,
176
  }
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
  const terserOptions = {
    parse: {
      ecma: 8,
    },
    compress: {
      ecma: 5,
      warnings: false,
      // The following two options are known to break valid JavaScript code
      comparisons: false,
      inline: 2, // https://github.com/zeit/next.js/issues/7178#issuecomment-493048965
    },
    mangle: { safari10: true },
    output: {
      ecma: 5,
      safari10: true,
      comments: false,
      // Fixes usage of Emoji and certain Regex
      ascii_only: true,
    },
  }

J
Joe Haddad 已提交
198
  const devtool = dev ? 'cheap-module-source-map' : false
199

200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
  // 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,
      },
    },
    prod: {
      chunks: 'all',
      cacheGroups: {
        default: false,
        vendors: false,
        commons: {
          name: 'commons',
          chunks: 'all',
          minChunks: totalPages > 2 ? totalPages * 0.5 : 2,
        },
        react: {
          name: 'commons',
          chunks: 'all',
223
          test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
224 225 226
        },
      },
    },
227
    prodGranular: {
228
      chunks: 'initial',
229 230 231 232
      cacheGroups: {
        default: false,
        vendors: false,
        framework: {
233 234 235
          // Framework chunk applies to modules in dynamic chunks, unlike shared chunks
          // TODO(atcastle): Analyze if other cache groups should be set to 'all' as well
          chunks: 'all',
236 237 238 239 240 241 242 243 244 245 246
          name: 'framework',
          test: /[\\/]node_modules[\\/](react|react-dom|scheduler|prop-types)[\\/]/,
          priority: 40,
        },
        lib: {
          test(module: { size: Function; identifier: Function }): boolean {
            return (
              module.size() > 160000 &&
              /node_modules[/\\]/.test(module.identifier())
            )
          },
247 248 249 250 251 252
          name(module: { libIdent: Function }): string {
            return crypto
              .createHash('sha1')
              .update(module.libIdent({ context: dir }))
              .digest('hex')
              .substring(0, 8)
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
          },
          priority: 30,
          minChunks: 1,
          reuseExistingChunk: true,
        },
        commons: {
          name: 'commons',
          minChunks: totalPages,
          priority: 20,
        },
        shared: {
          name(module, chunks) {
            return crypto
              .createHash('sha1')
              .update(
                chunks.reduce(
                  (acc: string, chunk: webpack.compilation.Chunk) => {
                    return acc + chunk.name
                  },
                  ''
                )
              )
275
              .digest('hex')
276 277 278 279 280 281 282 283
          },
          priority: 10,
          minChunks: 2,
          reuseExistingChunk: true,
        },
      },
      maxInitialRequests: 20,
    },
284 285 286 287 288 289 290
  }

  // Select appropriate SplitChunksPlugin config for this build
  let splitChunksConfig: webpack.Options.SplitChunksOptions
  if (dev) {
    splitChunksConfig = splitChunksConfigs.dev
  } else {
291 292 293
    splitChunksConfig = config.experimental.granularChunks
      ? splitChunksConfigs.prodGranular
      : splitChunksConfigs.prod
294 295
  }

296 297 298 299 300
  const crossOrigin =
    !config.crossOrigin && config.experimental.modern
      ? 'anonymous'
      : config.crossOrigin

J
Joe Haddad 已提交
301 302 303 304 305 306 307 308 309 310 311
  let customAppFile: string | null = config.experimental.css
    ? await findPageFile(
        path.join(dir, 'pages'),
        '/_app',
        config.pageExtensions
      )
    : null
  if (customAppFile) {
    customAppFile = path.resolve(path.join(dir, 'pages', customAppFile))
  }

312
  let webpackConfig: webpack.Configuration = {
J
JJ Kasper 已提交
313
    devtool,
T
Tim Neutkens 已提交
314
    mode: webpackMode,
T
Tim Neutkens 已提交
315 316
    name: isServer ? 'server' : 'client',
    target: isServer ? 'node' : 'web',
317 318
    externals: !isServer
      ? undefined
319
      : !isServerless
320 321 322 323 324 325 326 327 328 329
      ? [
          (context, request, callback) => {
            const notExternalModules = [
              'next/app',
              'next/document',
              'next/link',
              'next/error',
              'string-hash',
              'next/constants',
            ]
330

331 332 333
            if (notExternalModules.indexOf(request) !== -1) {
              return callback()
            }
K
k-kawakami 已提交
334

335 336 337
            // Resolve the import with the webpack provided context, this
            // ensures we're resolving the correct version when multiple
            // exist.
338 339
            let res
            try {
340
              res = resolveRequest(request, `${context}/`)
341
            } catch (err) {
342 343 344 345
              // This is a special case for the Next.js data experiment. This
              // will be removed in the future.
              // We're telling webpack to externalize a package that doesn't
              // exist because we know it won't ever be used at runtime.
346 347 348 349 350 351 352 353 354 355 356
              if (
                request === 'react-ssr-prepass' &&
                !config.experimental.ampBindInitData
              ) {
                if (
                  context.replace(/\\/g, '/').includes('next-server/server')
                ) {
                  return callback(undefined, `commonjs ${request}`)
                }
              }

357 358 359
              // 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).
360 361
              return callback()
            }
K
k-kawakami 已提交
362

363 364
            // Same as above, if the request cannot be resolved we need to have
            // webpack "bundle" it so it surfaces the not found error.
365 366 367
            if (!res) {
              return callback()
            }
K
k-kawakami 已提交
368

369 370 371 372
            // 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).
373 374
            let baseRes
            try {
375
              baseRes = resolveRequest(request, `${dir}/`)
376 377
            } catch (err) {}

378 379 380
            // 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.
381 382 383 384
            if (baseRes !== res) {
              return callback()
            }

385 386 387 388 389 390 391 392 393
            // Default pages have to be transpiled
            if (
              !res.match(/next[/\\]dist[/\\]next-server[/\\]/) &&
              (res.match(/next[/\\]dist[/\\]/) ||
                res.match(/node_modules[/\\]@babel[/\\]runtime[/\\]/) ||
                res.match(/node_modules[/\\]@babel[/\\]runtime-corejs2[/\\]/))
            ) {
              return callback()
            }
K
k-kawakami 已提交
394

395 396 397 398 399 400 401
            // 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()
            }
K
k-kawakami 已提交
402

403 404
            // Anything else that is standard JavaScript within `node_modules`
            // can be externalized.
405 406 407
            if (res.match(/node_modules[/\\].*\.js$/)) {
              return callback(undefined, `commonjs ${request}`)
            }
K
k-kawakami 已提交
408

409
            // Default behavior: bundle the code!
410
            callback()
411 412 413
          },
        ]
      : [
414 415
          // 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
416
          '@ampproject/toolbox-optimizer', // except this one
417 418 419 420 421 422 423
          (context, request, callback) => {
            if (
              request === 'react-ssr-prepass' &&
              !config.experimental.ampBindInitData
            ) {
              // if it's the Next.js' require mark it as external
              // since it's not used
424
              if (context.replace(/\\/g, '/').includes('next-server/server')) {
425 426 427 428 429
                return callback(undefined, `commonjs ${request}`)
              }
            }
            return callback()
          },
430
        ],
431 432 433 434 435 436 437 438 439
    optimization: {
      checkWasmTypes: false,
      nodeEnv: false,
      splitChunks: isServer ? false : splitChunksConfig,
      runtimeChunk: isServer
        ? undefined
        : { name: CLIENT_STATIC_FILES_RUNTIME_WEBPACK },
      minimize: !(dev || isServer),
      minimizer: [
J
Joe Haddad 已提交
440
        // Minify JavaScript
441 442 443 444
        new TerserPlugin({
          ...terserPluginConfig,
          terserOptions,
        }),
J
Joe Haddad 已提交
445 446
        // Minify CSS
        config.experimental.css &&
J
Joe Haddad 已提交
447 448
          new CssMinimizerPlugin({
            postcssOptions: {
J
Joe Haddad 已提交
449 450 451 452
              map: {
                // `inline: false` generates the source map in a separate file.
                // Otherwise, the CSS file is needlessly large.
                inline: false,
J
Joe Haddad 已提交
453 454 455
                // `annotation: false` skips appending the `sourceMappingURL`
                // to the end of the CSS file. Webpack already handles this.
                annotation: false,
J
Joe Haddad 已提交
456 457 458 459
              },
            },
          }),
      ].filter(Boolean),
460 461
    },
    recordsPath: path.join(outputPath, 'records.json'),
N
nkzawa 已提交
462
    context: dir,
463
    // Kept as function to be backwards compatible
T
Tim Neutkens 已提交
464 465
    entry: async () => {
      return {
466 467
        ...(clientEntries ? clientEntries : {}),
        ...entrypoints,
T
Tim Neutkens 已提交
468 469
      }
    },
N
nkzawa 已提交
470
    output: {
471
      path: outputPath,
472
      filename: ({ chunk }: { chunk: { name: string } }) => {
473
        // Use `[name]-[contenthash].js` in production
474 475 476 477 478
        if (
          !dev &&
          (chunk.name === CLIENT_STATIC_FILES_RUNTIME_MAIN ||
            chunk.name === CLIENT_STATIC_FILES_RUNTIME_WEBPACK)
        ) {
479
          return chunk.name.replace(/\.js$/, '-[contenthash].js')
480 481 482
        }
        return '[name]'
      },
T
Tim Neutkens 已提交
483
      libraryTarget: isServer ? 'commonjs2' : 'var',
484 485 486
      hotUpdateChunkFilename: 'static/webpack/[id].[hash].hot-update.js',
      hotUpdateMainFilename: 'static/webpack/[hash].hot-update.json',
      // This saves chunks with the name given via `import()`
487 488 489
      chunkFilename: isServer
        ? `${dev ? '[name]' : '[name].[contenthash]'}.js`
        : `static/chunks/${dev ? '[name]' : '[name].[contenthash]'}.js`,
A
Andy 已提交
490
      strictModuleExceptionHandling: true,
491
      crossOriginLoading: crossOrigin,
492
      futureEmitAssets: !dev,
493
      webassemblyModuleFilename: 'static/wasm/[modulehash].wasm',
N
nkzawa 已提交
494
    },
495
    performance: false,
T
Tim Neutkens 已提交
496
    resolve: resolveConfig,
N
nkzawa 已提交
497
    resolveLoader: {
498 499 500
      // The loaders Next.js provides
      alias: [
        'emit-file-loader',
501
        'error-loader',
502 503 504 505 506 507 508 509 510 511 512 513 514 515
        'next-babel-loader',
        'next-client-pages-loader',
        'next-data-loader',
        'next-serverless-loader',
        'noop-loader',
      ].reduce(
        (alias, loader) => {
          // using multiple aliases to replace `resolveLoader.modules`
          alias[loader] = path.join(__dirname, 'webpack', 'loaders', loader)

          return alias
        },
        {} as Record<string, string>
      ),
N
Naoyuki Kanezawa 已提交
516
      modules: [
517
        'node_modules',
518 519
        ...nodePathList, // Support for NODE_PATH environment variable
      ],
520
      plugins: [PnpWebpackPlugin],
N
nkzawa 已提交
521
    },
T
Tim Neutkens 已提交
522
    // @ts-ignore this is filtered
N
nkzawa 已提交
523
    module: {
524
      strictExportPresence: true,
525
      rules: [
526 527 528 529 530 531
        config.experimental.ampBindInitData &&
          !isServer && {
            test: /\.(tsx|ts|js|mjs|jsx)$/,
            include: [path.join(dir, 'data')],
            use: 'next-data-loader',
          },
T
Tim Neutkens 已提交
532
        {
533
          test: /\.(tsx|ts|js|mjs|jsx)$/,
534 535
          include: [
            dir,
536
            /next[\\/]dist[\\/]next-server[\\/]lib/,
537 538
            /next[\\/]dist[\\/]client/,
            /next[\\/]dist[\\/]pages/,
539
            /[\\/](strip-ansi|ansi-regex)[\\/]/,
540
          ],
541
          exclude: (path: string) => {
542
            if (
543
              /next[\\/]dist[\\/]next-server[\\/]lib/.test(path) ||
544
              /next[\\/]dist[\\/]client/.test(path) ||
545
              /next[\\/]dist[\\/]pages/.test(path) ||
546
              /[\\/](strip-ansi|ansi-regex)[\\/]/.test(path)
547
            ) {
548 549
              return false
            }
550

551
            return /node_modules/.test(path)
552
          },
553
          use: defaultLoaders.babel,
554
        },
J
Joe Haddad 已提交
555 556 557
        config.experimental.css &&
          // Support CSS imports
          ({
558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619
            oneOf: [
              {
                test: /\.css$/,
                issuer: { include: [customAppFile].filter(Boolean) },
                use: isServer
                  ? // Global CSS is ignored on the server because it's only needed
                    // on the client-side.
                    require.resolve('ignore-loader')
                  : [
                      // During development we load CSS via JavaScript so we can
                      // hot reload it without refreshing the page.
                      dev && {
                        loader: require.resolve('style-loader'),
                        options: {
                          // By default, style-loader injects CSS into the bottom
                          // of <head>. This causes ordering problems between dev
                          // and prod. To fix this, we render a <noscript> tag as
                          // an anchor for the styles to be placed before. These
                          // styles will be applied _before_ <style jsx global>.
                          insert: function(element: Node) {
                            // These elements should always exist. If they do not,
                            // this code should fail.
                            var anchorElement = document.querySelector(
                              '#__next_css__DO_NOT_USE__'
                            )!
                            var parentNode = anchorElement.parentNode! // Normally <head>

                            // Each style tag should be placed right before our
                            // anchor. By inserting before and not after, we do not
                            // need to track the last inserted element.
                            parentNode.insertBefore(element, anchorElement)

                            // Remember: this is development only code.
                            //
                            // After styles are injected, we need to remove the
                            // <style> tags that set `body { display: none; }`.
                            //
                            // We use `requestAnimationFrame` as a way to defer
                            // this operation since there may be multiple style
                            // tags.
                            ;(self.requestAnimationFrame || setTimeout)(
                              function() {
                                for (
                                  var x = document.querySelectorAll(
                                      '[data-next-hide-fouc]'
                                    ),
                                    i = x.length;
                                  i--;

                                ) {
                                  x[i].parentNode!.removeChild(x[i])
                                }
                              }
                            )
                          },
                        },
                      },
                      // When building for production we extract CSS into
                      // separate files.
                      !dev && {
                        loader: MiniCssExtractPlugin.loader,
                        options: {},
620
                      },
J
Joe Haddad 已提交
621

622 623 624 625 626
                      // Resolve CSS `@import`s and `url()`s
                      {
                        loader: require.resolve('css-loader'),
                        options: { importLoaders: 1, sourceMap: true },
                      },
J
Joe Haddad 已提交
627

628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675
                      // Compile CSS
                      {
                        loader: require.resolve('postcss-loader'),
                        options: {
                          ident: 'postcss',
                          plugins: () => [
                            // Make Flexbox behave like the spec cross-browser.
                            require('postcss-flexbugs-fixes'),
                            // Run Autoprefixer and compile new CSS features.
                            require('postcss-preset-env')({
                              autoprefixer: {
                                // Disable legacy flexbox support
                                flexbox: 'no-2009',
                              },
                              // Enable CSS features that have shipped to the
                              // web platform, i.e. in 2+ browsers unflagged.
                              stage: 3,
                            }),
                          ],
                          sourceMap: true,
                        },
                      },
                    ].filter(Boolean),
                // A global CSS import always has side effects. Webpack will tree
                // shake the CSS without this option if the issuer claims to have
                // no side-effects.
                // See https://github.com/webpack/webpack/issues/6571
                sideEffects: true,
              },
              {
                test: /\.css$/,
                use: isServer
                  ? require.resolve('ignore-loader')
                  : {
                      loader: 'error-loader',
                      options: {
                        reason:
                          `Global CSS ${chalk.bold(
                            'cannot'
                          )} be imported from files other than your ${chalk.bold(
                            'Custom <App>'
                          )}. Please move all global CSS imports to ${chalk.cyan(
                            customAppFile
                              ? path.relative(dir, customAppFile)
                              : 'pages/_app.js'
                          )}.\n` +
                          `Read more: https://err.sh/next.js/global-css`,
                      },
J
Joe Haddad 已提交
676
                    },
677 678
              },
            ],
J
Joe Haddad 已提交
679 680 681 682 683 684 685 686 687 688 689 690 691 692 693
          } as webpack.RuleSetRule),
        config.experimental.css &&
          ({
            loader: require.resolve('file-loader'),
            issuer: {
              // file-loader is only used for CSS files, e.g. url() for a SVG
              // or font files
              test: /\.css$/,
            },
            // Exclude extensions that webpack handles by default
            exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
            options: {
              name: 'static/media/[name].[hash].[ext]',
            },
          } as webpack.RuleSetRule),
694
      ].filter(Boolean),
N
nkzawa 已提交
695
    },
T
Tim Neutkens 已提交
696
    plugins: [
697 698
      // This plugin makes sure `output.filename` is used for entry chunks
      new ChunkNamesPlugin(),
699
      new webpack.DefinePlugin({
700
        ...Object.keys(config.env).reduce((acc, key) => {
701
          if (/^(?:NODE_.+)|^(?:__.+)$/i.test(key)) {
702 703 704
            throw new Error(
              `The key "${key}" under "env" in next.config.js is not allowed. https://err.sh/zeit/next.js/env-key-not-allowed`
            )
705 706 707
          }

          return {
708
            ...acc,
709
            [`process.env.${key}`]: JSON.stringify(config.env[key]),
710
          }
711
        }, {}),
712
        'process.env.NODE_ENV': JSON.stringify(webpackMode),
713
        'process.crossOrigin': JSON.stringify(crossOrigin),
714
        'process.browser': JSON.stringify(!isServer),
J
JJ Kasper 已提交
715 716 717
        'process.env.__NEXT_TEST_MODE': JSON.stringify(
          process.env.__NEXT_TEST_MODE
        ),
718
        // This is used in client/dev-error-overlay/hot-dev-client.js to replace the dist directory
719 720 721 722 723 724
        ...(dev && !isServer
          ? {
              'process.env.__NEXT_DIST_DIR': JSON.stringify(distDir),
            }
          : {}),
        'process.env.__NEXT_EXPORT_TRAILING_SLASH': JSON.stringify(
725
          config.exportTrailingSlash
726
        ),
727 728 729 730 731 732 733 734 735 736 737 738
        'process.env.__NEXT_MODERN_BUILD': JSON.stringify(
          config.experimental.modern && !dev
        ),
        'process.env.__NEXT_GRANULAR_CHUNKS': JSON.stringify(
          config.experimental.granularChunks && !dev
        ),
        'process.env.__NEXT_BUILD_INDICATOR': JSON.stringify(
          config.devIndicators.buildActivity
        ),
        'process.env.__NEXT_PRERENDER_INDICATOR': JSON.stringify(
          config.devIndicators.autoPrerender
        ),
739
        ...(isServer
740 741 742 743 744 745
          ? {
              // 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),
            }
746
          : undefined),
747
      }),
748 749 750 751
      !isServer &&
        new ReactLoadablePlugin({
          filename: REACT_LOADABLE_MANIFEST,
        }),
752
      !isServer && new DropClientPage(),
J
JJ Kasper 已提交
753 754 755 756 757
      new ChunkGraphPlugin(buildId, {
        dir,
        distDir,
        isServer,
      }),
758 759 760 761 762 763
      // 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 &&
        new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
764 765 766 767 768 769 770 771 772 773 774 775 776 777 778
      ...(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')
            const {
              UnlinkRemovedPagesPlugin,
            } = require('./webpack/plugins/unlink-removed-pages-plugin')
            const devPlugins = [
              new UnlinkRemovedPagesPlugin(),
              new webpack.NoEmitOnErrorsPlugin(),
              new NextJsRequireCacheHotReloader(),
            ]
779

780
            if (!isServer) {
781
              const AutoDllPlugin = importAutoDllPlugin({ distDir })
782 783 784 785 786 787 788 789 790
              devPlugins.push(
                new AutoDllPlugin({
                  filename: '[name]_[hash].js',
                  path: './static/development/dll',
                  context: dir,
                  entry: {
                    dll: ['react', 'react-dom'],
                  },
                  config: {
J
JJ Kasper 已提交
791
                    devtool,
792 793 794 795 796 797 798
                    mode: webpackMode,
                    resolve: resolveConfig,
                  },
                })
              )
              devPlugins.push(new webpack.HotModuleReplacementPlugin())
            }
799

800 801 802
            return devPlugins
          })()
        : []),
803
      !dev && new webpack.HashedModuleIdsPlugin(),
804 805 806 807 808 809 810 811 812 813 814 815
      !dev &&
        new webpack.IgnorePlugin({
          checkResource: (resource: string) => {
            return /react-is/.test(resource)
          },
          checkContext: (context: string) => {
            return (
              /next-server[\\/]dist[\\/]/.test(context) ||
              /next[\\/]dist[\\/]/.test(context)
            )
          },
        }),
J
Joe Haddad 已提交
816
      isServerless && isServer && new ServerlessPlugin(),
817 818
      isServer && new PagesManifestPlugin(isLikeServerless),
      target === 'server' &&
819 820
        isServer &&
        new NextJsSSRModuleCachePlugin({ outputPath }),
821
      isServer && new NextJsSsrImportPlugin(),
822 823 824 825 826 827
      !isServer &&
        new BuildManifestPlugin({
          buildId,
          clientManifest: config.experimental.granularChunks,
          modern: config.experimental.modern,
        }),
J
Joe Haddad 已提交
828 829 830 831 832 833 834 835
      // Extract CSS as CSS file(s) in the client-side production bundle.
      config.experimental.css &&
        !isServer &&
        !dev &&
        new MiniCssExtractPlugin({
          filename: 'static/css/[contenthash].css',
          chunkFilename: 'static/css/[contenthash].chunk.css',
        }),
836 837 838
      tracer &&
        new ProfilingPlugin({
          tracer,
839
        }),
840 841
      !isServer &&
        useTypeScript &&
M
Maël Nison 已提交
842 843 844 845 846 847 848 849 850 851 852 853 854
        new ForkTsCheckerWebpackPlugin(
          PnpWebpackPlugin.forkTsCheckerOptions({
            typescript: typeScriptPath,
            async: dev,
            useTypescriptIncrementalApi: true,
            checkSyntacticErrors: true,
            tsconfig: tsConfigPath,
            reportFiles: ['**', '!**/__tests__/**', '!**/?(*.)(spec|test).*'],
            compilerOptions: { isolatedModules: true, noEmit: true },
            silent: true,
            formatter: 'codeframe',
          })
        ),
855 856 857 858 859 860 861 862 863 864 865 866
      config.experimental.modern &&
        !isServer &&
        !dev &&
        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')
867 868 869
              : escapePathVariables(
                  args[0].chunk.name.replace(/\.js$/, '.module.js')
                )
870 871 872 873
          },
          chunkFilename: (inputChunkName: string) =>
            inputChunkName.replace(/\.js$/, '.module.js'),
        }),
874
    ].filter((Boolean as any) as ExcludesFalse),
875
  }
876

T
Tim Neutkens 已提交
877
  if (typeof config.webpack === 'function') {
878 879 880 881 882 883 884 885 886 887
    webpackConfig = config.webpack(webpackConfig, {
      dir,
      dev,
      isServer,
      buildId,
      config,
      defaultLoaders,
      totalPages,
      webpack,
    })
888 889 890

    // @ts-ignore: Property 'then' does not exist on type 'Configuration'
    if (typeof webpackConfig.then === 'function') {
891
      console.warn(
892
        '> Promise returned in next config. https://err.sh/zeit/next.js/promise-in-next-config'
893
      )
894
    }
895
  }
T
Tim Neutkens 已提交
896

897
  // check if using @zeit/next-typescript and show warning
898 899 900
  if (
    isServer &&
    webpackConfig.module &&
901 902 903 904
    Array.isArray(webpackConfig.module.rules)
  ) {
    let foundTsRule = false

905 906
    webpackConfig.module.rules = webpackConfig.module.rules.filter(
      (rule): boolean => {
907
        if (!(rule.test instanceof RegExp)) return true
908
        if ('noop.ts'.match(rule.test) && !'noop.js'.match(rule.test)) {
909 910 911 912 913
          // remove if it matches @zeit/next-typescript
          foundTsRule = rule.use === defaultLoaders.babel
          return !foundTsRule
        }
        return true
914 915
      }
    )
916 917

    if (foundTsRule) {
918
      console.warn(
919
        '\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'
920
      )
921 922 923
    }
  }

924
  // Patch `@zeit/next-sass` and `@zeit/next-less` compatibility
925
  if (webpackConfig.module && Array.isArray(webpackConfig.module.rules)) {
926 927 928 929 930 931 932
    ;[].forEach.call(webpackConfig.module.rules, function(
      rule: webpack.RuleSetRule
    ) {
      if (!(rule.test instanceof RegExp && Array.isArray(rule.use))) {
        return
      }

933 934 935 936 937 938 939
      const isSass =
        rule.test.source === '\\.scss$' || rule.test.source === '\\.sass$'
      const isLess = rule.test.source === '\\.less$'
      const isCss = rule.test.source === '\\.css$'

      // Check if the rule we're iterating over applies to Sass, Less, or CSS
      if (!(isSass || isLess || isCss)) {
940 941 942 943 944 945 946 947 948
        return
      }

      ;[].forEach.call(rule.use, function(use: webpack.RuleSetUseItem) {
        if (
          !(
            use &&
            typeof use === 'object' &&
            // Identify use statements only pertaining to `css-loader`
949 950
            (use.loader === 'css-loader' ||
              use.loader === 'css-loader/locals') &&
951 952 953 954 955 956 957
            use.options &&
            typeof use.options === 'object' &&
            // The `minimize` property is a good heuristic that we need to
            // perform this hack. The `minimize` property was only valid on
            // old `css-loader` versions. Custom setups (that aren't next-sass
            // or next-less) likely have the newer version.
            // We still handle this gracefully below.
958 959 960 961 962
            (Object.prototype.hasOwnProperty.call(use.options, 'minimize') ||
              Object.prototype.hasOwnProperty.call(
                use.options,
                'exportOnlyLocals'
              ))
963 964 965 966 967 968 969 970 971 972 973 974 975 976 977
          )
        ) {
          return
        }

        // Try to monkey patch within a try-catch. We shouldn't fail the build
        // if we cannot pull this off.
        // The user may not even be using the `next-sass` or `next-less`
        // plugins.
        // If it does work, great!
        try {
          // Resolve the version of `@zeit/next-css` as depended on by the Sass
          // or Less plugin.
          const correctNextCss = resolveRequest(
            '@zeit/next-css',
978 979 980 981 982 983 984 985 986 987 988
            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'
                    : 'next'
                )
989 990 991 992 993 994 995
          )

          // 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.
996
            const correctCssLoader = resolveRequest(use.loader, correctNextCss)
997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008
            if (correctCssLoader) {
              // We saved the user from a failed build!
              use.loader = correctCssLoader
            }
          }
        } catch (_) {
          // The error is not required to be handled.
        }
      })
    })
  }

1009
  // Backwards compat for `main.js` entry key
1010
  const originalEntry: any = webpackConfig.entry
1011 1012
  if (typeof originalEntry !== 'undefined') {
    webpackConfig.entry = async () => {
1013 1014 1015 1016
      const entry: WebpackEntrypoints =
        typeof originalEntry === 'function'
          ? await originalEntry()
          : originalEntry
1017 1018 1019
      // Server compilation doesn't have main.js
      if (clientEntries && entry['main.js'] && entry['main.js'].length > 0) {
        const originalFile = clientEntries[CLIENT_STATIC_FILES_RUNTIME_MAIN]
1020
        // @ts-ignore TODO: investigate type error
1021 1022
        entry[CLIENT_STATIC_FILES_RUNTIME_MAIN] = [
          ...entry['main.js'],
1023
          originalFile,
1024
        ]
1025
      }
1026
      delete entry['main.js']
1027

1028
      return entry
1029 1030 1031
    }
  }

1032
  if (!dev) {
1033 1034 1035 1036
    // @ts-ignore entry is always a function
    webpackConfig.entry = await webpackConfig.entry()
  }

T
Tim Neutkens 已提交
1037
  return webpackConfig
N
nkzawa 已提交
1038
}