webpack-config.ts 24.4 KB
Newer Older
1
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'
2 3
import {
  CLIENT_STATIC_FILES_RUNTIME_MAIN,
4 5 6
  CLIENT_STATIC_FILES_RUNTIME_WEBPACK,
  REACT_LOADABLE_MANIFEST,
  SERVER_DIRECTORY,
7
  SERVERLESS_DIRECTORY,
8
} from 'next-server/constants'
9 10
import resolve from 'next/dist/compiled/resolve/index.js'
import path from 'path'
11
import crypto from 'crypto'
12
import webpack from 'webpack'
13

14
import {
15
  DOT_NEXT_ALIAS,
16 17 18 19
  NEXT_PROJECT_ROOT,
  NEXT_PROJECT_ROOT_DIST_CLIENT,
  PAGES_DIR_ALIAS,
} from '../lib/constants'
20
import { fileExists } from '../lib/file-exists'
21
import { WebpackEntrypoints } from './entries'
22
import { AllModulesIdentifiedPlugin } from './webpack/plugins/all-modules-identified-plugin'
23
import BuildManifestPlugin from './webpack/plugins/build-manifest-plugin'
24
import { ChunkGraphPlugin } from './webpack/plugins/chunk-graph-plugin'
25
import ChunkNamesPlugin from './webpack/plugins/chunk-names-plugin'
26
import { importAutoDllPlugin } from './webpack/plugins/dll-import'
27
import { HashedChunkIdsPlugin } from './webpack/plugins/hashed-chunk-ids-plugin'
28
import { DropClientPage } from './webpack/plugins/next-drop-client-page-plugin'
29
import NextEsmPlugin from './webpack/plugins/next-esm-plugin'
30 31 32 33 34 35 36 37
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'
import { ReactLoadablePlugin } from './webpack/plugins/react-loadable-plugin'
import { ServerlessPlugin } from './webpack/plugins/serverless-plugin'
import { SharedRuntimePlugin } from './webpack/plugins/shared-runtime-plugin'
import { TerserPlugin } from './webpack/plugins/terser-webpack-plugin/src/index'

38
type ExcludesFalse = <T>(x: T | false) => x is T
39

40 41 42 43 44 45
const escapePathVariables = (value: any) => {
  return typeof value === 'string'
    ? value.replace(/\[(\\*[\w:]+\\*)\]/gi, '[\\$1\\]')
    : value
}

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

T
Tim Neutkens 已提交
84 85 86
  // Support for NODE_PATH
  const nodePathList = (process.env.NODE_PATH || '')
    .split(process.platform === 'win32' ? ';' : ':')
87
    .filter(p => !!p)
T
Tim Neutkens 已提交
88

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

113 114 115 116
  let typeScriptPath
  try {
    typeScriptPath = resolve.sync('typescript', { basedir: dir })
  } catch (_) {}
117
  const tsConfigPath = path.join(dir, 'tsconfig.json')
118 119 120
  const useTypeScript = Boolean(
    typeScriptPath && (await fileExists(tsConfigPath))
  )
121

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

T
Tim Neutkens 已提交
159 160
  const webpackMode = dev ? 'development' : 'production'

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

191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
  // 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,
      },
    },
    selective: {
      cacheGroups: {
        default: false,
        vendors: false,
        react: {
          name: 'commons',
          chunks: 'all',
208
          test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
        },
      },
    },
    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',
225
          test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
226 227 228
        },
      },
    },
229
    prodGranular: {
230
      chunks: 'initial',
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
      cacheGroups: {
        default: false,
        vendors: false,
        framework: {
          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())
            )
          },
          name(module: { identifier: Function; rawRequest: string }): string {
            const rawRequest =
              module.rawRequest &&
              module.rawRequest.replace(/^@(\w+)[/\\]/, '$1-')
            if (rawRequest) return rawRequest

            const identifier = module.identifier()
            const trimmedIdentifier = /(?:^|[/\\])node_modules[/\\](.*)/.exec(
              identifier
            )
            const processedIdentifier =
              trimmedIdentifier &&
              trimmedIdentifier[1].replace(/^@(\w+)[/\\]/, '$1-')

            return processedIdentifier || identifier
          },
          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
                  },
                  ''
                )
              )
              .digest('base64')
              .replace(/\//g, '')
          },
          priority: 10,
          minChunks: 2,
          reuseExistingChunk: true,
        },
      },
      maxInitialRequests: 20,
    },
