webpack-config.ts 36.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 130 131
  const ignoreTypeScriptErrors = dev
    ? config.typescript && config.typescript.ignoreDevErrors
    : config.typescript && config.typescript.ignoreBuildErrors
132

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

T
Tim Neutkens 已提交
171 172
  const webpackMode = dev ? 'development' : 'production'

173
  const terserPluginConfig = {
174
    cache: true,
175
    cpus: config.experimental.cpus,
176
    distDir: distDir,
177 178 179
    parallel: true,
    sourceMap: false,
    workerThreads: config.experimental.workerThreads,
180
  }
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
  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 已提交
202
  const devtool = dev ? 'cheap-module-source-map' : false
203

204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
  // 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',
227
          test: /[\\/]node_modules[\\/](react|react-dom|scheduler|use-subscription)[\\/]/,
228 229 230
        },
      },
    },
231
    prodGranular: {
232
      chunks: 'all',
233 234 235 236
      cacheGroups: {
        default: false,
        vendors: false,
        framework: {
237
          chunks: 'all',
238
          name: 'framework',
A
Alex Castle 已提交
239
          // This regex ignores nested copies of framework libraries so they're
240 241
          // bundled with their issuer.
          // https://github.com/zeit/next.js/pull/9012
242
          test: /(?<!node_modules.*)[\\/]node_modules[\\/](react|react-dom|scheduler|prop-types|use-subscription)[\\/]/,
243 244 245 246 247 248 249 250 251
          priority: 40,
        },
        lib: {
          test(module: { size: Function; identifier: Function }): boolean {
            return (
              module.size() > 160000 &&
              /node_modules[/\\]/.test(module.identifier())
            )
          },
252 253 254 255 256 257
          name(module: { libIdent: Function }): string {
            return crypto
              .createHash('sha1')
              .update(module.libIdent({ context: dir }))
              .digest('hex')
              .substring(0, 8)
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
          },
          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
                  },
                  ''
                )
              )
280
              .digest('hex')
281 282 283 284 285 286
          },
          priority: 10,
          minChunks: 2,
          reuseExistingChunk: true,
        },
      },
287 288
      maxInitialRequests: 25,
      minSize: 20000,
289
    },
290 291 292 293 294 295 296
  }

  // Select appropriate SplitChunksPlugin config for this build
  let splitChunksConfig: webpack.Options.SplitChunksOptions
  if (dev) {
    splitChunksConfig = splitChunksConfigs.dev
  } else {
297 298 299
    splitChunksConfig = config.experimental.granularChunks
      ? splitChunksConfigs.prodGranular
      : splitChunksConfigs.prod
300 301
  }

302 303 304 305 306
  const crossOrigin =
    !config.crossOrigin && config.experimental.modern
      ? 'anonymous'
      : config.crossOrigin

J
Joe Haddad 已提交
307
  let customAppFile: string | null = config.experimental.css
308
    ? await findPageFile(pagesDir, '/_app', config.pageExtensions)
J
Joe Haddad 已提交
309 310
    : null
  if (customAppFile) {
311
    customAppFile = path.resolve(path.join(pagesDir, customAppFile))
J
Joe Haddad 已提交
312 313
  }

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

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

337 338 339 340
            // 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.
J
Joe Haddad 已提交
341
            const start = request.charAt(0)
342
            if (start === '.' || start === '/') {
J
Joe Haddad 已提交
343
              return callback()
344 345
            }

346 347 348
            // Resolve the import with the webpack provided context, this
            // ensures we're resolving the correct version when multiple
            // exist.
349 350
            let res
            try {
351
              res = resolveRequest(request, `${context}/`)
352
            } catch (err) {
353 354 355 356
              // 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.
357 358 359 360 361 362 363 364 365 366 367
              if (
                request === 'react-ssr-prepass' &&
                !config.experimental.ampBindInitData
              ) {
                if (
                  context.replace(/\\/g, '/').includes('next-server/server')
                ) {
                  return callback(undefined, `commonjs ${request}`)
                }
              }

368 369 370
              // 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).
371 372
              return callback()
            }
K
k-kawakami 已提交
373

374 375
            // Same as above, if the request cannot be resolved we need to have
            // webpack "bundle" it so it surfaces the not found error.
376 377 378
            if (!res) {
              return callback()
            }
K
k-kawakami 已提交
379

380 381 382 383
            // 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).
384 385
            let baseRes
            try {
386
              baseRes = resolveRequest(request, `${dir}/`)
387 388
            } catch (err) {}

389 390 391
            // 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.
392 393 394 395
            if (baseRes !== res) {
              return callback()
            }

396 397 398 399 400 401 402 403 404
            // 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 已提交
405

406 407 408 409 410 411 412
            // 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 已提交
413

414 415
            // Anything else that is standard JavaScript within `node_modules`
            // can be externalized.
416 417 418
            if (res.match(/node_modules[/\\].*\.js$/)) {
              return callback(undefined, `commonjs ${request}`)
            }
K
k-kawakami 已提交
419

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

562
            return /node_modules/.test(path)
