From e75e1911afdf81c08184c5dd8e115a3b738f8c0a Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Fri, 31 Jul 2020 10:37:31 +0100 Subject: [PATCH] types: increase types coverage (#13) --- packages/nuxt3/src/builder/builder.ts | 35 +++++++++-- packages/nuxt3/src/builder/context/build.ts | 9 ++- .../nuxt3/src/builder/context/template.ts | 12 ++-- packages/nuxt3/src/builder/ignore.ts | 11 +++- packages/nuxt3/src/builder/index.ts | 6 +- packages/nuxt3/src/cli/command.ts | 63 ++++++++++++++----- packages/nuxt3/src/cli/commands/build.ts | 13 ++-- packages/nuxt3/src/cli/commands/dev.ts | 17 +++-- packages/nuxt3/src/cli/commands/export.ts | 3 +- packages/nuxt3/src/cli/commands/generate.ts | 10 +-- packages/nuxt3/src/cli/commands/help.ts | 2 +- packages/nuxt3/src/cli/commands/index.ts | 2 +- packages/nuxt3/src/cli/commands/serve.ts | 7 ++- packages/nuxt3/src/cli/commands/start.ts | 4 +- packages/nuxt3/src/cli/commands/webpack.ts | 4 +- packages/nuxt3/src/cli/list.ts | 2 +- packages/nuxt3/src/cli/run.ts | 6 +- packages/nuxt3/src/cli/setup.ts | 2 +- packages/nuxt3/src/cli/utils/banner.ts | 6 +- packages/nuxt3/src/cli/utils/config.ts | 4 +- packages/nuxt3/src/cli/utils/formatting.ts | 18 +++--- packages/nuxt3/src/cli/utils/index.ts | 8 +-- packages/nuxt3/src/cli/utils/webpack.ts | 2 +- packages/nuxt3/src/config/load.ts | 24 ++++--- packages/nuxt3/src/core/load.ts | 15 ++++- packages/nuxt3/src/core/module.ts | 28 +++++++-- packages/nuxt3/src/core/nuxt.ts | 18 +++++- packages/nuxt3/src/core/resolver.ts | 56 ++++++++--------- packages/nuxt3/src/generator/generator.ts | 24 ++++++- packages/nuxt3/src/generator/index.ts | 4 +- packages/nuxt3/src/server/context.ts | 9 ++- packages/nuxt3/src/server/jsdom.ts | 14 +++-- packages/nuxt3/src/server/listener.ts | 19 +++++- .../nuxt3/src/server/middleware/timing.ts | 17 +++-- packages/nuxt3/src/server/server.ts | 50 ++++++++++++--- packages/nuxt3/src/utils/cjs.ts | 12 ++-- packages/nuxt3/src/utils/constants.ts | 8 ++- packages/nuxt3/src/utils/context.ts | 30 ++++++--- packages/nuxt3/src/utils/lang.ts | 29 +++++---- packages/nuxt3/src/utils/locking.ts | 39 +++++++++--- packages/nuxt3/src/utils/modern.ts | 47 +++++++++----- packages/nuxt3/src/utils/resolve.ts | 37 +++++++---- packages/nuxt3/src/utils/route.ts | 51 ++++++++++----- packages/nuxt3/src/utils/serialize.ts | 10 ++- packages/nuxt3/src/utils/task.ts | 12 +++- packages/nuxt3/src/utils/timer.ts | 57 ++++++++++++----- packages/nuxt3/src/vue-renderer/renderer.ts | 31 ++++++--- .../nuxt3/src/vue-renderer/renderers/base.ts | 11 +++- .../src/vue-renderer/renderers/modern.ts | 9 ++- .../nuxt3/src/vue-renderer/renderers/spa.ts | 15 ++++- .../nuxt3/src/vue-renderer/renderers/ssr.ts | 6 +- 51 files changed, 667 insertions(+), 261 deletions(-) diff --git a/packages/nuxt3/src/builder/builder.ts b/packages/nuxt3/src/builder/builder.ts index 7c6c1697e7..52f6c1e861 100644 --- a/packages/nuxt3/src/builder/builder.ts +++ b/packages/nuxt3/src/builder/builder.ts @@ -8,6 +8,7 @@ import hash from 'hash-sum' import pify from 'pify' import upath from 'upath' import semver from 'semver' +import type { RouteLocationRaw } from 'vue-router' import debounce from 'lodash/debounce' import omit from 'lodash/omit' @@ -15,6 +16,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 +28,7 @@ import { determineGlobals, stripWhitespace, isIndexFileAndFolder, + DeterminedGlobals, scanRequireTree, TARGETS, isFullStatic @@ -37,7 +40,31 @@ import TemplateContext from './context/template' 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 diff --git a/packages/nuxt3/src/builder/context/build.ts b/packages/nuxt3/src/builder/context/build.ts index 9f0931e7f8..28efbfac28 100644 --- a/packages/nuxt3/src/builder/context/build.ts +++ b/packages/nuxt3/src/builder/context/build.ts @@ -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 diff --git a/packages/nuxt3/src/builder/context/template.ts b/packages/nuxt3/src/builder/context/template.ts index 7b11dd5b4c..952ecb001f 100644 --- a/packages/nuxt3/src/builder/context/template.ts +++ b/packages/nuxt3/src/builder/context/template.ts @@ -1,19 +1,23 @@ 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, features: options.features, extensions: options.extensions - .map(ext => ext.replace(/^\./, '')) + .map((ext: string) => ext.replace(/^\./, '')) .join('|'), messages: options.messages, splitChunks: options.build.splitChunks, @@ -62,7 +66,7 @@ export default class TemplateContext { hash, r, wp, - wChunk, + wChunk }, interpolate: /<%=([\s\S]+?)%>/g } diff --git a/packages/nuxt3/src/builder/ignore.ts b/packages/nuxt3/src/builder/ignore.ts index d929368960..4eb58048dd 100644 --- a/packages/nuxt3/src/builder/ignore.ts +++ b/packages/nuxt3/src/builder/ignore.ts @@ -2,7 +2,16 @@ import path from 'path' import fs from 'fs-extra' import ignore from 'ignore' +type IgnoreInstance = ReturnType +type IgnoreOptions = Parameters[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 || [])) } diff --git a/packages/nuxt3/src/builder/index.ts b/packages/nuxt3/src/builder/index.ts index e00c399dec..2fcfa1b131 100644 --- a/packages/nuxt3/src/builder/index.ts +++ b/packages/nuxt3/src/builder/index.ts @@ -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() } diff --git a/packages/nuxt3/src/cli/command.ts b/packages/nuxt3/src/cli/command.ts index 6b8824085e..4c7ef4efc8 100644 --- a/packages/nuxt3/src/cli/command.ts +++ b/packages/nuxt3/src/cli/command.ts @@ -1,36 +1,67 @@ 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 + run?: (nuxt: NuxtCommand) => any | Promise +} + +type Hooks = Parameters[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 + + 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 } @@ -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) @@ -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 @@ -131,7 +162,7 @@ export default class NuxtCommand extends Hookable { return nuxt } - async getBuilder (nuxt) { + async getBuilder (nuxt: Nuxt) { return new Builder(nuxt) } @@ -160,12 +191,12 @@ export default class NuxtCommand extends Hookable { } } - 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) | T }>(option: Option) { + return option.default instanceof Function ? option.default(this.cmd) : option.default } _getMinimistOptions () { diff --git a/packages/nuxt3/src/cli/commands/build.ts b/packages/nuxt3/src/cli/commands/build.ts index f234b3661b..ff4a771513 100644 --- a/packages/nuxt3/src/cli/commands/build.ts +++ b/packages/nuxt3/src/cli/commands/build.ts @@ -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) diff --git a/packages/nuxt3/src/cli/commands/dev.ts b/packages/nuxt3/src/cli/commands/dev.ts index 9674bca052..bfb05c8a3f 100644 --- a/packages/nuxt3/src/cli/commands/dev.ts +++ b/packages/nuxt3/src/cli/commands/dev.ts @@ -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: ParsedArgs) { 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) @@ -92,7 +97,7 @@ export default { return nuxt }, - logChanged ({ event, path }) { + logChanged ({ event, path }: { event: keyof typeof eventsMapping, path: string }) { const { icon, color, action } = eventsMapping[event] || eventsMapping.change consola.log({ @@ -110,7 +115,7 @@ export default { await this.startDev(cmd, argv) }, - onBundlerChange (path) { + onBundlerChange (path: string) { this.logChanged({ event: 'change', path }) } } diff --git a/packages/nuxt3/src/cli/commands/export.ts b/packages/nuxt3/src/cli/commands/export.ts index 026200c3fe..825d8bd941 100644 --- a/packages/nuxt3/src/cli/commands/export.ts +++ b/packages/nuxt3/src/cli/commands/export.ts @@ -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, diff --git a/packages/nuxt3/src/cli/commands/generate.ts b/packages/nuxt3/src/cli/commands/generate.ts index b7386626a8..d7c2e6f1cc 100644 --- a/packages/nuxt3/src/cli/commands/generate.ts +++ b/packages/nuxt3/src/cli/commands/generate.ts @@ -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, diff --git a/packages/nuxt3/src/cli/commands/help.ts b/packages/nuxt3/src/cli/commands/help.ts index 4b702cd1df..dc7b34bd9a 100644 --- a/packages/nuxt3/src/cli/commands/help.ts +++ b/packages/nuxt3/src/cli/commands/help.ts @@ -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() diff --git a/packages/nuxt3/src/cli/commands/index.ts b/packages/nuxt3/src/cli/commands/index.ts index 50e84854d4..52f282992b 100644 --- a/packages/nuxt3/src/cli/commands/index.ts +++ b/packages/nuxt3/src/cli/commands/index.ts @@ -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) } diff --git a/packages/nuxt3/src/cli/commands/serve.ts b/packages/nuxt3/src/cli/commands/serve.ts index 071c65f3b5..ee79274483 100644 --- a/packages/nuxt3/src/cli/commands/serve.ts +++ b/packages/nuxt3/src/cli/commands/serve.ts @@ -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) diff --git a/packages/nuxt3/src/cli/commands/start.ts b/packages/nuxt3/src/cli/commands/start.ts index 3da1c2c711..97746d020e 100644 --- a/packages/nuxt3/src/cli/commands/start.ts +++ b/packages/nuxt3/src/cli/commands/start.ts @@ -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`') diff --git a/packages/nuxt3/src/cli/commands/webpack.ts b/packages/nuxt3/src/cli/commands/webpack.ts index 5d453f3308..9c881cf94d 100644 --- a/packages/nuxt3/src/cli/commands/webpack.ts +++ b/packages/nuxt3/src/cli/commands/webpack.ts @@ -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._] diff --git a/packages/nuxt3/src/cli/list.ts b/packages/nuxt3/src/cli/list.ts index 13d21d997a..ca31a82df4 100644 --- a/packages/nuxt3/src/cli/list.ts +++ b/packages/nuxt3/src/cli/list.ts @@ -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( diff --git a/packages/nuxt3/src/cli/run.ts b/packages/nuxt3/src/cli/run.ts index 859c0479a7..3d64fdd94e 100644 --- a/packages/nuxt3/src/cli/run.ts +++ b/packages/nuxt3/src/cli/run.ts @@ -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])))) { diff --git a/packages/nuxt3/src/cli/setup.ts b/packages/nuxt3/src/cli/setup.ts index 1fba67e2ca..e42ab55a61 100644 --- a/packages/nuxt3/src/cli/setup.ts +++ b/packages/nuxt3/src/cli/setup.ts @@ -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' diff --git a/packages/nuxt3/src/cli/utils/banner.ts b/packages/nuxt3/src/cli/utils/banner.ts index 496c5d0faa..fad81f9f08 100644 --- a/packages/nuxt3/src/cli/utils/banner.ts +++ b/packages/nuxt3/src/cli/utils/banner.ts @@ -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 diff --git a/packages/nuxt3/src/cli/utils/config.ts b/packages/nuxt3/src/cli/utils/config.ts index 97804f738d..3296afa2fd 100644 --- a/packages/nuxt3/src/cli/utils/config.ts +++ b/packages/nuxt3/src/cli/utils/config.ts @@ -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'] diff --git a/packages/nuxt3/src/cli/utils/formatting.ts b/packages/nuxt3/src/cli/utils/formatting.ts index c085eab83c..d91cdf0c61 100644 --- a/packages/nuxt3/src/cli/utils/formatting.ts +++ b/packages/nuxt3/src/cli/utils/formatting.ts @@ -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')) } diff --git a/packages/nuxt3/src/cli/utils/index.ts b/packages/nuxt3/src/cli/utils/index.ts index 27c6dec9bf..b52c654fb7 100644 --- a/packages/nuxt3/src/cli/utils/index.ts +++ b/packages/nuxt3/src/cli/utils/index.ts @@ -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 # which has only a getter -export function createLock (...args) { +export function createLock (...args: Parameters) { return lock(...args) } diff --git a/packages/nuxt3/src/cli/utils/webpack.ts b/packages/nuxt3/src/cli/utils/webpack.ts index 53c3c1a84d..a95d461271 100644 --- a/packages/nuxt3/src/cli/utils/webpack.ts +++ b/packages/nuxt3/src/cli/utils/webpack.ts @@ -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 diff --git a/packages/nuxt3/src/config/load.ts b/packages/nuxt3/src/config/load.ts index 8e2c4cf1c7..5a256d182b 100644 --- a/packages/nuxt3/src/config/load.ts +++ b/packages/nuxt3/src/config/load.ts @@ -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, source: Record = {}, 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] diff --git a/packages/nuxt3/src/core/load.ts b/packages/nuxt3/src/core/load.ts index 690ddc81d8..64d71b1da6 100644 --- a/packages/nuxt3/src/core/load.ts +++ b/packages/nuxt3/src/core/load.ts @@ -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 } diff --git a/packages/nuxt3/src/core/module.ts b/packages/nuxt3/src/core/module.ts index c130942f9e..c3a0418bc5 100644 --- a/packages/nuxt3/src/core/module.ts +++ b/packages/nuxt3/src/core/module.ts @@ -5,8 +5,24 @@ import consola from 'consola' import { chainFn, sequence } from 'nuxt/utils' +import Nuxt from './nuxt' + +interface Module { + src: string + options: Record + handler: () => any +} + +interface Template { + +} + export default class ModuleContainer { - constructor (nuxt) { + nuxt: Nuxt + options: Nuxt['options'] + requiredModules: Record + + 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 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 } diff --git a/packages/nuxt3/src/core/nuxt.ts b/packages/nuxt3/src/core/nuxt.ts index 2cdd3160c2..8e61abb953 100644 --- a/packages/nuxt3/src/core/nuxt.ts +++ b/packages/nuxt3/src/core/nuxt.ts @@ -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 + _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) { await this.callHook('close', this) if (typeof callback === 'function') { await callback() } - this.clearHooks() + // Deleting as no longer exists on `hookable` + // this.clearHooks() } } diff --git a/packages/nuxt3/src/core/resolver.ts b/packages/nuxt3/src/core/resolver.ts index e1c1bb4914..08adfdd50a 100644 --- a/packages/nuxt3/src/core/resolver.ts +++ b/packages/nuxt3/src/core/resolver.ts @@ -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 (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 { diff --git a/packages/nuxt3/src/generator/generator.ts b/packages/nuxt3/src/generator/generator.ts index 9b3b79e45a..495c4d8ec0 100644 --- a/packages/nuxt3/src/generator/generator.ts +++ b/packages/nuxt3/src/generator/generator.ts @@ -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> + generatedRoutes: Set + + constructor (nuxt: Nuxt, builder?: Builder) { this.nuxt = nuxt this.options = nuxt.options this.builder = builder diff --git a/packages/nuxt3/src/generator/index.ts b/packages/nuxt3/src/generator/index.ts index 6e420c7e35..da3052300c 100644 --- a/packages/nuxt3/src/generator/index.ts +++ b/packages/nuxt3/src/generator/index.ts @@ -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) } diff --git a/packages/nuxt3/src/server/context.ts b/packages/nuxt3/src/server/context.ts index 99dc07c321..2e1f365cf6 100644 --- a/packages/nuxt3/src/server/context.ts +++ b/packages/nuxt3/src/server/context.ts @@ -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 diff --git a/packages/nuxt3/src/server/jsdom.ts b/packages/nuxt3/src/server/jsdom.ts index f61a775b82..2d8cad2cc8 100644 --- a/packages/nuxt3/src/server/jsdom.ts +++ b/packages/nuxt3/src/server/jsdom.ts @@ -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 } diff --git a/packages/nuxt3/src/server/listener.ts b/packages/nuxt3/src/server/listener.ts index 125a566f6c..3560475c43 100644 --- a/packages/nuxt3/src/server/listener.ts +++ b/packages/nuxt3/src/server/listener.ts @@ -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 diff --git a/packages/nuxt3/src/server/middleware/timing.ts b/packages/nuxt3/src/server/middleware/timing.ts index 5c62c02868..f510a4b223 100644 --- a/packages/nuxt3/src/server/middleware/timing.ts +++ b/packages/nuxt3/src/server/middleware/timing.ts @@ -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) { const desc = time.description ? `;desc="${time.description}"` : '' return `${time.name};dur=${time.duration}${desc}` } diff --git a/packages/nuxt3/src/server/server.ts b/packages/nuxt3/src/server/server.ts index 2019c04962..19c0c7f2c2 100644 --- a/packages/nuxt3/src/server/server.ts +++ b/packages/nuxt3/src/server/server.ts @@ -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 + 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('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) { + return this.renderer.renderRoute.apply(this.renderer, ...args.slice()) } - loadResources () { - return this.renderer.loadResources.apply(this.renderer, arguments) + loadResources (...args: Parameters) { + 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, diff --git a/packages/nuxt3/src/utils/cjs.ts b/packages/nuxt3/src/utils/cjs.ts index d294ff70a4..743af42823 100644 --- a/packages/nuxt3/src/utils/cjs.ts +++ b/packages/nuxt3/src/utils/cjs.ts @@ -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()) { 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')) } diff --git a/packages/nuxt3/src/utils/constants.ts b/packages/nuxt3/src/utils/constants.ts index 1af82f818c..9195fd516f 100644 --- a/packages/nuxt3/src/utils/constants.ts +++ b/packages/nuxt3/src/utils/constants.ts @@ -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 \ No newline at end of file diff --git a/packages/nuxt3/src/utils/context.ts b/packages/nuxt3/src/utils/context.ts index 9399809b9f..fc4d989e2b 100644 --- a/packages/nuxt3/src/utils/context.ts +++ b/packages/nuxt3/src/utils/context.ts @@ -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 = {} 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) { diff --git a/packages/nuxt3/src/utils/lang.ts b/packages/nuxt3/src/utils/lang.ts index 7ecf3fee94..f9882823ec 100644 --- a/packages/nuxt3/src/utils/lang.ts +++ b/packages/nuxt3/src/utils/lang.ts @@ -1,20 +1,25 @@ -export const encodeHtml = function encodeHtml (str) { +export const encodeHtml = function encodeHtml (str: string) { return str.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> => + !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 = (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) }) diff --git a/packages/nuxt3/src/utils/locking.ts b/packages/nuxt3/src/utils/locking.ts index 9380a53c47..f976586e8a 100644 --- a/packages/nuxt3/src/utils/locking.ts +++ b/packages/nuxt3/src/utils/locking.ts @@ -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() -export const defaultLockOptions = { +export const defaultLockOptions: Required< + Pick +> = { stale: 30000, onCompromised: err => consola.warn(err) } -export function getLockOptions (options) { +export function getLockOptions (options: Partial) { 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) { 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 +) { 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) | 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) } } diff --git a/packages/nuxt3/src/utils/modern.ts b/packages/nuxt3/src/utils/modern.ts index a080a5d7d6..12cdc18d75 100644 --- a/packages/nuxt3/src/utils/modern.ts +++ b/packages/nuxt3/src/utils/modern.ts @@ -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 diff --git a/packages/nuxt3/src/utils/resolve.ts b/packages/nuxt3/src/utils/resolve.ts index 4a54c6cfba..dcdceed509 100644 --- a/packages/nuxt3/src/utils/resolve.ts +++ b/packages/nuxt3/src/utils/resolve.ts @@ -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, + 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 + ) } diff --git a/packages/nuxt3/src/utils/route.ts b/packages/nuxt3/src/utils/route.ts index 95dc8f238e..f02f559b3c 100644 --- a/packages/nuxt3/src/utils/route.ts +++ b/packages/nuxt3/src/utils/route.ts @@ -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, 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 '*' } diff --git a/packages/nuxt3/src/utils/serialize.ts b/packages/nuxt3/src/utils/serialize.ts index f1b2ebdf09..d2ea21ebca 100644 --- a/packages/nuxt3/src/utils/serialize.ts +++ b/packages/nuxt3/src/utils/serialize.ts @@ -1,6 +1,10 @@ import serialize from 'serialize-javascript' -export function normalizeFunctions (obj) { +export function normalizeFunctions (obj: Array): Array +export function normalizeFunctions (obj: null): null +export function normalizeFunctions (obj: Function): Function +export function normalizeFunctions (obj: Record): Record +export function normalizeFunctions (obj: Array | null | Function | Record) { 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) diff --git a/packages/nuxt3/src/utils/task.ts b/packages/nuxt3/src/utils/task.ts index a6a82995c8..64e71576d6 100644 --- a/packages/nuxt3/src/utils/task.ts +++ b/packages/nuxt3/src/utils/task.ts @@ -1,11 +1,17 @@ -export const sequence = function sequence (tasks, fn) { +export const sequence = function sequence ( + 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 ( + tasks: T[], + fn: (task: T) => R +) { return Promise.all(tasks.map(fn)) } diff --git a/packages/nuxt3/src/utils/timer.ts b/packages/nuxt3/src/utils/timer.ts index 2add0401af..760de0d6f3 100644 --- a/packages/nuxt3/src/utils/timer.ts +++ b/packages/nuxt3/src/utils/timer.ts @@ -1,5 +1,8 @@ -async function promiseFinally (fn, finalFn) { - let result +async function promiseFinally ( + fn: (() => Promise) | Promise, + 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 + 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() } diff --git a/packages/nuxt3/src/vue-renderer/renderer.ts b/packages/nuxt3/src/vue-renderer/renderer.ts index d9ee549950..d6eee18d32 100644 --- a/packages/nuxt3/src/vue-renderer/renderer.ts +++ b/packages/nuxt3/src/vue-renderer/renderer.ts @@ -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 + 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 diff --git a/packages/nuxt3/src/vue-renderer/renderers/base.ts b/packages/nuxt3/src/vue-renderer/renderers/base.ts index ad1d3e3def..693fd5cb28 100644 --- a/packages/nuxt3/src/vue-renderer/renderers/base.ts +++ b/packages/nuxt3/src/vue-renderer/renderers/base.ts @@ -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) => void, opts: Record) { // 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') } } diff --git a/packages/nuxt3/src/vue-renderer/renderers/modern.ts b/packages/nuxt3/src/vue-renderer/renderers/modern.ts index 83fc24efc3..a98f0c45a9 100644 --- a/packages/nuxt3/src/vue-renderer/renderers/modern.ts +++ b/packages/nuxt3/src/vue-renderer/renderers/modern.ts @@ -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 + 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 = {} Object.keys(legacyAssets).forEach((componentHash) => { const modernComponentAssets = modernAssets[componentHash] || [] diff --git a/packages/nuxt3/src/vue-renderer/renderers/spa.ts b/packages/nuxt3/src/vue-renderer/renderers/spa.ts index f557752192..88ead6bc89 100644 --- a/packages/nuxt3/src/vue-renderer/renderers/spa.ts +++ b/packages/nuxt3/src/vue-renderer/renderers/spa.ts @@ -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 + 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') { diff --git a/packages/nuxt3/src/vue-renderer/renderers/ssr.ts b/packages/nuxt3/src/vue-renderer/renderers/ssr.ts index d75a05dc88..d542294144 100644 --- a/packages/nuxt3/src/vue-renderer/renderers/ssr.ts +++ b/packages/nuxt3/src/vue-renderer/renderers/ssr.ts @@ -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() }