webpack-config.ts 14.4 KB
Newer Older
1
import path from 'path'
N
nkzawa 已提交
2
import webpack from 'webpack'
T
Tim Neutkens 已提交
3
import resolve from 'next/dist/compiled/resolve/index.js'
4
import NextJsSsrImportPlugin from './webpack/plugins/nextjs-ssr-import'
5
import NextJsSSRModuleCachePlugin from './webpack/plugins/nextjs-ssr-module-cache'
6 7
import PagesManifestPlugin from './webpack/plugins/pages-manifest-plugin'
import BuildManifestPlugin from './webpack/plugins/build-manifest-plugin'
8 9
import ChunkNamesPlugin from './webpack/plugins/chunk-names-plugin'
import { ReactLoadablePlugin } from './webpack/plugins/react-loadable-plugin'
10
import { SERVER_DIRECTORY, REACT_LOADABLE_MANIFEST, CHUNK_GRAPH_MANIFEST, CLIENT_STATIC_FILES_RUNTIME_WEBPACK, CLIENT_STATIC_FILES_RUNTIME_MAIN } from 'next-server/constants'
11
import { NEXT_PROJECT_ROOT, NEXT_PROJECT_ROOT_NODE_MODULES, NEXT_PROJECT_ROOT_DIST_CLIENT, PAGES_DIR_ALIAS, DOT_NEXT_ALIAS } from '../lib/constants'
T
Tim Neutkens 已提交
12
import {TerserPlugin} from './webpack/plugins/terser-webpack-plugin/src/index'
13
import { ServerlessPlugin } from './webpack/plugins/serverless-plugin'
14
import { AllModulesIdentifiedPlugin } from './webpack/plugins/all-modules-identified-plugin'
J
Joe Haddad 已提交
15
import { SharedRuntimePlugin } from './webpack/plugins/shared-runtime-plugin'
16
import { HashedChunkIdsPlugin } from './webpack/plugins/hashed-chunk-ids-plugin'
17
import { ChunkGraphPlugin } from './webpack/plugins/chunk-graph-plugin'
18 19
import { WebpackEntrypoints } from './entries'
type ExcludesFalse = <T>(x: T | false) => x is T
20

