Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 28 additions & 20 deletions packages/next/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import crypto from 'crypto'
import { readFileSync, realpathSync } from 'fs'
import chalk from 'chalk'
import semver from 'next/dist/compiled/semver'
// @ts-ignore No typings yet
import TerserPlugin from './webpack/plugins/terser-webpack-plugin/src/index.js'
import path from 'path'
import { webpack, isWebpack5 } from 'next/dist/compiled/webpack/webpack'
import {
Expand Down Expand Up @@ -42,7 +40,6 @@ import { pluginLoaderOptions } from './webpack/loaders/next-plugin-loader'
import BuildManifestPlugin from './webpack/plugins/build-manifest-plugin'
import BuildStatsPlugin from './webpack/plugins/build-stats-plugin'
import ChunkNamesPlugin from './webpack/plugins/chunk-names-plugin'
import { CssMinimizerPlugin } from './webpack/plugins/css-minimizer-plugin'
import { JsConfigPathsPlugin } from './webpack/plugins/jsconfig-paths-plugin'
import { DropClientPage } from './webpack/plugins/next-drop-client-page-plugin'
import NextJsSsrImportPlugin from './webpack/plugins/nextjs-ssr-import'
Expand Down Expand Up @@ -332,7 +329,7 @@ export default async function getBaseWebpackConfig(
// jsconfig is a subset of tsconfig
if (useTypeScript) {
const ts = (await import(typeScriptPath!)) as typeof import('typescript')
const tsConfig = await getTypeScriptConfiguration(ts, tsConfigPath)
const tsConfig = await getTypeScriptConfiguration(ts, tsConfigPath, true)
jsConfig = { compilerOptions: tsConfig.options }
}

Expand Down Expand Up @@ -822,24 +819,35 @@ export default async function getBaseWebpackConfig(
minimize: !(dev || isServer),
minimizer: [
// Minify JavaScript
new TerserPlugin({
cacheDir: path.join(distDir, 'cache', 'next-minifier'),
parallel: config.experimental.cpus,
terserOptions,
}),
(compiler: webpack.Compiler) => {
// @ts-ignore No typings yet
const {
TerserPlugin,
} = require('./webpack/plugins/terser-webpack-plugin/src/index.js')
new TerserPlugin({
cacheDir: path.join(distDir, 'cache', 'next-minifier'),
parallel: config.experimental.cpus,
terserOptions,
}).apply(compiler)
},
// Minify CSS
new CssMinimizerPlugin({
postcssOptions: {
map: {
// `inline: false` generates the source map in a separate file.
// Otherwise, the CSS file is needlessly large.
inline: false,
// `annotation: false` skips appending the `sourceMappingURL`
// to the end of the CSS file. Webpack already handles this.
annotation: false,
(compiler: webpack.Compiler) => {
const {
CssMinimizerPlugin,
} = require('./webpack/plugins/css-minimizer-plugin')
new CssMinimizerPlugin({
postcssOptions: {
map: {
// `inline: false` generates the source map in a separate file.
// Otherwise, the CSS file is needlessly large.
inline: false,
// `annotation: false` skips appending the `sourceMappingURL`
// to the end of the CSS file. Webpack already handles this.
annotation: false,
},
},
},
}),
}).apply(compiler)
},
],
},
context: dir,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class Webpack4Cache {
}
}

class TerserPlugin {
export class TerserPlugin {
constructor(options = {}) {
const { cacheDir, terserOptions = {}, parallel } = options

Expand Down Expand Up @@ -400,5 +400,3 @@ class TerserPlugin {
})
}
}

export default TerserPlugin
18 changes: 15 additions & 3 deletions packages/next/lib/typescript/getTypeScriptConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { FatalTypeScriptError } from './FatalTypeScriptError'

export async function getTypeScriptConfiguration(
ts: typeof import('typescript'),
tsConfigPath: string
tsConfigPath: string,
metaOnly?: boolean
): Promise<import('typescript').ParsedCommandLine> {
try {
const formatDiagnosticsHost: import('typescript').FormatDiagnosticsHost = {
Expand All @@ -21,9 +22,20 @@ export async function getTypeScriptConfiguration(
)
}

let configToParse: any = config

const result = ts.parseJsonConfigFileContent(
config,
ts.sys,
configToParse,
// When only interested in meta info,
// avoid enumerating all files (for performance reasons)
metaOnly
? {
...ts.sys,
readDirectory(_path, extensions, _excludes, _includes, _depth) {
return [extensions ? `file${extensions[0]}` : `file.ts`]
},
}
: ts.sys,
path.dirname(tsConfigPath)
)

Expand Down
2 changes: 1 addition & 1 deletion packages/next/lib/typescript/writeConfigurationDefaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export async function writeConfigurationDefaults(
const {
options: tsOptions,
raw: rawConfig,
} = await getTypeScriptConfiguration(ts, tsConfigPath)
} = await getTypeScriptConfiguration(ts, tsConfigPath, true)

const userTsConfigContent = await fs.readFile(tsConfigPath, {
encoding: 'utf8',
Expand Down
4 changes: 3 additions & 1 deletion packages/next/lib/verifyTypeScriptSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
hasNecessaryDependencies,
NecessaryDependencies,
} from './typescript/hasNecessaryDependencies'
import { runTypeCheck, TypeCheckResult } from './typescript/runTypeCheck'
import type { TypeCheckResult } from './typescript/runTypeCheck'
import { TypeScriptCompileError } from './typescript/TypeScriptCompileError'
import { writeAppTypeDeclarations } from './typescript/writeAppTypeDeclarations'
import { writeConfigurationDefaults } from './typescript/writeConfigurationDefaults'
Expand Down Expand Up @@ -41,6 +41,8 @@ export async function verifyTypeScriptSetup(
await writeAppTypeDeclarations(dir)

if (typeCheckPreflight) {
const { runTypeCheck } = require('./typescript/runTypeCheck')

// Verify the project passes type-checking before we go to webpack phase:
return await runTypeCheck(ts, dir, tsConfigPath)
}
Expand Down
101 changes: 101 additions & 0 deletions packages/next/next-server/server/config-utils-worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { loadEnvConfig } from '@next/env'
import findUp from 'next/dist/compiled/find-up'
import { init as initWebpack } from 'next/dist/compiled/webpack/webpack'
import { CONFIG_FILE, PHASE_DEVELOPMENT_SERVER } from '../lib/constants'
import { NextConfig, normalizeConfig } from './config-shared'
import * as Log from '../../build/output/log'

let installed: boolean = false

export function install(useWebpack5: boolean) {
if (installed) {
return
}
installed = true

initWebpack(useWebpack5)

// hook the Node.js require so that webpack requires are
// routed to the bundled and now initialized webpack version
require('../../build/webpack/require-hook')
}

export type CheckReasons =
| 'test-mode'
| 'no-config'
| 'future-flag'
| 'no-future-flag'
| 'no-webpack-config'
| 'webpack-config'

export type CheckResult = {
enabled: boolean
reason: CheckReasons
}

export async function shouldLoadWithWebpack5(
phase: string,
dir: string
): Promise<CheckResult> {
await loadEnvConfig(dir, phase === PHASE_DEVELOPMENT_SERVER, Log)

const path = await findUp(CONFIG_FILE, {
cwd: dir,
})

if (Number(process.env.NEXT_PRIVATE_TEST_WEBPACK5_MODE) > 0) {
return {
enabled: true,
reason: 'test-mode',
}
}

// No `next.config.js`:
if (!path?.length) {
// Uncomment to add auto-enable when there is no next.config.js
// Use webpack 5 by default in new apps:
return {
enabled: true,
reason: 'no-config',
}
}

// Default to webpack 4 for backwards compatibility on boot:
install(false)

const userConfigModule = require(path)
const userConfig: Partial<NextConfig> = normalizeConfig(
phase,
userConfigModule.default || userConfigModule
)

// Opted-in manually
if (userConfig.future?.webpack5 === true) {
return {
enabled: true,
reason: 'future-flag',
}
}

// Opted-out manually
if (userConfig.future?.webpack5 === false) {
return {
enabled: false,
reason: 'no-future-flag',
}
}

// Uncomment to add auto-enable when there is no custom webpack config
// The user isn't configuring webpack
if (!userConfig.webpack) {
return {
enabled: true,
reason: 'no-webpack-config',
}
}

return {
enabled: false,
reason: 'webpack-config',
}
}
111 changes: 11 additions & 100 deletions packages/next/next-server/server/config-utils.ts
Original file line number Diff line number Diff line change
@@ -1,105 +1,10 @@
import { loadEnvConfig } from '@next/env'
import path from 'path'
import { Worker } from 'jest-worker'
import findUp from 'next/dist/compiled/find-up'
import { init as initWebpack } from 'next/dist/compiled/webpack/webpack'
import { CONFIG_FILE, PHASE_DEVELOPMENT_SERVER } from '../lib/constants'
import { NextConfig, normalizeConfig } from './config-shared'
import * as Log from '../../build/output/log'
import type { CheckReasons, CheckResult } from './config-utils-worker'
import { install, shouldLoadWithWebpack5 } from './config-utils-worker'

let installed: boolean = false

export function install(useWebpack5: boolean) {
if (installed) {
return
}
installed = true

initWebpack(useWebpack5)

// hook the Node.js require so that webpack requires are
// routed to the bundled and now initialized webpack version
require('../../build/webpack/require-hook')
}

type CheckReasons =
| 'test-mode'
| 'no-config'
| 'future-flag'
| 'no-future-flag'
| 'no-webpack-config'
| 'webpack-config'

type CheckResult = {
enabled: boolean
reason: CheckReasons
}

export async function shouldLoadWithWebpack5(
phase: string,
dir: string
): Promise<CheckResult> {
await loadEnvConfig(dir, phase === PHASE_DEVELOPMENT_SERVER, Log)

const path = await findUp(CONFIG_FILE, {
cwd: dir,
})

if (Number(process.env.NEXT_PRIVATE_TEST_WEBPACK5_MODE) > 0) {
return {
enabled: true,
reason: 'test-mode',
}
}

// No `next.config.js`:
if (!path?.length) {
// Uncomment to add auto-enable when there is no next.config.js
// Use webpack 5 by default in new apps:
return {
enabled: true,
reason: 'no-config',
}
}

// Default to webpack 4 for backwards compatibility on boot:
install(false)

const userConfigModule = require(path)
const userConfig: Partial<NextConfig> = normalizeConfig(
phase,
userConfigModule.default || userConfigModule
)

// Opted-in manually
if (userConfig.future?.webpack5 === true) {
return {
enabled: true,
reason: 'future-flag',
}
}

// Opted-out manually
if (userConfig.future?.webpack5 === false) {
return {
enabled: false,
reason: 'no-future-flag',
}
}

// Uncomment to add auto-enable when there is no custom webpack config
// The user isn't configuring webpack
if (!userConfig.webpack) {
return {
enabled: true,
reason: 'no-webpack-config',
}
}

return {
enabled: false,
reason: 'webpack-config',
}
}
export { install, shouldLoadWithWebpack5 }

function reasonMessage(reason: CheckReasons) {
switch (reason) {
Expand All @@ -122,7 +27,13 @@ function reasonMessage(reason: CheckReasons) {

export async function loadWebpackHook(phase: string, dir: string) {
let useWebpack5 = false
const worker: any = new Worker(__filename, { enableWorkerThreads: false })
const worker: any = new Worker(
path.resolve(__dirname, './config-utils-worker.js'),
{
enableWorkerThreads: false,
numWorkers: 1,
}
)
try {
const result: CheckResult = await worker.shouldLoadWithWebpack5(phase, dir)
Log.info(
Expand Down
Loading