webpack-config.ts 12.1 KB
Newer Older
1
import path from 'path'
N
nkzawa 已提交
2
import webpack from 'webpack'
T
Tim Neutkens 已提交
3
import resolve from 'resolve'
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 11
import { SERVER_DIRECTORY, REACT_LOADABLE_MANIFEST, CLIENT_STATIC_FILES_RUNTIME_WEBPACK, CLIENT_STATIC_FILES_RUNTIME_MAIN } from 'next-server/constants'
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 15
import { WebpackEntrypoints } from './entries'
type ExcludesFalse = <T>(x: T | false) => x is T
16

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

T
Tim Neutkens 已提交
29 30 31 32 33
  // Support for NODE_PATH
  const nodePathList = (process.env.NODE_PATH || '')
    .split(process.platform === 'win32' ? ';' : ':')
    .filter((p) => !!p)

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

T
Tim Neutkens 已提交
46
  const resolveConfig = {
47
    // Disable .mjs for node_modules bundling
48
    extensions: isServer ? ['.wasm', '.js', '.mjs', '.jsx', '.json'] : ['.wasm', '.mjs', '.js', '.jsx', '.json'],
T
Tim Neutkens 已提交
49 50 51 52 53
    modules: [
      'node_modules',
      ...nodePathList // Support for NODE_PATH environment variable
    ],
    alias: {
T
Tim Neutkens 已提交
54
      next: NEXT_PROJECT_ROOT,
55 56
      [PAGES_DIR_ALIAS]: path.join(dir, 'pages'),
      [DOT_NEXT_ALIAS]: distDir
57
    },
58
    mainFields: isServer ? ['main', 'module'] : ['browser', 'module', 'main']
T
Tim Neutkens 已提交
59 60
  }

T
Tim Neutkens 已提交
61 62
  const webpackMode = dev ? 'development' : 'production'

63 64 65
  const terserPluginConfig = {
    parallel: true,
    sourceMap: false,
66 67
    cache: true,
    cpus: config.experimental.cpus,
68 69 70
  }

  let webpackConfig: webpack.Configuration = {
T
Tim Neutkens 已提交
71
    mode: webpackMode,
72
    devtool: dev ? 'cheap-module-source-map' : false,
T
Tim Neutkens 已提交
73 74
    name: isServer ? 'server' : 'client',
    target: isServer ? 'node' : 'web',
75 76
    externals: isServer && target !== 'serverless' ? [
      (context, request, callback) => {
77 78 79 80 81
        const notExternalModules = [
          'next/app', 'next/document', 'next/link', 'next/router', 'next/error',
          'string-hash', 'hoist-non-react-statics', 'htmlescape','next/dynamic',
          'next/constants', 'next/config', 'next/head'
        ]
82 83 84 85

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

87
        resolve(request, { basedir: dir, preserveSymlinks: true }, (err, res) => {
88 89 90
          if (err) {
            return callback()
          }
K
k-kawakami 已提交
91

92 93 94
          if (!res) {
            return callback()
          }
K
k-kawakami 已提交
95

96 97 98 99
          // 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 已提交
100

101 102 103 104
          // 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 已提交
105

106 107 108 109
          // styled-jsx has to be transpiled
          if (res.match(/node_modules[/\\]styled-jsx/)) {
            return callback()
          }
K
k-kawakami 已提交
110

111 112 113
          if (res.match(/node_modules[/\\].*\.js$/)) {
            return callback(undefined, `commonjs ${request}`)
          }
K
k-kawakami 已提交
114

115 116 117 118 119 120 121 122 123
          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
    ],
    optimization: isServer ? {
      splitChunks: false,
J
Joe Haddad 已提交
124
      minimize: target === 'serverless',
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
      minimizer: target === 'serverless' ? [
        new TerserPlugin({...terserPluginConfig,
          terserOptions: {
            compress: false,
            mangle: false,
            module: false,
            keep_classnames: true,
            keep_fnames: true
          }
        })
      ] : undefined
    } : {
      runtimeChunk: {
        name: CLIENT_STATIC_FILES_RUNTIME_WEBPACK
      },
      splitChunks: dev ? {
        cacheGroups: {
          default: false,
          vendors: false
        }
      } : {
        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)[\\/]/
          }
        }
      },
J
Joe Haddad 已提交
162
      minimize: !dev,
163 164 165 166 167 168 169 170
      minimizer: !dev ? [
        new TerserPlugin({...terserPluginConfig,
          terserOptions: {
            safari10: true
          }
        })
      ] : undefined,
    },
171
    recordsPath: path.join(outputPath, 'records.json'),
N
nkzawa 已提交
172
    context: dir,
173
    // Kept as function to be backwards compatible
T
Tim Neutkens 已提交
174 175
    entry: async () => {
      return {
176
        ...clientEntries ? clientEntries : {},
177
        ...entrypoints
T
Tim Neutkens 已提交
178 179
      }
    },
N
nkzawa 已提交
180
    output: {
181
      path: outputPath,
182
      filename: ({chunk}: {chunk: {name: string}}) => {
183
        // Use `[name]-[contenthash].js` in production
184
        if (!dev && (chunk.name === CLIENT_STATIC_FILES_RUNTIME_MAIN || chunk.name === CLIENT_STATIC_FILES_RUNTIME_WEBPACK)) {
185
          return chunk.name.replace(/\.js$/, '-[contenthash].js')
186 187 188
        }
        return '[name]'
      },
189
      libraryTarget: isServer ? 'commonjs2' : 'jsonp',
190 191 192
      hotUpdateChunkFilename: 'static/webpack/[id].[hash].hot-update.js',
      hotUpdateMainFilename: 'static/webpack/[hash].hot-update.json',
      // This saves chunks with the name given via `import()`
193
      chunkFilename: isServer ? `${dev ? '[name]' : '[name].[contenthash]'}.js` : `static/chunks/${dev ? '[name]' : '[name].[contenthash]'}.js`,
A
Andy 已提交
194
      strictModuleExceptionHandling: true,
195
      crossOriginLoading: config.crossOrigin,
196
      futureEmitAssets: !dev,
A
Andy 已提交
197
      webassemblyModuleFilename: 'static/wasm/[modulehash].wasm'
N
nkzawa 已提交
198
    },
T
Tim Neutkens 已提交
199
    performance: { hints: false },
T
Tim Neutkens 已提交
200
    resolve: resolveConfig,
N
nkzawa 已提交
201
    resolveLoader: {
N
Naoyuki Kanezawa 已提交
202
      modules: [
203
        NEXT_PROJECT_ROOT_NODE_MODULES,
204
        'node_modules',
205
        path.join(__dirname, 'webpack', 'loaders'), // The loaders Next.js provides
T
Tim Neutkens 已提交
206
        ...nodePathList // Support for NODE_PATH environment variable
N
nkzawa 已提交
207 208 209
      ]
    },
    module: {
210
      rules: [
T
Tim Neutkens 已提交
211
        {
J
Jason Miller 已提交
212
          test: /\.(js|mjs|jsx)$/,
213
          include: [dir, /next-server[\\/]dist[\\/]lib/],
214 215
          exclude: (path: string) => {
            if (/next-server[\\/]dist[\\/]lib/.test(path)) {
216 217 218
              return false
            }

219
            return /node_modules/.test(path)
220
          },
T
Tim Neutkens 已提交
221 222 223
          use: defaultLoaders.babel
        }
      ].filter(Boolean)
N
nkzawa 已提交
224
    },
T
Tim Neutkens 已提交
225
    plugins: [
226 227
      // This plugin makes sure `output.filename` is used for entry chunks
      new ChunkNamesPlugin(),
228 229 230 231 232 233 234
      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 {
235
            ...acc,
236 237 238 239
            [`process.env.${key}`]: JSON.stringify(config.env[key])
          }
        }, {})),
        'process.crossOrigin': JSON.stringify(config.crossOrigin),
