mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-21 21:25:11 +00:00
feat(nuxt): automatically generate unique keys for keyed composables (#4955)
Co-authored-by: Pooya Parsa <pyapar@gmail.com>
This commit is contained in:
parent
4d607080f5
commit
23546a270c
@ -22,6 +22,7 @@
|
||||
"jsdoc/require-param": "off",
|
||||
"jsdoc/require-returns": "off",
|
||||
"jsdoc/require-param-type": "off",
|
||||
"no-redeclare": "off",
|
||||
"import/no-restricted-paths": [
|
||||
"error",
|
||||
{
|
||||
|
@ -5,13 +5,17 @@ Within your pages, components, and plugins you can use useAsyncData to get acces
|
||||
## Type
|
||||
|
||||
```ts [Signature]
|
||||
function useAsyncData(
|
||||
handler: (nuxtApp?: NuxtApp) => Promise<DataT>,
|
||||
options?: AsyncDataOptions<DataT>
|
||||
): AsyncData<DataT>
|
||||
function useAsyncData(
|
||||
key: string,
|
||||
handler: (nuxtApp?: NuxtApp) => Promise<DataT>,
|
||||
options?: AsyncDataOptions
|
||||
): Promise<DataT>
|
||||
options?: AsyncDataOptions<DataT>
|
||||
): Promise<AsyncData<DataT>>
|
||||
|
||||
type AsyncDataOptions = {
|
||||
type AsyncDataOptions<DataT> = {
|
||||
server?: boolean
|
||||
lazy?: boolean
|
||||
default?: () => DataT | Ref<DataT>
|
||||
@ -21,7 +25,7 @@ type AsyncDataOptions = {
|
||||
initialCache?: boolean
|
||||
}
|
||||
|
||||
type DataT = {
|
||||
type AsyncData<DataT> = {
|
||||
data: Ref<DataT>
|
||||
pending: Ref<boolean>
|
||||
refresh: () => Promise<void>
|
||||
@ -31,7 +35,7 @@ type DataT = {
|
||||
|
||||
## Params
|
||||
|
||||
* **key**: a unique key to ensure that data fetching can be properly de-duplicated across requests
|
||||
* **key**: a unique key to ensure that data fetching can be properly de-duplicated across requests. If you do not provide a key, then a key that is unique to the file name and line number of the instance of `useAsyncData` will be generated for you.
|
||||
* **handler**: an asynchronous function that returns a value
|
||||
* **options**:
|
||||
* _lazy_: whether to resolve the async function after loading the route, instead of blocking navigation (defaults to `false`)
|
||||
|
@ -6,9 +6,9 @@ This composable provides a convenient wrapper around [`useAsyncData`](/api/compo
|
||||
|
||||
```ts [Signature]
|
||||
function useFetch(
|
||||
url: string | Request,
|
||||
options?: UseFetchOptions
|
||||
): Promise<DataT>
|
||||
url: string | Request | Ref<string | Request> | () => string | Request,
|
||||
options?: UseFetchOptions<DataT>
|
||||
): Promise<AsyncData<DataT>>
|
||||
|
||||
type UseFetchOptions = {
|
||||
key?: string,
|
||||
@ -25,7 +25,7 @@ type UseFetchOptions = {
|
||||
watch?: WatchSource[]
|
||||
}
|
||||
|
||||
type DataT = {
|
||||
type AsyncData<DataT> = {
|
||||
data: Ref<DataT>
|
||||
pending: Ref<boolean>
|
||||
refresh: () => Promise<void>
|
||||
@ -51,6 +51,10 @@ type DataT = {
|
||||
* `watch`: watch reactive sources to auto-refresh
|
||||
* `transform`: A function that can be used to alter `handler` function result after resolving.
|
||||
|
||||
::alert{type=warning}
|
||||
If you provide a function or ref as the `url` parameter, or if you provide functions as arguments to the `options` parameter, then the `useFetch` call will not match other `useFetch` calls elsewhere in your codebase, even if the options seem to be identical. If you wish to force a match, you may provide your own key in `options`.
|
||||
::
|
||||
|
||||
## Return values
|
||||
|
||||
* **data**: the result of the asynchronous function that is passed in
|
||||
|
@ -1,10 +1,11 @@
|
||||
# `useState`
|
||||
|
||||
```ts
|
||||
useState<T>(init?: () => T | Ref<T>): Ref<T>
|
||||
useState<T>(key: string, init?: () => T | Ref<T>): Ref<T>
|
||||
```
|
||||
|
||||
* **key**: A unique key ensuring that data fetching is properly de-duplicated across requests
|
||||
* **key**: A unique key ensuring that data fetching is properly de-duplicated across requests. If you do not provide a key, then a key that is unique to the file and line number of the instance of `useState` will be generated for you.
|
||||
* **init**: A function that provides initial value for the state when not initiated. This function can also return a `Ref`.
|
||||
* **T**: (typescript only) Specify the type of state
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
const ctr = ref(0)
|
||||
const { data, pending, refresh } = await useAsyncData('/api/hello', () => $fetch(`/api/hello/${ctr.value}`), { watch: [ctr] })
|
||||
const { data, pending, refresh } = await useAsyncData(() => $fetch(`/api/hello/${ctr.value}`), { watch: [ctr] })
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -20,8 +20,8 @@
|
||||
"lint": "eslint --ext .vue,.ts,.js,.mjs .",
|
||||
"lint:docs": "markdownlint ./docs/content && case-police 'docs/content**/*.md'",
|
||||
"lint:docs:fix": "markdownlint ./docs/content --fix && case-police 'docs/content**/*.md' --fix",
|
||||
"nuxi": "NUXT_TELEMETRY_DISABLED=1 node ./packages/nuxi/bin/nuxi.mjs",
|
||||
"nuxt": "NUXT_TELEMETRY_DISABLED=1 node ./packages/nuxi/bin/nuxi.mjs",
|
||||
"nuxi": "NUXT_TELEMETRY_DISABLED=1 JITI_ESM_RESOLVE=1 node ./packages/nuxi/bin/nuxi.mjs",
|
||||
"nuxt": "NUXT_TELEMETRY_DISABLED=1 JITI_ESM_RESOLVE=1 node ./packages/nuxi/bin/nuxi.mjs",
|
||||
"play": "echo use yarn dev && exit 1",
|
||||
"release": "yarn && yarn lint && FORCE_COLOR=1 lerna publish -m \"chore: release\" && yarn stub",
|
||||
"stub": "lerna run prepack -- --stub",
|
||||
|
@ -46,7 +46,15 @@ export interface _AsyncData<DataT, ErrorT> {
|
||||
export type AsyncData<Data, Error> = _AsyncData<Data, Error> & Promise<_AsyncData<Data, Error>>
|
||||
|
||||
const getDefault = () => null
|
||||
|
||||
export function useAsyncData<
|
||||
DataT,
|
||||
DataE = Error,
|
||||
Transform extends _Transform<DataT> = _Transform<DataT, DataT>,
|
||||
PickKeys extends KeyOfRes<Transform> = KeyOfRes<Transform>
|
||||
> (
|
||||
handler: (ctx?: NuxtApp) => Promise<DataT>,
|
||||
options?: AsyncDataOptions<DataT, Transform, PickKeys>
|
||||
): AsyncData<PickFrom<ReturnType<Transform>, PickKeys>, DataE | null | true>
|
||||
export function useAsyncData<
|
||||
DataT,
|
||||
DataE = Error,
|
||||
@ -55,14 +63,26 @@ export function useAsyncData<
|
||||
> (
|
||||
key: string,
|
||||
handler: (ctx?: NuxtApp) => Promise<DataT>,
|
||||
options: AsyncDataOptions<DataT, Transform, PickKeys> = {}
|
||||
): AsyncData<PickFrom<ReturnType<Transform>, PickKeys>, DataE | null | true> {
|
||||
options?: AsyncDataOptions<DataT, Transform, PickKeys>
|
||||
): AsyncData<PickFrom<ReturnType<Transform>, PickKeys>, DataE | null | true>
|
||||
export function useAsyncData<
|
||||
DataT,
|
||||
DataE = Error,
|
||||
Transform extends _Transform<DataT> = _Transform<DataT, DataT>,
|
||||
PickKeys extends KeyOfRes<Transform> = KeyOfRes<Transform>
|
||||
> (...args): AsyncData<PickFrom<ReturnType<Transform>, PickKeys>, DataE | null | true> {
|
||||
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
|
||||
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
let [key, handler, options = {}] = args as [string, (ctx?: NuxtApp) => Promise<DataT>, AsyncDataOptions<DataT, Transform, PickKeys>]
|
||||
|
||||
// Validate arguments
|
||||
if (typeof key !== 'string') {
|
||||
throw new TypeError('asyncData key must be a string')
|
||||
throw new TypeError('[nuxt] [asyncData] key must be a string.')
|
||||
}
|
||||
if (typeof handler !== 'function') {
|
||||
throw new TypeError('asyncData handler must be a function')
|
||||
throw new TypeError('[nuxt] [asyncData] handler must be a function.')
|
||||
}
|
||||
|
||||
// Apply defaults
|
||||
@ -180,7 +200,15 @@ export function useAsyncData<
|
||||
|
||||
return asyncDataPromise as AsyncData<PickFrom<ReturnType<Transform>, PickKeys>, DataE>
|
||||
}
|
||||
|
||||
export function useLazyAsyncData<
|
||||
DataT,
|
||||
DataE = Error,
|
||||
Transform extends _Transform<DataT> = _Transform<DataT, DataT>,
|
||||
PickKeys extends KeyOfRes<Transform> = KeyOfRes<Transform>
|
||||
> (
|
||||
handler: (ctx?: NuxtApp) => Promise<DataT>,
|
||||
options?: Omit<AsyncDataOptions<DataT, Transform, PickKeys>, 'lazy'>
|
||||
): AsyncData<PickFrom<ReturnType<Transform>, PickKeys>, DataE | null | true>
|
||||
export function useLazyAsyncData<
|
||||
DataT,
|
||||
DataE = Error,
|
||||
@ -189,9 +217,19 @@ export function useLazyAsyncData<
|
||||
> (
|
||||
key: string,
|
||||
handler: (ctx?: NuxtApp) => Promise<DataT>,
|
||||
options: Omit<AsyncDataOptions<DataT, Transform, PickKeys>, 'lazy'> = {}
|
||||
): AsyncData<PickFrom<ReturnType<Transform>, PickKeys>, DataE | null | true> {
|
||||
return useAsyncData(key, handler, { ...options, lazy: true })
|
||||
options?: Omit<AsyncDataOptions<DataT, Transform, PickKeys>, 'lazy'>
|
||||
): AsyncData<PickFrom<ReturnType<Transform>, PickKeys>, DataE | null | true>
|
||||
export function useLazyAsyncData<
|
||||
DataT,
|
||||
DataE = Error,
|
||||
Transform extends _Transform<DataT> = _Transform<DataT, DataT>,
|
||||
PickKeys extends KeyOfRes<Transform> = KeyOfRes<Transform>
|
||||
> (...args): AsyncData<PickFrom<ReturnType<Transform>, PickKeys>, DataE | null | true> {
|
||||
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
|
||||
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
|
||||
const [key, handler, options] = args as [string, (ctx?: NuxtApp) => Promise<DataT>, AsyncDataOptions<DataT, Transform, PickKeys>]
|
||||
// @ts-ignore
|
||||
return useAsyncData(key, handler, { ...options, lazy: true }, null)
|
||||
}
|
||||
|
||||
export function refreshNuxtData (keys?: string | string[]): Promise<void> {
|
||||
|
@ -1,8 +1,7 @@
|
||||
import type { FetchOptions, FetchRequest } from 'ohmyfetch'
|
||||
import type { TypedInternalResponse, NitroFetchRequest } from 'nitropack'
|
||||
import { hash } from 'ohash'
|
||||
import { computed, isRef, Ref } from 'vue'
|
||||
import type { AsyncDataOptions, _Transform, KeyOfRes } from './asyncData'
|
||||
import type { AsyncDataOptions, _Transform, KeyOfRes, AsyncData, PickFrom } from './asyncData'
|
||||
import { useAsyncData } from './asyncData'
|
||||
|
||||
export type FetchResult<ReqT extends NitroFetchRequest> = TypedInternalResponse<ReqT, unknown>
|
||||
@ -24,12 +23,30 @@ export function useFetch<
|
||||
PickKeys extends KeyOfRes<Transform> = KeyOfRes<Transform>
|
||||
> (
|
||||
request: Ref<ReqT> | ReqT | (() => ReqT),
|
||||
opts: UseFetchOptions<_ResT, Transform, PickKeys> = {}
|
||||
opts?: UseFetchOptions<_ResT, Transform, PickKeys>
|
||||
): AsyncData<PickFrom<ReturnType<Transform>, PickKeys>, ErrorT | null | true>
|
||||
export function useFetch<
|
||||
ResT = void,
|
||||
ErrorT = Error,
|
||||
ReqT extends NitroFetchRequest = NitroFetchRequest,
|
||||
_ResT = ResT extends void ? FetchResult<ReqT> : ResT,
|
||||
Transform extends (res: _ResT) => any = (res: _ResT) => _ResT,
|
||||
PickKeys extends KeyOfRes<Transform> = KeyOfRes<Transform>
|
||||
> (
|
||||
request: Ref<ReqT> | ReqT | (() => ReqT),
|
||||
arg1?: string | UseFetchOptions<_ResT, Transform, PickKeys>,
|
||||
arg2?: string
|
||||
) {
|
||||
if (process.dev && !opts.key && Object.values(opts).some(v => typeof v === 'function' || v instanceof Blob)) {
|
||||
console.warn('[nuxt] [useFetch] You should provide a key when passing options that are not serializable to JSON:', opts)
|
||||
const [opts, autoKey] = typeof arg1 === 'string' ? [{}, arg1] : [arg1, arg2]
|
||||
const _key = opts.key || autoKey
|
||||
if (!_key || typeof _key !== 'string') {
|
||||
throw new TypeError('[nuxt] [useFetch] key must be a string: ' + _key)
|
||||
}
|
||||
const key = '$f_' + (opts.key || hash([request, { ...opts, transform: null }]))
|
||||
if (!request) {
|
||||
throw new Error('[nuxt] [useFetch] request is missing.')
|
||||
}
|
||||
const key = '$f' + _key
|
||||
|
||||
const _request = computed(() => {
|
||||
let r = request as Ref<FetchRequest> | FetchRequest | (() => FetchRequest)
|
||||
if (typeof r === 'function') {
|
||||
@ -83,10 +100,26 @@ export function useLazyFetch<
|
||||
PickKeys extends KeyOfRes<Transform> = KeyOfRes<Transform>
|
||||
> (
|
||||
request: Ref<ReqT> | ReqT | (() => ReqT),
|
||||
opts: Omit<UseFetchOptions<_ResT, Transform, PickKeys>, 'lazy'> = {}
|
||||
opts?: Omit<UseFetchOptions<_ResT, Transform, PickKeys>, 'lazy'>
|
||||
): AsyncData<PickFrom<ReturnType<Transform>, PickKeys>, ErrorT | null | true>
|
||||
export function useLazyFetch<
|
||||
ResT = void,
|
||||
ErrorT = Error,
|
||||
ReqT extends NitroFetchRequest = NitroFetchRequest,
|
||||
_ResT = ResT extends void ? FetchResult<ReqT> : ResT,
|
||||
Transform extends (res: _ResT) => any = (res: _ResT) => _ResT,
|
||||
PickKeys extends KeyOfRes<Transform> = KeyOfRes<Transform>
|
||||
> (
|
||||
request: Ref<ReqT> | ReqT | (() => ReqT),
|
||||
arg1?: string | Omit<UseFetchOptions<_ResT, Transform, PickKeys>, 'lazy'>,
|
||||
arg2?: string
|
||||
) {
|
||||
const [opts, autoKey] = typeof arg1 === 'string' ? [{}, arg1] : [arg1, arg2]
|
||||
|
||||
return useFetch<ResT, ErrorT, ReqT, _ResT, Transform, PickKeys>(request, {
|
||||
...opts,
|
||||
lazy: true
|
||||
})
|
||||
},
|
||||
// @ts-ignore
|
||||
autoKey)
|
||||
}
|
||||
|
@ -8,7 +8,20 @@ import { useNuxtApp } from '#app'
|
||||
* @param key a unique key ensuring that data fetching can be properly de-duplicated across requests
|
||||
* @param init a function that provides initial value for the state when it's not initiated
|
||||
*/
|
||||
export const useState = <T> (key: string, init?: (() => T | Ref<T>)): Ref<T> => {
|
||||
export function useState <T> (key?: string, init?: (() => T | Ref<T>)): Ref<T>
|
||||
export function useState <T> (init?: (() => T | Ref<T>)): Ref<T>
|
||||
export function useState <T> (...args): Ref<T> {
|
||||
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
|
||||
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
|
||||
const [_key, init] = args as [string, (() => T | Ref<T>)]
|
||||
if (!_key || typeof _key !== 'string') {
|
||||
throw new TypeError('[nuxt] [useState] key must be a string: ' + _key)
|
||||
}
|
||||
if (init !== undefined && typeof init !== 'function') {
|
||||
throw new Error('[nuxt] [useState] init must be a function: ' + init)
|
||||
}
|
||||
const key = '$s' + _key
|
||||
|
||||
const nuxt = useNuxtApp()
|
||||
const state = toRef(nuxt.payload.state, key)
|
||||
if (state.value === undefined && init) {
|
||||
|
@ -29,6 +29,7 @@
|
||||
"defu": "^6.0.0",
|
||||
"esbuild": "^0.14.48",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"estree-walker": "^3.0.1",
|
||||
"externality": "^0.2.2",
|
||||
"fs-extra": "^10.1.0",
|
||||
"get-port-please": "^2.5.0",
|
||||
@ -36,6 +37,7 @@
|
||||
"knitwork": "^0.1.2",
|
||||
"magic-string": "^0.26.2",
|
||||
"mlly": "^0.5.4",
|
||||
"ohash": "^0.1.0",
|
||||
"pathe": "^0.3.2",
|
||||
"perfect-debounce": "^0.1.3",
|
||||
"postcss": "^8.4.14",
|
||||
|
55
packages/vite/src/plugins/composable-keys.ts
Normal file
55
packages/vite/src/plugins/composable-keys.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { pathToFileURL } from 'node:url'
|
||||
import { createUnplugin } from 'unplugin'
|
||||
import { isAbsolute, relative } from 'pathe'
|
||||
import { walk } from 'estree-walker'
|
||||
import MagicString from 'magic-string'
|
||||
import { hash } from 'ohash'
|
||||
import type { CallExpression } from 'estree'
|
||||
import { parseURL } from 'ufo'
|
||||
|
||||
export interface ComposableKeysOptions {
|
||||
sourcemap?: boolean
|
||||
rootDir?: string
|
||||
}
|
||||
|
||||
const keyedFunctions = [
|
||||
'useState', 'useFetch', 'useAsyncData', 'useLazyAsyncData', 'useLazyFetch'
|
||||
]
|
||||
const KEYED_FUNCTIONS_RE = new RegExp(`(${keyedFunctions.join('|')})`)
|
||||
|
||||
export const composableKeysPlugin = createUnplugin((options: ComposableKeysOptions = {}) => {
|
||||
return {
|
||||
name: 'nuxt:composable-keys',
|
||||
enforce: 'post',
|
||||
transform (code, id) {
|
||||
const { pathname } = parseURL(decodeURIComponent(pathToFileURL(id).href))
|
||||
if (!pathname.match(/\.(m?[jt]sx?|vue)/)) { return }
|
||||
if (!KEYED_FUNCTIONS_RE.test(code)) { return }
|
||||
const { 0: script = code, index: codeIndex = 0 } = code.match(/(?<=<script[^>]*>)[\S\s.]*?(?=<\/script>)/) || []
|
||||
const s = new MagicString(code)
|
||||
// https://github.com/unjs/unplugin/issues/90
|
||||
const relativeID = isAbsolute(id) ? relative(options.rootDir, id) : id
|
||||
walk(this.parse(script, {
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 'latest'
|
||||
}), {
|
||||
enter (node: CallExpression) {
|
||||
if (node.type !== 'CallExpression' || node.callee.type !== 'Identifier') { return }
|
||||
if (keyedFunctions.includes(node.callee.name) && node.arguments.length < 4) {
|
||||
const end = (node as any).end
|
||||
s.appendLeft(
|
||||
codeIndex + end - 1,
|
||||
(node.arguments.length ? ', ' : '') + "'$" + hash(`${relativeID}-${codeIndex + end}`) + "'"
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
if (s.hasChanged()) {
|
||||
return {
|
||||
code: s.toString(),
|
||||
map: options.sourcemap && s.generateMap({ source: id, includeContent: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
@ -1,5 +1,5 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { promises as fsp, readdirSync, statSync } from 'node:fs'
|
||||
import { hash } from 'ohash'
|
||||
import { join } from 'pathe'
|
||||
|
||||
export function uniq<T> (arr: T[]): T[] {
|
||||
@ -28,13 +28,6 @@ export function hashId (id: string) {
|
||||
return '$id_' + hash(id)
|
||||
}
|
||||
|
||||
export function hash (input: string, length = 8) {
|
||||
return createHash('sha256')
|
||||
.update(input)
|
||||
.digest('hex')
|
||||
.slice(0, length)
|
||||
}
|
||||
|
||||
export function readDirRecursively (dir: string) {
|
||||
return readdirSync(dir).reduce((files, file) => {
|
||||
const name = join(dir, file)
|
||||
|
@ -13,6 +13,7 @@ import virtual from './plugins/virtual'
|
||||
import { DynamicBasePlugin } from './plugins/dynamic-base'
|
||||
import { warmupViteServer } from './utils/warmup'
|
||||
import { resolveCSSOptions } from './css'
|
||||
import { composableKeysPlugin } from './plugins/composable-keys'
|
||||
|
||||
export interface ViteOptions extends InlineConfig {
|
||||
vue?: Options
|
||||
@ -65,6 +66,7 @@ export async function bundle (nuxt: Nuxt) {
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
composableKeysPlugin.vite({ sourcemap: nuxt.options.sourcemap, rootDir: nuxt.options.rootDir }),
|
||||
replace({
|
||||
...Object.fromEntries([';', '(', '{', '}', ' ', '\t', '\n'].map(d => [`${d}global.`, `${d}globalThis.`])),
|
||||
preventAssignment: true
|
||||
|
@ -25,6 +25,7 @@
|
||||
"cssnano": "^5.1.12",
|
||||
"esbuild-loader": "^2.19.0",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"estree-walker": "^3.0.1",
|
||||
"file-loader": "^6.2.0",
|
||||
"fork-ts-checker-webpack-plugin": "^7.2.11",
|
||||
"fs-extra": "^10.1.0",
|
||||
|
@ -9,6 +9,7 @@ import type { Nuxt } from '@nuxt/schema'
|
||||
import { joinURL } from 'ufo'
|
||||
import { logger, useNuxt } from '@nuxt/kit'
|
||||
import { DynamicBasePlugin } from '../../vite/src/plugins/dynamic-base'
|
||||
import { composableKeysPlugin } from '../../vite/src/plugins/composable-keys'
|
||||
import { createMFS } from './utils/mfs'
|
||||
import { registerVirtualModules } from './virtual-modules'
|
||||
import { client, server } from './configs'
|
||||
@ -37,6 +38,10 @@ export async function bundle (nuxt: Nuxt) {
|
||||
sourcemap: nuxt.options.sourcemap,
|
||||
globalPublicPath: '__webpack_public_path__'
|
||||
}))
|
||||
config.plugins.push(composableKeysPlugin.webpack({
|
||||
sourcemap: nuxt.options.sourcemap,
|
||||
rootDir: nuxt.options.rootDir
|
||||
}))
|
||||
|
||||
// Create compiler
|
||||
const compiler = webpack(config)
|
||||
|
@ -326,6 +326,14 @@ describe('extends support', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('automatically keyed composables', () => {
|
||||
it('should automatically generate keys', async () => {
|
||||
const html = await $fetch('/keyed-composables')
|
||||
expect(html).toContain('true')
|
||||
expect(html).not.toContain('false')
|
||||
})
|
||||
})
|
||||
|
||||
describe('dynamic paths', () => {
|
||||
if (process.env.NUXT_TEST_DEV) {
|
||||
// TODO:
|
||||
|
31
test/fixtures/basic/pages/keyed-composables.vue
vendored
Normal file
31
test/fixtures/basic/pages/keyed-composables.vue
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
const useLocalState = () => useState(() => ({ foo: Math.random() }))
|
||||
const useStateTest1 = useLocalState()
|
||||
const useStateTest2 = useLocalState()
|
||||
|
||||
const useLocalAsyncData = () => useAsyncData(() => Promise.resolve({ foo: Math.random() }), { transform: data => data.foo })
|
||||
const { data: useAsyncDataTest1 } = await useLocalAsyncData()
|
||||
const { data: useAsyncDataTest2 } = await useLocalAsyncData()
|
||||
|
||||
const useLocalLazyAsyncData = () => useLazyAsyncData(() => Promise.resolve({ foo: Math.random() }), { transform: data => data.foo })
|
||||
const { data: useLazyAsyncDataTest1 } = await useLocalLazyAsyncData()
|
||||
const { data: useLazyAsyncDataTest2 } = await useLocalLazyAsyncData()
|
||||
|
||||
const useLocalFetch = () => useFetch('/api/counter', { transform: data => data.count })
|
||||
const { data: useFetchTest1 } = await useLocalFetch()
|
||||
const { data: useFetchTest2 } = await useLocalFetch()
|
||||
|
||||
const useLocalLazyFetch = () => useLazyFetch(() => '/api/counter')
|
||||
const { data: useLazyFetchTest1 } = await useLocalLazyFetch()
|
||||
const { data: useLazyFetchTest2 } = await useLocalLazyFetch()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<pre>
|
||||
{{ useStateTest1 === useStateTest2 }}
|
||||
{{ useAsyncDataTest1 === useAsyncDataTest2 }}
|
||||
{{ useLazyAsyncDataTest1 === useLazyAsyncDataTest2 }}
|
||||
{{ useFetchTest1 === useFetchTest2 }}
|
||||
{{ useLazyFetchTest1 === useLazyFetchTest2 }}
|
||||
</pre>
|
||||
</template>
|
15
test/fixtures/basic/types.ts
vendored
15
test/fixtures/basic/types.ts
vendored
@ -134,4 +134,19 @@ describe('composables', () => {
|
||||
expectTypeOf(useFetch('/test', { default: () => ref(500) }).data).toMatchTypeOf<Ref<number>>()
|
||||
expectTypeOf(useFetch('/test', { default: () => 500 }).data).toMatchTypeOf<Ref<number>>()
|
||||
})
|
||||
|
||||
it('provides proper type support when using overloads', () => {
|
||||
expectTypeOf(useState('test')).toMatchTypeOf(useState())
|
||||
expectTypeOf(useState('test', () => ({ foo: Math.random() }))).toMatchTypeOf(useState(() => ({ foo: Math.random() })))
|
||||
|
||||
expectTypeOf(useAsyncData('test', () => Promise.resolve({ foo: Math.random() })))
|
||||
.toMatchTypeOf(useAsyncData(() => Promise.resolve({ foo: Math.random() })))
|
||||
expectTypeOf(useAsyncData('test', () => Promise.resolve({ foo: Math.random() }), { transform: data => data.foo }))
|
||||
.toMatchTypeOf(useAsyncData(() => Promise.resolve({ foo: Math.random() }), { transform: data => data.foo }))
|
||||
|
||||
expectTypeOf(useLazyAsyncData('test', () => Promise.resolve({ foo: Math.random() })))
|
||||
.toMatchTypeOf(useLazyAsyncData(() => Promise.resolve({ foo: Math.random() })))
|
||||
expectTypeOf(useLazyAsyncData('test', () => Promise.resolve({ foo: Math.random() }), { transform: data => data.foo }))
|
||||
.toMatchTypeOf(useLazyAsyncData(() => Promise.resolve({ foo: Math.random() }), { transform: data => data.foo }))
|
||||
})
|
||||
})
|
||||
|
10
yarn.lock
10
yarn.lock
@ -1775,6 +1775,7 @@ __metadata:
|
||||
defu: ^6.0.0
|
||||
esbuild: ^0.14.48
|
||||
escape-string-regexp: ^5.0.0
|
||||
estree-walker: ^3.0.1
|
||||
externality: ^0.2.2
|
||||
fs-extra: ^10.1.0
|
||||
get-port-please: ^2.5.0
|
||||
@ -1782,6 +1783,7 @@ __metadata:
|
||||
knitwork: ^0.1.2
|
||||
magic-string: ^0.26.2
|
||||
mlly: ^0.5.4
|
||||
ohash: ^0.1.0
|
||||
pathe: ^0.3.2
|
||||
perfect-debounce: ^0.1.3
|
||||
postcss: ^8.4.14
|
||||
@ -1820,6 +1822,7 @@ __metadata:
|
||||
cssnano: ^5.1.12
|
||||
esbuild-loader: ^2.19.0
|
||||
escape-string-regexp: ^5.0.0
|
||||
estree-walker: ^3.0.1
|
||||
file-loader: ^6.2.0
|
||||
fork-ts-checker-webpack-plugin: ^7.2.11
|
||||
fs-extra: ^10.1.0
|
||||
@ -6278,6 +6281,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"estree-walker@npm:^3.0.1":
|
||||
version: 3.0.1
|
||||
resolution: "estree-walker@npm:3.0.1"
|
||||
checksum: 674096950819041f1ee471e63f7aa987f2ed3a3a441cc41a5176e9ed01ea5cfd6487822c3b9c2cddd0e2c8f9d7ef52d32d06147a19b5a9ca9f8ab0c094bd43b9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"esutils@npm:^2.0.2":
|
||||
version: 2.0.3
|
||||
resolution: "esutils@npm:2.0.3"
|
||||
|
Loading…
Reference in New Issue
Block a user