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

236
            return /node_modules/.test(path)
237
          },
T
Tim Neutkens 已提交
238 239 240
          use: defaultLoaders.babel
        }
      ].filter(Boolean)
N
nkzawa 已提交
241
    },
T
Tim Neutkens 已提交
242
    plugins: [
243 244
      // This plugin makes sure `output.filename` is used for entry chunks
      new ChunkNamesPlugin(),
245 246 247 248 249 250 251
      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 {
252
            ...acc,
253 254 255 256
            [`process.env.${key}`]: JSON.stringify(config.env[key])
          }
        }, {})),
        'process.crossOrigin': JSON.stringify(config.crossOrigin),
257 258 259 260 261
        '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)
        } : {}),
262
        'process.env.__NEXT_EXPERIMENTAL_DEBUG': JSON.stringify(debug),
263
        'process.env.__NEXT_EXPORT_TRAILING_SLASH': JSON.stringify(config.experimental.exportTrailingSlash)
264
      }),
265 266
      !isServer && new ReactLoadablePlugin({
        filename: REACT_LOADABLE_MANIFEST
267
      }),
J
Joe Haddad 已提交
268
      !isServer && __selectivePageBuilding && new ChunkGraphPlugin(buildId, path.resolve(dir), { filename: CHUNK_GRAPH_MANIFEST }),
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
      ...(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(),
305 306
      // This must come after HashedModuleIdsPlugin (it sets any modules that
      // were missed by HashedModuleIdsPlugin)
307
      !dev && __selectivePageBuilding && new AllModulesIdentifiedPlugin(dir),
308 309 310
      // This sets chunk ids to be hashed versions of their names to reduce
      // bundle churn
      !dev && new HashedChunkIdsPlugin(buildId),
J
Joe Haddad 已提交
311
      // On the client we want to share the same runtime cache
312
      !isServer && __selectivePageBuilding && new SharedRuntimePlugin(),
313
      !dev && new webpack.IgnorePlugin({
314
        checkResource: (resource: string) => {
315 316
          return /react-is/.test(resource)
        },
317
        checkContext: (context: string) => {
318 319
          return /next-server[\\/]dist[\\/]/.test(context) || /next[\\/]dist[\\/]/.test(context)
        }
320
      }),
321
      target === 'serverless' && (isServer || __selectivePageBuilding) && new ServerlessPlugin(buildId, { isServer }),
322 323 324 325
      target !== 'serverless' && isServer && new PagesManifestPlugin(),
      target !== 'serverless' && isServer && new NextJsSSRModuleCachePlugin({ outputPath }),
      isServer && new NextJsSsrImportPlugin(),
      !isServer && new BuildManifestPlugin(),
326 327 328
      config.experimental.profiling && new webpack.debug.ProfilingPlugin({
        outputPath: path.join(distDir, `profile-events-${isServer ? 'server' : 'client'}.json`)
      })
329
    ].filter(Boolean as any as ExcludesFalse)
330
  }
331

T
Tim Neutkens 已提交
332
  if (typeof config.webpack === 'function') {
333
    webpackConfig = config.webpack(webpackConfig, { dir, dev, isServer, buildId, config, defaultLoaders, totalPages, webpack })
334 335 336

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

341
  // Backwards compat for `main.js` entry key
342
  const originalEntry: any = webpackConfig.entry
343 344
  if (typeof originalEntry !== 'undefined') {
    webpackConfig.entry = async () => {
345 346 347 348 349 350 351 352
      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
        ]
353
      }
354
      delete entry['main.js']
355

356
      return entry
357 358 359
    }
  }

T
Tim Neutkens 已提交
360
  return webpackConfig
N
nkzawa 已提交
361
}