feat(kit, bridge): version constraint utils and checks (#442)

Co-authored-by: Pooya Parsa <pyapar@gmail.com>
This commit is contained in:
Anthony Fu 2021-10-03 02:31:00 +08:00 committed by GitHub
parent eb0332126e
commit 9503d62607
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 137 additions and 14 deletions

View File

@ -1,5 +1,5 @@
import { createRequire } from 'module' import { createRequire } from 'module'
import { defineNuxtModule, installModule } from '@nuxt/kit' import { defineNuxtModule, installModule, checkNuxtCompatibilityIssues } from '@nuxt/kit'
import { setupNitroBridge } from './nitro' import { setupNitroBridge } from './nitro'
import { setupAppBridge } from './app' import { setupAppBridge } from './app'
import { setupCAPIBridge } from './capi' import { setupCAPIBridge } from './capi'
@ -15,6 +15,7 @@ export default defineNuxtModule({
app: {}, app: {},
capi: {}, capi: {},
globalImports: true, globalImports: true,
constraints: true,
// TODO: Remove from 2.16 // TODO: Remove from 2.16
postcss8: true, postcss8: true,
swc: true, swc: true,
@ -50,5 +51,18 @@ export default defineNuxtModule({
if (opts.resolve) { if (opts.resolve) {
setupBetterResolve() setupBetterResolve()
} }
if (opts.constraints) {
nuxt.hook('modules:done', (moduleContainer: any) => {
for (const [name, m] of Object.entries(moduleContainer.requiredModules || {})) {
const requires = (m as any)?.handler?.meta?.requires
if (requires) {
const issues = checkNuxtCompatibilityIssues(requires, nuxt)
if (issues.length) {
console.warn(`[bridge] Detected module incompatibility issues for \`${name}\`:\n` + issues.toString())
}
}
}
})
}
} }
}) })

View File

