mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-21 21:25:11 +00:00
feat: typed nuxt (1)
This commit is contained in:
parent
f9005bb300
commit
38e72f86c2
@ -15,6 +15,7 @@ import template from 'lodash/template'
|
||||
import uniq from 'lodash/uniq'
|
||||
import uniqBy from 'lodash/uniqBy'
|
||||
|
||||
import type { Nuxt } from 'nuxt/core'
|
||||
import { BundleBuilder } from 'nuxt/webpack'
|
||||
import vueAppTemplate from 'nuxt/vue-app/template'
|
||||
|
||||
@ -26,6 +27,7 @@ import {
|
||||
determineGlobals,
|
||||
stripWhitespace,
|
||||
isIndexFileAndFolder,
|
||||
DeterminedGlobals,
|
||||
scanRequireTree,
|
||||
TARGETS,
|
||||
isFullStatic
|
||||
@ -34,10 +36,35 @@ import {
|
||||
import Ignore from './ignore'
|
||||
import BuildContext from './context/build'
|
||||
import TemplateContext from './context/template'
|
||||
import { RouteLocationRaw } from 'vue-router'
|
||||
|
||||
const glob = pify(Glob)
|
||||
export default class Builder {
|
||||
constructor (nuxt, bundleBuilder) {
|
||||
__closed?: boolean
|
||||
_buildStatus: typeof STATUS[keyof typeof STATUS]
|
||||
_defaultPage?: boolean
|
||||
_nuxtPages?: boolean
|
||||
|
||||
appFiles: string[]
|
||||
bundleBuilder: BundleBuilder
|
||||
globals: DeterminedGlobals
|
||||
ignore: Ignore
|
||||
nuxt: Nuxt
|
||||
options: Nuxt['options']
|
||||
plugins: Array<{
|
||||
src: string
|
||||
}>
|
||||
relativeToBuild: (...args: string[]) => string
|
||||
routes: RouteLocationRaw[]
|
||||
supportedExtensions: string[]
|
||||
template: typeof vueAppTemplate
|
||||
watchers: {
|
||||
files: null
|
||||
custom: null
|
||||
restart: null
|
||||
}
|
||||
|
||||
constructor (nuxt: Nuxt) {
|
||||
this.nuxt = nuxt
|
||||
this.plugins = []
|
||||
this.options = nuxt.options
|
||||
@ -51,7 +78,7 @@ export default class Builder {
|
||||
this.supportedExtensions = ['vue', 'js', ...(this.options.build.additionalExtensions || [])]
|
||||
|
||||
// Helper to resolve build paths
|
||||
this.relativeToBuild = (...args) => relativeTo(this.options.buildDir, ...args)
|
||||
this.relativeToBuild = (...args: string[]) => relativeTo(this.options.buildDir, ...args)
|
||||
|
||||
this._buildStatus = STATUS.INITIAL
|
||||
|
||||
@ -81,7 +108,7 @@ export default class Builder {
|
||||
this.template = vueAppTemplate
|
||||
|
||||
// Create a new bundle builder
|
||||
this.bundleBuilder = this.getBundleBuilder(bundleBuilder)
|
||||
this.bundleBuilder = this.getBundleBuilder()
|
||||
|
||||
this.ignore = new Ignore({
|
||||
rootDir: this.options.srcDir,
|
||||
@ -846,4 +873,4 @@ const STATUS = {
|
||||
INITIAL: 1,
|
||||
BUILD_DONE: 2,
|
||||
BUILDING: 3
|
||||
}
|
||||
} as const
|
||||
|
@ -1,5 +1,12 @@
|
||||
import type Builder from '../builder'
|
||||
|
||||
export default class BuildContext {
|
||||
constructor (builder) {
|
||||
_builder: Builder
|
||||
nuxt: Builder['nuxt']
|
||||
options: Builder['nuxt']['options']
|
||||
target: Builder['nuxt']['options']['target']
|
||||
|
||||
constructor (builder: Builder) {
|
||||
this._builder = builder
|
||||
this.nuxt = builder.nuxt
|
||||
this.options = builder.nuxt.options
|
||||
|
@ -1,13 +1,17 @@
|
||||
import hash from 'hash-sum'
|
||||
import consola from 'consola'
|
||||
import uniqBy from 'lodash/uniqBy'
|
||||
import serialize from 'serialize-javascript'
|
||||
|
||||
import devalue from '@nuxt/devalue'
|
||||
import { r, wp, wChunk, serializeFunction, isFullStatic } from 'nuxt/utils'
|
||||
|
||||
import type Builder from '../builder'
|
||||
|
||||
export default class TemplateContext {
|
||||
constructor(builder, options) {
|
||||
templateFiles: string[]
|
||||
templateVars: any
|
||||
|
||||
constructor (builder: Builder, options) {
|
||||
this.templateFiles = Array.from(builder.template.files)
|
||||
this.templateVars = {
|
||||
nuxtOptions: options,
|
||||
@ -53,7 +57,7 @@ export default class TemplateContext {
|
||||
}
|
||||
}
|
||||
|
||||
get templateOptions () {
|
||||
get templateOptions() {
|
||||
return {
|
||||
imports: {
|
||||
serialize,
|
||||
@ -62,7 +66,7 @@ export default class TemplateContext {
|
||||
hash,
|
||||
r,
|
||||
wp,
|
||||
wChunk,
|
||||
wChunk
|
||||
},
|
||||
interpolate: /<%=([\s\S]+?)%>/g
|
||||
}
|
||||
|
@ -2,7 +2,16 @@ import path from 'path'
|
||||
import fs from 'fs-extra'
|
||||
import ignore from 'ignore'
|
||||
|
||||
type IgnoreInstance = ReturnType<typeof ignore>
|
||||
type IgnoreOptions = Parameters<typeof ignore>[0]
|
||||
|
||||
export default class Ignore {
|
||||
rootDir: string
|
||||
ignore?: IgnoreInstance
|
||||
ignoreArray?: string | string
|
||||
ignoreFile?: string
|
||||
ignoreOptions?: IgnoreOptions
|
||||
|
||||
constructor (options) {
|
||||
this.rootDir = options.rootDir
|
||||
this.ignoreOptions = options.ignoreOptions
|
||||
@ -44,7 +53,7 @@ export default class Ignore {
|
||||
}
|
||||
}
|
||||
|
||||
filter (paths) {
|
||||
filter (paths: string[]) {
|
||||
if (this.ignore) {
|
||||
return this.ignore.filter([].concat(paths || []))
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
import type { Nuxt } from 'nuxt/core'
|
||||
|
||||
import Builder from './builder'
|
||||
export { default as Builder } from './builder'
|
||||
|
||||
export function getBuilder (nuxt) {
|
||||
export function getBuilder (nuxt: Nuxt) {
|
||||
return new Builder(nuxt)
|
||||
}
|
||||
|
||||
export function build (nuxt) {
|
||||
export function build (nuxt: Nuxt) {
|
||||
return getBuilder(nuxt).build()
|
||||
}
|
||||
|
@ -1,43 +1,74 @@
|
||||
|
||||
import path from 'path'
|
||||
import consola from 'consola'
|
||||
import minimist from 'minimist'
|
||||
import Hookable from 'hable'
|
||||
import minimist, { ParsedArgs } from 'minimist'
|
||||
import Hookable from 'hookable'
|
||||
|
||||
import { Nuxt } from 'nuxt/core'
|
||||
import { Builder } from 'nuxt/builder'
|
||||
import { Generator } from 'nuxt/generator'
|
||||
import type { Target } from 'nuxt/utils'
|
||||
|
||||
import { name, version } from '../../package.json'
|
||||
|
||||
import { forceExit } from './utils'
|
||||
import { loadNuxtConfig } from './utils/config'
|
||||
import { indent, foldLines, colorize } from './utils/formatting'
|
||||
import { startSpaces, optionSpaces, forceExitTimeout } from './utils/constants'
|
||||
import { Nuxt } from 'nuxt/core'
|
||||
import { Builder } from 'nuxt/builder'
|
||||
import { Generator } from 'nuxt/generator'
|
||||
|
||||
export interface Command {
|
||||
name: string
|
||||
usage: string
|
||||
description: string
|
||||
options?: Record<string, any>
|
||||
run?: (nuxt: NuxtCommand) => any | Promise<any>
|
||||
}
|
||||
|
||||
type Hooks = Parameters<Hookable['addHooks']>[0]
|
||||
|
||||
interface ExtraOptions {
|
||||
_build?: boolean
|
||||
_cli?: boolean
|
||||
_export?: boolean
|
||||
_generate?: boolean
|
||||
_start?: boolean
|
||||
dev?: boolean
|
||||
server?: boolean
|
||||
target?: Target
|
||||
}
|
||||
|
||||
export default class NuxtCommand extends Hookable {
|
||||
constructor (cmd = { name: '', usage: '', description: '' }, argv = process.argv.slice(2), hooks = {}) {
|
||||
_argv: string[]
|
||||
_parsedArgv: null | ParsedArgs
|
||||
_lockRelease?: () => Promise<any>
|
||||
|
||||
cmd: Command & { options: Command['options'] }
|
||||
|
||||
constructor(cmd: Command = { name: '', usage: '', description: '' }, argv = process.argv.slice(2), hooks: Hooks = {}) {
|
||||
super(consola)
|
||||
this.addHooks(hooks)
|
||||
|
||||
if (!cmd.options) {
|
||||
cmd.options = {}
|
||||
}
|
||||
this.cmd = cmd
|
||||
this.cmd = cmd as Command & { options: Command['options'] }
|
||||
|
||||
this._argv = Array.from(argv)
|
||||
this._parsedArgv = null // Lazy evaluate
|
||||
}
|
||||
|
||||
static run (cmd, argv, hooks) {
|
||||
static run(cmd: Command, argv: NodeJS.Process['argv'], hooks: Hooks) {
|
||||
return NuxtCommand.from(cmd, argv, hooks).run()
|
||||
}
|
||||
|
||||
static from (cmd, argv, hooks) {
|
||||
static from(cmd: Command, argv: NodeJS.Process['argv'], hooks: Hooks) {
|
||||
if (cmd instanceof NuxtCommand) {
|
||||
return cmd
|
||||
}
|
||||
return new NuxtCommand(cmd, argv, hooks)
|
||||
}
|
||||
|
||||
async run () {
|
||||
async run() {
|
||||
await this.callHook('run:before', {
|
||||
argv: this._argv,
|
||||
cmd: this.cmd,
|
||||
@ -54,11 +85,11 @@ export default class NuxtCommand extends Hookable {
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof this.cmd.run !== 'function') {
|
||||
if (!(this.cmd.run instanceof Function)) {
|
||||
throw new TypeError('Invalid command! Commands should at least implement run() function.')
|
||||
}
|
||||
|
||||
let cmdError
|
||||
let cmdError: any
|
||||
|
||||
try {
|
||||
await this.cmd.run(this)
|
||||
@ -86,15 +117,15 @@ export default class NuxtCommand extends Hookable {
|
||||
}
|
||||
}
|
||||
|
||||
showVersion () {
|
||||
showVersion() {
|
||||
process.stdout.write(`${name} v${version}\n`)
|
||||
}
|
||||
|
||||
showHelp () {
|
||||
showHelp() {
|
||||
process.stdout.write(this._getHelp())
|
||||
}
|
||||
|
||||
get argv () {
|
||||
get argv() {
|
||||
if (!this._parsedArgv) {
|
||||
const minimistOptions = this._getMinimistOptions()
|
||||
this._parsedArgv = minimist(this._argv, minimistOptions)
|
||||
@ -102,7 +133,7 @@ export default class NuxtCommand extends Hookable {
|
||||
return this._parsedArgv
|
||||
}
|
||||
|
||||
async getNuxtConfig (extraOptions = {}) {
|
||||
async getNuxtConfig(extraOptions: ExtraOptions = {}) {
|
||||
// Flag to indicate nuxt is running with CLI (not programmatic)
|
||||
extraOptions._cli = true
|
||||
|
||||
@ -123,7 +154,7 @@ export default class NuxtCommand extends Hookable {
|
||||
return options
|
||||
}
|
||||
|
||||
async getNuxt (options) {
|
||||
async getNuxt(options) {
|
||||
|
||||
const nuxt = new Nuxt(options)
|
||||
await nuxt.ready()
|
||||
@ -131,16 +162,16 @@ export default class NuxtCommand extends Hookable {
|
||||
return nuxt
|
||||
}
|
||||
|
||||
async getBuilder (nuxt) {
|
||||
async getBuilder(nuxt: Nuxt) {
|
||||
return new Builder(nuxt)
|
||||
}
|
||||
|
||||
async getGenerator (nuxt) {
|
||||
async getGenerator(nuxt) {
|
||||
const builder = await this.getBuilder(nuxt)
|
||||
return new Generator(nuxt, builder)
|
||||
}
|
||||
|
||||
async setLock (lockRelease) {
|
||||
async setLock(lockRelease) {
|
||||
if (lockRelease) {
|
||||
if (this._lockRelease) {
|
||||
consola.warn(`A previous unreleased lock was found, this shouldn't happen and is probably an error in 'nuxt ${this.cmd.name}' command. The lock will be removed but be aware of potential strange results`)
|
||||
@ -153,22 +184,22 @@ export default class NuxtCommand extends Hookable {
|
||||
}
|
||||
}
|
||||
|
||||
async releaseLock () {
|
||||
async releaseLock() {
|
||||
if (this._lockRelease) {
|
||||
await this._lockRelease()
|
||||
this._lockRelease = undefined
|
||||
}
|
||||
}
|
||||
|
||||
isUserSuppliedArg (option) {
|
||||
isUserSuppliedArg(option: string) {
|
||||
return this._argv.includes(`--${option}`) || this._argv.includes(`--no-${option}`)
|
||||
}
|
||||
|
||||
_getDefaultOptionValue (option) {
|
||||
return typeof option.default === 'function' ? option.default(this.cmd) : option.default
|
||||
_getDefaultOptionValue<T, Option extends { default: ((cmd: Command) => T) | T }>(option: Option) {
|
||||
return option.default instanceof Function ? option.default(this.cmd) : option.default
|
||||
}
|
||||
|
||||
_getMinimistOptions () {
|
||||
_getMinimistOptions() {
|
||||
const minimistOptions = {
|
||||
alias: {},
|
||||
boolean: [],
|
||||
@ -193,7 +224,7 @@ export default class NuxtCommand extends Hookable {
|
||||
return minimistOptions
|
||||
}
|
||||
|
||||
_getHelp () {
|
||||
_getHelp() {
|
||||
const options = []
|
||||
let maxOptionLength = 0
|
||||
|
||||
|
@ -1,5 +1,8 @@
|
||||
import consola from 'consola'
|
||||
import type { ParsedArgs } from 'minimist'
|
||||
|
||||
import { MODES, TARGETS } from 'nuxt/utils'
|
||||
import NuxtCommand from '../command'
|
||||
import { common, locking } from '../options'
|
||||
import { createLock } from '../utils'
|
||||
|
||||
@ -14,7 +17,7 @@ export default {
|
||||
alias: 'a',
|
||||
type: 'boolean',
|
||||
description: 'Launch webpack-bundle-analyzer to optimize your bundles',
|
||||
prepare (cmd, options, argv) {
|
||||
prepare (cmd: NuxtCommand, options, argv: ParsedArgs) {
|
||||
// Analyze option
|
||||
options.build = options.build || {}
|
||||
if (argv.analyze && typeof options.build.analyze !== 'object') {
|
||||
@ -26,7 +29,7 @@ export default {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Enable Vue devtools',
|
||||
prepare (cmd, options, argv) {
|
||||
prepare (cmd: NuxtCommand, options, argv: ParsedArgs) {
|
||||
options.vue = options.vue || {}
|
||||
options.vue.config = options.vue.config || {}
|
||||
if (argv.devtools) {
|
||||
@ -43,7 +46,7 @@ export default {
|
||||
alias: 'q',
|
||||
type: 'boolean',
|
||||
description: 'Disable output except for errors',
|
||||
prepare (cmd, options, argv) {
|
||||
prepare (cmd: NuxtCommand, options, argv: ParsedArgs) {
|
||||
// Silence output when using --quiet
|
||||
options.build = options.build || {}
|
||||
if (argv.quiet) {
|
||||
@ -55,14 +58,14 @@ export default {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Bundle all server dependencies (useful for nuxt-start)',
|
||||
prepare (cmd, options, argv) {
|
||||
prepare (cmd: NuxtCommand, options, argv: ParsedArgs) {
|
||||
if (argv.standalone) {
|
||||
options.build.standalone = true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
async run (cmd) {
|
||||
async run (cmd: NuxtCommand) {
|
||||
const config = await cmd.getNuxtConfig({ dev: false, server: false, _build: true })
|
||||
config.server = (config.mode === MODES.spa || config.ssr === false) && cmd.argv.generate !== false
|
||||
const nuxt = await cmd.getNuxt(config)
|
||||
|
@ -1,6 +1,11 @@
|
||||
import consola from 'consola'
|
||||
import chalk from 'chalk'
|
||||
import opener from 'opener'
|
||||
import type { ParsedArgs } from 'minimist'
|
||||
|
||||
import { Nuxt } from 'nuxt/core'
|
||||
|
||||
import type NuxtCommand from '../command'
|
||||
import { common, server } from '../options'
|
||||
import { eventsMapping, formatPath } from '../utils'
|
||||
import { showBanner } from '../utils/banner'
|
||||
@ -20,13 +25,13 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
async run (cmd) {
|
||||
async run (cmd: NuxtCommand) {
|
||||
const { argv } = cmd
|
||||
|
||||
await this.startDev(cmd, argv, argv.open)
|
||||
},
|
||||
|
||||
async startDev (cmd, argv) {
|
||||
async startDev (cmd: NuxtCommand, argv) {
|
||||
let nuxt
|
||||
try {
|
||||
nuxt = await this._listenDev(cmd, argv)
|
||||
@ -45,7 +50,7 @@ export default {
|
||||
return nuxt
|
||||
},
|
||||
|
||||
async _listenDev (cmd, argv) {
|
||||
async _listenDev (cmd: NuxtCommand, argv) {
|
||||
const config = await cmd.getNuxtConfig({ dev: true, _build: true })
|
||||
const nuxt = await cmd.getNuxt(config)
|
||||
|
||||
@ -73,7 +78,7 @@ export default {
|
||||
return nuxt
|
||||
},
|
||||
|
||||
async _buildDev (cmd, argv, nuxt) {
|
||||
async _buildDev (cmd: NuxtCommand, argv: ParsedArgs, nuxt: Nuxt) {
|
||||
// Create builder instance
|
||||
const builder = await cmd.getBuilder(nuxt)
|
||||
|
||||
@ -110,7 +115,7 @@ export default {
|
||||
await this.startDev(cmd, argv)
|
||||
},
|
||||
|
||||
onBundlerChange (path) {
|
||||
onBundlerChange (path: string) {
|
||||
this.logChanged({ event: 'change', path })
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import path from 'path'
|
||||
import consola from 'consola'
|
||||
import { TARGETS } from 'nuxt/utils'
|
||||
import type NuxtCommand from '../command'
|
||||
import { common, locking } from '../options'
|
||||
import { createLock } from '../utils'
|
||||
|
||||
@ -17,7 +18,7 @@ export default {
|
||||
description: 'Exit with non-zero status code if there are errors when exporting pages'
|
||||
}
|
||||
},
|
||||
async run (cmd) {
|
||||
async run (cmd: NuxtCommand) {
|
||||
const config = await cmd.getNuxtConfig({
|
||||
dev: false,
|
||||
target: TARGETS.static,
|
||||
|
@ -1,4 +1,6 @@
|
||||
import type { ParsedArgs } from 'minimist'
|
||||
import { TARGETS } from 'nuxt/utils'
|
||||
import type NuxtCommand from '../command'
|
||||
import { common, locking } from '../options'
|
||||
import { normalizeArg, createLock } from '../utils'
|
||||
|
||||
@ -18,7 +20,7 @@ export default {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Enable Vue devtools',
|
||||
prepare (cmd, options, argv) {
|
||||
prepare (_cmd: NuxtCommand, options, argv: ParsedArgs) {
|
||||
options.vue = options.vue || {}
|
||||
options.vue.config = options.vue.config || {}
|
||||
if (argv.devtools) {
|
||||
@ -30,7 +32,7 @@ export default {
|
||||
alias: 'q',
|
||||
type: 'boolean',
|
||||
description: 'Disable output except for errors',
|
||||
prepare (cmd, options, argv) {
|
||||
prepare (_cmd: NuxtCommand, options, argv: ParsedArgs) {
|
||||
// Silence output when using --quiet
|
||||
options.build = options.build || {}
|
||||
if (argv.quiet) {
|
||||
@ -41,7 +43,7 @@ export default {
|
||||
modern: {
|
||||
...common.modern,
|
||||
description: 'Generate app in modern build (modern mode can be only client)',
|
||||
prepare (cmd, options, argv) {
|
||||
prepare (_cmd: NuxtCommand, options, argv: ParsedArgs) {
|
||||
if (normalizeArg(argv.modern)) {
|
||||
options.modern = 'client'
|
||||
}
|
||||
@ -53,7 +55,7 @@ export default {
|
||||
description: 'Exit with non-zero status code if there are errors when generating pages'
|
||||
}
|
||||
},
|
||||
async run (cmd) {
|
||||
async run (cmd: NuxtCommand) {
|
||||
const config = await cmd.getNuxtConfig({
|
||||
dev: false,
|
||||
_build: cmd.argv.build,
|
||||
|
@ -12,7 +12,7 @@ export default {
|
||||
help: common.help,
|
||||
version: common.version
|
||||
},
|
||||
async run (cmd) {
|
||||
async run (cmd: NuxtCommand) {
|
||||
const [name] = cmd._argv
|
||||
if (!name) {
|
||||
return listCommands()
|
||||
|
@ -9,7 +9,7 @@ const _commands = {
|
||||
help: () => import('./help')
|
||||
}
|
||||
|
||||
export default function getCommand (name) {
|
||||
export default function getCommand (name: keyof typeof _commands) {
|
||||
if (!_commands[name]) {
|
||||
return Promise.resolve(null)
|
||||
}
|
||||
|
@ -5,10 +5,11 @@ import serveStatic from 'serve-static'
|
||||
import compression from 'compression'
|
||||
import { getNuxtConfig } from 'nuxt/config'
|
||||
import { TARGETS } from 'nuxt/utils'
|
||||
import { common, server } from '../options'
|
||||
import { showBanner } from '../utils/banner'
|
||||
import { Listener } from 'nuxt/server'
|
||||
import { Nuxt } from 'nuxt/core'
|
||||
import type NuxtCommand from '../command'
|
||||
import { common, server } from '../options'
|
||||
import { showBanner } from '../utils/banner'
|
||||
|
||||
export default {
|
||||
name: 'serve',
|
||||
@ -20,7 +21,7 @@ export default {
|
||||
help: common.help,
|
||||
...server
|
||||
},
|
||||
async run (cmd) {
|
||||
async run (cmd: NuxtCommand) {
|
||||
let options = await cmd.getNuxtConfig({ dev: false })
|
||||
// add default options
|
||||
options = getNuxtConfig(options)
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { TARGETS } from 'nuxt/utils'
|
||||
|
||||
import type NuxtCommand from '../command'
|
||||
import { common, server } from '../options'
|
||||
import { showBanner } from '../utils/banner'
|
||||
|
||||
@ -10,7 +12,7 @@ export default {
|
||||
...common,
|
||||
...server
|
||||
},
|
||||
async run (cmd) {
|
||||
async run (cmd: NuxtCommand) {
|
||||
const config = await cmd.getNuxtConfig({ dev: false, _start: true })
|
||||
if (config.target === TARGETS.static) {
|
||||
throw new Error('You cannot use `nuxt start` with ' + TARGETS.static + ' target, please use `nuxt export` and `nuxt serve`')
|
||||
|
@ -1,6 +1,8 @@
|
||||
import util from 'util'
|
||||
import consola from 'consola'
|
||||
import get from 'lodash/get'
|
||||
|
||||
import type NuxtCommand from '../command'
|
||||
import { common } from '../options'
|
||||
|
||||
export default {
|
||||
@ -32,7 +34,7 @@ export default {
|
||||
description: 'Inspect development mode webpack config'
|
||||
}
|
||||
},
|
||||
async run (cmd) {
|
||||
async run (cmd: NuxtCommand) {
|
||||
const { name } = cmd.argv
|
||||
const queries = [...cmd.argv._]
|
||||
|
||||
|
@ -4,7 +4,7 @@ import { startSpaces, optionSpaces } from './utils/constants'
|
||||
import getCommand from './commands'
|
||||
|
||||
export default async function listCommands () {
|
||||
const commandsOrder = ['dev', 'build', 'generate', 'start', 'help']
|
||||
const commandsOrder = ['dev', 'build', 'generate', 'start', 'help'] as const
|
||||
|
||||
// Load all commands
|
||||
const _commands = await Promise.all(
|
||||
|
@ -5,7 +5,7 @@ import NuxtCommand from './command'
|
||||
import setup from './setup'
|
||||
import getCommand from './commands'
|
||||
|
||||
function packageExists (name) {
|
||||
function packageExists (name: string) {
|
||||
try {
|
||||
require.resolve(name)
|
||||
return true
|
||||
@ -14,7 +14,7 @@ function packageExists (name) {
|
||||
}
|
||||
}
|
||||
|
||||
export default async function run(_argv, hooks = {}) {
|
||||
export default async function run (_argv: NodeJS.Process['argv'], hooks = {}) {
|
||||
// Check for not installing both nuxt and nuxt-edge
|
||||
const dupPkg = '@nuxt/' + (pkgName === '@nuxt/cli-edge' ? 'cli' : 'cli-edge')
|
||||
if (packageExists(dupPkg)) {
|
||||
@ -25,7 +25,7 @@ export default async function run(_argv, hooks = {}) {
|
||||
const argv = _argv ? Array.from(_argv) : process.argv.slice(2)
|
||||
|
||||
// Check for internal command
|
||||
let cmd = await getCommand(argv[0])
|
||||
let cmd = await getCommand(argv[0] as any)
|
||||
|
||||
// Matching `nuxt` or `nuxt [dir]` or `nuxt -*` for `nuxt dev` shortcut
|
||||
if (!cmd && (!argv[0] || argv[0][0] === '-' || (argv[0] !== 'static' && fs.existsSync(argv[0])))) {
|
||||
|
@ -4,7 +4,7 @@ import { fatalBox } from './utils/formatting'
|
||||
|
||||
let _setup = false
|
||||
|
||||
export default function setup ({ dev }) {
|
||||
export default function setup ({ dev }: { dev: boolean }) {
|
||||
// Apply default NODE_ENV if not provided
|
||||
if (!process.env.NODE_ENV) {
|
||||
process.env.NODE_ENV = dev ? 'development' : 'production'
|
||||
|
@ -1,10 +1,12 @@
|
||||
import consola from 'consola'
|
||||
import env from 'std-env'
|
||||
import chalk from 'chalk'
|
||||
|
||||
import { Nuxt } from 'nuxt/core'
|
||||
import { successBox } from './formatting'
|
||||
import { getFormattedMemoryUsage } from './memory'
|
||||
|
||||
export function showBanner (nuxt, showMemoryUsage = true) {
|
||||
export function showBanner (nuxt: Nuxt, showMemoryUsage = true) {
|
||||
if (env.test) {
|
||||
return
|
||||
}
|
||||
@ -23,7 +25,7 @@ export function showBanner (nuxt, showMemoryUsage = true) {
|
||||
const { bannerColor, badgeMessages } = nuxt.options.cli
|
||||
titleLines.push(`${chalk[bannerColor].bold('Nuxt.js')} @ ${nuxt.constructor.version || 'exotic'}\n`)
|
||||
|
||||
const label = name => chalk.bold.cyan(`▸ ${name}:`)
|
||||
const label = (name: string) => chalk.bold.cyan(`▸ ${name}:`)
|
||||
|
||||
// Environment
|
||||
const isDev = nuxt.options.dev
|
||||
|
@ -1,9 +1,11 @@
|
||||
import path from 'path'
|
||||
import defaultsDeep from 'lodash/defaultsDeep'
|
||||
import type { ParsedArgs } from 'minimist'
|
||||
|
||||
import { loadNuxtConfig as _loadNuxtConfig, getDefaultNuxtConfig } from 'nuxt/config'
|
||||
import { MODES } from 'nuxt/utils'
|
||||
|
||||
export async function loadNuxtConfig (argv, configContext) {
|
||||
export async function loadNuxtConfig (argv: ParsedArgs, configContext) {
|
||||
const rootDir = path.resolve(argv._[0] || '.')
|
||||
const configFile = argv['config-file']
|
||||
|
||||
|
@ -3,11 +3,11 @@ import chalk from 'chalk'
|
||||
import boxen from 'boxen'
|
||||
import { maxCharsPerLine } from './constants'
|
||||
|
||||
export function indent (count, chr = ' ') {
|
||||
export function indent (count: number, chr = ' ') {
|
||||
return chr.repeat(count)
|
||||
}
|
||||
|
||||
export function indentLines (string, spaces, firstLineSpaces) {
|
||||
export function indentLines (string: string | string[], spaces: number, firstLineSpaces?: number) {
|
||||
const lines = Array.isArray(string) ? string : string.split('\n')
|
||||
let s = ''
|
||||
if (lines.length) {
|
||||
@ -21,11 +21,11 @@ export function indentLines (string, spaces, firstLineSpaces) {
|
||||
return s
|
||||
}
|
||||
|
||||
export function foldLines (string, spaces, firstLineSpaces, charsPerLine = maxCharsPerLine()) {
|
||||
export function foldLines (string: string, spaces: number, firstLineSpaces?: number, charsPerLine = maxCharsPerLine()) {
|
||||
return indentLines(wrapAnsi(string, charsPerLine), spaces, firstLineSpaces)
|
||||
}
|
||||
|
||||
export function colorize (text) {
|
||||
export function colorize (text: string) {
|
||||
return text
|
||||
.replace(/\[[^ ]+]/g, m => chalk.grey(m))
|
||||
.replace(/<[^ ]+>/g, m => chalk.green(m))
|
||||
@ -33,7 +33,7 @@ export function colorize (text) {
|
||||
.replace(/`([^`]+)`/g, (_, m) => chalk.bold.cyan(m))
|
||||
}
|
||||
|
||||
export function box (message, title, options) {
|
||||
export function box (message: string, title: string, options?: boxen.Options) {
|
||||
return boxen([
|
||||
title || chalk.white('Nuxt Message'),
|
||||
'',
|
||||
@ -46,24 +46,24 @@ export function box (message, title, options) {
|
||||
}, options)) + '\n'
|
||||
}
|
||||
|
||||
export function successBox (message, title) {
|
||||
export function successBox (message: string, title?: string) {
|
||||
return box(message, title || chalk.green('✔ Nuxt Success'), {
|
||||
borderColor: 'green'
|
||||
})
|
||||
}
|
||||
|
||||
export function warningBox (message, title) {
|
||||
export function warningBox (message: string, title?: string) {
|
||||
return box(message, title || chalk.yellow('⚠ Nuxt Warning'), {
|
||||
borderColor: 'yellow'
|
||||
})
|
||||
}
|
||||
|
||||
export function errorBox (message, title) {
|
||||
export function errorBox (message: string, title?: string) {
|
||||
return box(message, title || chalk.red('✖ Nuxt Error'), {
|
||||
borderColor: 'red'
|
||||
})
|
||||
}
|
||||
|
||||
export function fatalBox (message, title) {
|
||||
export function fatalBox (message: string, title?: string) {
|
||||
return errorBox(message, title || chalk.red('✖ Nuxt Fatal Error'))
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ export const eventsMapping = {
|
||||
unlink: { icon: '-', color: 'red', action: 'Removed' }
|
||||
}
|
||||
|
||||
export function formatPath (filePath) {
|
||||
export function formatPath (filePath: string) {
|
||||
if (!filePath) {
|
||||
return
|
||||
}
|
||||
@ -27,7 +27,7 @@ export function formatPath (filePath) {
|
||||
* @param {*} defaultValue
|
||||
* @returns formatted argument
|
||||
*/
|
||||
export function normalizeArg (arg, defaultValue) {
|
||||
export function normalizeArg (arg: boolean | 'true' | '' | 'false', defaultValue?: boolean) {
|
||||
switch (arg) {
|
||||
case 'true': arg = true; break
|
||||
case '': arg = true; break
|
||||
@ -37,7 +37,7 @@ export function normalizeArg (arg, defaultValue) {
|
||||
return arg
|
||||
}
|
||||
|
||||
export function forceExit (cmdName, timeout) {
|
||||
export function forceExit (cmdName: string, timeout: number | false) {
|
||||
if (timeout !== false) {
|
||||
const exitTimeout = setTimeout(() => {
|
||||
const msg = `The command 'nuxt ${cmdName}' finished but did not exit after ${timeout}s
|
||||
@ -59,6 +59,6 @@ ${chalk.bold('DeprecationWarning: Starting with Nuxt version 3 this will be a fa
|
||||
|
||||
// An immediate export throws an error when mocking with jest
|
||||
// TypeError: Cannot set property createLock of #<Object> which has only a getter
|
||||
export function createLock (...args) {
|
||||
export function createLock (...args: Parameters<typeof lock>) {
|
||||
return lock(...args)
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { loadNuxt } from 'nuxt/core'
|
||||
import { getBuilder } from 'nuxt/builder'
|
||||
|
||||
export async function getWebpackConfig(name = 'client', loadOptions = {}) {
|
||||
export async function getWebpackConfig (name = 'client', loadOptions = {}) {
|
||||
const nuxt = await loadNuxt(loadOptions)
|
||||
const builder = await getBuilder(nuxt)
|
||||
const { bundleBuilder } = builder
|
||||
|
@ -8,18 +8,27 @@ import jiti from 'jiti'
|
||||
import _createRequire from 'create-require'
|
||||
import destr from 'destr'
|
||||
import * as rc from 'rc9'
|
||||
|
||||
import { LoadOptions } from 'nuxt/core/load'
|
||||
import { defaultNuxtConfigFile } from './config'
|
||||
|
||||
// @ts-ignore
|
||||
const isJest = typeof jest !== 'undefined'
|
||||
|
||||
export interface EnvConfig {
|
||||
dotenv?: string
|
||||
env?: NodeJS.ProcessEnv & { _applied?: boolean }
|
||||
expand?: boolean
|
||||
}
|
||||
|
||||
export async function loadNuxtConfig ({
|
||||
rootDir = '.',
|
||||
envConfig = {},
|
||||
configFile = defaultNuxtConfigFile,
|
||||
configContext = {},
|
||||
configOverrides = {},
|
||||
createRequire = module => isJest ? _createRequire(module.filename) : jiti(module.filename)
|
||||
} = {}) {
|
||||
createRequire = (module: NodeJS.Module) => isJest ? _createRequire(module.filename) : jiti(module.filename)
|
||||
}: LoadOptions = {}) {
|
||||
rootDir = path.resolve(rootDir)
|
||||
|
||||
let options = {}
|
||||
@ -120,7 +129,7 @@ export async function loadNuxtConfig ({
|
||||
return options
|
||||
}
|
||||
|
||||
function loadEnv (envConfig, rootDir = process.cwd()) {
|
||||
function loadEnv (envConfig: EnvConfig, rootDir = process.cwd()) {
|
||||
const env = Object.create(null)
|
||||
|
||||
// Read dotenv
|
||||
@ -147,13 +156,13 @@ function loadEnv (envConfig, rootDir = process.cwd()) {
|
||||
}
|
||||
|
||||
// Based on https://github.com/motdotla/dotenv-expand
|
||||
function expand (target, source = {}, parse = v => v) {
|
||||
function getValue (key) {
|
||||
function expand (target: Record<string, string>, source: Record<string, string> = {}, parse = (v: string) => v) {
|
||||
function getValue (key: string) {
|
||||
// Source value 'wins' over target value
|
||||
return source[key] !== undefined ? source[key] : target[key]
|
||||
}
|
||||
|
||||
function interpolate (value) {
|
||||
function interpolate (value: string): string {
|
||||
if (typeof value !== 'string') {
|
||||
return value
|
||||
}
|
||||
@ -162,7 +171,8 @@ function expand (target, source = {}, parse = v => v) {
|
||||
const parts = /(.?)\${?([a-zA-Z0-9_:]+)?}?/g.exec(match)
|
||||
const prefix = parts[1]
|
||||
|
||||
let value, replacePart
|
||||
let value: string
|
||||
let replacePart: string
|
||||
|
||||
if (prefix === '\\') {
|
||||
replacePart = parts[0]
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { EnvConfig } from 'nuxt/config/load'
|
||||
import { loadNuxtConfig } from '../config'
|
||||
import Nuxt from './nuxt'
|
||||
|
||||
@ -8,7 +9,19 @@ const OVERRIDES = {
|
||||
start: { dev: false, _start: true }
|
||||
}
|
||||
|
||||
export async function loadNuxt (loadOptions) {
|
||||
export interface LoadOptions {
|
||||
for?: keyof typeof OVERRIDES
|
||||
ready?: boolean
|
||||
|
||||
rootDir?: string
|
||||
envConfig?: EnvConfig
|
||||
configFile?: string
|
||||
configContext?: {}
|
||||
configOverrides?: {},
|
||||
createRequire?: (module: NodeJS.Module) => NodeJS.Require
|
||||
}
|
||||
|
||||
export async function loadNuxt (loadOptions: LoadOptions | LoadOptions['for']) {
|
||||
// Normalize loadOptions
|
||||
if (typeof loadOptions === 'string') {
|
||||
loadOptions = { for: loadOptions }
|
||||
|
@ -5,8 +5,24 @@ import consola from 'consola'
|
||||
|
||||
import { chainFn, sequence } from 'nuxt/utils'
|
||||
|
||||
import Nuxt from './nuxt'
|
||||
|
||||
interface Module {
|
||||
src: string
|
||||
options: Record<string, any>
|
||||
handler: () => any
|
||||
}
|
||||
|
||||
interface Template {
|
||||
|
||||
}
|
||||
|
||||
export default class ModuleContainer {
|
||||
constructor (nuxt) {
|
||||
nuxt: Nuxt
|
||||
options: Nuxt['options']
|
||||
requiredModules: Record<string, Module>
|
||||
|
||||
constructor (nuxt: Nuxt) {
|
||||
this.nuxt = nuxt
|
||||
this.options = nuxt.options
|
||||
this.requiredModules = {}
|
||||
@ -83,7 +99,7 @@ export default class ModuleContainer {
|
||||
})
|
||||
}
|
||||
|
||||
addLayout (template, name) {
|
||||
addLayout (template, name: string) {
|
||||
const { dst, src } = this.addTemplate(template)
|
||||
const layoutName = name || path.parse(src).name
|
||||
const layout = this.options.layouts[layoutName]
|
||||
@ -101,7 +117,7 @@ export default class ModuleContainer {
|
||||
}
|
||||
}
|
||||
|
||||
addErrorLayout (dst) {
|
||||
addErrorLayout (dst: string) {
|
||||
const relativeBuildDir = path.relative(this.options.rootDir, this.options.buildDir)
|
||||
this.options.ErrorPage = `~/${relativeBuildDir}/${dst}`
|
||||
}
|
||||
@ -121,13 +137,13 @@ export default class ModuleContainer {
|
||||
)
|
||||
}
|
||||
|
||||
requireModule (moduleOpts) {
|
||||
requireModule (moduleOpts: Module) {
|
||||
return this.addModule(moduleOpts)
|
||||
}
|
||||
|
||||
async addModule (moduleOpts) {
|
||||
let src
|
||||
let options
|
||||
let options: Record<string, any>
|
||||
let handler
|
||||
|
||||
// Type 1: String or Function
|
||||
@ -142,7 +158,7 @@ export default class ModuleContainer {
|
||||
}
|
||||
|
||||
// Define handler if src is a function
|
||||
if (typeof src === 'function') {
|
||||
if (src instanceof Function) {
|
||||
handler = src
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
import isPlainObject from 'lodash/isPlainObject'
|
||||
import consola from 'consola'
|
||||
import Hookable from 'hable'
|
||||
import Hookable from 'hookable'
|
||||
|
||||
import { defineAlias } from 'nuxt/utils'
|
||||
import { getNuxtConfig } from 'nuxt/config'
|
||||
@ -13,6 +13,17 @@ import ModuleContainer from './module'
|
||||
import Resolver from './resolver'
|
||||
|
||||
export default class Nuxt extends Hookable {
|
||||
_ready?: Promise<this>
|
||||
_initCalled?: boolean
|
||||
|
||||
options: any
|
||||
resolver: Resolver
|
||||
moduleContainer: ModuleContainer
|
||||
server?: Server
|
||||
renderer?: Server
|
||||
render?: Server['app']
|
||||
showReady?: () => void
|
||||
|
||||
constructor (options = {}) {
|
||||
super(consola)
|
||||
|
||||
@ -103,13 +114,14 @@ export default class Nuxt extends Hookable {
|
||||
defineAlias(this, this.server, ['renderRoute', 'renderAndGetWindow', 'listen'])
|
||||
}
|
||||
|
||||
async close (callback) {
|
||||
async close (callback?: () => any | Promise<any>) {
|
||||
await this.callHook('close', this)
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
await callback()
|
||||
}
|
||||
|
||||
this.clearHooks()
|
||||
// Deleting as no longer exists on `hookable`
|
||||
// this.clearHooks()
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { resolve, join } from 'path'
|
||||
import fs from 'fs-extra'
|
||||
import consola from 'consola'
|
||||
|
||||
import { Nuxt } from 'nuxt/core'
|
||||
import {
|
||||
startsWithRootAlias,
|
||||
startsWithSrcAlias,
|
||||
@ -9,8 +9,25 @@ import {
|
||||
clearRequireCache
|
||||
} from 'nuxt/utils'
|
||||
|
||||
interface ResolvePathOptions {
|
||||
isAlias?: boolean
|
||||
isModule?: boolean
|
||||
isStyle?: boolean
|
||||
}
|
||||
|
||||
interface RequireModuleOptions {
|
||||
useESM?: boolean
|
||||
isAlias?: boolean
|
||||
interopDefault?: any
|
||||
}
|
||||
|
||||
export default class Resolver {
|
||||
constructor (nuxt) {
|
||||
_require: NodeJS.Require
|
||||
_resolve: RequireResolve
|
||||
nuxt: Nuxt
|
||||
options: Nuxt['options']
|
||||
|
||||
constructor (nuxt: Nuxt) {
|
||||
this.nuxt = nuxt
|
||||
this.options = this.nuxt.options
|
||||
|
||||
@ -26,7 +43,7 @@ export default class Resolver {
|
||||
this._resolve = require.resolve
|
||||
}
|
||||
|
||||
resolveModule (path) {
|
||||
resolveModule (path: string) {
|
||||
try {
|
||||
return this._resolve(path, {
|
||||
paths: this.options.modulesDir
|
||||
@ -42,7 +59,7 @@ export default class Resolver {
|
||||
}
|
||||
}
|
||||
|
||||
resolveAlias (path) {
|
||||
resolveAlias (path: string) {
|
||||
if (startsWithRootAlias(path)) {
|
||||
return join(this.options.rootDir, path.substr(2))
|
||||
}
|
||||
@ -54,21 +71,13 @@ export default class Resolver {
|
||||
return resolve(this.options.srcDir, path)
|
||||
}
|
||||
|
||||
resolvePath (path, { alias, isAlias = alias, module, isModule = module, isStyle } = {}) {
|
||||
// TODO: Remove in Nuxt 3
|
||||
if (alias) {
|
||||
consola.warn('Using alias is deprecated and will be removed in Nuxt 3. Use `isAlias` instead.')
|
||||
}
|
||||
if (module) {
|
||||
consola.warn('Using module is deprecated and will be removed in Nuxt 3. Use `isModule` instead.')
|
||||
}
|
||||
|
||||
resolvePath (path: string, { isAlias, isModule, isStyle }: ResolvePathOptions = {}) {
|
||||
// Fast return in case of path exists
|
||||
if (fs.existsSync(path)) {
|
||||
return path
|
||||
}
|
||||
|
||||
let resolvedPath
|
||||
let resolvedPath: string
|
||||
|
||||
// Try to resolve it as a regular module
|
||||
if (isModule !== false) {
|
||||
@ -85,7 +94,7 @@ export default class Resolver {
|
||||
resolvedPath = path
|
||||
}
|
||||
|
||||
let isDirectory
|
||||
let isDirectory: boolean
|
||||
|
||||
// Check if resolvedPath exits and is not a directory
|
||||
if (fs.existsSync(resolvedPath)) {
|
||||
@ -119,22 +128,11 @@ export default class Resolver {
|
||||
throw new Error(`Cannot resolve "${path}" from "${resolvedPath}"`)
|
||||
}
|
||||
|
||||
requireModule (path, { esm, useESM = esm, alias, isAlias = alias, intropDefault, interopDefault = intropDefault } = {}) {
|
||||
requireModule <T>(path: string, { useESM, isAlias, interopDefault }: RequireModuleOptions = {}): T {
|
||||
let resolvedPath = path
|
||||
let requiredModule
|
||||
let requiredModule: any
|
||||
|
||||
// TODO: Remove in Nuxt 3
|
||||
if (intropDefault) {
|
||||
consola.warn('Using intropDefault is deprecated and will be removed in Nuxt 3. Use `interopDefault` instead.')
|
||||
}
|
||||
if (alias) {
|
||||
consola.warn('Using alias is deprecated and will be removed in Nuxt 3. Use `isAlias` instead.')
|
||||
}
|
||||
if (esm) {
|
||||
consola.warn('Using esm is deprecated and will be removed in Nuxt 3. Use `useESM` instead.')
|
||||
}
|
||||
|
||||
let lastError
|
||||
let lastError: any
|
||||
|
||||
// Try to resolve path
|
||||
try {
|
||||
|
@ -6,10 +6,32 @@ import defu from 'defu'
|
||||
import htmlMinifier from 'html-minifier'
|
||||
import { parse } from 'node-html-parser'
|
||||
|
||||
import type { Builder } from 'nuxt/builder'
|
||||
import type { Nuxt } from 'nuxt/core'
|
||||
import { isFullStatic, flatRoutes, isString, isUrl, promisifyRoute, waitFor, TARGETS } from 'nuxt/utils'
|
||||
|
||||
export default class Generator {
|
||||
constructor (nuxt, builder) {
|
||||
_payload: null
|
||||
setPayload: (payload: any) => void
|
||||
|
||||
builder?: Builder
|
||||
isFullStatic: boolean
|
||||
nuxt: Nuxt
|
||||
options: Nuxt['options']
|
||||
staticRoutes: string
|
||||
srcBuiltPath: string
|
||||
distPath: string
|
||||
distNuxtPath: string
|
||||
|
||||
staticAssetsDir?: string
|
||||
staticAssetsBase?: string
|
||||
|
||||
payloadDir?: string
|
||||
|
||||
routes: Array<{ route: string } & Record<string, any>>
|
||||
generatedRoutes: Set<string>
|
||||
|
||||
constructor (nuxt: Nuxt, builder?: Builder) {
|
||||
this.nuxt = nuxt
|
||||
this.options = nuxt.options
|
||||
this.builder = builder
|
||||
|
@ -1,6 +1,8 @@
|
||||
import type { Nuxt } from 'nuxt/core'
|
||||
|
||||
import Generator from './generator'
|
||||
export { default as Generator } from './generator'
|
||||
|
||||
export function getGenerator (nuxt) {
|
||||
export function getGenerator (nuxt: Nuxt) {
|
||||
return new Generator(nuxt)
|
||||
}
|
||||
|
@ -1,5 +1,12 @@
|
||||
import { Server } from 'nuxt/server'
|
||||
|
||||
export default class ServerContext {
|
||||
constructor (server) {
|
||||
nuxt: Server['nuxt']
|
||||
globals: Server['globals']
|
||||
options: Server['options']
|
||||
resources: Server['resources']
|
||||
|
||||
constructor (server: Server) {
|
||||
this.nuxt = server.nuxt
|
||||
this.globals = server.globals
|
||||
this.options = server.options
|
||||
|
@ -1,5 +1,11 @@
|
||||
import consola from 'consola'
|
||||
import { timeout } from 'nuxt/utils'
|
||||
import { DeterminedGlobals, timeout } from 'nuxt/utils'
|
||||
|
||||
interface Options {
|
||||
globals: DeterminedGlobals
|
||||
loadedCallback: string
|
||||
loadingTimeout?: number
|
||||
}
|
||||
|
||||
export default async function renderAndGetWindow (
|
||||
url = 'http://localhost:3000',
|
||||
@ -8,7 +14,7 @@ export default async function renderAndGetWindow (
|
||||
loadedCallback,
|
||||
loadingTimeout = 2000,
|
||||
globals
|
||||
} = {}
|
||||
}: Options
|
||||
) {
|
||||
const jsdom = await import('jsdom')
|
||||
.then(m => m.default || m)
|
||||
@ -27,13 +33,13 @@ export default async function renderAndGetWindow (
|
||||
resources: 'usable',
|
||||
runScripts: 'dangerously',
|
||||
virtualConsole: true,
|
||||
beforeParse (window) {
|
||||
beforeParse (window: Window) {
|
||||
// Mock window.scrollTo
|
||||
window.scrollTo = () => {}
|
||||
}
|
||||
}, jsdomOpts)
|
||||
|
||||
const jsdomErrHandler = (err) => {
|
||||
const jsdomErrHandler = (err: any) => {
|
||||
throw err
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import http from 'http'
|
||||
import https from 'https'
|
||||
import type { ListenOptions } from 'net'
|
||||
import enableDestroy from 'server-destroy'
|
||||
import ip from 'ip'
|
||||
import consola from 'consola'
|
||||
@ -8,6 +9,19 @@ import pify from 'pify'
|
||||
let RANDOM_PORT = '0'
|
||||
|
||||
export default class Listener {
|
||||
port: number | string
|
||||
host: string
|
||||
socket: string
|
||||
https: boolean
|
||||
app: any
|
||||
dev: boolean
|
||||
baseURL: string
|
||||
|
||||
listening: boolean
|
||||
_server: null | http.Server
|
||||
server: null | http.Server
|
||||
address: null
|
||||
url: null | string
|
||||
constructor ({ port, host, socket, https, app, dev, baseURL }) {
|
||||
// Options
|
||||
this.port = port
|
||||
@ -43,6 +57,9 @@ export default class Listener {
|
||||
|
||||
computeURL () {
|
||||
const address = this.server.address()
|
||||
if (typeof address === 'string') {
|
||||
return address
|
||||
}
|
||||
if (!this.socket) {
|
||||
switch (address.address) {
|
||||
case '127.0.0.1': this.host = 'localhost'; break
|
||||
@ -68,7 +85,7 @@ export default class Listener {
|
||||
|
||||
// Call server.listen
|
||||
// Prepare listenArgs
|
||||
const listenArgs = this.socket ? { path: this.socket } : { host: this.host, port: this.port }
|
||||
const listenArgs: ListenOptions = this.socket ? { path: this.socket } : { host: this.host, port: Number(this.port) }
|
||||
listenArgs.exclusive = false
|
||||
|
||||
// Call server.listen
|
||||
|
@ -1,8 +1,11 @@
|
||||
import type { ServerResponse } from 'http'
|
||||
import type { IncomingMessage } from 'connect'
|
||||
|
||||
import consola from 'consola'
|
||||
import onHeaders from 'on-headers'
|
||||
import { Timer } from 'nuxt/utils'
|
||||
|
||||
export default options => (req, res, next) => {
|
||||
export default options => (_req: IncomingMessage, res: ServerResponse & { timing?: ServerTiming }, next: (err?: any) => void) => {
|
||||
if (res.timing) {
|
||||
consola.warn('server-timing is already registered.')
|
||||
}
|
||||
@ -31,13 +34,15 @@ export default options => (req, res, next) => {
|
||||
}
|
||||
|
||||
class ServerTiming extends Timer {
|
||||
constructor (...args) {
|
||||
super(...args)
|
||||
headers: string[]
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
this.headers = []
|
||||
}
|
||||
|
||||
end (...args) {
|
||||
const time = super.end(...args)
|
||||
end (name?: string) {
|
||||
const time = super.end(name)
|
||||
if (time) {
|
||||
this.headers.push(this.formatHeader(time))
|
||||
}
|
||||
@ -49,7 +54,7 @@ class ServerTiming extends Timer {
|
||||
this.headers.length = 0
|
||||
}
|
||||
|
||||
formatHeader (time) {
|
||||
formatHeader (time: ReturnType<Timer['end']>) {
|
||||
const desc = time.description ? `;desc="${time.description}"` : ''
|
||||
return `${time.name};dur=${time.duration}${desc}`
|
||||
}
|
||||
|
@ -1,10 +1,14 @@
|
||||
import path from 'path'
|
||||
import { ServerResponse } from 'http'
|
||||
import consola from 'consola'
|
||||
import launchMiddleware from 'launch-editor-middleware'
|
||||
import serveStatic from 'serve-static'
|
||||
import servePlaceholder from 'serve-placeholder'
|
||||
import connect from 'connect'
|
||||
import { determineGlobals, isUrl } from 'nuxt/utils'
|
||||
import connect, { IncomingMessage } from 'connect'
|
||||
import type { TemplateExecutor } from 'lodash'
|
||||
|
||||
import { Nuxt } from 'nuxt/core'
|
||||
import { DeterminedGlobals, determineGlobals, isUrl } from 'nuxt/utils'
|
||||
import { VueRenderer } from 'nuxt/vue-renderer'
|
||||
|
||||
import ServerContext from './context'
|
||||
@ -14,8 +18,34 @@ import errorMiddleware from './middleware/error'
|
||||
import Listener from './listener'
|
||||
import createTimingMiddleware from './middleware/timing'
|
||||
|
||||
interface Manifest {
|
||||
assetsMapping: Record<string, string[]>
|
||||
publicPath: string
|
||||
}
|
||||
|
||||
export default class Server {
|
||||
constructor (nuxt) {
|
||||
__closed?: boolean
|
||||
_readyCalled?: boolean
|
||||
|
||||
app: connect.Server
|
||||
devMiddleware: (req: IncomingMessage, res: ServerResponse, next: (err?: any) => void) => any
|
||||
listeners: Listener[]
|
||||
nuxt: Nuxt
|
||||
globals: DeterminedGlobals
|
||||
options: Nuxt['options']
|
||||
publicPath: boolean
|
||||
renderer: VueRenderer
|
||||
resources: {
|
||||
clientManifest?: Manifest
|
||||
modernManifest?: Manifest
|
||||
serverManifest?: Manifest
|
||||
ssrTemplate?: TemplateExecutor
|
||||
spaTemplate?: TemplateExecutor
|
||||
errorTemplate?: TemplateExecutor
|
||||
}
|
||||
serverContext: ServerContext
|
||||
|
||||
constructor (nuxt: Nuxt) {
|
||||
this.nuxt = nuxt
|
||||
this.options = nuxt.options
|
||||
|
||||
@ -76,7 +106,7 @@ export default class Server {
|
||||
const { compressor } = this.options.render
|
||||
if (typeof compressor === 'object') {
|
||||
// If only setting for `compression` are provided, require the module and insert
|
||||
const compression = this.nuxt.resolver.requireModule('compression')
|
||||
const compression = this.nuxt.resolver.requireModule<typeof import('compression')>('compression')
|
||||
this.useMiddleware(compression(compressor))
|
||||
} else if (compressor) {
|
||||
// Else, require own compression middleware if compressor is actually truthy
|
||||
@ -317,12 +347,12 @@ export default class Server {
|
||||
return this.app.stack.map(({ handle }) => handle._middleware && handle._middleware.entry).filter(Boolean)
|
||||
}
|
||||
|
||||
renderRoute () {
|
||||
return this.renderer.renderRoute.apply(this.renderer, arguments)
|
||||
renderRoute (...args: Parameters<VueRenderer['renderRoute']>) {
|
||||
return this.renderer.renderRoute.apply(this.renderer, ...args.slice())
|
||||
}
|
||||
|
||||
loadResources () {
|
||||
return this.renderer.loadResources.apply(this.renderer, arguments)
|
||||
loadResources (...args: Parameters<VueRenderer['loadResources']>) {
|
||||
return this.renderer.loadResources.apply(this.renderer, ...args)
|
||||
}
|
||||
|
||||
renderAndGetWindow (url, opts = {}, {
|
||||
@ -337,13 +367,13 @@ export default class Server {
|
||||
})
|
||||
}
|
||||
|
||||
async listen (port, host, socket) {
|
||||
async listen (port?: string | number, host?: string, socket?: string) {
|
||||
// Ensure nuxt is ready
|
||||
await this.nuxt.ready()
|
||||
|
||||
// Create a new listener
|
||||
const listener = new Listener({
|
||||
port: isNaN(parseInt(port)) ? this.options.server.port : port,
|
||||
port: typeof port !== 'number' && isNaN(parseInt(port)) ? this.options.server.port : port,
|
||||
host: host || this.options.server.host,
|
||||
socket: socket || this.options.server.socket,
|
||||
https: this.options.server.https,
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { join } from 'path'
|
||||
|
||||
export function isExternalDependency (id) {
|
||||
export function isExternalDependency (id: string) {
|
||||
return /[/\\]node_modules[/\\]/.test(id)
|
||||
}
|
||||
|
||||
export function clearRequireCache (id) {
|
||||
export function clearRequireCache (id: string) {
|
||||
if (isExternalDependency(id)) {
|
||||
return
|
||||
}
|
||||
@ -27,7 +27,7 @@ export function clearRequireCache (id) {
|
||||
delete require.cache[id]
|
||||
}
|
||||
|
||||
export function scanRequireTree (id, files = new Set()) {
|
||||
export function scanRequireTree (id: string, files = new Set<string>()) {
|
||||
if (isExternalDependency(id) || files.has(id)) {
|
||||
return files
|
||||
}
|
||||
@ -48,20 +48,20 @@ export function scanRequireTree (id, files = new Set()) {
|
||||
return files
|
||||
}
|
||||
|
||||
export function getRequireCacheItem (id) {
|
||||
export function getRequireCacheItem (id: string) {
|
||||
try {
|
||||
return require.cache[id]
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
export function tryRequire (id) {
|
||||
export function tryRequire (id: string) {
|
||||
try {
|
||||
return require(id)
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getPKG (id) {
|
||||
export function getPKG (id: string) {
|
||||
return tryRequire(join(id, 'package.json'))
|
||||
}
|
||||
|
@ -1,9 +1,13 @@
|
||||
export const TARGETS = {
|
||||
server: 'server',
|
||||
static: 'static'
|
||||
}
|
||||
} as const
|
||||
|
||||
export type Target = keyof typeof TARGETS
|
||||
|
||||
export const MODES = {
|
||||
universal: 'universal',
|
||||
spa: 'spa'
|
||||
}
|
||||
} as const
|
||||
|
||||
export type Mode = keyof typeof MODES
|
@ -1,19 +1,35 @@
|
||||
import type { ServerResponse } from 'http'
|
||||
import type { IncomingMessage } from 'connect'
|
||||
|
||||
import { TARGETS } from './constants'
|
||||
|
||||
export const getContext = function getContext (req, res) {
|
||||
export const getContext = function getContext (req: IncomingMessage, res: ServerResponse) {
|
||||
return { req, res }
|
||||
}
|
||||
|
||||
export const determineGlobals = function determineGlobals (globalName, globals) {
|
||||
const _globals = {}
|
||||
type NuxtGlobal = string | ((globalName: string) => string)
|
||||
|
||||
type Globals = 'id' | 'nuxt' | 'context' | 'pluginPrefix' | 'readyCallback' | 'loadedCallback'
|
||||
|
||||
type NuxtGlobals = {
|
||||
[key in Globals]: NuxtGlobal
|
||||
}
|
||||
|
||||
export type DeterminedGlobals = {
|
||||
[key in keyof NuxtGlobals]: string
|
||||
}
|
||||
|
||||
export const determineGlobals = function determineGlobals (globalName: string, globals: NuxtGlobals) {
|
||||
const _globals: Partial<DeterminedGlobals> = {}
|
||||
for (const global in globals) {
|
||||
if (typeof globals[global] === 'function') {
|
||||
_globals[global] = globals[global](globalName)
|
||||
const currentGlobal = globals[global]
|
||||
if (currentGlobal instanceof Function) {
|
||||
_globals[global] = currentGlobal(globalName)
|
||||
} else {
|
||||
_globals[global] = globals[global]
|
||||
_globals[global] = currentGlobal
|
||||
}
|
||||
}
|
||||
return _globals
|
||||
return _globals as DeterminedGlobals
|
||||
}
|
||||
|
||||
export const isFullStatic = function (options) {
|
||||
|
@ -1,20 +1,25 @@
|
||||
export const encodeHtml = function encodeHtml (str) {
|
||||
export const encodeHtml = function encodeHtml (str: string) {
|
||||
return str.replace(/</g, '<').replace(/>/g, '>')
|
||||
}
|
||||
|
||||
export const isString = obj => typeof obj === 'string' || obj instanceof String
|
||||
export const isString = (obj: unknown): obj is string =>
|
||||
typeof obj === 'string' || obj instanceof String
|
||||
|
||||
export const isNonEmptyString = obj => Boolean(obj && isString(obj))
|
||||
export const isNonEmptyString = (obj: unknown): obj is string =>
|
||||
Boolean(obj && isString(obj))
|
||||
|
||||
export const isPureObject = obj => !Array.isArray(obj) && typeof obj === 'object'
|
||||
export const isPureObject = (
|
||||
obj: unknown
|
||||
): obj is Exclude<object, Array<any>> =>
|
||||
!Array.isArray(obj) && typeof obj === 'object'
|
||||
|
||||
export const isUrl = function isUrl (url) {
|
||||
export const isUrl = function isUrl (url: string) {
|
||||
return ['http', '//'].some(str => url.startsWith(str))
|
||||
}
|
||||
|
||||
export const urlJoin = function urlJoin () {
|
||||
export const urlJoin = function urlJoin (...args: string[]) {
|
||||
return [].slice
|
||||
.call(arguments)
|
||||
.call(args)
|
||||
.join('/')
|
||||
.replace(/\/+/g, '/')
|
||||
.replace(':/', '://')
|
||||
@ -22,13 +27,11 @@ export const urlJoin = function urlJoin () {
|
||||
|
||||
/**
|
||||
* Wraps value in array if it is not already an array
|
||||
*
|
||||
* @param {any} value
|
||||
* @return {array}
|
||||
*/
|
||||
export const wrapArray = value => Array.isArray(value) ? value : [value]
|
||||
export const wrapArray = <T>(value: T | T[]): T[] =>
|
||||
Array.isArray(value) ? value : [value]
|
||||
|
||||
const WHITESPACE_REPLACEMENTS = [
|
||||
const WHITESPACE_REPLACEMENTS: [RegExp, string][] = [
|
||||
[/[ \t\f\r]+\n/g, '\n'], // strip empty indents
|
||||
[/{\n{2,}/g, '{\n'], // strip start padding from blocks
|
||||
[/\n{2,}([ \t\f\r]*})/g, '\n$1'], // strip end padding from blocks
|
||||
@ -36,7 +39,7 @@ const WHITESPACE_REPLACEMENTS = [
|
||||
[/\n{2,}$/g, '\n'] // strip blank lines EOF (0 allowed)
|
||||
]
|
||||
|
||||
export const stripWhitespace = function stripWhitespace (string) {
|
||||
export const stripWhitespace = function stripWhitespace (string: string) {
|
||||
WHITESPACE_REPLACEMENTS.forEach(([regex, newSubstr]) => {
|
||||
string = string.replace(regex, newSubstr)
|
||||
})
|
||||
|
@ -2,27 +2,42 @@ import path from 'path'
|
||||
import consola from 'consola'
|
||||
import hash from 'hash-sum'
|
||||
import fs from 'fs-extra'
|
||||
import properlock from 'proper-lockfile'
|
||||
import properlock, { LockOptions } from 'proper-lockfile'
|
||||
import onExit from 'signal-exit'
|
||||
|
||||
export const lockPaths = new Set()
|
||||
export const lockPaths = new Set<string>()
|
||||
|
||||
export const defaultLockOptions = {
|
||||
export const defaultLockOptions: Required<
|
||||
Pick<LockOptions, 'stale' | 'onCompromised'>
|
||||
> = {
|
||||
stale: 30000,
|
||||
onCompromised: err => consola.warn(err)
|
||||
}
|
||||
|
||||
export function getLockOptions (options) {
|
||||
export function getLockOptions (options: Partial<LockOptions>) {
|
||||
return Object.assign({}, defaultLockOptions, options)
|
||||
}
|
||||
|
||||
export function createLockPath ({ id = 'nuxt', dir, root }) {
|
||||
interface NuxtLockOptions {
|
||||
id?: string
|
||||
dir: string
|
||||
root: string
|
||||
options?: LockOptions
|
||||
}
|
||||
|
||||
export function createLockPath ({
|
||||
id = 'nuxt',
|
||||
dir,
|
||||
root
|
||||
}: Pick<NuxtLockOptions, 'id' | 'dir' | 'root'>) {
|
||||
const sum = hash(`${root}-${dir}`)
|
||||
|
||||
return path.resolve(root, 'node_modules/.cache/nuxt', `${id}-lock-${sum}`)
|
||||
}
|
||||
|
||||
export async function getLockPath (config) {
|
||||
export async function getLockPath (
|
||||
config: Pick<NuxtLockOptions, 'id' | 'dir' | 'root'>
|
||||
) {
|
||||
const lockPath = createLockPath(config)
|
||||
|
||||
// the lock is created for the lockPath as ${lockPath}.lock
|
||||
@ -32,8 +47,12 @@ export async function getLockPath (config) {
|
||||
return lockPath
|
||||
}
|
||||
|
||||
export async function lock ({ id, dir, root, options }) {
|
||||
const lockPath = await getLockPath({ id, dir, root })
|
||||
export async function lock ({ id, dir, root, options }: NuxtLockOptions) {
|
||||
const lockPath = await getLockPath({
|
||||
id,
|
||||
dir,
|
||||
root
|
||||
})
|
||||
|
||||
try {
|
||||
const locked = await properlock.check(lockPath)
|
||||
@ -45,7 +64,7 @@ export async function lock ({ id, dir, root, options }) {
|
||||
}
|
||||
|
||||
let lockWasCompromised = false
|
||||
let release
|
||||
let release: (() => Promise<void>) | undefined
|
||||
|
||||
try {
|
||||
options = getLockOptions(options)
|
||||
@ -94,7 +113,7 @@ export async function lock ({ id, dir, root, options }) {
|
||||
// as well, but in our case its much more likely the lock was
|
||||
// compromised due to mtime update timeouts
|
||||
const lockDir = `${lockPath}.lock`
|
||||
if (await fs.exists(lockDir)) {
|
||||
if (await fs.pathExists(lockDir)) {
|
||||
await fs.remove(lockDir)
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
import UAParser from 'ua-parser-js'
|
||||
import { UAParser } from 'ua-parser-js'
|
||||
|
||||
import type { SemVer } from 'semver'
|
||||
import type { IncomingMessage } from 'connect'
|
||||
|
||||
export const ModernBrowsers = {
|
||||
Edge: '16',
|
||||
@ -12,49 +15,65 @@ export const ModernBrowsers = {
|
||||
Yandex: '18',
|
||||
Vivaldi: '1.14',
|
||||
'Mobile Safari': '10.3'
|
||||
}
|
||||
} as const
|
||||
|
||||
let semver
|
||||
let __modernBrowsers
|
||||
type ModernBrowsers = { -readonly [key in keyof typeof ModernBrowsers]: SemVer }
|
||||
|
||||
let semver: typeof import('semver')
|
||||
let __modernBrowsers: ModernBrowsers
|
||||
|
||||
const getModernBrowsers = () => {
|
||||
if (__modernBrowsers) {
|
||||
return __modernBrowsers
|
||||
}
|
||||
|
||||
__modernBrowsers = Object.keys(ModernBrowsers)
|
||||
.reduce((allBrowsers, browser) => {
|
||||
allBrowsers[browser] = semver.coerce(ModernBrowsers[browser])
|
||||
__modernBrowsers = (Object.keys(ModernBrowsers) as Array<
|
||||
keyof typeof ModernBrowsers
|
||||
>).reduce(
|
||||
(allBrowsers, browser) => {
|
||||
const version = semver.coerce(ModernBrowsers[browser])
|
||||
if (version) { allBrowsers[browser] = version }
|
||||
return allBrowsers
|
||||
}, {})
|
||||
},
|
||||
{} as ModernBrowsers
|
||||
)
|
||||
return __modernBrowsers
|
||||
}
|
||||
|
||||
export const isModernBrowser = (ua) => {
|
||||
interface NuxtRequest extends IncomingMessage {
|
||||
socket: IncomingMessage['socket'] & {
|
||||
_modern?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const isModernBrowser = (ua: string) => {
|
||||
if (!ua) {
|
||||
return false
|
||||
}
|
||||
if (!semver) {
|
||||
semver = require('semver')
|
||||
}
|
||||
const { browser } = UAParser(ua)
|
||||
const browser = new UAParser(ua).getBrowser()
|
||||
const browserVersion = semver.coerce(browser.version)
|
||||
if (!browserVersion) {
|
||||
return false
|
||||
}
|
||||
const modernBrowsers = getModernBrowsers()
|
||||
return Boolean(modernBrowsers[browser.name] && semver.gte(browserVersion, modernBrowsers[browser.name]))
|
||||
const name = browser.name as keyof typeof modernBrowsers
|
||||
return Boolean(
|
||||
name && name in modernBrowsers && semver.gte(browserVersion, modernBrowsers[name])
|
||||
)
|
||||
}
|
||||
|
||||
export const isModernRequest = (req, modernMode = false) => {
|
||||
export const isModernRequest = (req: NuxtRequest, modernMode = false) => {
|
||||
if (modernMode === false) {
|
||||
return false
|
||||
}
|
||||
|
||||
const { socket = {}, headers } = req
|
||||
const { socket = {} as NuxtRequest['socket'], headers } = req
|
||||
if (socket._modern === undefined) {
|
||||
const ua = headers && headers['user-agent']
|
||||
socket._modern = isModernBrowser(ua)
|
||||
socket._modern = ua && isModernBrowser(ua)
|
||||
}
|
||||
|
||||
return socket._modern
|
||||
|
@ -2,7 +2,8 @@ import path from 'path'
|
||||
import consola from 'consola'
|
||||
import escapeRegExp from 'lodash/escapeRegExp'
|
||||
|
||||
export const startsWithAlias = aliasArray => str => aliasArray.some(c => str.startsWith(c))
|
||||
export const startsWithAlias = (aliasArray: string[]) => (str: string) =>
|
||||
aliasArray.some(c => str.startsWith(c))
|
||||
|
||||
export const startsWithSrcAlias = startsWithAlias(['@', '~'])
|
||||
|
||||
@ -24,9 +25,9 @@ export const wChunk = function wChunk (p = '') {
|
||||
|
||||
const reqSep = /\//g
|
||||
const sysSep = escapeRegExp(path.sep)
|
||||
const normalize = string => string.replace(reqSep, sysSep)
|
||||
const normalize = (string: string) => string.replace(reqSep, sysSep)
|
||||
|
||||
export const r = function r (...args) {
|
||||
export const r = function r (...args: string[]) {
|
||||
const lastArg = args[args.length - 1]
|
||||
|
||||
if (startsWithSrcAlias(lastArg)) {
|
||||
@ -36,14 +37,12 @@ export const r = function r (...args) {
|
||||
return wp(path.resolve(...args.map(normalize)))
|
||||
}
|
||||
|
||||
export const relativeTo = function relativeTo (...args) {
|
||||
const dir = args.shift()
|
||||
|
||||
export const relativeTo = function relativeTo (dir: string, ...args: string[]): string {
|
||||
// Keep webpack inline loader intact
|
||||
if (args[0].includes('!')) {
|
||||
const loaders = args.shift().split('!')
|
||||
const loaders = args.shift()!.split('!')
|
||||
|
||||
return loaders.concat(relativeTo(dir, loaders.pop(), ...args)).join('!')
|
||||
return loaders.concat(relativeTo(dir, loaders.pop()!, ...args)).join('!')
|
||||
}
|
||||
|
||||
// Resolve path
|
||||
@ -63,7 +62,17 @@ export const relativeTo = function relativeTo (...args) {
|
||||
return wp(rp)
|
||||
}
|
||||
|
||||
export function defineAlias (src, target, prop, opts = {}) {
|
||||
interface AliasOptions {
|
||||
bind?: boolean
|
||||
warn?: boolean
|
||||
}
|
||||
|
||||
export function defineAlias (
|
||||
src: string,
|
||||
target: Record<string, any>,
|
||||
prop: string | string[],
|
||||
opts: AliasOptions = {}
|
||||
) {
|
||||
const { bind = true, warn = false } = opts
|
||||
|
||||
if (Array.isArray(prop)) {
|
||||
@ -94,9 +103,9 @@ export function defineAlias (src, target, prop, opts = {}) {
|
||||
})
|
||||
}
|
||||
|
||||
const isIndex = s => /(.*)\/index\.[^/]+$/.test(s)
|
||||
const isIndex = (s: string) => /(.*)\/index\.[^/]+$/.test(s)
|
||||
|
||||
export function isIndexFileAndFolder (pluginFiles) {
|
||||
export function isIndexFileAndFolder (pluginFiles: string[]) {
|
||||
// Return early in case the matching file count exceeds 2 (index.js + folder)
|
||||
if (pluginFiles.length !== 2) {
|
||||
return false
|
||||
@ -105,5 +114,9 @@ export function isIndexFileAndFolder (pluginFiles) {
|
||||
}
|
||||
|
||||
export const getMainModule = () => {
|
||||
return require.main || (module && module.main) || module
|
||||
return (
|
||||
require.main ||
|
||||
(module && ((module as any).main as NodeJS.Module)) ||
|
||||
module
|
||||
)
|
||||
}
|
||||
|
@ -2,9 +2,12 @@ import path from 'path'
|
||||
import get from 'lodash/get'
|
||||
import consola from 'consola'
|
||||
|
||||
import type { Component } from 'vue'
|
||||
import type { _RouteRecordBase } from 'vue-router'
|
||||
|
||||
import { r } from './resolve'
|
||||
|
||||
export const flatRoutes = function flatRoutes (router, fileName = '', routes = []) {
|
||||
export const flatRoutes = function flatRoutes (router, fileName = '', routes: string[] = []) {
|
||||
router.forEach((r) => {
|
||||
if ([':', '*'].some(c => r.path.includes(c))) {
|
||||
return
|
||||
@ -30,12 +33,12 @@ export const flatRoutes = function flatRoutes (router, fileName = '', routes = [
|
||||
return routes
|
||||
}
|
||||
|
||||
function cleanChildrenRoutes (routes, isChild = false, routeNameSplitter = '-') {
|
||||
function cleanChildrenRoutes (routes: NuxtRouteConfig[], isChild = false, routeNameSplitter = '-') {
|
||||
let start = -1
|
||||
const regExpIndex = new RegExp(`${routeNameSplitter}index$`)
|
||||
const routesIndex = []
|
||||
const routesIndex: string[][] = []
|
||||
routes.forEach((route) => {
|
||||
if (regExpIndex.test(route.name) || route.name === 'index') {
|
||||
if (route.name && typeof route.name === 'string' && (regExpIndex.test(route.name) || route.name === 'index')) {
|
||||
// Save indexOf 'index' key in name
|
||||
const res = route.name.split(routeNameSplitter)
|
||||
const s = res.indexOf('index')
|
||||
@ -46,7 +49,7 @@ function cleanChildrenRoutes (routes, isChild = false, routeNameSplitter = '-')
|
||||
routes.forEach((route) => {
|
||||
route.path = isChild ? route.path.replace('/', '') : route.path
|
||||
if (route.path.includes('?')) {
|
||||
const names = route.name.split(routeNameSplitter)
|
||||
const names = typeof route.name === 'string' && route.name.split(routeNameSplitter) || []
|
||||
const paths = route.path.split('/')
|
||||
if (!isChild) {
|
||||
paths.shift()
|
||||
@ -66,7 +69,9 @@ function cleanChildrenRoutes (routes, isChild = false, routeNameSplitter = '-')
|
||||
})
|
||||
route.path = (isChild ? '' : '/') + paths.join('/')
|
||||
}
|
||||
route.name = route.name.replace(regExpIndex, '')
|
||||
if (route.name) {
|
||||
route.name = typeof route.name === 'string' && route.name.replace(regExpIndex, '')
|
||||
}
|
||||
if (route.children) {
|
||||
if (route.children.find(child => child.path === '')) {
|
||||
delete route.name
|
||||
@ -79,7 +84,7 @@ function cleanChildrenRoutes (routes, isChild = false, routeNameSplitter = '-')
|
||||
|
||||
const DYNAMIC_ROUTE_REGEX = /^\/([:*])/
|
||||
|
||||
export const sortRoutes = function sortRoutes (routes) {
|
||||
export const sortRoutes = function sortRoutes (routes: NuxtRouteConfig[]) {
|
||||
routes.sort((a, b) => {
|
||||
if (!a.path.length) {
|
||||
return -1
|
||||
@ -136,6 +141,22 @@ export const sortRoutes = function sortRoutes (routes) {
|
||||
return routes
|
||||
}
|
||||
|
||||
interface CreateRouteOptions {
|
||||
files: string[]
|
||||
srcDir: string
|
||||
pagesDir?: string
|
||||
routeNameSplitter?: string
|
||||
supportedExtensions?: string[]
|
||||
trailingSlash: boolean
|
||||
}
|
||||
|
||||
interface NuxtRouteConfig extends Omit<_RouteRecordBase, 'children'> {
|
||||
component?: Component | string
|
||||
chunkName?: string
|
||||
pathToRegexpOptions?: any
|
||||
children?: NuxtRouteConfig[]
|
||||
}
|
||||
|
||||
export const createRoutes = function createRoutes ({
|
||||
files,
|
||||
srcDir,
|
||||
@ -143,8 +164,8 @@ export const createRoutes = function createRoutes ({
|
||||
routeNameSplitter = '-',
|
||||
supportedExtensions = ['vue', 'js'],
|
||||
trailingSlash
|
||||
}) {
|
||||
const routes = []
|
||||
}: CreateRouteOptions) {
|
||||
const routes: NuxtRouteConfig[] = []
|
||||
files.forEach((file) => {
|
||||
const keys = file
|
||||
.replace(new RegExp(`^${pagesDir}`), '')
|
||||
@ -152,13 +173,13 @@ export const createRoutes = function createRoutes ({
|
||||
.replace(/\/{2,}/g, '/')
|
||||
.split('/')
|
||||
.slice(1)
|
||||
const route = { name: '', path: '', component: r(srcDir, file) }
|
||||
const route: NuxtRouteConfig = { name: '', path: '', component: r(srcDir, file) }
|
||||
let parent = routes
|
||||
keys.forEach((key, i) => {
|
||||
// remove underscore only, if its the prefix
|
||||
const sanitizedKey = key.startsWith('_') ? key.substr(1) : key
|
||||
|
||||
route.name = route.name
|
||||
route.name = route.name && typeof route.name === 'string'
|
||||
? route.name + routeNameSplitter + sanitizedKey
|
||||
: sanitizedKey
|
||||
route.name += key === '_' ? 'all' : ''
|
||||
@ -192,9 +213,9 @@ export const createRoutes = function createRoutes ({
|
||||
}
|
||||
|
||||
// Guard dir1 from dir2 which can be indiscriminately removed
|
||||
export const guardDir = function guardDir (options, key1, key2) {
|
||||
const dir1 = get(options, key1, false)
|
||||
const dir2 = get(options, key2, false)
|
||||
export const guardDir = function guardDir (options: Record<string, unknown>, key1: string, key2: string) {
|
||||
const dir1 = get(options, key1, false) as string
|
||||
const dir2 = get(options, key2, false) as string
|
||||
|
||||
if (
|
||||
dir1 &&
|
||||
@ -213,7 +234,7 @@ export const guardDir = function guardDir (options, key1, key2) {
|
||||
}
|
||||
}
|
||||
|
||||
const getRoutePathExtension = (key) => {
|
||||
const getRoutePathExtension = (key: string) => {
|
||||
if (key === '_') {
|
||||
return '*'
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
import serialize from 'serialize-javascript'
|
||||
|
||||
export function normalizeFunctions (obj) {
|
||||
export function normalizeFunctions (obj: Array<any>): Array<any>
|
||||
export function normalizeFunctions (obj: null): null
|
||||
export function normalizeFunctions (obj: Function): Function
|
||||
export function normalizeFunctions (obj: Record<string, any>): Record<string, any>
|
||||
export function normalizeFunctions (obj: Array<unknown> | null | Function | Record<string, any>) {
|
||||
if (typeof obj !== 'object' || Array.isArray(obj) || obj === null) {
|
||||
return obj
|
||||
}
|
||||
@ -22,14 +26,14 @@ export function normalizeFunctions (obj) {
|
||||
functionBody = `return ${functionBody}`
|
||||
}
|
||||
// eslint-disable-next-line no-new-func
|
||||
obj[key] = new Function(...match[1].split(',').map(arg => arg.trim()), functionBody)
|
||||
obj[key] = new Function(...match[1].split(',').map((arg: string) => arg.trim()), functionBody)
|
||||
}
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
export function serializeFunction (func) {
|
||||
export function serializeFunction (func: Function) {
|
||||
let open = false
|
||||
func = normalizeFunctions(func)
|
||||
return serialize(func)
|
||||
|
@ -1,11 +1,17 @@
|
||||
export const sequence = function sequence (tasks, fn) {
|
||||
export const sequence = function sequence<T, R> (
|
||||
tasks: T[],
|
||||
fn: (task: T) => R
|
||||
) {
|
||||
return tasks.reduce(
|
||||
(promise, task) => promise.then(() => fn(task)),
|
||||
(promise, task): any => promise.then(() => fn(task)),
|
||||
Promise.resolve()
|
||||
)
|
||||
}
|
||||
|
||||
export const parallel = function parallel (tasks, fn) {
|
||||
export const parallel = function parallel<T, R> (
|
||||
tasks: T[],
|
||||
fn: (task: T) => R
|
||||
) {
|
||||
return Promise.all(tasks.map(fn))
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,8 @@
|
||||
async function promiseFinally (fn, finalFn) {
|
||||
let result
|
||||
async function promiseFinally<T> (
|
||||
fn: (() => Promise<T>) | Promise<T>,
|
||||
finalFn: () => any
|
||||
) {
|
||||
let result: T
|
||||
try {
|
||||
if (typeof fn === 'function') {
|
||||
result = await fn()
|
||||
@ -12,8 +15,12 @@ async function promiseFinally (fn, finalFn) {
|
||||
return result
|
||||
}
|
||||
|
||||
export const timeout = function timeout (fn, ms, msg) {
|
||||
let timerId
|
||||
export const timeout = function timeout (
|
||||
fn: () => any,
|
||||
ms: number,
|
||||
msg: string
|
||||
) {
|
||||
let timerId: NodeJS.Timeout
|
||||
const warpPromise = promiseFinally(fn, () => clearTimeout(timerId))
|
||||
const timerPromise = new Promise((resolve, reject) => {
|
||||
timerId = setTimeout(() => reject(new Error(msg)), ms)
|
||||
@ -21,16 +28,25 @@ export const timeout = function timeout (fn, ms, msg) {
|
||||
return Promise.race([warpPromise, timerPromise])
|
||||
}
|
||||
|
||||
export const waitFor = function waitFor (ms) {
|
||||
export const waitFor = function waitFor (ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms || 0))
|
||||
}
|
||||
|
||||
interface Time {
|
||||
name: string
|
||||
description: string
|
||||
start: [number, number] | bigint
|
||||
duration?: bigint | [number, number]
|
||||
}
|
||||
export class Timer {
|
||||
_times: Map<string, Time>
|
||||
|
||||
constructor () {
|
||||
this._times = new Map()
|
||||
}
|
||||
|
||||
start (name, description) {
|
||||
const time = {
|
||||
start (name: string, description: string) {
|
||||
const time: Time = {
|
||||
name,
|
||||
description,
|
||||
start: this.hrtime()
|
||||
@ -39,22 +55,33 @@ export class Timer {
|
||||
return time
|
||||
}
|
||||
|
||||
end (name) {
|
||||
end (name: string) {
|
||||
if (this._times.has(name)) {
|
||||
const time = this._times.get(name)
|
||||
time.duration = this.hrtime(time.start)
|
||||
const time = this._times.get(name)!
|
||||
if (typeof time.start === 'bigint') {
|
||||
time.duration = this.hrtime(time.start)
|
||||
} else {
|
||||
time.duration = this.hrtime(time.start)
|
||||
}
|
||||
this._times.delete(name)
|
||||
return time
|
||||
}
|
||||
}
|
||||
|
||||
hrtime (start) {
|
||||
hrtime (start?: bigint): bigint
|
||||
hrtime (start?: [number, number]): [number, number]
|
||||
hrtime (start?: [number, number] | bigint) {
|
||||
const useBigInt = typeof process.hrtime.bigint === 'function'
|
||||
if (start) {
|
||||
const end = useBigInt ? process.hrtime.bigint() : process.hrtime(start)
|
||||
return useBigInt
|
||||
? (end - start) / BigInt(1000000)
|
||||
: (end[0] * 1e3) + (end[1] * 1e-6)
|
||||
if (typeof start === 'bigint') {
|
||||
if (!useBigInt) { throw new Error('bigint is not supported.') }
|
||||
|
||||
const end = process.hrtime.bigint()
|
||||
return (end - start) / BigInt(1000000)
|
||||
}
|
||||
|
||||
const end = process.hrtime(start)
|
||||
return end[0] * 1e3 + end[1] * 1e-6
|
||||
}
|
||||
return useBigInt ? process.hrtime.bigint() : process.hrtime()
|
||||
}
|
||||
|
@ -7,9 +7,22 @@ import { TARGETS, isModernRequest, waitFor } from 'nuxt/utils'
|
||||
import SPARenderer from './renderers/spa'
|
||||
import SSRRenderer from './renderers/ssr'
|
||||
import ModernRenderer from './renderers/modern'
|
||||
import ServerContext from 'nuxt/server/context'
|
||||
|
||||
export default class VueRenderer {
|
||||
constructor (context) {
|
||||
__closed?: boolean
|
||||
_state?: 'created' | 'loading' | 'ready' | 'error'
|
||||
_error?: null
|
||||
_readyPromise?: Promise<any>
|
||||
distPath: string
|
||||
serverContext: ServerContext
|
||||
renderer: {
|
||||
ssr: any
|
||||
modern: any
|
||||
spa: any
|
||||
}
|
||||
|
||||
constructor (context: ServerContext) {
|
||||
this.serverContext = context
|
||||
this.options = this.serverContext.options
|
||||
|
||||
@ -86,10 +99,10 @@ export default class VueRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
async loadResources (_fs) {
|
||||
async loadResources (_fs: typeof import('fs-extra')) {
|
||||
const updated = []
|
||||
|
||||
const readResource = async (fileName, encoding) => {
|
||||
const readResource = async (fileName: string, encoding: string) => {
|
||||
try {
|
||||
const fullPath = path.resolve(this.distPath, fileName)
|
||||
|
||||
@ -311,16 +324,16 @@ export default class VueRenderer {
|
||||
return {
|
||||
clientManifest: {
|
||||
fileName: 'client.manifest.json',
|
||||
transform: src => JSON.parse(src)
|
||||
transform: (src: string) => JSON.parse(src)
|
||||
},
|
||||
modernManifest: {
|
||||
fileName: 'modern.manifest.json',
|
||||
transform: src => JSON.parse(src)
|
||||
transform: (src: string) => JSON.parse(src)
|
||||
},
|
||||
serverManifest: {
|
||||
fileName: 'server.manifest.json',
|
||||
// BundleRenderer needs resolved contents
|
||||
transform: async (src, { readResource }) => {
|
||||
transform: async (src: string, { readResource }) => {
|
||||
const serverManifest = JSON.parse(src)
|
||||
|
||||
const readResources = async (obj) => {
|
||||
@ -357,16 +370,16 @@ export default class VueRenderer {
|
||||
},
|
||||
ssrTemplate: {
|
||||
fileName: 'index.ssr.html',
|
||||
transform: src => this.parseTemplate(src)
|
||||
transform: (src: string) => this.parseTemplate(src)
|
||||
},
|
||||
spaTemplate: {
|
||||
fileName: 'index.spa.html',
|
||||
transform: src => this.parseTemplate(src)
|
||||
transform: (src: string) => this.parseTemplate(src)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parseTemplate (templateStr) {
|
||||
parseTemplate (templateStr: string) {
|
||||
return template(templateStr, {
|
||||
interpolate: /{{([\s\S]+?)}}/g,
|
||||
evaluate: /{%([\s\S]+?)%}/g
|
||||
|
@ -1,10 +1,15 @@
|
||||
import ServerContext from "nuxt/server/context"
|
||||
|
||||
export default class BaseRenderer {
|
||||
constructor (serverContext) {
|
||||
serverContext: ServerContext
|
||||
options: ServerContext['options']
|
||||
|
||||
constructor (serverContext: ServerContext) {
|
||||
this.serverContext = serverContext
|
||||
this.options = serverContext.options
|
||||
}
|
||||
|
||||
renderTemplate (templateFn, opts) {
|
||||
renderTemplate (templateFn: (options: Record<string, any>) => void, opts: Record<string, any>) {
|
||||
// Fix problem with HTMLPlugin's minify option (#3392)
|
||||
opts.html_attrs = opts.HTML_ATTRS
|
||||
opts.head_attrs = opts.HEAD_ATTRS
|
||||
@ -13,7 +18,7 @@ export default class BaseRenderer {
|
||||
return templateFn(opts)
|
||||
}
|
||||
|
||||
render () {
|
||||
render (renderContext) {
|
||||
throw new Error('`render()` needs to be implemented')
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,13 @@
|
||||
import ServerContext from 'nuxt/server/context'
|
||||
import { isUrl, urlJoin, safariNoModuleFix } from 'nuxt/utils'
|
||||
|
||||
import SSRRenderer from './ssr'
|
||||
|
||||
export default class ModernRenderer extends SSRRenderer {
|
||||
constructor (serverContext) {
|
||||
_assetsMapping?: Record<string, string>
|
||||
publicPath: string
|
||||
|
||||
constructor (serverContext: ServerContext) {
|
||||
super(serverContext)
|
||||
|
||||
const { build: { publicPath }, router: { base } } = this.options
|
||||
@ -17,7 +22,7 @@ export default class ModernRenderer extends SSRRenderer {
|
||||
const { clientManifest, modernManifest } = this.serverContext.resources
|
||||
const legacyAssets = clientManifest.assetsMapping
|
||||
const modernAssets = modernManifest.assetsMapping
|
||||
const mapping = {}
|
||||
const mapping: Record<string, string> = {}
|
||||
|
||||
Object.keys(legacyAssets).forEach((componentHash) => {
|
||||
const modernComponentAssets = modernAssets[componentHash] || []
|
||||
|
@ -3,11 +3,22 @@ import cloneDeep from 'lodash/cloneDeep'
|
||||
import VueMeta from 'vue-meta'
|
||||
import LRU from 'lru-cache'
|
||||
import devalue from '@nuxt/devalue'
|
||||
|
||||
import { TARGETS, isModernRequest } from 'nuxt/utils'
|
||||
import ServerContext from 'nuxt/server/context'
|
||||
import BaseRenderer from './base'
|
||||
|
||||
export default class SPARenderer extends BaseRenderer {
|
||||
constructor (serverContext) {
|
||||
cache: LRU<unknown, unknown>
|
||||
vueMetaConfig: {
|
||||
ssrAppId: string
|
||||
keyName: string
|
||||
attribute: string
|
||||
ssrAttribute: string
|
||||
tagIDKeyName: string
|
||||
}
|
||||
|
||||
constructor (serverContext: ServerContext) {
|
||||
super(serverContext)
|
||||
|
||||
this.cache = new LRU()
|
||||
@ -188,7 +199,7 @@ export default class SPARenderer extends BaseRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
static getPreloadType (ext) {
|
||||
static getPreloadType (ext: string) {
|
||||
if (ext === 'js') {
|
||||
return 'script'
|
||||
} else if (ext === 'css') {
|
||||
|
@ -3,13 +3,15 @@ import crypto from 'crypto'
|
||||
import { format } from 'util'
|
||||
import fs from 'fs-extra'
|
||||
import consola from 'consola'
|
||||
import { TARGETS, urlJoin } from 'nuxt/utils'
|
||||
import devalue from '@nuxt/devalue'
|
||||
import { createBundleRenderer } from 'vue-bundle-renderer'
|
||||
|
||||
import { TARGETS, urlJoin } from 'nuxt/utils'
|
||||
import ServerContext from 'nuxt/server/context'
|
||||
import BaseRenderer from './base'
|
||||
|
||||
export default class SSRRenderer extends BaseRenderer {
|
||||
constructor (serverContext) {
|
||||
constructor (serverContext: ServerContext) {
|
||||
super(serverContext)
|
||||
this.createRenderer()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user