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

9
import {
10
  DOT_NEXT_ALIAS,
11 12 13 14
  NEXT_PROJECT_ROOT,
  NEXT_PROJECT_ROOT_DIST_CLIENT,
  PAGES_DIR_ALIAS,
} from '../lib/constants'
15
import { fileExists } from '../lib/file-exists'
16 17 18 19 20 21 22 23
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 已提交
24
import { findPageFile } from '../server/lib/find-page-file'
25 26
import { WebpackEntrypoints } from './entries'
import BuildManifestPlugin from './webpack/plugins/build-manifest-plugin'
27
import { ChunkGraphPlugin } from './webpack/plugins/chunk-graph-plugin'
28
import ChunkNamesPlugin from './webpack/plugins/chunk-names-plugin'
J
Joe Haddad 已提交
29
import { CssMinimizerPlugin } from './webpack/plugins/css-minimizer-plugin'
30
import { importAutoDllPlugin } from './webpack/plugins/dll-import'
31
import { DropClientPage } from './webpack/plugins/next-drop-client-page-plugin'
32
import NextEsmPlugin from './webpack/plugins/next-esm-plugin'
33 34 35
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'
36 37
// @ts-ignore: JS file
import { ProfilingPlugin } from './webpack/plugins/profiling-plugin'
38 39 40 41
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'

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

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

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

T
Tim Neutkens 已提交
88 89 90
  // Support for NODE_PATH
  const nodePathList = (process.env.NODE_PATH || '')
    .split(process.platform === 'win32' ? ';' : ':')
91
    .filter(p => !!p)
T
Tim Neutkens 已提交
92

93 94 95 96 97 98
  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 已提交
99
  const outputPath = path.join(distDir, isServer ? outputDir : '')
100
  const totalPages = Object.keys(entrypoints).length
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
  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 已提交
116

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

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

T
Tim Neutkens 已提交
164 165
  const webpackMode = dev ? 'development' : 'production'

166 167 168
  const terserPluginConfig = {
    parallel: true,
    sourceMap: false,
169
    cache: true,
170
    cpus: config.experimental.cpus,
171
    distDir: distDir,
172
  }
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
  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 已提交
194
  const devtool = dev ? 'cheap-module-source-map' : false
195

196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
  // 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',
219
          test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
220 221 222
        },
      },
    },
223
    prodGranular: {
224
      chunks: 'initial',
225 226 227 228
      cacheGroups: {
        default: false,
        vendors: false,
        framework: {
229 230 231
          // 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',
232 233 234 235 236 237 238 239 240 241 242
          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())
            )
          },
243 244 245 246 247 248
          name(module: { libIdent: Function }): string {
            return crypto
              .createHash('sha1')
              .update(module.libIdent({ context: dir }))
              .digest('hex')
              .substring(0, 8)
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
          },
          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
                  },
                  ''
                )
              )
271
              .digest('hex')
272 273 274 275 276 277 278 279
          },
          priority: 10,
          minChunks: 2,
          reuseExistingChunk: true,
        },
      },
      maxInitialRequests: 20,
    },
280 281 282 283 284 285 286
  }

  // Select appropriate SplitChunksPlugin config for this build
  let splitChunksConfig: webpack.Options.SplitChunksOptions
  if (dev) {
    splitChunksConfig = splitChunksConfigs.dev
  } else {
287 288 289
    splitChunksConfig = config.experimental.granularChunks
      ? splitChunksConfigs.prodGranular
      : splitChunksConfigs.prod
290 291
  }

292 293 294 295 296
  const crossOrigin =
    !config.crossOrigin && config.experimental.modern
      ? 'anonymous'
      : config.crossOrigin

J
Joe Haddad 已提交
297 298 299 300 301 302 303 304 305 306 307
  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))
  }

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

327 328 329
            if (notExternalModules.indexOf(request) !== -1) {
              return callback()
            }
K
k-kawakami 已提交
330

331 332 333
            // Resolve the import with the webpack provided context, this
            // ensures we're resolving the correct version when multiple
            // exist.
334 335
            let res
            try {
336
              res = resolveRequest(request, `${context}/`)
337
            } catch (err) {
338 339 340 341
              // 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.
342 343 344 345 346 347 348 349 350 351 352
              if (
                request === 'react-ssr-prepass' &&
                !config.experimental.ampBindInitData
              ) {
                if (
                  context.replace(/\\/g, '/').includes('next-server/server')
                ) {
                  return callback(undefined, `commonjs ${request}`)
                }
              }

353 354 355
              // 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).
356 357
              return callback()
            }
K
k-kawakami 已提交
358

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

365 366 367 368
            // 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).
