webpack-config.ts 39.6 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
import { resolveRequest } from '../lib/resolve-request'
import {
  CLIENT_STATIC_FILES_RUNTIME_MAIN,
20
  CLIENT_STATIC_FILES_RUNTIME_POLYFILLS,
21 22 23 24 25
  CLIENT_STATIC_FILES_RUNTIME_WEBPACK,
  REACT_LOADABLE_MANIFEST,
  SERVER_DIRECTORY,
  SERVERLESS_DIRECTORY,
} from '../next-server/lib/constants'
J
Joe Haddad 已提交
26
import { findPageFile } from '../server/lib/find-page-file'
27
import { WebpackEntrypoints } from './entries'
J
Joe Haddad 已提交
28 29 30 31 32 33 34
import {
  collectPlugins,
  PluginMetaData,
  VALID_MIDDLEWARE,
} from './plugins/collect-plugins'
// @ts-ignore: JS file
import { pluginLoaderOptions } from './webpack/loaders/next-plugin-loader'
35 36
import BuildManifestPlugin from './webpack/plugins/build-manifest-plugin'
import ChunkNamesPlugin from './webpack/plugins/chunk-names-plugin'
J
Joe Haddad 已提交
37
import { CssMinimizerPlugin } from './webpack/plugins/css-minimizer-plugin'
38
import { importAutoDllPlugin } from './webpack/plugins/dll-import'
39
import { DropClientPage } from './webpack/plugins/next-drop-client-page-plugin'
40
import NextEsmPlugin from './webpack/plugins/next-esm-plugin'
41 42 43
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'
44
import { ProfilingPlugin } from './webpack/plugins/profiling-plugin'
45 46 47 48
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'

49
type ExcludesFalse = <T>(x: T | false) => x is T
50

51 52 53 54 55 56
const escapePathVariables = (value: any) => {
  return typeof value === 'string'
    ? value.replace(/\[(\\*[\w:]+\\*)\]/gi, '[\\$1\\]')
    : value
}

57 58 59 60 61
function getOptimizedAliases(isServer: boolean): { [pkg: string]: string } {
  if (isServer) {
    return {}
  }

62
  const stubWindowFetch = path.join(__dirname, 'polyfills', 'fetch', 'index.js')
63 64 65
  const stubObjectAssign = path.join(__dirname, 'polyfills', 'object-assign.js')

  const shimAssign = path.join(__dirname, 'polyfills', 'object.assign')
66
  return {
67
    // Polyfill: Window#fetch
68 69 70
    __next_polyfill__fetch: require.resolve('whatwg-fetch'),
    unfetch$: stubWindowFetch,
    'isomorphic-unfetch$': stubWindowFetch,
71 72 73 74 75 76
    'whatwg-fetch$': path.join(
      __dirname,
      'polyfills',
      'fetch',
      'whatwg-fetch.js'
    ),
77 78 79 80 81 82 83 84 85 86 87 88

    // Polyfill: Object.assign
    __next_polyfill__object_assign: require.resolve('object-assign'),
    'object-assign$': stubObjectAssign,
    '@babel/runtime-corejs2/core-js/object/assign': stubObjectAssign,

    // Stub Package: object.assign
    'object.assign/auto': path.join(shimAssign, 'auto.js'),
    'object.assign/implementation': path.join(shimAssign, 'implementation.js'),
    'object.assign$': path.join(shimAssign, 'index.js'),
    'object.assign/polyfill': path.join(shimAssign, 'polyfill.js'),
    'object.assign/shim': path.join(shimAssign, 'shim.js'),
89 90 91
  }
}

