Skip to content

Commit

Permalink
Merge branch 'nuxt:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
Ibochkarev authored Jun 11, 2024
2 parents 8601ea7 + 7777f05 commit e70d858
Show file tree
Hide file tree
Showing 24 changed files with 147 additions and 117 deletions.
40 changes: 40 additions & 0 deletions docs/1.getting-started/12.upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,46 @@ export default defineNuxtConfig({

Please report an issue if you are doing this, as we do not plan to keep this as configurable.

#### Removal of deprecated `boolean` values for `dedupe` option when calling `refresh` in `useAsyncData` and `useFetch`

🚦 **Impact Level**: Minimal

##### What Changed

Previously it was possible to pass `dedupe: boolean` to `refresh`. These were aliases of `cancel` (`true`) and `defer` (`false`).

```ts twoslash [app.vue]
const { refresh } = await useAsyncData(async () => ({ message: 'Hello, Nuxt 3!' }))

async function refreshData () {
await refresh({ dedupe: true })
}
```

##### Reasons for Change

These aliases were removed, for greater clarity.

The issue came up when adding `dedupe` as an option to `useAsyncData`, and we removed the boolean values as they ended up being _opposites_.

`refresh({ dedupe: false })` meant 'do not _cancel_ existing requests in favour of this new one'. But passing `dedupe: true` within the options of `useAsyncData` means 'do not make any new requests if there is an existing pending request.' (See [PR](https://github.com/nuxt/nuxt/pull/24564#pullrequestreview-1764584361).)

##### Migration Steps

The migration should be straightforward:

```diff
const { refresh } = await useAsyncData(async () => ({ message: 'Hello, Nuxt 3!' }))

async function refreshData () {
- await refresh({ dedupe: true })
+ await refresh({ dedupe: 'cancel' })

- await refresh({ dedupe: false })
+ await refresh({ dedupe: 'defer' })
}
```

#### Respect defaults when clearing `data` in `useAsyncData` and `useFetch`

🚦 **Impact Level**: Minimal
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nuxt/kit",
"version": "3.11.2",
"version": "3.12.0",
"repository": {
"type": "git",
"url": "git+https://github.com/nuxt/nuxt.git",
Expand Down
57 changes: 16 additions & 41 deletions packages/kit/src/module/define.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { performance } from 'node:perf_hooks'
import { defu } from 'defu'
import { applyDefaults } from 'untyped'
import { dirname } from 'pathe'
import type { ModuleDefinition, ModuleOptions, ModuleSetupInstallResult, ModuleSetupReturn, Nuxt, NuxtModule, NuxtOptions, ResolvedModuleOptions, ResolvedNuxtTemplate } from '@nuxt/schema'
import type { ModuleDefinition, ModuleOptions, ModuleSetupReturn, Nuxt, NuxtModule, NuxtOptions, ResolvedNuxtTemplate } from '@nuxt/schema'
import { logger } from '../logger'
import { nuxtCtx, tryUseNuxt, useNuxt } from '../context'
import { checkNuxtCompatibility, isNuxt2 } from '../compatibility'
Expand All @@ -13,53 +13,28 @@ import { compileTemplate, templateUtils } from '../internal/template'
* Define a Nuxt module, automatically merging defaults with user provided options, installing
* any hooks that are provided, and calling an optional setup function for full control.
*/
export function defineNuxtModule<TOptions extends ModuleOptions> (definition: ModuleDefinition<TOptions> | NuxtModule<TOptions>): NuxtModule<TOptions>

export function defineNuxtModule<TOptions extends ModuleOptions> (): {
with: <TOptionsDefaults extends Partial<TOptions>> (
definition: ModuleDefinition<TOptions, TOptionsDefaults> | NuxtModule<TOptions, TOptionsDefaults>
) => NuxtModule<TOptions, TOptionsDefaults>
}

export function defineNuxtModule<TOptions extends ModuleOptions> (definition?: ModuleDefinition<TOptions> | NuxtModule<TOptions>) {
if (definition) {
return _defineNuxtModule(definition)
}

return {
with: <TOptionsDefaults extends Partial<TOptions>>(
definition: ModuleDefinition<TOptions, TOptionsDefaults> | NuxtModule<TOptions, TOptionsDefaults>,
) => _defineNuxtModule(definition),
}
}

function _defineNuxtModule<TOptions extends ModuleOptions, TOptionsDefaults extends Partial<TOptions>> (definition: ModuleDefinition<TOptions, TOptionsDefaults> | NuxtModule<TOptions, TOptionsDefaults>): NuxtModule<TOptions, TOptionsDefaults> {
if (typeof definition === 'function') { return _defineNuxtModule<TOptions, TOptionsDefaults>({ setup: definition }) }
export function defineNuxtModule<OptionsT extends ModuleOptions> (definition: ModuleDefinition<OptionsT> | NuxtModule<OptionsT>): NuxtModule<OptionsT> {
if (typeof definition === 'function') { return defineNuxtModule({ setup: definition }) }

// Normalize definition and meta
const module: ModuleDefinition<TOptions, TOptionsDefaults> & Required<Pick<ModuleDefinition<TOptions, TOptionsDefaults>, 'meta'>> = defu(definition, { meta: {} })

module.meta.configKey ||= module.meta.name
const module: ModuleDefinition<OptionsT> & Required<Pick<ModuleDefinition<OptionsT>, 'meta'>> = defu(definition, { meta: {} })
if (module.meta.configKey === undefined) {
module.meta.configKey = module.meta.name
}

// Resolves module options from inline options, [configKey] in nuxt.config, defaults and schema
async function getOptions (inlineOptions?: Partial<TOptions>, nuxt: Nuxt = useNuxt()): Promise<ResolvedModuleOptions<TOptions, TOptionsDefaults>> {
const nuxtConfigOptionsKey = module.meta.configKey || module.meta.name

const nuxtConfigOptions: Partial<TOptions> = nuxtConfigOptionsKey && nuxtConfigOptionsKey in nuxt.options ? nuxt.options[<keyof NuxtOptions> nuxtConfigOptionsKey] : {}

const optionsDefaults: TOptionsDefaults = module.defaults instanceof Function ? module.defaults(nuxt) : module.defaults ?? <TOptionsDefaults> {}

let options: ResolvedModuleOptions<TOptions, TOptionsDefaults> = defu(inlineOptions, nuxtConfigOptions, optionsDefaults)

async function getOptions (inlineOptions?: OptionsT, nuxt: Nuxt = useNuxt()) {
const configKey = module.meta.configKey || module.meta.name!
const _defaults = module.defaults instanceof Function ? module.defaults(nuxt) : module.defaults
let _options = defu(inlineOptions, nuxt.options[configKey as keyof NuxtOptions], _defaults) as OptionsT
if (module.schema) {
options = await applyDefaults(module.schema, options) as any
_options = await applyDefaults(module.schema, _options) as OptionsT
}

return Promise.resolve(options)
return Promise.resolve(_options)
}

// Module format is always a simple function
async function normalizedModule (this: any, inlineOptions: Partial<TOptions>, nuxt: Nuxt): Promise<ModuleSetupReturn> {
async function normalizedModule (this: any, inlineOptions: OptionsT, nuxt: Nuxt) {
if (!nuxt) {
nuxt = tryUseNuxt() || this.nuxt /* invoked by nuxt 2 */
}
Expand Down Expand Up @@ -112,7 +87,7 @@ function _defineNuxtModule<TOptions extends ModuleOptions, TOptionsDefaults exte
if (res === false) { return false }

// Return module install result
return defu(res, <ModuleSetupInstallResult> {
return defu(res, <ModuleSetupReturn> {
timings: {
setup: setupTime,
},
Expand All @@ -123,7 +98,7 @@ function _defineNuxtModule<TOptions extends ModuleOptions, TOptionsDefaults exte
normalizedModule.getMeta = () => Promise.resolve(module.meta)
normalizedModule.getOptions = getOptions

return <NuxtModule<TOptions, TOptionsDefaults>> normalizedModule
return normalizedModule as NuxtModule<OptionsT>
}

// -- Nuxt 2 compatibility shims --
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/module/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export async function installModule<
isNuxt2()
// @ts-expect-error Nuxt 2 `moduleContainer` is not typed
? await nuxtModule.call(nuxt.moduleContainer, inlineOptions, nuxt)
: await nuxtModule(inlineOptions || {}, nuxt)
: await nuxtModule(inlineOptions, nuxt)
) ?? {}
if (res === false /* setup aborted */) {
return
Expand Down
6 changes: 3 additions & 3 deletions packages/kit/src/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export async function _generateTypes (nuxt: Nuxt) {
/* Base options: */
esModuleInterop: true,
skipLibCheck: true,
target: 'es2022',
target: 'ESNext',
allowJs: true,
resolveJsonModule: true,
moduleDetection: 'force',
Expand All @@ -147,11 +147,11 @@ export async function _generateTypes (nuxt: Nuxt) {
forceConsistentCasingInFileNames: true,
noImplicitOverride: true,
/* If NOT transpiling with TypeScript: */
module: hasTypescriptVersionWithModulePreserve ? 'preserve' : 'es2022',
module: hasTypescriptVersionWithModulePreserve ? 'preserve' : 'ESNext',
noEmit: true,
/* If your code runs in the DOM: */
lib: [
'es2022',
'ESNext',
'dom',
'dom.iterable',
],
Expand Down
2 changes: 1 addition & 1 deletion packages/nuxt/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nuxt",
"version": "3.11.2",
"version": "3.12.0",
"repository": {
"type": "git",
"url": "git+https://github.com/nuxt/nuxt.git",
Expand Down
10 changes: 9 additions & 1 deletion packages/nuxt/src/app/components/nuxt-island.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Component } from 'vue'
import type { Component, PropType } from 'vue'
import { Fragment, Teleport, computed, createStaticVNode, createVNode, defineComponent, getCurrentInstance, h, nextTick, onMounted, ref, toRaw, watch, withMemo } from 'vue'
import { debounce } from 'perfect-debounce'
import { hash } from 'ohash'
Expand Down Expand Up @@ -59,6 +59,10 @@ export default defineComponent({
type: Object,
default: () => ({}),
},
scopeId: {
type: String as PropType<string | undefined | null>,
default: () => undefined,
},
source: {
type: String,
default: () => undefined,
Expand Down Expand Up @@ -131,6 +135,10 @@ export default defineComponent({
const currentSlots = Object.keys(slots)
let html = ssrHTML.value

if (props.scopeId) {
html = html.replace(/^<[^> ]*/, full => full + ' ' + props.scopeId)
}

if (import.meta.client && !canLoadClientComponent.value) {
for (const [key, value] of Object.entries(payloads.components || {})) {
html = html.replace(new RegExp(` data-island-uid="${uid.value}" data-island-component="${key}"[^>]*>`), (full) => {
Expand Down
5 changes: 2 additions & 3 deletions packages/nuxt/src/app/composables/asyncData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { onNuxtReady } from './ready'
import { asyncDataDefaults, resetAsyncDataToUndefined } from '#build/nuxt.config.mjs'

// TODO: temporary module for backwards compatibility
import type { DefaultAsyncDataErrorValue, DefaultAsyncDataValue } from '#app/defaults'
import type { DedupeOption, DefaultAsyncDataErrorValue, DefaultAsyncDataValue } from '#app/defaults'

export type AsyncDataRequestStatus = 'idle' | 'pending' | 'success' | 'error'

Expand Down Expand Up @@ -99,7 +99,6 @@ export interface AsyncDataOptions<

export interface AsyncDataExecuteOptions {
_initial?: boolean
// TODO: remove boolean option in Nuxt 4
/**
* Force a refresh, even if there is already a pending request. Previous requests will
* not be cancelled, but their result will not affect the data/pending state - and any
Expand All @@ -108,7 +107,7 @@ export interface AsyncDataExecuteOptions {
* Instead of using `boolean` values, use `cancel` for `true` and `defer` for `false`.
* Boolean values will be removed in a future release.
*/
dedupe?: boolean | 'cancel' | 'defer'
dedupe?: DedupeOption
}

export interface _AsyncData<DataT, ErrorT> {
Expand Down
1 change: 1 addition & 0 deletions packages/nuxt/src/app/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
export type DefaultAsyncDataErrorValue = null
export type DefaultAsyncDataValue = null
export type DefaultErrorValue = null
export type DedupeOption = boolean | 'cancel' | 'defer'

export {}
4 changes: 2 additions & 2 deletions packages/nuxt/src/app/nuxt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -563,8 +563,8 @@ function wrappedConfig (runtimeConfig: Record<string, unknown>) {
const keys = Object.keys(runtimeConfig).map(key => `\`${key}\``)
const lastKey = keys.pop()
return new Proxy(runtimeConfig, {
get (target, p: string, receiver) {
if (p !== 'public' && !(p in target) && !p.startsWith('__v') /* vue check for reactivity, e.g. `__v_isRef` */) {
get (target, p, receiver) {
if (typeof p === 'string' && p !== 'public' && !(p in target) && !p.startsWith('__v') /* vue check for reactivity, e.g. `__v_isRef` */) {
console.warn(`[nuxt] Could not access \`${p}\`. The only available runtime config keys on the client side are ${keys.join(', ')} and ${lastKey}. See \`https://nuxt.com/docs/guide/going-further/runtime-config\` for more information.`)
}
return Reflect.get(target, p, receiver)
Expand Down
4 changes: 3 additions & 1 deletion packages/nuxt/src/components/runtime/server-component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { defineComponent, h, ref } from 'vue'
import { defineComponent, getCurrentInstance, h, ref } from 'vue'
import NuxtIsland from '#app/components/nuxt-island'
import { useRoute } from '#app/composables/router'
import { isPrerendered } from '#app/composables/payload'
Expand All @@ -11,6 +11,7 @@ export const createServerComponent = (name: string) => {
props: { lazy: Boolean },
emits: ['error'],
setup (props, { attrs, slots, expose, emit }) {
const vm = getCurrentInstance()
const islandRef = ref<null | typeof NuxtIsland>(null)

expose({
Expand All @@ -22,6 +23,7 @@ export const createServerComponent = (name: string) => {
name,
lazy: props.lazy,
props: attrs,
scopeId: vm?.vnode.scopeId,
ref: islandRef,
onError: (err) => {
emit('error', err)
Expand Down
5 changes: 4 additions & 1 deletion packages/nuxt/src/core/nitro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,10 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
}

// Init nitro
const nitro = await createNitro(nitroConfig)
const nitro = await createNitro(nitroConfig, {
// @ts-expect-error this will be valid in a future version of Nitro
compatibilityDate: nuxt.options.compatibilityDate,
})

// Trigger Nitro reload when SPA loading template changes
const spaLoadingTemplateFilePath = await spaLoadingTemplatePath(nuxt)
Expand Down
9 changes: 6 additions & 3 deletions packages/nuxt/src/core/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ export const pluginsDeclaration: NuxtTemplate = {

const pluginsName = (await annotatePlugins(ctx.nuxt, ctx.app.plugins)).filter(p => p.name).map(p => `'${p.name}'`)

const isV4 = ctx.nuxt.options.future.compatibilityVersion === 4

return `// Generated by Nuxt'
import type { Plugin } from '#app'
Expand All @@ -131,9 +133,10 @@ declare module '#app' {
}
declare module '#app/defaults' {
type DefaultAsyncDataErrorValue = ${ctx.nuxt.options.future.compatibilityVersion === 4 ? 'undefined' : 'null'}
type DefaultAsyncDataValue = ${ctx.nuxt.options.future.compatibilityVersion === 4 ? 'undefined' : 'null'}
type DefaultErrorValue = ${ctx.nuxt.options.future.compatibilityVersion === 4 ? 'undefined' : 'null'}
type DefaultAsyncDataErrorValue = ${isV4 ? 'undefined' : 'null'}
type DefaultAsyncDataValue = ${isV4 ? 'undefined' : 'null'}
type DefaultErrorValue = ${isV4 ? 'undefined' : 'null'}
type DedupeOption = ${isV4 ? '\'cancel\' | \'defer\'' : 'boolean | \'cancel\' | \'defer\''}
}
declare module 'vue' {
Expand Down
5 changes: 3 additions & 2 deletions packages/nuxt/src/pages/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,12 @@ export function generateRoutesFromFiles (files: ScannedFile[], options: Generate
return prepareRoutes(routes)
}

export async function augmentPages (routes: NuxtPage[], vfs: Record<string, string>, augmentedPages = new Set<NuxtPage>()) {
export async function augmentPages (routes: NuxtPage[], vfs: Record<string, string>, augmentedPages = new Set<string>()) {
for (const route of routes) {
if (!augmentedPages.has(route) && route.file) {
if (route.file && !augmentedPages.has(route.file)) {
const fileContent = route.file in vfs ? vfs[route.file] : fs.readFileSync(await resolvePath(route.file), 'utf-8')
Object.assign(route, await getRouteMeta(fileContent, route.file))
augmentedPages.add(route.file)
}

if (route.children && route.children.length > 0) {
Expand Down
5 changes: 3 additions & 2 deletions packages/schema/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nuxt/schema",
"version": "3.11.2",
"version": "3.12.0",
"repository": {
"type": "git",
"url": "git+https://github.com/nuxt/nuxt.git",
Expand Down Expand Up @@ -63,6 +63,7 @@
"webpack-dev-middleware": "7.2.1"
},
"dependencies": {
"compatx": "^0.1.3",
"consola": "^3.2.3",
"defu": "^6.1.4",
"hookable": "^5.5.3",
Expand All @@ -71,8 +72,8 @@
"scule": "^1.3.0",
"std-env": "^3.7.0",
"ufo": "^1.5.3",
"unimport": "^3.7.2",
"uncrypto": "^0.1.3",
"unimport": "^3.7.2",
"untyped": "^1.4.2"
},
"engines": {
Expand Down
12 changes: 12 additions & 0 deletions packages/schema/src/config/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ export default defineUntypedSchema({
*/
extends: null,

/**
* Specify a compatibility date for your app.
*
* This is used to control the behavior of presets in Nitro, Nuxt Image
* and other modules that may change behavior without a major version bump.
*
* We plan to improve the tooling around this feature in the future.
*
* @type {typeof import('compatx').DateString | Record<string, typeof import('compatx').DateString>}
*/
compatibilityDate: undefined,

/**
* Extend project from a local or remote source.
*
Expand Down
Loading

0 comments on commit e70d858

Please sign in to comment.