369 370
            let baseRes
            try {
371
              baseRes = resolveRequest(request, `${dir}/`)
372 373
            } catch (err) {}

374 375 376
            // 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.
377 378 379 380
            if (baseRes !== res) {
              return callback()
            }

381 382 383 384 385 386 387 388 389
            // 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 已提交
390

391 392 393 394 395 396 397
            // 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 已提交
398

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

405
            // Default behavior: bundle the code!
406
            callback()
407 408 409
          },
        ]
      : [
410 411
          // 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
412
          '@ampproject/toolbox-optimizer', // except this one
413 414 415 416 417 418 419
          (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
420
              if (context.replace(/\\/g, '/').includes('next-server/server')) {
421 422 423 424 425
                return callback(undefined, `commonjs ${request}`)
              }
            }
            return callback()
          },
426
        ],
427 428 429 430 431 432 433 434 435
    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 已提交
436
        // Minify JavaScript
437 438 439 440
        new TerserPlugin({
          ...terserPluginConfig,
          terserOptions,
        }),
J
Joe Haddad 已提交
441 442
        // Minify CSS
        config.experimental.css &&
J
Joe Haddad 已提交
443 444
          new CssMinimizerPlugin({
            postcssOptions: {
J
Joe Haddad 已提交
445 446 447 448
              map: {
                // `inline: false` generates the source map in a separate file.
                // Otherwise, the CSS file is needlessly large.
                inline: false,
J
Joe Haddad 已提交
449 450 451
                // `annotation: false` skips appending the `sourceMappingURL`
                // to the end of the CSS file. Webpack already handles this.
                annotation: false,
J
Joe Haddad 已提交
452 453 454 455
              },
            },
          }),
      ].filter(Boolean),
456 457
    },
    recordsPath: path.join(outputPath, 'records.json'),
N
nkzawa 已提交
458
    context: dir,
459
    // Kept as function to be backwards compatible
T
Tim Neutkens 已提交
460 461
    entry: async () => {
      return {
462 463
        ...(clientEntries ? clientEntries : {}),
        ...entrypoints,
T
Tim Neutkens 已提交
464 465
      }
    },
N
nkzawa 已提交
466
    output: {
467
      path: outputPath,
468
      filename: ({ chunk }: { chunk: { name: string } }) => {
469
        // Use `[name]-[contenthash].js` in production
470 471 472 473 474
        if (
          !dev &&
          (chunk.name === CLIENT_STATIC_FILES_RUNTIME_MAIN ||
            chunk.name === CLIENT_STATIC_FILES_RUNTIME_WEBPACK)
        ) {
475
          return chunk.name.replace(/\.js$/, '-[contenthash].js')
476 477 478
        }
        return '[name]'
      },
T
Tim Neutkens 已提交
479
      libraryTarget: isServer ? 'commonjs2' : 'var',
480 481 482
      hotUpdateChunkFilename: 'static/webpack/[id].[hash].hot-update.js',
      hotUpdateMainFilename: 'static/webpack/[hash].hot-update.json',
      // This saves chunks with the name given via `import()`
483 484 485
      chunkFilename: isServer
        ? `${dev ? '[name]' : '[name].[contenthash]'}.js`
        : `static/chunks/${dev ? '[name]' : '[name].[contenthash]'}.js`,
A
Andy 已提交
486
      strictModuleExceptionHandling: true,
487
      crossOriginLoading: crossOrigin,
488
      futureEmitAssets: !dev,
489
      webassemblyModuleFilename: 'static/wasm/[modulehash].wasm',
N
nkzawa 已提交
490
    },
491
    performance: false,
T
Tim Neutkens 已提交
492
    resolve: resolveConfig,