92 93 94 95 96
export default async function getBaseWebpackConfig(
  dir: string,
  {
    buildId,
    config,
J
JJ Kasper 已提交
97 98 99 100
    dev = false,
    isServer = false,
    pagesDir,
    tracer,
101 102 103 104 105
    target = 'server',
    entrypoints,
  }: {
    buildId: string
    config: any
J
JJ Kasper 已提交
106 107 108
    dev?: boolean
    isServer?: boolean
    pagesDir: string
109
    target?: string
J
JJ Kasper 已提交
110
    tracer?: any
111 112 113
    entrypoints: WebpackEntrypoints
  }
): Promise<webpack.Configuration> {
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
  let plugins: PluginMetaData[] = []
  let babelPresetPlugins: { dir: string; config: any }[] = []

  if (config.experimental.plugins) {
    plugins = await collectPlugins(dir, config.env, config.plugins)
    pluginLoaderOptions.plugins = plugins

    for (const plugin of plugins) {
      if (plugin.middleware.includes('babel-preset-build')) {
        babelPresetPlugins.push({
          dir: plugin.directory,
          config: plugin.config,
        })
      }
    }
  }
130
  const distDir = path.join(dir, config.distDir)
T
Tim Neutkens 已提交
131 132
  const defaultLoaders = {
    babel: {
133
      loader: 'next-babel-loader',
134 135 136
      options: {
        isServer,
        distDir,
J
JJ Kasper 已提交
137
        pagesDir,
138
        cwd: dir,
139
        cache: true,
140 141
        babelPresetPlugins,
        hasModern: !!config.experimental.modern,
142
      },
143
    },
144
    // Backwards compat
145
    hotSelfAccept: {
146 147
      loader: 'noop-loader',
    },
T
Tim Neutkens 已提交
148 149
  }

150 151 152 153 154 155
  const babelIncludeRegexes: RegExp[] = [
    /next[\\/]dist[\\/]next-server[\\/]lib/,
    /next[\\/]dist[\\/]client/,
    /next[\\/]dist[\\/]pages/,
    /[\\/](strip-ansi|ansi-regex)[\\/]/,
    ...(config.experimental.plugins
J
JJ Kasper 已提交
156
      ? VALID_MIDDLEWARE.map(name => new RegExp(`src(\\\\|/)${name}`))
157 158 159
      : []),
  ]

T
Tim Neutkens 已提交
160 161 162
  // Support for NODE_PATH
  const nodePathList = (process.env.NODE_PATH || '')
    .split(process.platform === 'win32' ? ';' : ':')
163
    .filter(p => !!p)
T
Tim Neutkens 已提交
164

165 166 167 168 169 170
  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 已提交
171
  const outputPath = path.join(distDir, isServer ? outputDir : '')
172
  const totalPages = Object.keys(entrypoints).length
173 174 175 176 177 178 179 180 181 182 183 184 185
  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'
            )
          ),
186 187 188 189
        [CLIENT_STATIC_FILES_RUNTIME_POLYFILLS]: path.join(
          NEXT_PROJECT_ROOT_DIST_CLIENT,
          'polyfills.js'
        ),
190 191
      }
    : undefined
N
nkzawa 已提交
192

193 194
  let typeScriptPath
  try {
195
    typeScriptPath = resolveRequest('typescript', `${dir}/`)
196
  } catch (_) {}
197
  const tsConfigPath = path.join(dir, 'tsconfig.json')
198 199 200
  const useTypeScript = Boolean(
    typeScriptPath && (await fileExists(tsConfigPath))
  )
201 202 203
  const ignoreTypeScriptErrors = dev
    ? config.typescript && config.typescript.ignoreDevErrors
    : config.typescript && config.typescript.ignoreBuildErrors
204

