1
0
Fork 0
mirror of https://github.com/terribleplan/next.js.git synced 2024-01-19 02:48:18 +00:00
next.js/server/build/webpack.js
Tim Neutkens 68626c5147 Improved stacktraces (minor) (#4156)
* Handle production errors correctly

* Improved source map support

* Make react-hot-loader hold state again

* Remove console.log

* Load modules on demand

* Catch errors in rewriteErrorTrace

* Update comment

* Update comment

* Remove source-map-support

* Load modules in next-dev

* Make sure error logged has sourcemaps too

* Add tests for production runtime errors

* Add tests for development runtime errors. Fix issue with client side errors in development

* Move functionality back to renderError now that error handling is consistent

* Rename to applySourcemaps
2018-04-18 21:48:06 +05:30

311 lines
11 KiB
JavaScript

import path, {sep} from 'path'
import webpack from 'webpack'
import resolve from 'resolve'
import UglifyJSPlugin from 'uglifyjs-webpack-plugin'
import CaseSensitivePathPlugin from 'case-sensitive-paths-webpack-plugin'
import WriteFilePlugin from 'write-file-webpack-plugin'
import FriendlyErrorsWebpackPlugin from 'friendly-errors-webpack-plugin'
import {loadPartialConfig, createConfigItem} from '@babel/core'
import {getPages} from './webpack/utils'
import PagesPlugin from './plugins/pages-plugin'
import NextJsSsrImportPlugin from './plugins/nextjs-ssr-import'
import DynamicChunksPlugin from './plugins/dynamic-chunks-plugin'
import UnlinkFilePlugin from './plugins/unlink-file-plugin'
import PagesManifestPlugin from './plugins/pages-manifest-plugin'
import BuildManifestPlugin from './plugins/build-manifest-plugin'
const presetItem = createConfigItem(require('./babel/preset'), {type: 'preset'})
const hotLoaderItem = createConfigItem(require('react-hot-loader/babel'), {type: 'plugin'})
const reactJsxSourceItem = createConfigItem(require('@babel/plugin-transform-react-jsx-source'), {type: 'plugin'})
const nextDir = path.join(__dirname, '..', '..', '..')
const nextNodeModulesDir = path.join(nextDir, 'node_modules')
const nextPagesDir = path.join(nextDir, 'pages')
const defaultPages = [
'_error.js',
'_document.js',
'_app.js'
]
const interpolateNames = new Map(defaultPages.map((p) => {
return [path.join(nextPagesDir, p), `dist/bundles/pages/${p}`]
}))
function babelConfig (dir, {isServer, dev}) {
const mainBabelOptions = {
cacheDirectory: true,
presets: [],
plugins: [
dev && !isServer && hotLoaderItem,
dev && reactJsxSourceItem
].filter(Boolean)
}
const filename = path.join(dir, 'filename.js')
const externalBabelConfig = loadPartialConfig({ babelrc: true, filename })
if (externalBabelConfig && externalBabelConfig.babelrc) {
// Log it out once
if (!isServer) {
console.log(`> Using external babel configuration`)
console.log(`> Location: "${externalBabelConfig.babelrc}"`)
}
// By default babel-loader will look for babelrc, so no need to set it to true
} else {
mainBabelOptions.babelrc = false
}
// Add our default preset if the no "babelrc" found.
if (!mainBabelOptions.babelrc) {
mainBabelOptions.presets.push(presetItem)
}
return mainBabelOptions
}
function externalsConfig (dir, isServer) {
const externals = []
if (!isServer) {
return externals
}
externals.push((context, request, callback) => {
resolve(request, { basedir: dir, preserveSymlinks: true }, (err, res) => {
if (err) {
return callback()
}
// Default pages have to be transpiled
if (res.match(/node_modules[/\\]next[/\\]dist[/\\]pages/)) {
return callback()
}
// Webpack itself has to be compiled because it doesn't always use module relative paths
if (res.match(/node_modules[/\\]webpack/)) {
return callback()
}
if (res.match(/node_modules[/\\].*\.js$/)) {
return callback(null, `commonjs ${request}`)
}
callback()
})
})
return externals
}
export default async function getBaseWebpackConfig (dir, {dev = false, isServer = false, buildId, config}) {
const babelLoaderOptions = babelConfig(dir, {dev, isServer})
const defaultLoaders = {
babel: {
loader: 'babel-loader',
options: babelLoaderOptions
}
}
// Support for NODE_PATH
const nodePathList = (process.env.NODE_PATH || '')
.split(process.platform === 'win32' ? ';' : ':')
.filter((p) => !!p)
const pagesEntries = await getPages(dir, {dev, isServer, pageExtensions: config.pageExtensions.join('|')})
const totalPages = Object.keys(pagesEntries).length
const clientEntries = !isServer ? {
'main.js': [
dev && !isServer && path.join(__dirname, '..', '..', 'client', 'webpack-hot-middleware-client'),
dev && !isServer && path.join(__dirname, '..', '..', 'client', 'on-demand-entries-client'),
require.resolve(`../../client/next${dev ? '-dev' : ''}`)
].filter(Boolean)
} : {}
let webpackConfig = {
devtool: dev ? 'cheap-module-source-map' : false,
name: isServer ? 'server' : 'client',
cache: true,
target: isServer ? 'node' : 'web',
externals: externalsConfig(dir, isServer),
context: dir,
// Kept as function to be backwards compatible
entry: async () => {
return {
...clientEntries,
// Only _error and _document when in development. The rest is handled by on-demand-entries
...pagesEntries
}
},
output: {
path: path.join(dir, config.distDir, isServer ? 'dist' : ''), // server compilation goes to `.next/dist`
filename: '[name]',
libraryTarget: 'commonjs2',
// This saves chunks with the name given via require.ensure()
chunkFilename: '[name]-[chunkhash].js',
strictModuleExceptionHandling: true
},
performance: { hints: false },
resolve: {
extensions: ['.js', '.jsx', '.json'],
modules: [
nextNodeModulesDir,
'node_modules',
...nodePathList // Support for NODE_PATH environment variable
],
alias: {
next: nextDir,
// React already does something similar to this.
// But if the user has react-devtools, it'll throw an error showing that
// we haven't done dead code elimination (via uglifyjs).
// We purposly do not uglify React code to save the build time.
// (But it didn't increase the overall build size)
// Here we are doing an exact match with '$'
// So, you can still require nested modules like `react-dom/server`
react$: dev ? 'react/cjs/react.development.js' : 'react/cjs/react.production.min.js',
'react-dom$': dev ? 'react-dom/cjs/react-dom.development.js' : 'react-dom/cjs/react-dom.production.min.js'
}
},
resolveLoader: {
modules: [
nextNodeModulesDir,
'node_modules',
path.join(__dirname, 'loaders'),
...nodePathList // Support for NODE_PATH environment variable
]
},
module: {
rules: [
dev && !isServer && {
test: /\.(js|jsx)$/,
loader: 'hot-self-accept-loader',
include: [
path.join(dir, 'pages'),
nextPagesDir
],
options: {
extensions: /\.(js|jsx)$/
}
},
{
test: /\.(js|jsx)$/,
include: [dir],
exclude: /node_modules/,
use: defaultLoaders.babel
}
].filter(Boolean)
},
plugins: [
new webpack.IgnorePlugin(/(precomputed)/, /node_modules.+(elliptic)/),
dev && new webpack.NoEmitOnErrorsPlugin(),
dev && !isServer && new FriendlyErrorsWebpackPlugin(),
dev && new webpack.NamedModulesPlugin(),
dev && !isServer && new webpack.HotModuleReplacementPlugin(), // Hot module replacement
dev && new UnlinkFilePlugin(),
dev && new CaseSensitivePathPlugin(), // Since on macOS the filesystem is case-insensitive this will make sure your path are case-sensitive
dev && new webpack.LoaderOptionsPlugin({
options: {
context: dir,
customInterpolateName (url, name, opts) {
return interpolateNames.get(this.resourcePath) || url
}
}
}),
dev && new WriteFilePlugin({
exitOnErrors: false,
log: false,
// required not to cache removed files
useHashIndex: false
}),
!dev && new webpack.IgnorePlugin(/react-hot-loader/),
!isServer && !dev && new UglifyJSPlugin({
exclude: /react\.js/,
parallel: true,
sourceMap: false,
uglifyOptions: {
compress: {
arrows: false,
booleans: false,
collapse_vars: false,
comparisons: false,
computed_props: false,
hoist_funs: false,
hoist_props: false,
hoist_vars: false,
if_return: false,
inline: false,
join_vars: false,
keep_infinity: true,
loops: false,
negate_iife: false,
properties: false,
reduce_funcs: false,
reduce_vars: false,
sequences: false,
side_effects: false,
switches: false,
top_retain: false,
toplevel: false,
typeofs: false,
unused: false,
conditionals: true,
dead_code: true,
evaluate: true
}
}
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(dev ? 'development' : 'production')
}),
!dev && new webpack.optimize.ModuleConcatenationPlugin(),
isServer && new PagesManifestPlugin(),
!isServer && new BuildManifestPlugin(),
!isServer && new PagesPlugin(),
!isServer && new DynamicChunksPlugin(),
isServer && new NextJsSsrImportPlugin(),
// In dev mode, we don't move anything to the commons bundle.
// In production we move common modules into the existing main.js bundle
!isServer && new webpack.optimize.CommonsChunkPlugin({
name: 'main.js',
filename: dev ? 'static/commons/main.js' : 'static/commons/main-[chunkhash].js',
minChunks (module, count) {
// React and React DOM are used everywhere in Next.js. So they should always be common. Even in development mode, to speed up compilation.
if (module.resource && module.resource.includes(`${sep}react-dom${sep}`) && count >= 0) {
return true
}
if (module.resource && module.resource.includes(`${sep}react${sep}`) && count >= 0) {
return true
}
// In the dev we use on-demand-entries.
// So, it makes no sense to use commonChunks based on the minChunks count.
// Instead, we move all the code in node_modules into each of the pages.
if (dev) {
return false
}
// commons
// If there are one or two pages, only move modules to common if they are
// used in all of the pages. Otherwise, move modules used in at-least
// 1/2 of the total pages into commons.
if (totalPages <= 2) {
return count >= totalPages
}
return count >= totalPages * 0.5
// commons end
}
}),
// We use a manifest file in development to speed up HMR
dev && !isServer && new webpack.optimize.CommonsChunkPlugin({
name: 'manifest.js',
filename: dev ? 'static/commons/manifest.js' : 'static/commons/manifest-[chunkhash].js'
})
].filter(Boolean)
}
if (typeof config.webpack === 'function') {
webpackConfig = config.webpack(webpackConfig, {dir, dev, isServer, buildId, config, defaultLoaders, totalPages})
}
return webpackConfig
}