240 241 242 243 244
        '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)
        } : {}),
245
      }),
246 247
      !isServer && new ReactLoadablePlugin({
        filename: REACT_LOADABLE_MANIFEST
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
      ...(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(),
285
      !dev && new webpack.IgnorePlugin({
286
        checkResource: (resource: string) => {
287 288
          return /react-is/.test(resource)
        },
289
        checkContext: (context: string) => {
290 291
          return /next-server[\\/]dist[\\/]/.test(context) || /next[\\/]dist[\\/]/.test(context)
        }
292
      }),
293
      target === 'serverless' && isServer && new ServerlessPlugin(buildId, { sourceMap: dev }),
294 295 296 297
      target !== 'serverless' && isServer && new PagesManifestPlugin(),
      target !== 'serverless' && isServer && new NextJsSSRModuleCachePlugin({ outputPath }),
      isServer && new NextJsSsrImportPlugin(),
      !isServer && new BuildManifestPlugin(),
298 299 300
      config.experimental.profiling && new webpack.debug.ProfilingPlugin({
        outputPath: path.join(distDir, `profile-events-${isServer ? 'server' : 'client'}.json`)
      })
301
    ].filter(Boolean as any as ExcludesFalse)
302
  }
303

T
Tim Neutkens 已提交
304
  if (typeof config.webpack === 'function') {
305
    webpackConfig = config.webpack(webpackConfig, { dir, dev, isServer, buildId, config, defaultLoaders, totalPages, webpack })
306 307 308

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

313 314
  // Backwards compat for `main.js` entry key
  const originalEntry = webpackConfig.entry
315 316 317 318 319 320 321 322 323 324
  if (typeof originalEntry !== 'undefined') {
    webpackConfig.entry = async () => {
      const entry = typeof originalEntry === 'function' ? await originalEntry() : originalEntry
      if (entry && typeof entry !== 'string' && !Array.isArray(entry)) {
        // Server compilation doesn't have main.js
        if (typeof entry['main.js'] !== 'undefined') {
          entry[CLIENT_STATIC_FILES_RUNTIME_MAIN] = [
            ...entry['main.js'],
            ...entry[CLIENT_STATIC_FILES_RUNTIME_MAIN]
          ]
325

326 327 328
          delete entry['main.js']
        }
      }
329

330
      return entry
331 332 333
    }
  }

T
Tim Neutkens 已提交
334
  return webpackConfig
N
nkzawa 已提交
335
}