T
Tim Neutkens 已提交
205
  const resolveConfig = {
206
    // Disable .mjs for node_modules bundling
207
    extensions: isServer
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
      ? [
          ...(useTypeScript ? ['.tsx', '.ts'] : []),
          '.js',
          '.mjs',
          '.jsx',
          '.json',
          '.wasm',
        ]
      : [
          ...(useTypeScript ? ['.tsx', '.ts'] : []),
          '.mjs',
          '.js',
          '.jsx',
          '.json',
          '.wasm',
        ],
T
Tim Neutkens 已提交
224 225
    modules: [
      'node_modules',
226
      ...nodePathList, // Support for NODE_PATH environment variable
T
Tim Neutkens 已提交
227 228
    ],
    alias: {
229 230
      // 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
231
      'next/head': 'next/dist/next-server/lib/head.js',
232
      'next/router': 'next/dist/client/router.js',
233 234
      'next/config': 'next/dist/next-server/lib/runtime-config.js',
      'next/dynamic': 'next/dist/next-server/lib/dynamic.js',
T
Tim Neutkens 已提交
235
      next: NEXT_PROJECT_ROOT,
J
JJ Kasper 已提交
236
      [PAGES_DIR_ALIAS]: pagesDir,
237
      [DOT_NEXT_ALIAS]: distDir,
238
      ...getOptimizedAliases(isServer),
239
    },
240
    mainFields: isServer ? ['main', 'module'] : ['browser', 'module', 'main'],
241
    plugins: [PnpWebpackPlugin],
T
Tim Neutkens 已提交
242 243
  }

T
Tim Neutkens 已提交
244 245
  const webpackMode = dev ? 'development' : 'production'

246
  const terserPluginConfig = {
247
    cache: true,
248
    cpus: config.experimental.cpus,
249
    distDir: distDir,
250 251 252
    parallel: true,
    sourceMap: false,
    workerThreads: config.experimental.workerThreads,
253
  }
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
  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 已提交
275
  const devtool = dev ? 'cheap-module-source-map' : false
276

277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
  // 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',
300
          test: /[\\/]node_modules[\\/](react|react-dom|scheduler|use-subscription)[\\/]/,
301 302 303
        },
      },
    },
304
    prodGranular: {
305
      chunks: 'all',
306 307 308 309
      cacheGroups: {
        default: false,
        vendors: false,
        framework: {
310
          chunks: 'all',
311
          name: 'framework',
A
Alex Castle 已提交
312
          // This regex ignores nested copies of framework libraries so they're
313 314
          // bundled with their issuer.
          // https://github.com/zeit/next.js/pull/9012
315
          test: /(?<!node_modules.*)[\\/]node_modules[\\/](react|react-dom|scheduler|prop-types|use-subscription)[\\/]/,
316
          priority: 40,
J
Joe Haddad 已提交
317 318 319
          // Don't let webpack eliminate this chunk (prevents this chunk from
          // becoming a part of the commons chunk)
          enforce: true,
320 321 322 323 324 325 326 327
        },
        lib: {
          test(module: { size: Function; identifier: Function }): boolean {
            return (
              module.size() > 160000 &&
              /node_modules[/\\]/.test(module.identifier())
            )
          },
328 329 330 331 332 333
          name(module: { libIdent: Function }): string {
            return crypto
              .createHash('sha1')
              .update(module.libIdent({ context: dir }))
              .digest('hex')
              .substring(0, 8)
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
          },
          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
                  },
                  ''
                )
              )
356
              .digest('hex')
357 358 359 360 361 362
          },
          priority: 10,
          minChunks: 2,
          reuseExistingChunk: true,
        },
      },
363 364
      maxInitialRequests: 25,
      minSize: 20000,
365
    },
366 367 368 369 370 371 372
  }

  // Select appropriate SplitChunksPlugin config for this build
  let splitChunksConfig: webpack.Options.SplitChunksOptions
  if (dev) {
    splitChunksConfig = splitChunksConfigs.dev
  } else {
373 374 375
    splitChunksConfig = config.experimental.granularChunks
      ? splitChunksConfigs.prodGranular
      : splitChunksConfigs.prod
376 377
  }

378 379 380 381 382
  const crossOrigin =
    !config.crossOrigin && config.experimental.modern
      ? 'anonymous'
      : config.crossOrigin

J
Joe Haddad 已提交
383
  let customAppFile: string | null = config.experimental.css
384
    ? await findPageFile(pagesDir, '/_app', config.pageExtensions)