293 294 295 296 297 298 299 300 301
  }

  // Select appropriate SplitChunksPlugin config for this build
  let splitChunksConfig: webpack.Options.SplitChunksOptions
  if (dev) {
    splitChunksConfig = splitChunksConfigs.dev
  } else if (selectivePageBuilding) {
    splitChunksConfig = splitChunksConfigs.selective
  } else {
302 303 304
    splitChunksConfig = config.experimental.granularChunks
      ? splitChunksConfigs.prodGranular
      : splitChunksConfigs.prod
305 306
  }

307 308 309 310 311
  const crossOrigin =
    !config.crossOrigin && config.experimental.modern
      ? 'anonymous'
      : config.crossOrigin

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 338 339 340 341
            resolve(
              request,
              { basedir: dir, preserveSymlinks: true },
              (err, res) => {
                if (err) {
                  return callback()
                }
K
k-kawakami 已提交
342

343 344 345
                if (!res) {
                  return callback()
                }
K
k-kawakami 已提交
346

347 348 349 350 351 352 353 354
                // Default pages have to be transpiled
                if (
                  res.match(/next[/\\]dist[/\\]/) ||
                  res.match(/node_modules[/\\]@babel[/\\]runtime[/\\]/) ||
                  res.match(/node_modules[/\\]@babel[/\\]runtime-corejs2[/\\]/)
                ) {
                  return callback()
                }
K
k-kawakami 已提交
355

356 357 358 359 360 361 362
                // 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 已提交
363

364 365 366
                if (res.match(/node_modules[/\\].*\.js$/)) {
                  return callback(undefined, `commonjs ${request}`)
                }
K
k-kawakami 已提交
367

368 369 370 371 372 373
                callback()
              }
            )
          },
        ]
      : [
374 375
          // 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
376
          '@ampproject/toolbox-optimizer', // except this one
377 378 379 380 381 382 383
          (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
384 385 386
              if (
                context.replace(/\\/g, '/').includes('next-server/dist/server')
              ) {
387 388 389 390 391
                return callback(undefined, `commonjs ${request}`)
              }
            }
            return callback()
          },
392 393 394 395 396
        ],
    optimization: Object.assign(
      {
        checkWasmTypes: false,
        nodeEnv: false,
397
      },
398 399 400 401
      isServer
        ? {
            splitChunks: false,
            minimize: false,
J
Joe Haddad 已提交
402
          }
403 404 405 406 407 408
        : {
            runtimeChunk: selectivePageBuilding
              ? false
              : {
                  name: CLIENT_STATIC_FILES_RUNTIME_WEBPACK,
                },
409
            splitChunks: splitChunksConfig,
J
Joe Haddad 已提交
410 411
            minimize: !dev,
            minimizer: !dev
412 413 414 415
              ? [
                  new TerserPlugin({
                    ...terserPluginConfig,
                    terserOptions: {
416 417
                      ...terserOptions,
                      // Disable compress when using terser loader
418 419
                      ...(selectivePageBuilding ||
                      config.experimental.terserLoader
420
                        ? { compress: false }
421 422 423 424 425
                        : undefined),
                    },
                  }),
                ]
              : undefined,
426
          },
427 428 429 430 431
      selectivePageBuilding
        ? {
            providedExports: false,
            usedExports: false,
            concatenateModules: false,
432
          }
433 434 435 436 437
        : undefined
    ),
    recordsPath: selectivePageBuilding
      ? undefined
      : path.join(outputPath, 'records.json'),
N
nkzawa 已提交
438
    context: dir,
439
    // Kept as function to be backwards compatible