21
export default function getBaseWebpackConfig (dir: string, {dev = false, debug = false, isServer = false, buildId, config, target = 'server', entrypoints, __selectivePageBuilding = false}: {dev?: boolean, debug?: boolean, isServer?: boolean, buildId: string, config: any, target?: string, entrypoints: WebpackEntrypoints, __selectivePageBuilding?: boolean}): webpack.Configuration {
T
Tim Neutkens 已提交
22 23
  const defaultLoaders = {
    babel: {
24
      loader: 'next-babel-loader',
25
      options: { isServer, cwd: dir }
26
    },
27
    // Backwards compat
28
    hotSelfAccept: {
29
      loader: 'noop-loader'
N
nkzawa 已提交
30
    }
T
Tim Neutkens 已提交
31 32
  }

T
Tim Neutkens 已提交
33 34 35 36 37
  // Support for NODE_PATH
  const nodePathList = (process.env.NODE_PATH || '')
    .split(process.platform === 'win32' ? ';' : ':')
    .filter((p) => !!p)

38
  const distDir = path.join(dir, config.distDir)
T
Tim Neutkens 已提交
39 40
  const outputDir = target === 'serverless' ? 'serverless' : SERVER_DIRECTORY
  const outputPath = path.join(distDir, isServer ? outputDir : '')
41
  const totalPages = Object.keys(entrypoints).length
42
  const clientEntries = !isServer ? {
43 44
    // Backwards compatibility
    'main.js': [],
45
    [CLIENT_STATIC_FILES_RUNTIME_MAIN]: `.${path.sep}` + path.relative(dir, path.join(NEXT_PROJECT_ROOT_DIST_CLIENT, (dev ? `next-dev.js` : 'next.js')))
46
  } : undefined
N
nkzawa 已提交
47

T
Tim Neutkens 已提交
48
  const resolveConfig = {
49
    // Disable .mjs for node_modules bundling
50
    extensions: isServer ? ['.wasm', '.js', '.mjs', '.jsx', '.json'] : ['.wasm', '.mjs', '.js', '.jsx', '.json'],
T
Tim Neutkens 已提交
51 52 53 54 55
    modules: [
      'node_modules',
      ...nodePathList // Support for NODE_PATH environment variable
    ],
    alias: {
56 57 58 59 60 61
      // 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 已提交
62
      next: NEXT_PROJECT_ROOT,
63 64
      [PAGES_DIR_ALIAS]: path.join(dir, 'pages'),
      [DOT_NEXT_ALIAS]: distDir
65
    },
66
    mainFields: isServer ? ['main', 'module'] : ['browser', 'module', 'main']
T
Tim Neutkens 已提交
67 68
  }

T
Tim Neutkens 已提交
69 70
  const webpackMode = dev ? 'development' : 'production'

71 72 73
  const terserPluginConfig = {
    parallel: true,
    sourceMap: false,
74 75
    cache: true,
    cpus: config.experimental.cpus,
76 77 78
  }

  let webpackConfig: webpack.Configuration = {
T
Tim Neutkens 已提交
79
    mode: webpackMode,
80
    devtool: (dev || debug) ? 'cheap-module-source-map' : false,
T
Tim Neutkens 已提交
81 82
    name: isServer ? 'server' : 'client',
    target: isServer ? 'node' : 'web',
83 84
    externals: isServer && target !== 'serverless' ? [
      (context, request, callback) => {
85
        const notExternalModules = [
86 87 88
          'next/app', 'next/document', 'next/link', 'next/error',
          'string-hash',
          'next/constants'
89
        ]
90 91 92 93

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

95
        resolve(request, { basedir: dir, preserveSymlinks: true }, (err, res) => {
96 97 98
          if (err) {
            return callback()
          }
K
k-kawakami 已提交
99

100 101 102
          if (!res) {
            return callback()
          }
K
k-kawakami 已提交
103

104 105 106 107
          // 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 已提交
108

109 110 111 112
          // 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 已提交
113

114 115 116 117
          // styled-jsx has to be transpiled
          if (res.match(/node_modules[/\\]styled-jsx/)) {
            return callback()
          }
K
k-kawakami 已提交
118

119 120 121
          if (res.match(/node_modules[/\\].*\.js$/)) {
            return callback(undefined, `commonjs ${request}`)
          }
K
k-kawakami 已提交
122

123 124 125 126 127 128
          callback()
        })
      }
    ] : [
      // 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
J
JJ Kasper 已提交
129
      'amp-toolbox-optimizer' // except this one
130 131 132
    ],
    optimization: isServer ? {
      splitChunks: false,
J
Joe Haddad 已提交
133
      minimize: target === 'serverless',
134 135 136 137 138 139 140 141 142 143 144
      minimizer: target === 'serverless' ? [
        new TerserPlugin({...terserPluginConfig,
          terserOptions: {
            compress: false,
            mangle: false,
            module: false,
            keep_classnames: true,
            keep_fnames: true
          }
        })
      ] : undefined
145
    } : Object.assign({
146
      runtimeChunk: __selectivePageBuilding ? false : {
147
        name: CLIENT_STATIC_FILES_RUNTIME_WEBPACK
148
      },
149 150 151 152 153
      splitChunks: dev ? {
        cacheGroups: {
          default: false,
          vendors: false
        }
154
      } : __selectivePageBuilding ? {
J
Joe Haddad 已提交
155 156 157 158 159 160
        cacheGroups: {
          default: false,
          vendors: false,
          react: {
            name: 'commons',
            chunks: 'all',
161
            test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/
J
Joe Haddad 已提交
162 163
          }
        }
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
      } : {
        chunks: 'all',
        cacheGroups: {
          default: false,
          vendors: false,
          commons: {
            name: 'commons',
            chunks: 'all',
            minChunks: totalPages > 2 ? totalPages * 0.5 : 2
          },
          react: {
            name: 'commons',
            chunks: 'all',
            test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/
          }
        }
      },
181 182
      minimize: !(dev || debug),
      minimizer: !(dev || debug) ? [
183 184 185 186 187 188
        new TerserPlugin({...terserPluginConfig,
          terserOptions: {
            safari10: true
          }
        })
      ] : undefined,
189 190 191 192 193
    }, __selectivePageBuilding ? {
      providedExports: false,
      usedExports: false,
      concatenateModules: false,
    } : undefined),
194
    recordsPath: path.join(outputPath, 'records.json'),
N
nkzawa 已提交
195
    context: dir,
196
    // Kept as function to be backwards compatible
T
Tim Neutkens 已提交
197 198
    entry: async () => {
      return {
199
        ...clientEntries ? clientEntries : {},
200
        ...entrypoints
T
Tim Neutkens 已提交
201 202
      }
    },
N
nkzawa 已提交
203
    output: {
204
      path: outputPath,
205
      filename: ({chunk}: {chunk: {name: string}}) => {
206
        // Use `[name]-[contenthash].js` in production
207
        if (!dev && (chunk.name === CLIENT_STATIC_FILES_RUNTIME_MAIN || chunk.name === CLIENT_STATIC_FILES_RUNTIME_WEBPACK)) {
208
          return chunk.name.replace(/\.js$/, '-[contenthash].js')
209 210 211
        }
        return '[name]'
      },
T
Tim Neutkens 已提交
212
      libraryTarget: isServer ? 'commonjs2' : 'var',
213 214 215
      hotUpdateChunkFilename: 'static/webpack/[id].[hash].hot-update.js',
      hotUpdateMainFilename: 'static/webpack/[hash].hot-update.json',
      // This saves chunks with the name given via `import()`
216
      chunkFilename: isServer ? `${dev ? '[name]' : '[name].[contenthash]'}.js` : `static/chunks/${dev ? '[name]' : '[name].[contenthash]'}.js`,
A
Andy 已提交
217
      strictModuleExceptionHandling: true,
218
      crossOriginLoading: config.crossOrigin,
219
      futureEmitAssets: !dev,
A
Andy 已提交
220
      webassemblyModuleFilename: 'static/wasm/[modulehash].wasm'
N
nkzawa 已提交
221
    },
T
Tim Neutkens 已提交
222
    performance: { hints: false },
T
Tim Neutkens 已提交
223
    resolve: resolveConfig,
N
nkzawa 已提交
224
    resolveLoader: {
N
Naoyuki Kanezawa 已提交
225
      modules: [
226
        NEXT_PROJECT_ROOT_NODE_MODULES,
227
        'node_modules',
228
        path.join(__dirname, 'webpack', 'loaders'), // The loaders Next.js provides
T
Tim Neutkens 已提交
229
        ...nodePathList // Support for NODE_PATH environment variable
N
nkzawa 已提交
230 231
      ]
    },
T
Tim Neutkens 已提交
232
    // @ts-ignore this is filtered
N
nkzawa 已提交
233
    module: {
234
      rules: [
T
Tim Neutkens 已提交
235 236 237 238 239
        config.experimental.ampBindInitData && !isServer && {
          test: /\.(js|mjs|jsx)$/,
          include: [path.join(dir, 'data')],
          use: 'next-data-loader'
        },
T
Tim Neutkens 已提交
240
        {
J
Jason Miller 已提交
241
          test: /\.(js|mjs|jsx)$/,
242
          include: [dir, /next-server[\\/]dist[\\/]lib/],
243 244
          exclude: (path: string) => {
            if (/next-server[\\/]dist[\\/]lib/.test(path)) {
245 246 247
              return false
            }

248
            return /node_modules/.test(path)
249
          },
T
Tim Neutkens 已提交
250 251 252
          use: defaultLoaders.babel
        }
      ].filter(Boolean)
N
nkzawa 已提交
253
    },
T
Tim Neutkens 已提交
254
    plugins: [
255 256
      // This plugin makes sure `output.filename` is used for entry chunks
      new ChunkNamesPlugin(),
257 258 259 260 261 262 263
      new webpack.DefinePlugin({
        ...(Object.keys(config.env).reduce((acc, key) => {
          if (/^(?:NODE_.+)|(?:__.+)$/i.test(key)) {
            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`)
          }

          return {
264
            ...acc,
265 266 267 268
            [`process.env.${key}`]: JSON.stringify(config.env[key])
          }
        }, {})),
        'process.crossOrigin': JSON.stringify(config.crossOrigin),
269 270 271 272 273
        'process.browser': JSON.stringify(!isServer),
        // This is used in client/dev-error-overlay/hot-dev-client.js to replace the dist directory
        ...(dev && !isServer ? {
          'process.env.__NEXT_DIST_DIR': JSON.stringify(distDir)
        } : {}),
274
        'process.env.__NEXT_EXPERIMENTAL_DEBUG': JSON.stringify(debug),
275
        'process.env.__NEXT_EXPORT_TRAILING_SLASH': JSON.stringify(config.experimental.exportTrailingSlash)
276
      }),
277 278
      !isServer && new ReactLoadablePlugin({
        filename: REACT_LOADABLE_MANIFEST
279
      }),
J
Joe Haddad 已提交
280
      !isServer && __selectivePageBuilding && new ChunkGraphPlugin(buildId, path.resolve(dir), { filename: CHUNK_GRAPH_MANIFEST }),
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
      ...(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(),
        ]

        if(!isServer) {
          const AutoDllPlugin = require('autodll-webpack-plugin')
          devPlugins.push(
            new AutoDllPlugin({
              filename: '[name]_[hash].js',
              path: './static/development/dll',
              context: dir,
              entry: {
                dll: [
                  'react',
                  'react-dom'
                ]
              },
              config: {
                mode: webpackMode,
                resolve: resolveConfig
              }
            })
          )
          devPlugins.push(new webpack.HotModuleReplacementPlugin())
        }

        return devPlugins
      })() : []),
      !dev && new webpack.HashedModuleIdsPlugin(),
317 318
      // This must come after HashedModuleIdsPlugin (it sets any modules that
      // were missed by HashedModuleIdsPlugin)
319
      !dev && __selectivePageBuilding && new AllModulesIdentifiedPlugin(dir),
320 321 322
      // This sets chunk ids to be hashed versions of their names to reduce
      // bundle churn
      !dev && new HashedChunkIdsPlugin(buildId),
J
Joe Haddad 已提交
323
      // On the client we want to share the same runtime cache
324
      !isServer && __selectivePageBuilding && new SharedRuntimePlugin(),
325
      !dev && new webpack.IgnorePlugin({
326
        checkResource: (resource: string) => {
327 328
          return /react-is/.test(resource)
        },
329
        checkContext: (context: string) => {
330 331
          return /next-server[\\/]dist[\\/]/.test(context) || /next[\\/]dist[\\/]/.test(context)
        }
332
      }),
333
      target === 'serverless' && (isServer || __selectivePageBuilding) && new ServerlessPlugin(buildId, { isServer }),
334 335 336 337
      target !== 'serverless' && isServer && new PagesManifestPlugin(),
      target !== 'serverless' && isServer && new NextJsSSRModuleCachePlugin({ outputPath }),
      isServer && new NextJsSsrImportPlugin(),
      !isServer && new BuildManifestPlugin(),
338 339 340
      config.experimental.profiling && new webpack.debug.ProfilingPlugin({
        outputPath: path.join(distDir, `profile-events-${isServer ? 'server' : 'client'}.json`)
      })
341
    ].filter(Boolean as any as ExcludesFalse)
342
  }
343

T
Tim Neutkens 已提交
344
  if (typeof config.webpack === 'function') {
345
    webpackConfig = config.webpack(webpackConfig, { dir, dev, isServer, buildId, config, defaultLoaders, totalPages, webpack })
346 347 348

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

353
  // Backwards compat for `main.js` entry key
354
  const originalEntry: any = webpackConfig.entry
355 356
  if (typeof originalEntry !== 'undefined') {
    webpackConfig.entry = async () => {
357 358 359 360 361 362 363 364
      const entry: WebpackEntrypoints = typeof originalEntry === 'function' ? await originalEntry() : originalEntry
      // 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'],
          originalFile
        ]
365
      }
366
      delete entry['main.js']
367

368
      return entry
369 370 371
    }
  }

T
Tim Neutkens 已提交
372
  return webpackConfig
N
nkzawa 已提交
373
}