J
Joe Haddad 已提交
385 386
    : null
  if (customAppFile) {
387
    customAppFile = path.resolve(path.join(pagesDir, customAppFile))
J
Joe Haddad 已提交
388 389
  }

390
  let webpackConfig: webpack.Configuration = {
J
JJ Kasper 已提交
391
    devtool,
T
Tim Neutkens 已提交
392
    mode: webpackMode,
T
Tim Neutkens 已提交
393 394
    name: isServer ? 'server' : 'client',
    target: isServer ? 'node' : 'web',
395 396
    externals: !isServer
      ? undefined
397
      : !isServerless
398 399 400 401 402 403 404 405 406 407
      ? [
          (context, request, callback) => {
            const notExternalModules = [
              'next/app',
              'next/document',
              'next/link',
              'next/error',
              'string-hash',
              'next/constants',
            ]
408

409 410 411
            if (notExternalModules.indexOf(request) !== -1) {
              return callback()
            }
K
k-kawakami 已提交
412

413 414 415 416 417 418
            // make sure we don't externalize anything that is
            // supposed to be transpiled
            if (babelIncludeRegexes.some(r => r.test(request))) {
              return callback()
            }

419 420 421 422
            // 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 已提交
423
            const start = request.charAt(0)
424
            if (start === '.' || start === '/') {
J
Joe Haddad 已提交
425
              return callback()
426 427
            }

428 429 430
            // Resolve the import with the webpack provided context, this
            // ensures we're resolving the correct version when multiple
            // exist.
431 432
            let res
            try {
433
              res = resolveRequest(request, `${context}/`)
434
            } catch (err) {
435 436 437 438
              // 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.
439 440 441 442 443 444 445 446 447 448 449
              if (
                request === 'react-ssr-prepass' &&
                !config.experimental.ampBindInitData
              ) {
                if (
                  context.replace(/\\/g, '/').includes('next-server/server')
                ) {
                  return callback(undefined, `commonjs ${request}`)
                }
              }

450 451 452
              // 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).
453 454
              return callback()
            }
K
k-kawakami 已提交
455

456 457
            // Same as above, if the request cannot be resolved we need to have
            // webpack "bundle" it so it surfaces the not found error.
458 459 460
            if (!res) {
              return callback()
            }
K
k-kawakami 已提交
461

462 463 464 465
            // 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).
466 467
            let baseRes
            try {
468
              baseRes = resolveRequest(request, `${dir}/`)
469 470
            } catch (err) {}

471 472 473
            // 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.
474 475 476 477
            if (baseRes !== res) {
              return callback()
            }

478 479 480 481 482 483 484 485 486
            // 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 已提交
487

488 489 490 491 492 493 494
            // 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 已提交
495

496 497
            // Anything else that is standard JavaScript within `node_modules`
            // can be externalized.
498 499 500
            if (res.match(/node_modules[/\\].*\.js$/)) {
              return callback(undefined, `commonjs ${request}`)
            }
K
k-kawakami 已提交
501

502
            // Default behavior: bundle the code!
503
            callback()
504 505 506
          },
        ]
      : [
507 508
          // 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
509
          '@ampproject/toolbox-optimizer', // except this one
510 511 512 513 514 515 516
          (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
517
              if (context.replace(/\\/g, '/').includes('next-server/server')) {
518 519 520 521 522
                return callback(undefined, `commonjs ${request}`)
              }
            }
            return callback()
          },
523
        ],
524 525 526 527 528 529 530 531 532
    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 已提交
533
        // Minify JavaScript
534 535 536 537
        new TerserPlugin({
          ...terserPluginConfig,
          terserOptions,
        }),
J
Joe Haddad 已提交
538 539
        // Minify CSS
        config.experimental.css &&