N
nkzawa 已提交
493
    resolveLoader: {
494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510
      // The loaders Next.js provides
      alias: [
        'emit-file-loader',
        '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 已提交
511
      modules: [
512
        'node_modules',
513 514
        ...nodePathList, // Support for NODE_PATH environment variable
      ],
515
      plugins: [PnpWebpackPlugin],
N
nkzawa 已提交
516
    },
T
Tim Neutkens 已提交
517
    // @ts-ignore this is filtered
N
nkzawa 已提交
518
    module: {
519
      strictExportPresence: true,
520
      rules: [
521 522 523 524 525 526
        config.experimental.ampBindInitData &&
          !isServer && {
            test: /\.(tsx|ts|js|mjs|jsx)$/,
            include: [path.join(dir, 'data')],
            use: 'next-data-loader',
          },
T
Tim Neutkens 已提交
527
        {
528
          test: /\.(tsx|ts|js|mjs|jsx)$/,
529 530
          include: [
            dir,
531
            /next[\\/]dist[\\/]next-server[\\/]lib/,
532 533
            /next[\\/]dist[\\/]client/,
            /next[\\/]dist[\\/]pages/,
534
            /[\\/](strip-ansi|ansi-regex)[\\/]/,
535
          ],
536
          exclude: (path: string) => {
537
            if (
538
              /next[\\/]dist[\\/]next-server[\\/]lib/.test(path) ||
539
              /next[\\/]dist[\\/]client/.test(path) ||
540
              /next[\\/]dist[\\/]pages/.test(path) ||
541
              /[\\/](strip-ansi|ansi-regex)[\\/]/.test(path)
542
            ) {
543 544
              return false
            }
545

546
            return /node_modules/.test(path)
547
          },
548
          use: defaultLoaders.babel,
549
        },
J
Joe Haddad 已提交
550 551 552 553 554 555 556 557 558 559 560 561
        config.experimental.css &&
          // Support CSS imports
          ({
            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.
562 563 564 565 566
                  dev && {
                    loader: require.resolve('style-loader'),
                    options: {
                      // By default, style-loader injects CSS into the bottom
                      // of <head>. This causes ordering problems between dev
567
                      // and prod. To fix this, we render a <noscript> tag as
568
                      // an anchor for the styles to be placed before. These
569 570
                      // styles will be applied _before_ <style jsx global>.
                      insert: (element: Node) => {
571 572 573
                        // These elements should always exist. If they do not,
                        // this code should fail.
                        const anchorElement = document.querySelector(
574
                          '#__next_css__DO_NOT_USE__'
575 576
                        )!
                        const parentNode = anchorElement.parentNode! // Normally <head>
577

578 579 580 581
                        // 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)
582
                      },
583 584
                    },
                  },
J
Joe Haddad 已提交
585 586 587 588 589 590 591 592 593 594
                  // When building for production we extract CSS into
                  // separate files.
                  !dev && {
                    loader: MiniCssExtractPlugin.loader,
                    options: {},
                  },

                  // Resolve CSS `@import`s and `url()`s
                  {
                    loader: require.resolve('css-loader'),
595
                    options: { importLoaders: 1, sourceMap: true },
J
Joe Haddad 已提交
596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616
                  },

                  // 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,
                        }),
                      ],
617
                      sourceMap: true,
J
Joe Haddad 已提交
618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
                    },
                  },
                ].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,
          } 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),
641
      ].filter(Boolean),
N
nkzawa 已提交
642
    },