T
Tim Neutkens 已提交
440 441
    entry: async () => {
      return {
442 443
        ...(clientEntries ? clientEntries : {}),
        ...entrypoints,
T
Tim Neutkens 已提交
444 445
      }
    },
N
nkzawa 已提交
446
    output: {
447
      path: outputPath,
448
      filename: ({ chunk }: { chunk: { name: string } }) => {
449
        // Use `[name]-[contenthash].js` in production
450 451 452 453 454
        if (
          !dev &&
          (chunk.name === CLIENT_STATIC_FILES_RUNTIME_MAIN ||
            chunk.name === CLIENT_STATIC_FILES_RUNTIME_WEBPACK)
        ) {
455
          return chunk.name.replace(/\.js$/, '-[contenthash].js')
456 457 458
        }
        return '[name]'
      },
T
Tim Neutkens 已提交
459
      libraryTarget: isServer ? 'commonjs2' : 'var',
460 461 462
      hotUpdateChunkFilename: 'static/webpack/[id].[hash].hot-update.js',
      hotUpdateMainFilename: 'static/webpack/[hash].hot-update.json',
      // This saves chunks with the name given via `import()`
463 464 465
      chunkFilename: isServer
        ? `${dev ? '[name]' : '[name].[contenthash]'}.js`
        : `static/chunks/${dev ? '[name]' : '[name].[contenthash]'}.js`,
A
Andy 已提交
466
      strictModuleExceptionHandling: true,
467
      crossOriginLoading: crossOrigin,
468
      futureEmitAssets: !dev,
469
      webassemblyModuleFilename: 'static/wasm/[modulehash].wasm',
N
nkzawa 已提交
470
    },
471
    performance: false,
T
Tim Neutkens 已提交
472
    resolve: resolveConfig,
N
nkzawa 已提交
473
    resolveLoader: {
N
Naoyuki Kanezawa 已提交
474
      modules: [
475
        path.join(__dirname, 'webpack', 'loaders'), // The loaders Next.js provides
476
        'node_modules',
477 478
        ...nodePathList, // Support for NODE_PATH environment variable
      ],
N
nkzawa 已提交
479
    },
T
Tim Neutkens 已提交
480
    // @ts-ignore this is filtered
N
nkzawa 已提交
481
    module: {
482
      strictExportPresence: true,
483
      rules: [
484
        (selectivePageBuilding || config.experimental.terserLoader) &&
J
Joe Haddad 已提交
485
          !isServer && {
486 487 488 489 490 491
            test: /\.(js|mjs|jsx)$/,
            exclude: /\.min\.(js|mjs|jsx)$/,
            use: {
              loader: 'next-minify-loader',
              options: {
                terserOptions: {
492
                  ...terserOptions,
493 494 495 496 497 498 499 500 501 502 503
                  mangle: false,
                },
              },
            },
          },
        config.experimental.ampBindInitData &&
          !isServer && {
            test: /\.(tsx|ts|js|mjs|jsx)$/,
            include: [path.join(dir, 'data')],
            use: 'next-data-loader',
          },
T
Tim Neutkens 已提交
504
        {
505
          test: /\.(tsx|ts|js|mjs|jsx)$/,
506 507 508 509 510
          include: [
            dir,
            /next-server[\\/]dist[\\/]lib/,
            /next[\\/]dist[\\/]client/,
            /next[\\/]dist[\\/]pages/,
511
            /[\\/](strip-ansi|ansi-regex)[\\/]/,
512
          ],
513
          exclude: (path: string) => {
514 515 516
            if (
              /next-server[\\/]dist[\\/]lib/.test(path) ||
              /next[\\/]dist[\\/]client/.test(path) ||
517
              /next[\\/]dist[\\/]pages/.test(path) ||
518
              /[\\/](strip-ansi|ansi-regex)[\\/]/.test(path)
519
            ) {
520 521
              return false
            }
522

523
            return /node_modules/.test(path)
524
          },
525
          use: defaultLoaders.babel,
526
        },
527
      ].filter(Boolean),
N
nkzawa 已提交
528
    },