J
Joe Haddad 已提交
540 541
          new CssMinimizerPlugin({
            postcssOptions: {
J
Joe Haddad 已提交
542 543 544 545
              map: {
                // `inline: false` generates the source map in a separate file.
                // Otherwise, the CSS file is needlessly large.
                inline: false,
J
Joe Haddad 已提交
546 547 548
                // `annotation: false` skips appending the `sourceMappingURL`
                // to the end of the CSS file. Webpack already handles this.
                annotation: false,
J
Joe Haddad 已提交
549 550 551 552
              },
            },
          }),
      ].filter(Boolean),
553 554
    },
    recordsPath: path.join(outputPath, 'records.json'),
N
nkzawa 已提交
555
    context: dir,
556
    // Kept as function to be backwards compatible
T
Tim Neutkens 已提交
557 558
    entry: async () => {
      return {
559 560
        ...(clientEntries ? clientEntries : {}),
        ...entrypoints,
561 562 563 564 565 566 567
        ...(isServer
          ? {
              'init-server.js': 'next-plugin-loader?middleware=on-init-server!',
              'on-error-server.js':
                'next-plugin-loader?middleware=on-error-server!',
            }
          : {}),
T
Tim Neutkens 已提交
568 569
      }
    },
N
nkzawa 已提交
570
    output: {
571
      path: outputPath,
572
      filename: ({ chunk }: { chunk: { name: string } }) => {
573
        // Use `[name]-[contenthash].js` in production
574 575 576
        if (
          !dev &&
          (chunk.name === CLIENT_STATIC_FILES_RUNTIME_MAIN ||
577 578
            chunk.name === CLIENT_STATIC_FILES_RUNTIME_WEBPACK ||
            chunk.name === CLIENT_STATIC_FILES_RUNTIME_POLYFILLS)
579
        ) {
580
          return chunk.name.replace(/\.js$/, '-[contenthash].js')
581 582 583
        }
        return '[name]'
      },
T
Tim Neutkens 已提交
584
      libraryTarget: isServer ? 'commonjs2' : 'var',
585 586 587
      hotUpdateChunkFilename: 'static/webpack/[id].[hash].hot-update.js',
      hotUpdateMainFilename: 'static/webpack/[hash].hot-update.json',
      // This saves chunks with the name given via `import()`
588 589 590
      chunkFilename: isServer
        ? `${dev ? '[name]' : '[name].[contenthash]'}.js`
        : `static/chunks/${dev ? '[name]' : '[name].[contenthash]'}.js`,
A
Andy 已提交
591
      strictModuleExceptionHandling: true,
592
      crossOriginLoading: crossOrigin,
593
      futureEmitAssets: !dev,
594
      webassemblyModuleFilename: 'static/wasm/[modulehash].wasm',
N
nkzawa 已提交
595
    },
596
    performance: false,
T
Tim Neutkens 已提交
597
    resolve: resolveConfig,
N
nkzawa 已提交
598
    resolveLoader: {
599 600 601
      // The loaders Next.js provides
      alias: [
        'emit-file-loader',
602
        'error-loader',
603 604 605 606 607
        'next-babel-loader',
        'next-client-pages-loader',
        'next-data-loader',
        'next-serverless-loader',
        'noop-loader',
608
        'next-plugin-loader',
609 610 611
      ].reduce((alias, loader) => {
        // using multiple aliases to replace `resolveLoader.modules`
        alias[loader] = path.join(__dirname, 'webpack', 'loaders', loader)
612

613 614
        return alias
      }, {} as Record<string, string>),
N
Naoyuki Kanezawa 已提交
615
      modules: [
616
        'node_modules',
617 618
        ...nodePathList, // Support for NODE_PATH environment variable
      ],
619
      plugins: [PnpWebpackPlugin],
N
nkzawa 已提交
620
    },
T
Tim Neutkens 已提交
621
    // @ts-ignore this is filtered
