mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-27 08:02:01 +00:00
530 lines
17 KiB
JavaScript
530 lines
17 KiB
JavaScript
import path from 'path'
|
|
import fs from 'fs'
|
|
import { defaultsDeep, pick, uniq } from 'lodash'
|
|
import defu from 'defu'
|
|
import consola from 'consola'
|
|
import destr from 'destr'
|
|
import { TARGETS, MODES, createRequire, guardDir, isNonEmptyString, isPureObject, isUrl, getMainModule, getPKG } from '@nuxt/utils'
|
|
import { isRelative, joinURL, normalizeURL, withTrailingSlash } from 'ufo'
|
|
import { defaultNuxtConfigFile, getDefaultNuxtConfig } from './config'
|
|
|
|
export function getNuxtConfig (_options) {
|
|
// Prevent duplicate calls
|
|
if (_options.__normalized__) {
|
|
return _options
|
|
}
|
|
|
|
// Clone options to prevent unwanted side-effects
|
|
const options = Object.assign({}, _options)
|
|
options.__normalized__ = true
|
|
|
|
// Normalize options
|
|
if (options.loading === true) {
|
|
delete options.loading
|
|
}
|
|
|
|
if (
|
|
options.router &&
|
|
options.router.middleware &&
|
|
!Array.isArray(options.router.middleware)
|
|
) {
|
|
options.router.middleware = [options.router.middleware]
|
|
}
|
|
|
|
if (options.router && typeof options.router.base === 'string') {
|
|
options._routerBaseSpecified = true
|
|
}
|
|
|
|
// TODO: Remove for Nuxt 3
|
|
// router.scrollBehavior -> app/router.scrollBehavior.js
|
|
if (options.router && typeof options.router.scrollBehavior !== 'undefined') {
|
|
consola.warn('`router.scrollBehavior` property is deprecated in favor of using `~/app/router.scrollBehavior.js` file, learn more: https://nuxtjs.org/api/configuration-router#scrollbehavior')
|
|
}
|
|
|
|
// TODO: Remove for Nuxt 3
|
|
// transition -> pageTransition
|
|
if (typeof options.transition !== 'undefined') {
|
|
consola.warn('`transition` property is deprecated in favor of `pageTransition` and will be removed in Nuxt 3')
|
|
options.pageTransition = options.transition
|
|
delete options.transition
|
|
}
|
|
|
|
if (typeof options.pageTransition === 'string') {
|
|
options.pageTransition = { name: options.pageTransition }
|
|
}
|
|
|
|
if (typeof options.layoutTransition === 'string') {
|
|
options.layoutTransition = { name: options.layoutTransition }
|
|
}
|
|
|
|
if (typeof options.extensions === 'string') {
|
|
options.extensions = [options.extensions]
|
|
}
|
|
|
|
options.globalName = (isNonEmptyString(options.globalName) && /^[a-zA-Z]+$/.test(options.globalName))
|
|
? options.globalName.toLowerCase()
|
|
// use `` for preventing replacing to nuxt-edge
|
|
: `nuxt`
|
|
|
|
// Resolve rootDir
|
|
options.rootDir = isNonEmptyString(options.rootDir) ? path.resolve(options.rootDir) : process.cwd()
|
|
|
|
// Apply defaults by ${buildDir}/dist/build.config.js
|
|
// TODO: Unsafe operation.
|
|
// const buildDir = options.buildDir || defaults.buildDir
|
|
// const buildConfig = resolve(options.rootDir, buildDir, 'build.config.js')
|
|
// if (existsSync(buildConfig)) {
|
|
// defaultsDeep(options, require(buildConfig))
|
|
// }
|
|
|
|
// Apply defaults
|
|
const nuxtConfig = getDefaultNuxtConfig()
|
|
|
|
nuxtConfig.build._publicPath = nuxtConfig.build.publicPath
|
|
|
|
// Fall back to default if publicPath is falsy
|
|
if (options.build && !options.build.publicPath) {
|
|
options.build.publicPath = undefined
|
|
}
|
|
|
|
defaultsDeep(options, nuxtConfig)
|
|
|
|
// Target
|
|
options.target = options.target || 'server'
|
|
if (!Object.values(TARGETS).includes(options.target)) {
|
|
consola.warn(`Unknown target: ${options.target}. Falling back to server`)
|
|
options.target = 'server'
|
|
}
|
|
|
|
// Deprecate Mode
|
|
if (options.mode) {
|
|
if ((options.mode === MODES.universal && options.ssr) || (options.mode === MODES.spa && !options.ssr)) {
|
|
consola.warn('`mode` option is deprecated. You can safely remove it from `nuxt.config`')
|
|
} else {
|
|
consola.warn('`mode` option is deprecated. Please use `ssr: true` for universal mode or `ssr: false` for spa mode and remove `mode` from `nuxt.config`')
|
|
}
|
|
} else {
|
|
// For backward compat we need default value
|
|
options.mode = MODES.universal
|
|
}
|
|
|
|
// SSR root option
|
|
if (options.ssr === false) {
|
|
options.mode = MODES.spa
|
|
}
|
|
|
|
// Apply mode preset
|
|
const modePreset = options.modes[options.mode || MODES.universal]
|
|
|
|
if (!modePreset) {
|
|
consola.warn(`Unknown mode: ${options.mode}. Falling back to ${MODES.universal}`)
|
|
}
|
|
defaultsDeep(options, modePreset || options.modes[MODES.universal])
|
|
|
|
// Sanitize router.base
|
|
options.router.base = withTrailingSlash(normalizeURL(options.router.base))
|
|
|
|
// Legacy support for export
|
|
if (options.export) {
|
|
consola.warn('export option is deprecated and will be removed in a future version! Please switch to generate')
|
|
options.generate = defu(options.export, options.generate)
|
|
}
|
|
exports.export = options.generate
|
|
|
|
// Check srcDir and generate.dir existence
|
|
const hasSrcDir = isNonEmptyString(options.srcDir)
|
|
const hasGenerateDir = isNonEmptyString(options.generate.dir)
|
|
|
|
// Resolve srcDir
|
|
options.srcDir = hasSrcDir
|
|
? path.resolve(options.rootDir, options.srcDir)
|
|
: options.rootDir
|
|
|
|
// Resolve buildDir
|
|
options.buildDir = path.resolve(options.rootDir, options.buildDir)
|
|
|
|
// Aliases
|
|
const { rootDir, srcDir, dir: { assets: assetsDir, static: staticDir } } = options
|
|
options.alias = {
|
|
'~~': rootDir,
|
|
'@@': rootDir,
|
|
'~': srcDir,
|
|
'@': srcDir,
|
|
[assetsDir]: path.join(srcDir, assetsDir),
|
|
[staticDir]: path.join(srcDir, staticDir),
|
|
...options.alias
|
|
}
|
|
|
|
// Default value for _nuxtConfigFile
|
|
if (!options._nuxtConfigFile) {
|
|
options._nuxtConfigFile = path.resolve(options.rootDir, `${defaultNuxtConfigFile}.js`)
|
|
}
|
|
|
|
if (!options._nuxtConfigFiles) {
|
|
options._nuxtConfigFiles = [
|
|
options._nuxtConfigFile
|
|
]
|
|
}
|
|
|
|
// Watch for config file changes
|
|
options.watch.push(...options._nuxtConfigFiles)
|
|
|
|
// Protect rootDir against buildDir
|
|
guardDir(options, 'rootDir', 'buildDir')
|
|
|
|
if (hasGenerateDir) {
|
|
// Resolve generate.dir
|
|
options.generate.dir = path.resolve(options.rootDir, options.generate.dir)
|
|
|
|
// Protect rootDir against buildDir
|
|
guardDir(options, 'rootDir', 'generate.dir')
|
|
}
|
|
|
|
if (hasSrcDir) {
|
|
// Protect srcDir against buildDir
|
|
guardDir(options, 'srcDir', 'buildDir')
|
|
|
|
if (hasGenerateDir) {
|
|
// Protect srcDir against generate.dir
|
|
guardDir(options, 'srcDir', 'generate.dir')
|
|
}
|
|
}
|
|
|
|
// Populate modulesDir
|
|
options.modulesDir = uniq(
|
|
getMainModule().paths.concat(
|
|
[].concat(options.modulesDir).map(dir => path.resolve(options.rootDir, dir))
|
|
)
|
|
)
|
|
|
|
const mandatoryExtensions = ['js', 'mjs']
|
|
|
|
options.extensions = mandatoryExtensions
|
|
.filter(ext => !options.extensions.includes(ext))
|
|
.concat(options.extensions)
|
|
|
|
// If app.html is defined, set the template path to the user template
|
|
if (options.appTemplatePath === undefined) {
|
|
options.appTemplatePath = path.resolve(options.buildDir, 'views/app.template.html')
|
|
if (fs.existsSync(path.join(options.srcDir, 'app.html'))) {
|
|
options.appTemplatePath = path.join(options.srcDir, 'app.html')
|
|
}
|
|
} else {
|
|
options.appTemplatePath = path.resolve(options.srcDir, options.appTemplatePath)
|
|
}
|
|
|
|
options.build.publicPath = options.build.publicPath.replace(/([^/])$/, '$1/')
|
|
options.build._publicPath = options.build._publicPath.replace(/([^/])$/, '$1/')
|
|
|
|
// Ignore publicPath on dev
|
|
if (options.dev && isUrl(options.build.publicPath)) {
|
|
options.build.publicPath = options.build._publicPath
|
|
}
|
|
|
|
// If store defined, update store options to true unless explicitly disabled
|
|
if (
|
|
options.store !== false &&
|
|
fs.existsSync(path.join(options.srcDir, options.dir.store)) &&
|
|
fs.readdirSync(path.join(options.srcDir, options.dir.store))
|
|
.find(filename => filename !== 'README.md' && filename[0] !== '.')
|
|
) {
|
|
options.store = true
|
|
}
|
|
|
|
// SPA loadingIndicator
|
|
if (options.loadingIndicator) {
|
|
// Normalize loadingIndicator
|
|
if (!isPureObject(options.loadingIndicator)) {
|
|
options.loadingIndicator = { name: options.loadingIndicator }
|
|
}
|
|
|
|
// Apply defaults
|
|
options.loadingIndicator = Object.assign(
|
|
{
|
|
name: 'default',
|
|
color: (options.loading && options.loading.color) || '#D3D3D3',
|
|
color2: '#F5F5F5',
|
|
background: (options.manifest && options.manifest.theme_color) || 'white',
|
|
dev: options.dev,
|
|
loading: options.messages.loading
|
|
},
|
|
options.loadingIndicator
|
|
)
|
|
}
|
|
|
|
// Debug errors
|
|
if (options.debug === undefined) {
|
|
options.debug = options.dev
|
|
}
|
|
|
|
// Validate that etag.hash is a function, if not unset it
|
|
if (options.render.etag) {
|
|
const { hash } = options.render.etag
|
|
if (hash) {
|
|
const isFn = typeof hash === 'function'
|
|
if (!isFn) {
|
|
options.render.etag.hash = undefined
|
|
|
|
if (options.dev) {
|
|
consola.warn(`render.etag.hash should be a function, received ${typeof hash} instead`)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply default hash to CSP option
|
|
if (options.render.csp) {
|
|
options.render.csp = defu(options.render.csp, {
|
|
hashAlgorithm: 'sha256',
|
|
allowedSources: undefined,
|
|
policies: undefined,
|
|
addMeta: Boolean(options.target === TARGETS.static),
|
|
unsafeInlineCompatibility: false,
|
|
reportOnly: options.debug
|
|
})
|
|
|
|
// TODO: Remove this if statement in Nuxt 3, we will stop supporting this typo (more on: https://github.com/nuxt/nuxt.js/pull/6583)
|
|
if (options.render.csp.unsafeInlineCompatiblity) {
|
|
consola.warn('Using `unsafeInlineCompatiblity` is deprecated and will be removed in Nuxt 3. Use `unsafeInlineCompatibility` instead.')
|
|
options.render.csp.unsafeInlineCompatibility = options.render.csp.unsafeInlineCompatiblity
|
|
delete options.render.csp.unsafeInlineCompatiblity
|
|
}
|
|
}
|
|
|
|
// cssSourceMap
|
|
if (options.build.cssSourceMap === undefined) {
|
|
options.build.cssSourceMap = options.dev
|
|
}
|
|
|
|
const babelConfig = options.build.babel
|
|
// babel cacheDirectory
|
|
if (babelConfig.cacheDirectory === undefined) {
|
|
babelConfig.cacheDirectory = options.dev
|
|
}
|
|
|
|
// TODO: remove this warn in Nuxt 3
|
|
if (Array.isArray(babelConfig.presets)) {
|
|
const warnPreset = (presetName) => {
|
|
const oldPreset = '@nuxtjs/babel-preset-app'
|
|
const newPreset = '@nuxt/babel-preset-app'
|
|
if (presetName.includes(oldPreset)) {
|
|
presetName = presetName.replace(oldPreset, newPreset)
|
|
consola.warn('@nuxtjs/babel-preset-app has been deprecated, please use @nuxt/babel-preset-app.')
|
|
}
|
|
return presetName
|
|
}
|
|
babelConfig.presets = babelConfig.presets.map((preset) => {
|
|
const hasOptions = Array.isArray(preset)
|
|
if (hasOptions) {
|
|
preset[0] = warnPreset(preset[0])
|
|
} else if (typeof preset === 'string') {
|
|
preset = warnPreset(preset)
|
|
}
|
|
return preset
|
|
})
|
|
}
|
|
|
|
// Vue config
|
|
const vueConfig = options.vue.config
|
|
|
|
if (vueConfig.silent === undefined) {
|
|
vueConfig.silent = !options.dev
|
|
}
|
|
if (vueConfig.performance === undefined) {
|
|
vueConfig.performance = options.dev
|
|
}
|
|
|
|
// merge custom env with variables
|
|
const eligibleEnvVariables = pick(process.env, Object.keys(process.env).filter(k => k.startsWith('NUXT_ENV_')))
|
|
Object.assign(options.env, eligibleEnvVariables)
|
|
|
|
// Normalize ignore
|
|
options.ignore = options.ignore ? [].concat(options.ignore) : []
|
|
|
|
// Append ignorePrefix glob to ignore
|
|
if (typeof options.ignorePrefix === 'string') {
|
|
options.ignore.push(`**/${options.ignorePrefix}*.*`)
|
|
}
|
|
|
|
// Compression middleware legacy
|
|
if (options.render.gzip) {
|
|
consola.warn('render.gzip is deprecated and will be removed in a future version! Please switch to render.compressor')
|
|
options.render.compressor = options.render.gzip
|
|
delete options.render.gzip
|
|
}
|
|
|
|
// If no server-side rendering, add appear true transition
|
|
if (options.render.ssr === false && options.pageTransition) {
|
|
options.pageTransition.appear = true
|
|
}
|
|
|
|
options.render.ssrLog = options.dev
|
|
? options.render.ssrLog === undefined || options.render.ssrLog
|
|
: false
|
|
|
|
// We assume the SPA fallback path is 404.html (for GitHub Pages, Surge, etc.)
|
|
if (options.generate.fallback === true) {
|
|
options.generate.fallback = '404.html'
|
|
}
|
|
|
|
if (options.build.stats === 'none' || options.build.quiet === true) {
|
|
options.build.stats = false
|
|
}
|
|
|
|
// Vendor backward compatibility with nuxt 1.x
|
|
if (typeof options.build.vendor !== 'undefined') {
|
|
delete options.build.vendor
|
|
consola.warn('vendor has been deprecated due to webpack4 optimization')
|
|
}
|
|
|
|
// Disable CSS extraction due to incompatibility with thread-loader
|
|
if (options.build.extractCSS && options.build.parallel) {
|
|
options.build.parallel = false
|
|
consola.warn('extractCSS cannot work with parallel build due to limited work pool in thread-loader')
|
|
}
|
|
|
|
// build.extractCSS.allChunks has no effect
|
|
if (typeof options.build.extractCSS.allChunks !== 'undefined') {
|
|
consola.warn('build.extractCSS.allChunks has no effect from v2.0.0. Please use build.optimization.splitChunks settings instead.')
|
|
}
|
|
|
|
// devModules has been renamed to buildModules
|
|
if (typeof options.devModules !== 'undefined') {
|
|
consola.warn('`devModules` has been renamed to `buildModules` and will be removed in Nuxt 3.')
|
|
options.buildModules.push(...options.devModules)
|
|
delete options.devModules
|
|
}
|
|
|
|
// Enable minimize for production builds
|
|
if (options.build.optimization.minimize === undefined) {
|
|
options.build.optimization.minimize = !options.dev
|
|
}
|
|
|
|
// Enable optimizeCSS only when extractCSS is enabled
|
|
if (options.build.optimizeCSS === undefined) {
|
|
options.build.optimizeCSS = options.build.extractCSS ? {} : false
|
|
}
|
|
|
|
const { loaders } = options.build
|
|
const vueLoader = loaders.vue
|
|
if (vueLoader.productionMode === undefined) {
|
|
vueLoader.productionMode = !options.dev
|
|
}
|
|
const styleLoaders = [
|
|
'css', 'cssModules', 'less',
|
|
'sass', 'scss', 'stylus', 'vueStyle'
|
|
]
|
|
for (const name of styleLoaders) {
|
|
const loader = loaders[name]
|
|
if (loader && loader.sourceMap === undefined) {
|
|
loader.sourceMap = Boolean(options.build.cssSourceMap)
|
|
}
|
|
}
|
|
|
|
options.build.transpile = [].concat(options.build.transpile || [])
|
|
|
|
if (options.build.quiet === true) {
|
|
consola.level = 0
|
|
}
|
|
|
|
// Use runInNewContext for dev mode by default
|
|
const { bundleRenderer } = options.render
|
|
if (typeof bundleRenderer.runInNewContext === 'undefined') {
|
|
bundleRenderer.runInNewContext = options.dev
|
|
}
|
|
|
|
// TODO: Remove this if statement in Nuxt 3
|
|
if (options.build.crossorigin) {
|
|
consola.warn('Using `build.crossorigin` is deprecated and will be removed in Nuxt 3. Please use `render.crossorigin` instead.')
|
|
options.render.crossorigin = options.build.crossorigin
|
|
delete options.build.crossorigin
|
|
}
|
|
|
|
const { timing } = options.server
|
|
if (timing) {
|
|
options.server.timing = { total: true, ...timing }
|
|
}
|
|
|
|
if (isPureObject(options.serverMiddleware)) {
|
|
options.serverMiddleware = Object.entries(options.serverMiddleware)
|
|
.map(([path, handler]) => ({ path, handler }))
|
|
}
|
|
|
|
// App config (internal for nuxt2 at this stage)
|
|
const useCDN = isUrl(options.build.publicPath) && !options.dev
|
|
const isRelativePublicPath = isRelative(options.build.publicPath)
|
|
|
|
options.app = defu(options.app, {
|
|
basePath: options.router.base,
|
|
assetsPath: isRelativePublicPath ? options.build.publicPath : useCDN ? '/' : joinURL(options.router.base, options.build.publicPath),
|
|
cdnURL: useCDN ? options.build.publicPath : null
|
|
})
|
|
// Expose app config to $config._app
|
|
options.publicRuntimeConfig = options.publicRuntimeConfig || {}
|
|
options.publicRuntimeConfig._app = options.app
|
|
|
|
// Generate staticAssets
|
|
const { staticAssets } = options.generate
|
|
if (!staticAssets.version) {
|
|
staticAssets.version = String(Math.round(Date.now() / 1000))
|
|
}
|
|
if (!staticAssets.base) {
|
|
staticAssets.base = joinURL(options.app.assetsPath, staticAssets.dir)
|
|
}
|
|
if (!staticAssets.versionBase) {
|
|
staticAssets.versionBase = joinURL(staticAssets.base, staticAssets.version)
|
|
}
|
|
|
|
// createRequire
|
|
const isJest = typeof jest !== 'undefined'
|
|
const defaultCreateRequire = isJest ? 'native' : 'jiti'
|
|
|
|
options.createRequire = process.env.NUXT_CREATE_REQUIRE || options.createRequire || defaultCreateRequire
|
|
|
|
if (options.createRequire === 'native' || options.createRequire === 'jiti') {
|
|
const useJiti = options.createRequire === 'jiti'
|
|
options.createRequire = p => createRequire(typeof p === 'string' ? p : p.filename, useJiti)
|
|
} else if (typeof options.createRequire !== 'function') {
|
|
throw new TypeError(
|
|
`Unsupported createRequire value ${options.createRequire}! Possible values: "native", "jiti", <Function>`
|
|
)
|
|
}
|
|
|
|
// Indicator
|
|
// Change boolean true to default nuxt value
|
|
if (options.build.indicator === true) {
|
|
options.build.indicator = nuxtConfig.build.indicator
|
|
}
|
|
|
|
// ----- Builtin modules -----
|
|
|
|
// Loading screen
|
|
// Force disable for production and programmatic users
|
|
if (!options.dev || !options._cli || !getPKG('@nuxt/loading-screen')) {
|
|
options.build.loadingScreen = false
|
|
}
|
|
if (options.build.loadingScreen) {
|
|
options._modules.push(['@nuxt/loading-screen', options.build.loadingScreen])
|
|
} else {
|
|
// When loadingScreen is disabled we should also disable build indicator
|
|
options.build.indicator = false
|
|
}
|
|
|
|
// Components Module
|
|
if (!options._start && getPKG('@nuxt/components')) {
|
|
options._modules.push('@nuxt/components')
|
|
}
|
|
|
|
// Nuxt Telemetry
|
|
if (
|
|
options.telemetry !== false &&
|
|
!options.test &&
|
|
!destr(process.env.NUXT_TELEMETRY_DISABLED) &&
|
|
getPKG('@nuxt/telemetry')
|
|
) {
|
|
options._modules.push('@nuxt/telemetry')
|
|
}
|
|
|
|
return options
|
|
}
|