T
Tim Neutkens 已提交
529
    plugins: [
530 531
      // This plugin makes sure `output.filename` is used for entry chunks
      new ChunkNamesPlugin(),
532
      new webpack.DefinePlugin({
533
        ...Object.keys(config.env).reduce((acc, key) => {
534
          if (/^(?:NODE_.+)|^(?:__.+)$/i.test(key)) {
535 536 537
            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`
            )
538 539 540
          }

          return {
541
            ...acc,
542
            [`process.env.${key}`]: JSON.stringify(config.env[key]),
543
          }
544
        }, {}),
545
        'process.env.NODE_ENV': JSON.stringify(webpackMode),
546
        'process.crossOrigin': JSON.stringify(crossOrigin),
547
        'process.browser': JSON.stringify(!isServer),
548 549 550
        'process.env.__NEXT_EXPERIMENTAL_SELECTIVEPAGEBUILDING': JSON.stringify(
          selectivePageBuilding
        ),
551
        // This is used in client/dev-error-overlay/hot-dev-client.js to replace the dist directory
552 553 554 555 556 557
        ...(dev && !isServer
          ? {
              'process.env.__NEXT_DIST_DIR': JSON.stringify(distDir),
            }
          : {}),
        'process.env.__NEXT_EXPORT_TRAILING_SLASH': JSON.stringify(
558
          config.exportTrailingSlash
559
        ),
560
        'process.env.__NEXT_MODERN_BUILD': config.experimental.modern && !dev,
561 562
        'process.env.__NEXT_GRANULAR_CHUNKS':
          config.experimental.granularChunks && !selectivePageBuilding && !dev,
563
        ...(isServer
564 565 566 567 568 569
          ? {
              // 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),
            }
570
          : undefined),
571
      }),
572 573 574 575
      !isServer &&
        new ReactLoadablePlugin({
          filename: REACT_LOADABLE_MANIFEST,
        }),
576
      !isServer && new DropClientPage(),
J
JJ Kasper 已提交
577 578 579 580 581
      new ChunkGraphPlugin(buildId, {
        dir,
        distDir,
        isServer,
      }),
582 583 584 585 586 587 588 589 590 591 592 593 594 595 596
      ...(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(),
            ]
597

598
            if (!isServer) {
599
              const AutoDllPlugin = importAutoDllPlugin({ distDir })
600 601 602 603 604 605 606 607 608
              devPlugins.push(
                new AutoDllPlugin({
                  filename: '[name]_[hash].js',
                  path: './static/development/dll',
                  context: dir,
                  entry: {
                    dll: ['react', 'react-dom'],
                  },
                  config: {
J
JJ Kasper 已提交
609
                    devtool,
610 611 612 613 614 615 616
                    mode: webpackMode,
                    resolve: resolveConfig,
                  },
                })
              )
              devPlugins.push(new webpack.HotModuleReplacementPlugin())
            }
617

618 619 620
            return devPlugins
          })()
        : []),
621
      !dev && new webpack.HashedModuleIdsPlugin(),
622 623
      // This must come after HashedModuleIdsPlugin (it sets any modules that
      // were missed by HashedModuleIdsPlugin)
624
      !dev && selectivePageBuilding && new AllModulesIdentifiedPlugin(dir),
625 626
      // This sets chunk ids to be hashed versions of their names to reduce
      // bundle churn
627
      !dev && selectivePageBuilding && new HashedChunkIdsPlugin(buildId),
J
Joe Haddad 已提交
628
      // On the client we want to share the same runtime cache
629
      !isServer && selectivePageBuilding && new SharedRuntimePlugin(),
630 631 632 633 634 635 636 637 638 639 640 641
      !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)
            )
          },
        }),
642 643 644 645 646 647 648 649
      isLikeServerless &&
        new ServerlessPlugin(buildId, {
          isServer,
          isFlyingShuttle: selectivePageBuilding,
          isTrace: isServerlessTrace,
        }),
      isServer && new PagesManifestPlugin(isLikeServerless),
      target === 'server' &&
650 651
        isServer &&
        new NextJsSSRModuleCachePlugin({ outputPath }),
652
      isServer && new NextJsSsrImportPlugin(),
653 654 655 656 657 658
      !isServer &&
        new BuildManifestPlugin({
          buildId,
          clientManifest: config.experimental.granularChunks,
          modern: config.experimental.modern,
        }),
659 660 661 662 663 664 665
      config.experimental.profiling &&
        new webpack.debug.ProfilingPlugin({
          outputPath: path.join(
            distDir,
            `profile-events-${isServer ? 'server' : 'client'}.json`
          ),
        }),
666 667
      !isServer &&
        useTypeScript &&
668
        new ForkTsCheckerWebpackPlugin({
669
          typescript: typeScriptPath,
670
          async: dev,
671 672 673 674 675 676 677 678
          useTypescriptIncrementalApi: true,
          checkSyntacticErrors: true,
          tsconfig: tsConfigPath,
          reportFiles: ['**', '!**/__tests__/**', '!**/?(*.)(spec|test).*'],
          compilerOptions: { isolatedModules: true, noEmit: true },
          silent: true,
          formatter: 'codeframe',
        }),
679 680 681 682 683 684 685 686 687 688 689 690
      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')
691 692 693
              : escapePathVariables(
                  args[0].chunk.name.replace(/\.js$/, '.module.js')
                )
694 695 696 697
          },
          chunkFilename: (inputChunkName: string) =>
            inputChunkName.replace(/\.js$/, '.module.js'),
        }),
698
    ].filter((Boolean as any) as ExcludesFalse),
699
  }
700

T
Tim Neutkens 已提交
701
  if (typeof config.webpack === 'function') {
702 703 704 705 706 707 708 709 710 711
    webpackConfig = config.webpack(webpackConfig, {
      dir,
      dev,
      isServer,
      buildId,
      config,
      defaultLoaders,
      totalPages,
      webpack,
    })
712 713 714

    // @ts-ignore: Property 'then' does not exist on type 'Configuration'
    if (typeof webpackConfig.then === 'function') {
715 716 717
      console.warn(
        '> Promise returned in next config. https://err.sh/zeit/next.js/promise-in-next-config.md'
      )
718
    }
719
  }
T
Tim Neutkens 已提交
720

721
  // check if using @zeit/next-typescript and show warning
722 723 724
  if (
    isServer &&
    webpackConfig.module &&
725 726 727 728
    Array.isArray(webpackConfig.module.rules)
  ) {
    let foundTsRule = false

729 730
    webpackConfig.module.rules = webpackConfig.module.rules.filter(
      (rule): boolean => {
731
        if (!(rule.test instanceof RegExp)) return true
732
        if ('noop.ts'.match(rule.test) && !'noop.js'.match(rule.test)) {
733 734 735 736 737
          // remove if it matches @zeit/next-typescript
          foundTsRule = rule.use === defaultLoaders.babel
          return !foundTsRule
        }
        return true
738 739
      }
    )
740 741

    if (foundTsRule) {
742
      console.warn(
743
        '\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'
744
      )
745 746 747
    }
  }

748
  // Backwards compat for `main.js` entry key
749
  const originalEntry: any = webpackConfig.entry
750 751
  if (typeof originalEntry !== 'undefined') {
    webpackConfig.entry = async () => {
752 753 754 755
      const entry: WebpackEntrypoints =
        typeof originalEntry === 'function'
          ? await originalEntry()
          : originalEntry
756 757 758 759 760
      // 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]
        entry[CLIENT_STATIC_FILES_RUNTIME_MAIN] = [
          ...entry['main.js'],
761
          originalFile,
762
        ]
763
      }
764
      delete entry['main.js']
765

766
      return entry
767 768 769
    }
  }

770
  if (!dev) {
771 772 773 774
    // @ts-ignore entry is always a function
    webpackConfig.entry = await webpackConfig.entry()
  }

T
Tim Neutkens 已提交
775
  return webpackConfig
N
nkzawa 已提交
776
}