N
nkzawa 已提交
622
    module: {
623
      strictExportPresence: true,
624
      rules: [
625 626 627 628 629 630
        config.experimental.ampBindInitData &&
          !isServer && {
            test: /\.(tsx|ts|js|mjs|jsx)$/,
            include: [path.join(dir, 'data')],
            use: 'next-data-loader',
          },
T
Tim Neutkens 已提交
631
        {
632
          test: /\.(tsx|ts|js|mjs|jsx)$/,
633
          include: [dir, ...babelIncludeRegexes],
634
          exclude: (path: string) => {
635
            if (babelIncludeRegexes.some(r => r.test(path))) {
636 637
              return false
            }
638
            return /node_modules/.test(path)
639
          },
640
          use: defaultLoaders.babel,
641
        },
J
Joe Haddad 已提交
642 643 644
        config.experimental.css &&
          // Support CSS imports
          ({
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 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706
            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: {},
707
                      },
J
Joe Haddad 已提交
708

709 710 711 712 713
                      // Resolve CSS `@import`s and `url()`s
                      {
                        loader: require.resolve('css-loader'),
                        options: { importLoaders: 1, sourceMap: true },
                      },
J
Joe Haddad 已提交
714

715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757
                      // 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 已提交
758
                              ? path.relative(dir, customAppFile)
759 760 761 762
                              : 'pages/_app.js'
                          )}.\n` +
                          `Read more: https://err.sh/next.js/global-css`,
                      },
J
Joe Haddad 已提交
763
                    },
764 765
              },
            ],
J
Joe Haddad 已提交
766 767 768 769 770 771 772 773 774 775 776 777 778 779 780
          } 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),
781
      ].filter(Boolean),
N
nkzawa 已提交
782
    },
