fix(cli): config cache invalidation + refactors (#5500)

This commit is contained in:
Pooya Parsa 2019-04-11 14:34:21 +04:30 committed by GitHub
parent 52ad79baeb
commit d0afaa1daf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 184 additions and 127 deletions

View File

@ -3,7 +3,8 @@ import path from 'path'
import consola from 'consola' import consola from 'consola'
import minimist from 'minimist' import minimist from 'minimist'
import { name, version } from '../package.json' import { name, version } from '../package.json'
import { loadNuxtConfig, forceExit } from './utils' import { forceExit } from './utils'
import { loadNuxtConfig } from './utils/config'
import { indent, foldLines, colorize } from './utils/formatting' import { indent, foldLines, colorize } from './utils/formatting'
import { startSpaces, optionSpaces, forceExitTimeout } from './utils/constants' import { startSpaces, optionSpaces, forceExitTimeout } from './utils/constants'
import { detectTypeScript } from './utils/typescript' import { detectTypeScript } from './utils/typescript'

View File

@ -2,7 +2,8 @@ import consola from 'consola'
import chalk from 'chalk' import chalk from 'chalk'
import opener from 'opener' import opener from 'opener'
import { common, server } from '../options' import { common, server } from '../options'
import { showBanner, eventsMapping, formatPath } from '../utils' import { eventsMapping, formatPath } from '../utils'
import { showBanner } from '../utils/banner'
export default { export default {
name: 'dev', name: 'dev',

View File

@ -1,5 +1,5 @@
import { common, server } from '../options' import { common, server } from '../options'
import { showBanner } from '../utils' import { showBanner } from '../utils/banner'
export default { export default {
name: 'start', name: 'start',

View File

@ -7,4 +7,4 @@ export const imports = _imports
export { default as NuxtCommand } from './command' export { default as NuxtCommand } from './command'
export { default as setup } from './setup' export { default as setup } from './setup'
export { default as run } from './run' export { default as run } from './run'
export { loadNuxtConfig } from './utils' export { loadNuxtConfig } from './utils/config'

View File

@ -0,0 +1,47 @@
import consola from 'consola'
import prettyBytes from 'pretty-bytes'
import env from 'std-env'
import chalk from 'chalk'
import { successBox } from './formatting'
export function showBanner(nuxt) {
if (env.test) {
return
}
if (env.minimalCLI) {
for (const listener of nuxt.server.listeners) {
consola.info('Listening on: ' + listener.url)
}
return
}
const titleLines = []
const messageLines = []
// Name and version
titleLines.push(`${chalk.green.bold('Nuxt.js')} ${nuxt.constructor.version}`)
// Running mode
titleLines.push(`Running in ${nuxt.options.dev ? chalk.bold.blue('development') : chalk.bold.green('production')} mode (${chalk.bold(nuxt.options.mode)})`)
if (nuxt.options._typescript && nuxt.options._typescript.runtime) {
titleLines.push(`TypeScript support is ${chalk.green.bold('enabled')}`)
}
// https://nodejs.org/api/process.html#process_process_memoryusage
const { heapUsed, rss } = process.memoryUsage()
titleLines.push(`Memory usage: ${chalk.bold(prettyBytes(heapUsed))} (RSS: ${prettyBytes(rss)})`)
// Listeners
for (const listener of nuxt.server.listeners) {
messageLines.push(chalk.bold('Listening on: ') + chalk.underline.blue(listener.url))
}
// Add custom badge messages
if (nuxt.options.cli.badgeMessages.length) {
messageLines.push('', ...nuxt.options.cli.badgeMessages)
}
process.stdout.write(successBox(messageLines.join('\n'), titleLines.join('\n')))
}

View File

@ -0,0 +1,70 @@
import path from 'path'
import consola from 'consola'
import defaultsDeep from 'lodash/defaultsDeep'
import { defaultNuxtConfigFile, getDefaultNuxtConfig } from '@nuxt/config'
import { clearRequireCache, scanRequireTree } from '@nuxt/utils'
import esm from 'esm'
export async function loadNuxtConfig(argv) {
const rootDir = path.resolve(argv._[0] || '.')
let nuxtConfigFile
let options = {}
try {
nuxtConfigFile = require.resolve(path.resolve(rootDir, argv['config-file']))
} catch (e) {
if (e.code !== 'MODULE_NOT_FOUND') {
throw (e)
} else if (argv['config-file'] !== defaultNuxtConfigFile) {
consola.fatal('Could not load config file: ' + argv['config-file'])
}
}
if (nuxtConfigFile) {
if (nuxtConfigFile.endsWith('.ts')) {
options = require(nuxtConfigFile) || {}
} else {
clearRequireCache(nuxtConfigFile)
options = esm(module, { cache: false, cjs: { cache: false } })(nuxtConfigFile) || {}
}
if (options.default) {
options = options.default
}
if (typeof options === 'function') {
try {
options = await options()
if (options.default) {
options = options.default
}
} catch (error) {
consola.error(error)
consola.fatal('Error while fetching async configuration')
}
}
// Keep _nuxtConfigFile for watching
options._nuxtConfigFile = nuxtConfigFile
// Keep all related files for watching
options._nuxtConfigFiles = Array.from(scanRequireTree(nuxtConfigFile))
}
if (typeof options.rootDir !== 'string') {
options.rootDir = rootDir
}
// Nuxt Mode
options.mode =
(argv.spa && 'spa') || (argv.universal && 'universal') || options.mode
// Server options
options.server = defaultsDeep({
port: argv.port || undefined,
host: argv.hostname || undefined,
socket: argv['unix-socket'] || undefined
}, options.server || {}, getDefaultNuxtConfig().server)
return options
}

View File

@ -1,23 +1,10 @@
import path from 'path' import path from 'path'
import consola from 'consola'
import esm from 'esm'
import exit from 'exit' import exit from 'exit'
import defaultsDeep from 'lodash/defaultsDeep'
import { defaultNuxtConfigFile, getDefaultNuxtConfig } from '@nuxt/config'
import { lock } from '@nuxt/utils' import { lock } from '@nuxt/utils'
import chalk from 'chalk' import chalk from 'chalk'
import prettyBytes from 'pretty-bytes'
import env from 'std-env' import env from 'std-env'
import { successBox, warningBox } from './formatting' import { warningBox } from './formatting'
const esmOptions = {
cache: false,
cjs: {
cache: true,
vars: true,
namedExports: true
}
}
export const eventsMapping = { export const eventsMapping = {
add: { icon: '+', color: 'green', action: 'Created' }, add: { icon: '+', color: 'green', action: 'Created' },
@ -25,103 +12,6 @@ export const eventsMapping = {
unlink: { icon: '-', color: 'red', action: 'Removed' } unlink: { icon: '-', color: 'red', action: 'Removed' }
} }
export async function loadNuxtConfig(argv) {
const rootDir = path.resolve(argv._[0] || '.')
let nuxtConfigFile
let options = {}
try {
nuxtConfigFile = require.resolve(path.resolve(rootDir, argv['config-file']))
} catch (e) {
if (e.code !== 'MODULE_NOT_FOUND') {
throw (e)
} else if (argv['config-file'] !== defaultNuxtConfigFile) {
consola.fatal('Could not load config file: ' + argv['config-file'])
}
}
if (nuxtConfigFile) {
options = (nuxtConfigFile.endsWith('.ts') ? require(nuxtConfigFile) : esm(module, esmOptions)(nuxtConfigFile)) || {}
if (options.default) {
options = options.default
}
if (typeof options === 'function') {
try {
options = await options()
if (options.default) {
options = options.default
}
} catch (error) {
consola.error(error)
consola.fatal('Error while fetching async configuration')
}
}
// Keep _nuxtConfigFile for watching
options._nuxtConfigFile = nuxtConfigFile
}
if (typeof options.rootDir !== 'string') {
options.rootDir = rootDir
}
// Nuxt Mode
options.mode =
(argv.spa && 'spa') || (argv.universal && 'universal') || options.mode
// Server options
options.server = defaultsDeep({
port: argv.port || undefined,
host: argv.hostname || undefined,
socket: argv['unix-socket'] || undefined
}, options.server || {}, getDefaultNuxtConfig().server)
return options
}
export function showBanner(nuxt) {
if (env.test) {
return
}
if (env.minimalCLI) {
for (const listener of nuxt.server.listeners) {
consola.info('Listening on: ' + listener.url)
}
return
}
const titleLines = []
const messageLines = []
// Name and version
titleLines.push(`${chalk.green.bold('Nuxt.js')} ${nuxt.constructor.version}`)
// Running mode
titleLines.push(`Running in ${nuxt.options.dev ? chalk.bold.blue('development') : chalk.bold.green('production')} mode (${chalk.bold(nuxt.options.mode)})`)
if (nuxt.options._typescript && nuxt.options._typescript.runtime) {
titleLines.push(`TypeScript support is ${chalk.green.bold('enabled')}`)
}
// https://nodejs.org/api/process.html#process_process_memoryusage
const { heapUsed, rss } = process.memoryUsage()
titleLines.push(`Memory usage: ${chalk.bold(prettyBytes(heapUsed))} (RSS: ${prettyBytes(rss)})`)
// Listeners
for (const listener of nuxt.server.listeners) {
messageLines.push(chalk.bold('Listening on: ') + chalk.underline.blue(listener.url))
}
// Add custom badge messages
if (nuxt.options.cli.badgeMessages.length) {
messageLines.push('', ...nuxt.options.cli.badgeMessages)
}
process.stdout.write(successBox(messageLines.join('\n'), titleLines.join('\n')))
}
export function formatPath(filePath) { export function formatPath(filePath) {
if (!filePath) { if (!filePath) {
return return

View File

@ -1,5 +1,5 @@
import { consola } from '../utils' import { consola } from '../utils'
import * as utils from '../../src/utils' import { showBanner } from '../../src/utils/banner'
jest.mock('std-env', () => ({ jest.mock('std-env', () => ({
test: false, test: false,
@ -15,7 +15,7 @@ describe('cli/utils', () => {
{ url: 'second' } { url: 'second' }
] ]
utils.showBanner({ showBanner({
options: { options: {
cli: {} cli: {}
}, },

View File

@ -1,6 +1,8 @@
import { getDefaultNuxtConfig } from '@nuxt/config' import { getDefaultNuxtConfig } from '@nuxt/config'
import { consola } from '../utils' import { consola } from '../utils'
import { loadNuxtConfig } from '../../src/utils/config'
import * as utils from '../../src/utils' import * as utils from '../../src/utils'
import { showBanner } from '../../src/utils/banner'
import * as fmt from '../../src/utils/formatting' import * as fmt from '../../src/utils/formatting'
jest.mock('std-env', () => ({ jest.mock('std-env', () => ({
@ -18,7 +20,7 @@ describe('cli/utils', () => {
universal: true universal: true
} }
const options = await utils.loadNuxtConfig(argv) const options = await loadNuxtConfig(argv)
expect(options.rootDir).toBe(process.cwd()) expect(options.rootDir).toBe(process.cwd())
expect(options.mode).toBe('universal') expect(options.mode).toBe('universal')
expect(options.server.host).toBe('localhost') expect(options.server.host).toBe('localhost')
@ -33,7 +35,7 @@ describe('cli/utils', () => {
spa: true spa: true
} }
const options = await utils.loadNuxtConfig(argv) const options = await loadNuxtConfig(argv)
expect(options.testOption).toBe(true) expect(options.testOption).toBe(true)
expect(options.rootDir).toBe('/some/path') expect(options.rootDir).toBe('/some/path')
expect(options.mode).toBe('spa') expect(options.mode).toBe('spa')
@ -48,7 +50,7 @@ describe('cli/utils', () => {
'config-file': '../fixtures/nuxt.doesnt-exist.js' 'config-file': '../fixtures/nuxt.doesnt-exist.js'
} }
const options = await utils.loadNuxtConfig(argv) const options = await loadNuxtConfig(argv)
expect(options.testOption).not.toBeDefined() expect(options.testOption).not.toBeDefined()
expect(consola.fatal).toHaveBeenCalledTimes(1) expect(consola.fatal).toHaveBeenCalledTimes(1)
@ -64,7 +66,7 @@ describe('cli/utils', () => {
'unix-socket': '/var/run/async.sock' 'unix-socket': '/var/run/async.sock'
} }
const options = await utils.loadNuxtConfig(argv) const options = await loadNuxtConfig(argv)
expect(options.testOption).toBe(true) expect(options.testOption).toBe(true)
expect(options.mode).toBe('supercharged') expect(options.mode).toBe('supercharged')
expect(options.server.host).toBe('async-host') expect(options.server.host).toBe('async-host')
@ -78,7 +80,7 @@ describe('cli/utils', () => {
'config-file': '../fixtures/nuxt.async-error.js' 'config-file': '../fixtures/nuxt.async-error.js'
} }
const options = await utils.loadNuxtConfig(argv) const options = await loadNuxtConfig(argv)
expect(options.testOption).not.toBeDefined() expect(options.testOption).not.toBeDefined()
expect(consola.error).toHaveBeenCalledTimes(1) expect(consola.error).toHaveBeenCalledTimes(1)
@ -130,7 +132,7 @@ describe('cli/utils', () => {
{ url: 'second' } { url: 'second' }
] ]
utils.showBanner({ showBanner({
options: { options: {
cli: { cli: {
badgeMessages badgeMessages

View File

@ -96,8 +96,14 @@ export function getNuxtConfig(_options) {
options._nuxtConfigFile = path.resolve(options.rootDir, `${defaultNuxtConfigFile}.js`) options._nuxtConfigFile = path.resolve(options.rootDir, `${defaultNuxtConfigFile}.js`)
} }
// Watch for _nuxtConfigFile changes if (!options._nuxtConfigFiles) {
options.watch.push(options._nuxtConfigFile) options._nuxtConfigFiles = [
options._nuxtConfigFile
]
}
// Watch for config file changes
options.watch.push(...options._nuxtConfigFiles)
// Protect rootDir against buildDir // Protect rootDir against buildDir
guardDir(options, 'rootDir', 'buildDir') guardDir(options, 'rootDir', 'buildDir')

View File

@ -5,6 +5,9 @@ Object {
"ErrorPage": null, "ErrorPage": null,
"__normalized__": true, "__normalized__": true,
"_nuxtConfigFile": "/var/nuxt/test/nuxt.config.js", "_nuxtConfigFile": "/var/nuxt/test/nuxt.config.js",
"_nuxtConfigFiles": Array [
"/var/nuxt/test/nuxt.config.js",
],
"appTemplatePath": "/var/nuxt/test/.nuxt/views/app.template.html", "appTemplatePath": "/var/nuxt/test/.nuxt/views/app.template.html",
"build": Object { "build": Object {
"_publicPath": "/_nuxt/", "_publicPath": "/_nuxt/",

34
packages/utils/src/cjs.js Normal file
View File

@ -0,0 +1,34 @@
export function clearRequireCache(id) {
const entry = require.cache[id]
if (!entry || id.includes('node_modules')) {
return
}
if (entry.parent) {
const i = entry.parent.children.findIndex(e => e.id === id)
if (i > -1) {
entry.parent.children.splice(i, 1)
}
}
for (const child of entry.children) {
clearRequireCache(child.id)
}
delete require.cache[id]
}
export function scanRequireTree(id, files = new Set()) {
const entry = require.cache[id]
if (!entry || id.includes('node_modules') || files.has(id)) {
return files
}
files.add(entry.id)
for (const child of entry.children) {
scanRequireTree(child.id, files)
}
return files
}

View File

@ -6,3 +6,4 @@ export * from './route'
export * from './serialize' export * from './serialize'
export * from './task' export * from './task'
export * from './timer' export * from './timer'
export * from './cjs'

View File

@ -7,6 +7,7 @@ import * as route from '../src/route'
import * as serialize from '../src/serialize' import * as serialize from '../src/serialize'
import * as task from '../src/task' import * as task from '../src/task'
import * as timer from '../src/timer' import * as timer from '../src/timer'
import * as cjs from '../src/cjs'
describe('util: entry', () => { describe('util: entry', () => {
test('should export all methods from utils folder', () => { test('should export all methods from utils folder', () => {
@ -18,7 +19,8 @@ describe('util: entry', () => {
...route, ...route,
...serialize, ...serialize,
...task, ...task,
...timer ...timer,
...cjs
}) })
}) })
}) })