T
Tim Neutkens 已提交
643
    plugins: [
644 645
      // This plugin makes sure `output.filename` is used for entry chunks
      new ChunkNamesPlugin(),
646
      new webpack.DefinePlugin({
647
        ...Object.keys(config.env).reduce((acc, key) => {
648
          if (/^(?:NODE_.+)|^(?:__.+)$/i.test(key)) {
649 650 651
            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`
            )
652 653 654
          }

          return {
655
            ...acc,
656
            [`process.env.${key}`]: JSON.stringify(config.env[key]),
657
          }
658
        }, {}),
659
        'process.env.NODE_ENV': JSON.stringify(webpackMode),
660
        'process.crossOrigin': JSON.stringify(crossOrigin),
661
        'process.browser': JSON.stringify(!isServer),
J
JJ Kasper 已提交
662 663 664
        'process.env.__NEXT_TEST_MODE': JSON.stringify(
          process.env.__NEXT_TEST_MODE
        ),
665
        // This is used in client/dev-error-overlay/hot-dev-client.js to replace the dist directory
666 667 668 669 670 671
        ...(dev && !isServer
          ? {
              'process.env.__NEXT_DIST_DIR': JSON.stringify(distDir),
            }
          : {}),
        'process.env.__NEXT_EXPORT_TRAILING_SLASH': JSON.stringify(
672
          config.exportTrailingSlash
673
        ),
674 675 676 677 678 679 680 681 682 683 684 685
        '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
        ),
686
        ...(isServer
687 688 689 690 691 692
          ? {
              // 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),
            }
693
          : undefined),
694
      }),
695 696 697 698
      !isServer &&
        new ReactLoadablePlugin({
          filename: REACT_LOADABLE_MANIFEST,
        }),
699
      !isServer && new DropClientPage(),
J
JJ Kasper 已提交
700 701 702 703 704
      new ChunkGraphPlugin(buildId, {
        dir,
        distDir,
        isServer,
      }),
705 706 707 708 709 710
      // 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$/),
711 712 713 714 715 716 717 718 719 720 721 722 723 724 725
      ...(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(),
            ]
726

727
            if (!isServer) {
728
              const AutoDllPlugin = importAutoDllPlugin({ distDir })
729 730 731 732 733 734 735 736 737
              devPlugins.push(
                new AutoDllPlugin({
                  filename: '[name]_[hash].js',
                  path: './static/development/dll',
                  context: dir,
                  entry: {
                    dll: ['react', 'react-dom'],
                  },
                  config: {
J
JJ Kasper 已提交
738
                    devtool,
739 740 741 742 743 744 745
                    mode: webpackMode,
                    resolve: resolveConfig,
                  },
                })
              )
              devPlugins.push(new webpack.HotModuleReplacementPlugin())
            }
746

747 748 749
            return devPlugins
          })()
        : []),
750
      !dev && new webpack.HashedModuleIdsPlugin(),
751 752 753 754 755 756 757 758 759 760 761 762
      !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 已提交
763
      isServerless && isServer && new ServerlessPlugin(),
764 765
      isServer && new PagesManifestPlugin(isLikeServerless),
      target === 'server' &&
766 767
        isServer &&
        new NextJsSSRModuleCachePlugin({ outputPath }),
768
      isServer && new NextJsSsrImportPlugin(),
769 770 771 772 773 774
      !isServer &&
        new BuildManifestPlugin({
          buildId,
          clientManifest: config.experimental.granularChunks,
          modern: config.experimental.modern,
        }),
J
Joe Haddad 已提交
775 776 777 778 779 780 781 782
      // 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',
        }),
783 784 785
      tracer &&
        new ProfilingPlugin({
          tracer,
786
        }),
787 788
      !isServer &&
        useTypeScript &&
M
Maël Nison 已提交
789 790 791 792 793 794 795 796 797 798 799 800 801
        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',
          })
        ),
802 803 804 805 806 807 808 809 810 811 812 813
      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')
814 815 816
              : escapePathVariables(
                  args[0].chunk.name.replace(/\.js$/, '.module.js')
                )
817 818 819 820
          },
          chunkFilename: (inputChunkName: string) =>
            inputChunkName.replace(/\.js$/, '.module.js'),
        }),
821
    ].filter((Boolean as any) as ExcludesFalse),
822
  }
823

T
Tim Neutkens 已提交
824
  if (typeof config.webpack === 'function') {
825 826 827 828 829 830 831 832 833 834
    webpackConfig = config.webpack(webpackConfig, {
      dir,
      dev,
      isServer,
      buildId,
      config,
      defaultLoaders,
      totalPages,
      webpack,
    })
835 836 837

    // @ts-ignore: Property 'then' does not exist on type 'Configuration'
    if (typeof webpackConfig.then === 'function') {
838
      console.warn(
839
        '> Promise returned in next config. https://err.sh/zeit/next.js/promise-in-next-config'
840
      )
841
    }
842
  }
T
Tim Neutkens 已提交
843

844
  // check if using @zeit/next-typescript and show warning
845 846 847
  if (
    isServer &&
    webpackConfig.module &&
848 849 850 851
    Array.isArray(webpackConfig.module.rules)
  ) {
    let foundTsRule = false

852 853
    webpackConfig.module.rules = webpackConfig.module.rules.filter(
      (rule): boolean => {
854
        if (!(rule.test instanceof RegExp)) return true
855
        if ('noop.ts'.match(rule.test) && !'noop.js'.match(rule.test)) {
856 857 858 859 860
          // remove if it matches @zeit/next-typescript
          foundTsRule = rule.use === defaultLoaders.babel
          return !foundTsRule
        }
        return true
861 862
      }
    )
863 864

    if (foundTsRule) {
865
      console.warn(
866
        '\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'
867
      )
868 869 870
    }
  }

871
  // Backwards compat for `main.js` entry key
872
  const originalEntry: any = webpackConfig.entry
873 874
  if (typeof originalEntry !== 'undefined') {
    webpackConfig.entry = async () => {
875 876 877 878
      const entry: WebpackEntrypoints =
        typeof originalEntry === 'function'
          ? await originalEntry()
          : originalEntry
879 880 881
      // 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]
882
        // @ts-ignore TODO: investigate type error
883 884
        entry[CLIENT_STATIC_FILES_RUNTIME_MAIN] = [
          ...entry['main.js'],
885
          originalFile,
886
        ]
887
      }
888
      delete entry['main.js']
889

890
      return entry
891 892 893
    }
  }

894
  if (!dev) {
895 896 897 898
    // @ts-ignore entry is always a function
    webpackConfig.entry = await webpackConfig.entry()
  }

T
Tim Neutkens 已提交
899
  return webpackConfig
N
nkzawa 已提交
900
}