T
Tim Neutkens 已提交
783
    plugins: [
784 785
      // This plugin makes sure `output.filename` is used for entry chunks
      new ChunkNamesPlugin(),
786
      new webpack.DefinePlugin({
787
        ...Object.keys(config.env).reduce((acc, key) => {
788
          if (/^(?:NODE_.+)|^(?:__.+)$/i.test(key)) {
789 790 791
            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`
            )
792 793 794
          }

          return {
795
            ...acc,
796
            [`process.env.${key}`]: JSON.stringify(config.env[key]),
797
          }
798
        }, {}),
799
        'process.env.NODE_ENV': JSON.stringify(webpackMode),
800
        'process.crossOrigin': JSON.stringify(crossOrigin),
801
        'process.browser': JSON.stringify(!isServer),
J
JJ Kasper 已提交
802 803 804
        'process.env.__NEXT_TEST_MODE': JSON.stringify(
          process.env.__NEXT_TEST_MODE
        ),
805
        // This is used in client/dev-error-overlay/hot-dev-client.js to replace the dist directory
806 807 808 809 810 811
        ...(dev && !isServer
          ? {
              'process.env.__NEXT_DIST_DIR': JSON.stringify(distDir),
            }
          : {}),
        'process.env.__NEXT_EXPORT_TRAILING_SLASH': JSON.stringify(
812
          config.exportTrailingSlash
813
        ),
814 815 816
        'process.env.__NEXT_DEFER_SCRIPTS': JSON.stringify(
          config.experimental.deferScripts
        ),
817 818 819 820 821 822 823 824 825 826 827 828
        '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
        ),
829 830 831
        'process.env.__NEXT_PLUGINS': JSON.stringify(
          config.experimental.plugins
        ),
G
Gerald Monaco 已提交
832 833 834
        'process.env.__NEXT_STRICT_MODE': JSON.stringify(
          config.reactStrictMode
        ),
835 836 837
        'process.env.__NEXT_REACT_MODE': JSON.stringify(
          config.experimental.reactMode
        ),
838
        ...(isServer
839 840 841 842 843 844
          ? {
              // 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),
            }
845
          : undefined),
846
      }),
847 848 849 850
      !isServer &&
        new ReactLoadablePlugin({
          filename: REACT_LOADABLE_MANIFEST,
        }),
851
      !isServer && new DropClientPage(),
852 853 854 855 856 857
      // 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$/),
858 859 860 861 862 863 864 865 866 867 868 869 870 871 872
      ...(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(),
            ]
873

874
            if (!isServer) {
875
              const AutoDllPlugin = importAutoDllPlugin({ distDir })
876 877 878 879 880 881 882 883 884
              devPlugins.push(
                new AutoDllPlugin({
                  filename: '[name]_[hash].js',
                  path: './static/development/dll',
                  context: dir,
                  entry: {
                    dll: ['react', 'react-dom'],
                  },
                  config: {
J
JJ Kasper 已提交
885
                    devtool,
886 887 888 889 890 891 892
                    mode: webpackMode,
                    resolve: resolveConfig,
                  },
                })
              )
              devPlugins.push(new webpack.HotModuleReplacementPlugin())
            }
893

894 895 896
            return devPlugins
          })()
        : []),
897
      !dev && new webpack.HashedModuleIdsPlugin(),
898 899 900 901 902 903 904 905 906 907 908 909
      !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 已提交
910
      isServerless && isServer && new ServerlessPlugin(),
911 912
      isServer && new PagesManifestPlugin(isLikeServerless),
      target === 'server' &&
913 914
        isServer &&
        new NextJsSSRModuleCachePlugin({ outputPath }),
915
      isServer && new NextJsSsrImportPlugin(),
916 917 918 919 920 921
      !isServer &&
        new BuildManifestPlugin({
          buildId,
          clientManifest: config.experimental.granularChunks,
          modern: config.experimental.modern,
        }),
J
Joe Haddad 已提交
922 923 924 925 926 927 928 929
      // 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',
        }),
930 931 932
      tracer &&
        new ProfilingPlugin({
          tracer,
933
        }),
934 935
      !isServer &&
        useTypeScript &&
936
        !ignoreTypeScriptErrors &&
M
Maël Nison 已提交
937 938 939 940 941 942 943 944 945 946 947 948 949
        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',
          })
        ),
950 951 952 953 954 955 956 957 958 959 960 961
      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')
962 963 964
              : escapePathVariables(
                  args[0].chunk.name.replace(/\.js$/, '.module.js')
                )
965 966 967 968
          },
          chunkFilename: (inputChunkName: string) =>
            inputChunkName.replace(/\.js$/, '.module.js'),
        }),
969
    ].filter((Boolean as any) as ExcludesFalse),
970
  }
971

T
Tim Neutkens 已提交
972
  if (typeof config.webpack === 'function') {
973 974 975 976 977 978 979 980 981 982
    webpackConfig = config.webpack(webpackConfig, {
      dir,
      dev,
      isServer,
      buildId,
      config,
      defaultLoaders,
      totalPages,
      webpack,
    })
983 984 985

    // @ts-ignore: Property 'then' does not exist on type 'Configuration'
    if (typeof webpackConfig.then === 'function') {
986
      console.warn(
987
        '> Promise returned in next config. https://err.sh/zeit/next.js/promise-in-next-config'
988
      )
989
    }
990
  }
T
Tim Neutkens 已提交
991

992
  // check if using @zeit/next-typescript and show warning
993 994 995
  if (
    isServer &&
    webpackConfig.module &&
996 997 998 999
    Array.isArray(webpackConfig.module.rules)
  ) {
    let foundTsRule = false

1000 1001
    webpackConfig.module.rules = webpackConfig.module.rules.filter(
      (rule): boolean => {
1002
        if (!(rule.test instanceof RegExp)) return true
1003
        if ('noop.ts'.match(rule.test) && !'noop.js'.match(rule.test)) {
1004 1005 1006 1007 1008
          // remove if it matches @zeit/next-typescript
          foundTsRule = rule.use === defaultLoaders.babel
          return !foundTsRule
        }
        return true
1009 1010
      }
    )
1011 1012

    if (foundTsRule) {
1013
      console.warn(
1014
        '\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'
1015
      )
1016 1017 1018
    }
  }

1019
  // Patch `@zeit/next-sass`, `@zeit/next-less`, `@zeit/next-stylus` for compatibility
1020
  if (webpackConfig.module && Array.isArray(webpackConfig.module.rules)) {
1021 1022 1023 1024 1025 1026 1027
    ;[].forEach.call(webpackConfig.module.rules, function(
      rule: webpack.RuleSetRule
    ) {
      if (!(rule.test instanceof RegExp && Array.isArray(rule.use))) {
        return
      }

1028 1029 1030 1031
      const isSass =
        rule.test.source === '\\.scss$' || rule.test.source === '\\.sass$'
      const isLess = rule.test.source === '\\.less$'
      const isCss = rule.test.source === '\\.css$'
1032
      const isStylus = rule.test.source === '\\.styl$'
1033 1034

      // Check if the rule we're iterating over applies to Sass, Less, or CSS
1035
      if (!(isSass || isLess || isCss || isStylus)) {
1036 1037 1038 1039 1040 1041 1042 1043 1044
        return
      }

      ;[].forEach.call(rule.use, function(use: webpack.RuleSetUseItem) {
        if (
          !(
            use &&
            typeof use === 'object' &&
            // Identify use statements only pertaining to `css-loader`
1045 1046
            (use.loader === 'css-loader' ||
              use.loader === 'css-loader/locals') &&
1047 1048 1049 1050
            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
1051 1052
            // old `css-loader` versions. Custom setups (that aren't next-sass,
            // next-less or next-stylus) likely have the newer version.
1053
            // We still handle this gracefully below.
1054 1055 1056 1057 1058
            (Object.prototype.hasOwnProperty.call(use.options, 'minimize') ||
              Object.prototype.hasOwnProperty.call(
                use.options,
                'exportOnlyLocals'
              ))
1059 1060 1061 1062 1063 1064 1065
          )
        ) {
          return
        }

        // Try to monkey patch within a try-catch. We shouldn't fail the build
        // if we cannot pull this off.
1066 1067
        // The user may not even be using the `next-sass` or `next-less` or
        // `next-stylus` plugins.
1068 1069
        // If it does work, great!
        try {
1070 1071
          // Resolve the version of `@zeit/next-css` as depended on by the Sass,
          // Less or Stylus plugin.
1072 1073
          const correctNextCss = resolveRequest(
            '@zeit/next-css',
1074 1075 1076 1077 1078 1079 1080 1081 1082
            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'
1083 1084
                    : isStylus
                    ? '@zeit/next-stylus'
1085 1086
                    : 'next'
                )
1087 1088 1089 1090 1091 1092 1093
          )

          // 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.
1094
            const correctCssLoader = resolveRequest(use.loader, correctNextCss)
1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106
            if (correctCssLoader) {
              // We saved the user from a failed build!
              use.loader = correctCssLoader
            }
          }
        } catch (_) {
          // The error is not required to be handled.
        }
      })
    })
  }

1107
  // Backwards compat for `main.js` entry key
1108
  const originalEntry: any = webpackConfig.entry
1109 1110
  if (typeof originalEntry !== 'undefined') {
    webpackConfig.entry = async () => {
1111 1112 1113 1114
      const entry: WebpackEntrypoints =
        typeof originalEntry === 'function'
          ? await originalEntry()
          : originalEntry
1115 1116 1117
      // 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]
1118
        // @ts-ignore TODO: investigate type error
1119 1120
        entry[CLIENT_STATIC_FILES_RUNTIME_MAIN] = [
          ...entry['main.js'],
1121
          originalFile,
1122
        ]
1123
      }
1124
      delete entry['main.js']
1125

1126
      return entry
1127 1128 1129
    }
  }

1130
  if (!dev) {
1131 1132 1133 1134
    // @ts-ignore entry is always a function
    webpackConfig.entry = await webpackConfig.entry()
  }

T
Tim Neutkens 已提交
1135
  return webpackConfig
N
nkzawa 已提交
1136
}