563
          },
564
          use: defaultLoaders.babel,
565
        },
J
Joe Haddad 已提交
566 567 568
        config.experimental.css &&
          // Support CSS imports
          ({
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 620 621 622 623 624 625 626 627 628 629 630
            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: {},
631
                      },
J
Joe Haddad 已提交
632

633 634 635 636 637
                      // Resolve CSS `@import`s and `url()`s
                      {
                        loader: require.resolve('css-loader'),
                        options: { importLoaders: 1, sourceMap: true },
                      },
J
Joe Haddad 已提交
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 676 677 678 679 680 681
                      // 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
J
JJ Kasper 已提交
682
                              ? path.relative(dir, customAppFile)
683 684 685 686
                              : 'pages/_app.js'
                          )}.\n` +
                          `Read more: https://err.sh/next.js/global-css`,
                      },
J
Joe Haddad 已提交
687
                    },
688 689
              },
            ],
J
Joe Haddad 已提交
690 691 692 693 694 695 696 697 698 699 700 701 702 703 704
          } 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),
705
      ].filter(Boolean),
N
nkzawa 已提交
706
    },
T
Tim Neutkens 已提交
707
    plugins: [
708 709
      // This plugin makes sure `output.filename` is used for entry chunks
      new ChunkNamesPlugin(),
710
      new webpack.DefinePlugin({
711
        ...Object.keys(config.env).reduce((acc, key) => {
712
          if (/^(?:NODE_.+)|^(?:__.+)$/i.test(key)) {
713 714 715
            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`
            )
716 717 718
          }

          return {
719
            ...acc,
720
            [`process.env.${key}`]: JSON.stringify(config.env[key]),
721
          }
722
        }, {}),
723
        'process.env.NODE_ENV': JSON.stringify(webpackMode),
724
        'process.crossOrigin': JSON.stringify(crossOrigin),
725
        'process.browser': JSON.stringify(!isServer),
J
JJ Kasper 已提交
726 727 728
        'process.env.__NEXT_TEST_MODE': JSON.stringify(
          process.env.__NEXT_TEST_MODE
        ),
729
        // This is used in client/dev-error-overlay/hot-dev-client.js to replace the dist directory
730 731 732 733 734 735
        ...(dev && !isServer
          ? {
              'process.env.__NEXT_DIST_DIR': JSON.stringify(distDir),
            }
          : {}),
        'process.env.__NEXT_EXPORT_TRAILING_SLASH': JSON.stringify(
736
          config.exportTrailingSlash
737
        ),
738 739 740
        'process.env.__NEXT_DEFER_SCRIPTS': JSON.stringify(
          config.experimental.deferScripts
        ),
741 742 743 744 745 746 747 748 749 750 751 752
        '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
        ),
G
Gerald Monaco 已提交
753 754 755
        'process.env.__NEXT_STRICT_MODE': JSON.stringify(
          config.reactStrictMode
        ),
756 757 758
        'process.env.__NEXT_REACT_MODE': JSON.stringify(
          config.experimental.reactMode
        ),
759
        ...(isServer
760 761 762 763 764 765
          ? {
              // 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),
            }
766
          : undefined),
767
      }),
768 769 770 771
      !isServer &&
        new ReactLoadablePlugin({
          filename: REACT_LOADABLE_MANIFEST,
        }),
772
      !isServer && new DropClientPage(),
J
JJ Kasper 已提交
773 774 775 776 777
      new ChunkGraphPlugin(buildId, {
        dir,
        distDir,
        isServer,
      }),
778 779 780 781 782 783
      // 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$/),
784 785 786 787 788 789 790 791 792 793 794 795 796 797 798
      ...(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(),
            ]
799

800
            if (!isServer) {
801
              const AutoDllPlugin = importAutoDllPlugin({ distDir })
802 803 804 805 806 807 808 809 810
              devPlugins.push(
                new AutoDllPlugin({
                  filename: '[name]_[hash].js',
                  path: './static/development/dll',
                  context: dir,
                  entry: {
                    dll: ['react', 'react-dom'],
                  },
                  config: {
J
JJ Kasper 已提交
811
                    devtool,
812 813 814 815 816 817 818
                    mode: webpackMode,
                    resolve: resolveConfig,
                  },
                })
              )
              devPlugins.push(new webpack.HotModuleReplacementPlugin())
            }
819

820 821 822
            return devPlugins
          })()
        : []),
823
      !dev && new webpack.HashedModuleIdsPlugin(),
824 825 826 827 828 829 830 831 832 833 834 835
      !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 已提交
836
      isServerless && isServer && new ServerlessPlugin(),
837 838
      isServer && new PagesManifestPlugin(isLikeServerless),
      target === 'server' &&
839 840
        isServer &&
        new NextJsSSRModuleCachePlugin({ outputPath }),
841
      isServer && new NextJsSsrImportPlugin(),
842 843 844 845 846 847
      !isServer &&
        new BuildManifestPlugin({
          buildId,
          clientManifest: config.experimental.granularChunks,
          modern: config.experimental.modern,
        }),
J
Joe Haddad 已提交
848 849 850 851 852 853 854 855
      // 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',
        }),
856 857 858
      tracer &&
        new ProfilingPlugin({
          tracer,
859
        }),
860 861
      !isServer &&
        useTypeScript &&
862
        !ignoreTypeScriptErrors &&
M
Maël Nison 已提交
863 864 865 866 867 868 869 870 871 872 873 874 875
        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',
          })
        ),
876 877 878 879 880 881 882 883 884 885 886 887
      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')
888 889 890
              : escapePathVariables(
                  args[0].chunk.name.replace(/\.js$/, '.module.js')
                )
891 892 893 894
          },
          chunkFilename: (inputChunkName: string) =>
            inputChunkName.replace(/\.js$/, '.module.js'),
        }),
895
    ].filter((Boolean as any) as ExcludesFalse),
896
  }
897

T
Tim Neutkens 已提交
898
  if (typeof config.webpack === 'function') {
899 900 901 902 903 904 905 906 907 908
    webpackConfig = config.webpack(webpackConfig, {
      dir,
      dev,
      isServer,
      buildId,
      config,
      defaultLoaders,
      totalPages,
      webpack,
    })
909 910 911

    // @ts-ignore: Property 'then' does not exist on type 'Configuration'
    if (typeof webpackConfig.then === 'function') {
912
      console.warn(
913
        '> Promise returned in next config. https://err.sh/zeit/next.js/promise-in-next-config'
914
      )
915
    }
916
  }
T
Tim Neutkens 已提交
917

918
  // check if using @zeit/next-typescript and show warning
919 920 921
  if (
    isServer &&
    webpackConfig.module &&
922 923 924 925
    Array.isArray(webpackConfig.module.rules)
  ) {
    let foundTsRule = false

926 927
    webpackConfig.module.rules = webpackConfig.module.rules.filter(
      (rule): boolean => {
928
        if (!(rule.test instanceof RegExp)) return true
929
        if ('noop.ts'.match(rule.test) && !'noop.js'.match(rule.test)) {
930 931 932 933 934
          // remove if it matches @zeit/next-typescript
          foundTsRule = rule.use === defaultLoaders.babel
          return !foundTsRule
        }
        return true
935 936
      }
    )
937 938

    if (foundTsRule) {
939
      console.warn(
940
        '\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'
941
      )
942 943 944
    }
  }

945
  // Patch `@zeit/next-sass` and `@zeit/next-less` compatibility
946
  if (webpackConfig.module && Array.isArray(webpackConfig.module.rules)) {
947 948 949 950 951 952 953
    ;[].forEach.call(webpackConfig.module.rules, function(
      rule: webpack.RuleSetRule
    ) {
      if (!(rule.test instanceof RegExp && Array.isArray(rule.use))) {
        return
      }

954 955 956 957 958 959 960
      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)) {
961 962 963 964 965 966 967 968 969
        return
      }

      ;[].forEach.call(rule.use, function(use: webpack.RuleSetUseItem) {
        if (
          !(
            use &&
            typeof use === 'object' &&
            // Identify use statements only pertaining to `css-loader`
970 971
            (use.loader === 'css-loader' ||
              use.loader === 'css-loader/locals') &&
972 973 974 975 976 977 978
            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.
979 980 981 982 983
            (Object.prototype.hasOwnProperty.call(use.options, 'minimize') ||
              Object.prototype.hasOwnProperty.call(
                use.options,
                'exportOnlyLocals'
              ))
984 985 986 987 988 989 990 991 992 993 994 995 996 997 998
          )
        ) {
          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',
999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009
            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'
                )
1010 1011 1012 1013 1014 1015 1016
          )

          // 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.
1017
            const correctCssLoader = resolveRequest(use.loader, correctNextCss)
1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029
            if (correctCssLoader) {
              // We saved the user from a failed build!
              use.loader = correctCssLoader
            }
          }
        } catch (_) {
          // The error is not required to be handled.
        }
      })
    })
  }

1030
  // Backwards compat for `main.js` entry key
1031
  const originalEntry: any = webpackConfig.entry
1032 1033
  if (typeof originalEntry !== 'undefined') {
    webpackConfig.entry = async () => {
1034 1035 1036 1037
      const entry: WebpackEntrypoints =
        typeof originalEntry === 'function'
          ? await originalEntry()
          : originalEntry
1038 1039 1040
      // 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]
1041
        // @ts-ignore TODO: investigate type error
1042 1043
        entry[CLIENT_STATIC_FILES_RUNTIME_MAIN] = [
          ...entry['main.js'],
1044
          originalFile,
1045
        ]
1046
      }
1047
      delete entry['main.js']
1048

1049
      return entry
1050 1051 1052
    }
  }

1053
  if (!dev) {
1054 1055 1056 1057
    // @ts-ignore entry is always a function
    webpackConfig.entry = await webpackConfig.entry()
  }

T
Tim Neutkens 已提交
1058
  return webpackConfig
N
nkzawa 已提交
1059
}