@ -15,6 +15,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/lodash.template": "^4", "@types/lodash.template": "^4",
"@types/semver": "^7",
"unbuild": "latest" "unbuild": "latest"
}, },
"dependencies": { "dependencies": {
@ -30,6 +31,7 @@
"pathe": "^0.2.0", "pathe": "^0.2.0",
"rc9": "^1.2.0", "rc9": "^1.2.0",
"scule": "^0.2.1", "scule": "^0.2.1",
"semver": "^7.3.5",
"std-env": "^2.3.1", "std-env": "^2.3.1",
"ufo": "^0.7.9", "ufo": "^0.7.9",
"unctx": "^1.0.2", "unctx": "^1.0.2",

View File

@ -1,10 +1,11 @@
import { promises as fsp } from 'fs' import { promises as fsp } from 'fs'
import defu from 'defu' import defu from 'defu'
import { applyDefaults } from 'untyped' import { applyDefaults } from 'untyped'
import consola from 'consola'
import { useNuxt, nuxtCtx } from '../nuxt' import { useNuxt, nuxtCtx } from '../nuxt'
import type { Nuxt, NuxtTemplate } from '../types/nuxt' import type { Nuxt, NuxtTemplate } from '../types/nuxt'
import type { NuxtModule, LegacyNuxtModule, ModuleOptions } from '../types/module' import type { NuxtModule, LegacyNuxtModule, ModuleOptions } from '../types/module'
import { compileTemplate, isNuxt2, templateUtils } from './utils' import { checkNuxtCompatibilityIssues, compileTemplate, isNuxt2, templateUtils } from './utils'
/** /**
* Define a Nuxt module, automatically merging defaults with user provided options, installing * Define a Nuxt module, automatically merging defaults with user provided options, installing
@ -34,6 +35,15 @@ export function defineNuxtModule<OptionsT extends ModuleOptions> (input: NuxtMod
return return
} }
// check nuxt version range
if (mod.requires) {
const issues = checkNuxtCompatibilityIssues(mod.requires, nuxt)
if (issues.length) {
consola.warn(`Module \`${mod.name}\` is disabled due to incompatibility issues:\n${issues.toString()}`)
return
}
}
// Resolve options // Resolve options
const configKey = mod.configKey || mod.name const configKey = mod.configKey || mod.name
const userOptions = defu(inlineOptions, nuxt.options[configKey]) as OptionsT const userOptions = defu(inlineOptions, nuxt.options[configKey]) as OptionsT

View File

@ -5,6 +5,9 @@ import hash from 'hash-sum'
import type { WebpackPluginInstance, Configuration as WebpackConfig } from 'webpack' import type { WebpackPluginInstance, Configuration as WebpackConfig } from 'webpack'
import type { Plugin as VitePlugin, UserConfig as ViteConfig } from 'vite' import type { Plugin as VitePlugin, UserConfig as ViteConfig } from 'vite'
import { camelCase } from 'scule' import { camelCase } from 'scule'
import semver from 'semver'
import { NuxtCompatibilityConstraints, NuxtCompatibilityIssues } from '../types/module'
import { Nuxt } from '../types/nuxt'
import { useNuxt } from '../nuxt' import { useNuxt } from '../nuxt'
import type { NuxtTemplate, NuxtPlugin, NuxtPluginTemplate } from '../types/nuxt' import type { NuxtTemplate, NuxtPlugin, NuxtPluginTemplate } from '../types/nuxt'
@ -255,15 +258,6 @@ export function addVitePlugin (plugin: VitePlugin, options?: ExtendViteConfigOpt
}, options) }, options)
} }
/**
* Check if current nuxt instance is version 2 legacy
*/
export function isNuxt2 (nuxt?: any) {
nuxt = nuxt || useNuxt()
const version = (nuxt?.version || nuxt?.constructor?.version || '').replace(/^v|-.*$/g, '')
return version.startsWith('2.')
}
export async function compileTemplate (template: NuxtTemplate, ctx: any) { export async function compileTemplate (template: NuxtTemplate, ctx: any) {
const data = { ...ctx, ...template.options } const data = { ...ctx, ...template.options }
if (template.src) { if (template.src) {
@ -302,3 +296,64 @@ export const templateUtils = {
importName, importName,
importSources importSources
} }
/**
* Check if current nuxt instance is version 2 legacy
*/
export function isNuxt2 (nuxt: Nuxt = useNuxt()) {
return getNuxtVersion(nuxt).startsWith('2.')
}
/**
* Check if current nuxt instance is version 2 legacy
*/
export function isNuxt3 (nuxt: Nuxt = useNuxt()) {
return getNuxtVersion(nuxt).startsWith('3.')
}
/**
* Get nuxt version
*/
export function getNuxtVersion (nuxt: Nuxt | any = useNuxt() /* TODO: LegacyNuxt */) {
const version = (nuxt?._version || nuxt?.version || nuxt?.constructor?.version || '').replace(/^v/g, '')
if (!version) {
throw new Error('Cannot determine nuxt version! Is currect instance passed?')
}
return version
}
/**
* Check version constraints and return incompatibility issues as an array
*/
export function checkNuxtCompatibilityIssues (constraints: NuxtCompatibilityConstraints, nuxt: Nuxt = useNuxt()): NuxtCompatibilityIssues {
const issues: NuxtCompatibilityIssues = []
if (constraints.nuxt) {
const nuxtVersion = getNuxtVersion(nuxt)
if (!semver.satisfies(nuxtVersion, constraints.nuxt)) {
issues.push({
name: 'nuxt',
message: `Nuxt version \`${constraints.nuxt}\` is required but currently using \`${nuxtVersion}\``
})
}
}
issues.toString = () => issues.map(issue => ` - [${issue.name}] ${issue.message}`).join('\n')
return issues
}
/**
* Check version constraints and throw a detailed error if has any, otherwise returns true
*/
export function ensureNuxtCompatibility (constraints: NuxtCompatibilityConstraints, nuxt: Nuxt = useNuxt()): true {
const issues = checkNuxtCompatibilityIssues(constraints, nuxt)
if (issues.length) {
throw new Error('Nuxt compatibility issues found:\n' + issues.toString())
}
return true
}
/**
* Check version constraints and return true if passed, otherwise returns false
*/
export function hasNuxtCompatibility (constraints: NuxtCompatibilityConstraints, nuxt: Nuxt = useNuxt()) {
return !checkNuxtCompatibilityIssues(constraints, nuxt).length
}

View File

@ -2,14 +2,43 @@ import type { ModuleContainer } from '../module/container'
import { Nuxt } from './nuxt' import { Nuxt } from './nuxt'
import { NuxtHooks } from './hooks' import { NuxtHooks } from './hooks'
export interface NuxtCompatibilityConstraints {
/**
* Required nuxt version. for example, `^2.14.0` or `>=3.0.0-27219851.6e49637`.
*/
nuxt?: string
}
export interface NuxtCompatibilityIssue {
name: string
message: string
}
export interface NuxtCompatibilityIssues extends Array<NuxtCompatibilityIssue> {
/**
* Return formatted error message
*/
toString(): string
}
export interface ModuleMeta { export interface ModuleMeta {
/** The module name. */ /** Module name */
name?: string name?: string
/** Module version */
version?: string
/** /**
* The configuration key used within `nuxt.config` for this module's options. * The configuration key used within `nuxt.config` for this module's options.
* For example, `@nuxtjs/axios` uses `axios`. * For example, `@nuxtjs/axios` uses `axios`.
*/ */
configKey?: string configKey?: string
/**
* Semver constraints for the versions of Nuxt or features this module are supported.
*/
requires?: NuxtCompatibilityConstraints
[key: string]: any [key: string]: any
} }

View File

@ -3,9 +3,11 @@ import type { NuxtHooks } from './hooks'
import type { NuxtOptions } from './config' import type { NuxtOptions } from './config'
export interface Nuxt { export interface Nuxt {
// Private fields
_version: string
/** The resolved Nuxt configuration. */ /** The resolved Nuxt configuration. */
options: NuxtOptions options: NuxtOptions
hooks: Hookable<NuxtHooks> hooks: Hookable<NuxtHooks>
hook: Nuxt['hooks']['hook'] hook: Nuxt['hooks']['hook']
callHook: Nuxt['hooks']['callHook'] callHook: Nuxt['hooks']['callHook']

View File

@ -1,6 +1,6 @@
{ {
"name": "nuxt3", "name": "nuxt3",
"version": "0.10.0", "version": "3.0.0",
"repository": "nuxt/framework", "repository": "nuxt/framework",
"license": "MIT", "license": "MIT",
"type": "module", "type": "module",

View File

@ -6,12 +6,14 @@ import metaModule from '../meta/module'
import componentsModule from '../components/module' import componentsModule from '../components/module'
import globalImportsModule from '../global-imports/module' import globalImportsModule from '../global-imports/module'
import { distDir, pkgDir } from '../dirs' import { distDir, pkgDir } from '../dirs'
import { version } from '../../package.json'
import { initNitro } from './nitro' import { initNitro } from './nitro'
export function createNuxt (options: NuxtOptions): Nuxt { export function createNuxt (options: NuxtOptions): Nuxt {
const hooks = createHooks<NuxtHooks>() const hooks = createHooks<NuxtHooks>()
const nuxt: Nuxt = { const nuxt: Nuxt = {
_version: version,
options, options,
hooks, hooks,
callHook: hooks.callHook, callHook: hooks.callHook,

View File

@ -1494,6 +1494,7 @@ __metadata:
resolution: "@nuxt/kit@workspace:packages/kit" resolution: "@nuxt/kit@workspace:packages/kit"
dependencies: dependencies:
"@types/lodash.template": ^4 "@types/lodash.template": ^4
"@types/semver": ^7
consola: ^2.15.3 consola: ^2.15.3
create-require: ^1.1.1 create-require: ^1.1.1
defu: ^5.0.0 defu: ^5.0.0
@ -1506,6 +1507,7 @@ __metadata:
pathe: ^0.2.0 pathe: ^0.2.0
rc9: ^1.2.0 rc9: ^1.2.0
scule: ^0.2.1 scule: ^0.2.1
semver: ^7.3.5
std-env: ^2.3.1 std-env: ^2.3.1
ufo: ^0.7.9 ufo: ^0.7.9
unbuild: latest unbuild: latest
@ -2408,6 +2410,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/semver@npm:^7":
version: 7.3.8
resolution: "@types/semver@npm:7.3.8"
checksum: bc90f5a9d5430e36f766c08c898e3c28af88830ebc7736baef8ffc74783bad2efb32f29c40d450e85fc341847ee74e2dd97b76cfc7da407e4232ba9ecae4ff9c
languageName: node
linkType: hard
"@types/serve-static@npm:^1.13.10": "@types/serve-static@npm:^1.13.10":
version: 1.13.10 version: 1.13.10
resolution: "@types/serve-static@npm:1.13.10" resolution: "@types/serve-static@npm:1.13.10"