Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into feat/hydration
Browse files Browse the repository at this point in the history
  • Loading branch information
danielroe committed May 7, 2024
2 parents 81644e8 + 0b8a0ad commit 885356e
Show file tree
Hide file tree
Showing 21 changed files with 1,065 additions and 471 deletions.
6 changes: 3 additions & 3 deletions docs/1.getting-started/6.data-fetching.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ Both `useFetch` and `useAsyncData` share a common set of options and patterns th

Before that, it's imperative to know why these composables exist in the first place.

## Why using specific composables?
## Why use specific composables for data fetching?

When using a framework like Nuxt that can perform calls and render pages on both client and server environments, some challenges must be addressed. This is why Nuxt provides composables to wrap your queries, instead of letting the developer rely on [`$fetch`](/docs/api/utils/dollarfetch) calls alone.
Nuxt is a framework which can run isomorphic (or universal) code in both server and client environments. If the [`$fetch` function](/docs/api/utils/dollarfetch) is used to perform data fetching in the setup function of a Vue component, this may cause data to be fetched twice, once on the server (to render the HTML) and once again on the client (when the HTML is hydrated). This is why Nuxt offers specific data fetching composables so data is fetched only once.

### Network calls duplication

Expand Down Expand Up @@ -76,7 +76,7 @@ async function addTodo() {
```

::warning
Beware that using only `$fetch` will not provide [network calls de-duplication and navigation prevention](#why-using-specific-composables). :br
Beware that using only `$fetch` will not provide [network calls de-duplication and navigation prevention](#why-use-specific-composables-for-data-fetching). :br
It is recommended to use `$fetch` for client-side interactions (event based) or combined with [`useAsyncData`](#useasyncdata) when fetching the initial component data.
::

Expand Down
14 changes: 10 additions & 4 deletions docs/3.api/4.commands/init.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,17 @@ The `init` command initializes a fresh Nuxt project using [unjs/giget](https://g

Option | Default | Description
-------------------------|-----------------|------------------
`--cwd` | | Current working directory
`--log-level` | | Log level
`--template, -t` | `v3` | Specify template name or git repository to use as a template. Format is `gh:org/name` to use a custom github template.
`--force` | `false` | Force clone to any existing directory.
`--offline` | `false` | Do not attempt to download from github and only use local cache.
`--prefer-offline` | `false` | Try local cache first to download templates.
`--shell` | `false` | Open shell in cloned directory (experimental).
`--force, -f` | `false` | Force clone to any existing directory.
`--offline` | `false` | Force offline mode (do not attempt to download template from GitHub and only use local cache).
`--prefer-offline` | `false` | Prefer offline mode (try local cache first to download templates).
`--no-install` | `false` | Skip installing dependencies.
`--git-init` | `false` | Initialize git repository.
`--shell` | `false` | Start shell after installation in project directory (experimental).
`--package-manager` | `npm` | Package manager choice (npm, pnpm, yarn, bun).
`--dir` | | Project directory.

## Environment variables

Expand Down
22 changes: 11 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,34 +42,34 @@
"nuxt": "workspace:*",
"rollup": "^4.17.2",
"vite": "5.2.11",
"vue": "3.4.26"
"vue": "3.4.27"
},
"devDependencies": {
"@eslint/js": "9.1.1",
"@eslint/js": "9.2.0",
"@nuxt/eslint-config": "0.3.10",
"@nuxt/kit": "workspace:*",
"@nuxt/test-utils": "3.12.1",
"@nuxt/webpack-builder": "workspace:*",
"@testing-library/vue": "8.0.3",
"@types/eslint__js": "8.42.3",
"@types/fs-extra": "11.0.4",
"@types/node": "20.12.8",
"@types/node": "20.12.10",
"@types/semver": "7.5.8",
"@vitest/coverage-v8": "1.5.3",
"@vue/test-utils": "2.4.5",
"@vitest/coverage-v8": "1.6.0",
"@vue/test-utils": "2.4.6",
"case-police": "0.6.1",
"changelogen": "0.5.5",
"consola": "3.2.3",
"devalue": "5.0.0",
"eslint": "9.1.1",
"eslint": "9.2.0",
"eslint-plugin-no-only-tests": "3.1.0",
"eslint-plugin-perfectionist": "2.10.0",
"eslint-typegen": "0.2.4",
"execa": "8.0.1",
"fs-extra": "11.2.0",
"globby": "14.0.1",
"h3": "1.11.1",
"happy-dom": "14.7.1",
"happy-dom": "14.10.1",
"jiti": "1.21.0",
"markdownlint-cli": "0.40.0",
"nitropack": "2.9.6",
Expand All @@ -78,19 +78,19 @@
"nuxt-content-twoslash": "0.0.10",
"ofetch": "1.3.4",
"pathe": "1.1.2",
"playwright-core": "1.43.1",
"playwright-core": "1.44.0",
"rimraf": "5.0.5",
"semver": "7.6.0",
"std-env": "3.7.0",
"typescript": "5.4.5",
"ufo": "1.5.3",
"vitest": "1.5.3",
"vitest": "1.6.0",
"vitest-environment-nuxt": "1.0.0",
"vue": "3.4.26",
"vue": "3.4.27",
"vue-router": "4.3.2",
"vue-tsc": "2.0.16"
},
"packageManager": "pnpm@9.0.6",
"packageManager": "pnpm@9.1.0",
"engines": {
"node": "^14.18.0 || >=16.10.0"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"nitropack": "2.9.6",
"unbuild": "latest",
"vite": "5.2.11",
"vitest": "1.5.3",
"vitest": "1.6.0",
"webpack": "5.91.0"
},
"engines": {
Expand Down
14 changes: 7 additions & 7 deletions packages/nuxt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,18 @@
"@nuxt/schema": "workspace:*",
"@nuxt/telemetry": "^2.5.4",
"@nuxt/vite-builder": "workspace:*",
"@unhead/dom": "^1.9.8",
"@unhead/ssr": "^1.9.8",
"@unhead/vue": "^1.9.8",
"@vue/shared": "^3.4.26",
"@unhead/dom": "^1.9.10",
"@unhead/ssr": "^1.9.10",
"@unhead/vue": "^1.9.10",
"@vue/shared": "^3.4.27",
"acorn": "8.11.3",
"c12": "^1.10.0",
"chokidar": "^3.6.0",
"cookie-es": "^1.1.0",
"defu": "^6.1.4",
"destr": "^2.0.3",
"devalue": "^5.0.0",
"esbuild": "^0.20.2",
"esbuild": "^0.21.0",
"escape-string-regexp": "^5.0.0",
"estree-walker": "^3.0.3",
"fs-extra": "^11.2.0",
Expand Down Expand Up @@ -111,7 +111,7 @@
"unplugin-vue-router": "^0.7.0",
"unstorage": "^1.10.2",
"untyped": "^1.4.2",
"vue": "^3.4.26",
"vue": "^3.4.27",
"vue-bundle-renderer": "^2.0.0",
"vue-devtools-stub": "^0.1.0",
"vue-router": "^4.3.2"
Expand All @@ -124,7 +124,7 @@
"@vitejs/plugin-vue": "5.0.4",
"unbuild": "latest",
"vite": "5.2.11",
"vitest": "1.5.3"
"vitest": "1.6.0"
},
"peerDependencies": {
"@parcel/watcher": "^2.1.0",
Expand Down
8 changes: 2 additions & 6 deletions packages/nuxt/src/app/composables/asyncData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,9 +378,7 @@ export function useAsyncData<
const hasScope = getCurrentScope()
if (options.watch) {
const unsub = watch(options.watch, () => asyncData.refresh())
if (instance) {
onUnmounted(unsub)
} else if (hasScope) {
if (hasScope) {
onScopeDispose(unsub)
}
}
Expand All @@ -389,9 +387,7 @@ export function useAsyncData<
await asyncData.refresh()
}
})
if (instance) {
onUnmounted(off)
} else if (hasScope) {
if (hasScope) {
onScopeDispose(off)
}
}
Expand Down
102 changes: 102 additions & 0 deletions packages/nuxt/src/app/composables/script-stubs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import type { UseScriptInput } from '@unhead/vue'
import { createError } from './error'

function renderStubMessage (name: string) {
const message = `\`${name}\` is provided by @nuxt/scripts. Check your console to install it or run 'npx nuxi@latest module add @nuxt/scripts' to install it.`
if (import.meta.client) {
throw createError({
fatal: true,
statusCode: 500,
statusMessage: message,
})
}
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScript<T extends Record<string | symbol, any>> (input: UseScriptInput, options?: Record<string, unknown>) {
renderStubMessage('useScript')
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useElementScriptTrigger (...args: unknown[]) {
renderStubMessage('useElementScriptTrigger')
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useConsentScriptTrigger (...args: unknown[]) {
renderStubMessage('useConsentScriptTrigger')
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useAnalyticsPageEvent (...args: unknown[]) {
renderStubMessage('useAnalyticsPageEvent')
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptGoogleAnalytics (...args: unknown[]) {
renderStubMessage('useScriptGoogleAnalytics')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptPlausibleAnalytics (...args: unknown[]) {
renderStubMessage('useScriptPlausibleAnalytics')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptCloudflareWebAnalytics (...args: unknown[]) {
renderStubMessage('useScriptCloudflareWebAnalytics')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptFathomAnalytics (...args: unknown[]) {
renderStubMessage('useScriptFathomAnalytics')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptMatomoAnalytics (...args: unknown[]) {
renderStubMessage('useScriptMatomoAnalytics')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptGoogleTagManager (...args: unknown[]) {
renderStubMessage('useScriptGoogleTagManager')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptSegment (...args: unknown[]) {
renderStubMessage('useScriptSegment')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptFacebookPixel (...args: unknown[]) {
renderStubMessage('useScriptFacebookPixel')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptXPixel (...args: unknown[]) {
renderStubMessage('useScriptXPixel')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptIntercom (...args: unknown[]) {
renderStubMessage('useScriptIntercom')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptHotjar (...args: unknown[]) {
renderStubMessage('useScriptHotjar')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptStripe (...args: unknown[]) {
renderStubMessage('useScriptStripe')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptLemonSqueezy (...args: unknown[]) {
renderStubMessage('useScriptLemonSqueezy')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptVimeoPlayer (...args: unknown[]) {
renderStubMessage('useScriptVimeoPlayer')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptYouTubeIframe (...args: unknown[]) {
renderStubMessage('useScriptYouTubeIframe')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptGoogleMaps (...args: unknown[]) {
renderStubMessage('useScriptGoogleMaps')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptNpm (...args: unknown[]) {
renderStubMessage('useScriptNpm')
}
4 changes: 2 additions & 2 deletions packages/nuxt/src/app/nuxt.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { effectScope, getCurrentInstance, hasInjectionContext, reactive } from 'vue'
import { effectScope, getCurrentInstance, getCurrentScope, hasInjectionContext, reactive } from 'vue'
import type { App, EffectScope, Ref, VNode, onErrorCaptured } from 'vue'
import type { RouteLocationNormalizedLoaded } from '#vue-router'
import type { HookCallback, Hookable } from 'hookable'
Expand Down Expand Up @@ -255,7 +255,7 @@ export function createNuxtApp (options: CreateOptions) {
data: {},
},
runWithContext (fn: any) {
if (nuxtApp._scope.active) {
if (nuxtApp._scope.active && !getCurrentScope()) {
return nuxtApp._scope.run(() => callWithNuxt(nuxtApp, fn))
}
return callWithNuxt(nuxtApp, fn)
Expand Down
15 changes: 15 additions & 0 deletions packages/nuxt/src/core/nuxt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import importsModule from '../imports/module'

import { distDir, pkgDir } from '../dirs'
import { version } from '../../package.json'
import { scriptsStubsPreset } from '../imports/presets'
import { ImportProtectionPlugin, nuxtImportProtections } from './plugins/import-protection'
import type { UnctxTransformPluginOptions } from './plugins/unctx'
import { UnctxTransformPlugin } from './plugins/unctx'
Expand Down Expand Up @@ -126,6 +127,14 @@ async function initNuxt (nuxt: Nuxt) {
}
})

// Prompt to install `@nuxt/scripts` if user has configured it
// @ts-expect-error scripts types are not present as the module is not installed
if (nuxt.options.scripts) {
if (!nuxt.options._modules.some(m => m === '@nuxt/scripts' || m === '@nuxt/scripts-nightly')) {
await import('../core/features').then(({ installNuxtModule }) => installNuxtModule('@nuxt/scripts'))
}
}

// Add plugin normalization plugin
addBuildPlugin(RemovePluginMetadataPlugin(nuxt))

Expand Down Expand Up @@ -554,6 +563,12 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
}
}

if (!options._modules.some(m => m === '@nuxt/scripts' || m === '@nuxt/scripts-nightly')) {
options.imports = defu(options.imports, {
presets: [scriptsStubsPreset],
})
}

// Nuxt Webpack Builder is currently opt-in
if (options.builder === '@nuxt/webpack-builder') {
if (!await import('./features').then(r => r.ensurePackageInstalled('@nuxt/webpack-builder', {
Expand Down
27 changes: 27 additions & 0 deletions packages/nuxt/src/imports/presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,33 @@ const granularAppPresets: InlinePreset[] = [
},
]

export const scriptsStubsPreset = {
imports: [
'useConsentScriptTrigger',
'useAnalyticsPageEvent',
'useElementScriptTrigger',
'useScript',
'useScriptGoogleAnalytics',
'useScriptPlausibleAnalytics',
'useScriptCloudflareWebAnalytics',
'useScriptFathomAnalytics',
'useScriptMatomoAnalytics',
'useScriptGoogleTagManager',
'useScriptSegment',
'useScriptFacebookPixel',
'useScriptXPixel',
'useScriptIntercom',
'useScriptHotjar',
'useScriptStripe',
'useScriptLemonSqueezy',
'useScriptVimeoPlayer',
'useScriptYouTubeIframe',
'useScriptGoogleMaps',
'useScriptNpm',
],
from: '#app/composables/script-stubs',
} satisfies InlinePreset

// This is a separate preset as we'll swap these out for import from `vue-router` itself in `pages` module
const routerPreset = defineUnimportPreset({
imports: ['onBeforeRouteLeave', 'onBeforeRouteUpdate'],
Expand Down
7 changes: 6 additions & 1 deletion packages/nuxt/src/imports/transform.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createUnplugin } from 'unplugin'
import type { Unimport } from 'unimport'
import { normalize } from 'pathe'
import { tryUseNuxt } from '@nuxt/kit'
import type { ImportsOptions } from 'nuxt/schema'
import { isJS, isVue } from '../core/utils'

Expand Down Expand Up @@ -37,7 +38,11 @@ export const TransformPlugin = createUnplugin(({ ctx, options, sourcemap }: { ct
return
}

const { s } = await ctx.injectImports(code, id, { autoImport: options.autoImport && !isNodeModule })
const { s, imports } = await ctx.injectImports(code, id, { autoImport: options.autoImport && !isNodeModule })
if (imports.some(i => i.from === '#app/composables/script-stubs') && tryUseNuxt()?.options.test === false) {
import('../core/features').then(({ installNuxtModule }) => installNuxtModule('@nuxt/scripts'))
}

if (s.hasChanged()) {
return {
code: s.toString(),
Expand Down

0 comments on commit 885356e

Please sign in to comment.