From 91882ee9dbf84f3b8d9d71982f43a7d62d9aa261 Mon Sep 17 00:00:00 2001 From: Elliott Johnson Date: Thu, 13 Nov 2025 15:59:55 -0700 Subject: [PATCH] feat: --- .../98-reference/.generated/client-errors.md | 29 ++++- .../.generated/client-warnings.md | 17 +++ .../98-reference/.generated/server-errors.md | 35 ++++++ .../98-reference/.generated/shared-errors.md | 6 + .../svelte/messages/client-errors/errors.md | 23 +++- .../messages/client-warnings/warnings.md | 15 +++ .../svelte/messages/server-errors/errors.md | 29 +++++ .../svelte/messages/shared-errors/errors.md | 4 + packages/svelte/package.json | 1 + packages/svelte/src/index-client.js | 1 + packages/svelte/src/index-server.js | 2 + packages/svelte/src/internal/client/errors.js | 42 +++++-- .../svelte/src/internal/client/hydratable.js | 112 ++++++++++++++++++ .../src/internal/client/reactivity/batch.js | 2 +- .../svelte/src/internal/client/types.d.ts | 9 ++ .../svelte/src/internal/client/warnings.js | 24 ++++ .../svelte/src/internal/server/context.js | 5 +- packages/svelte/src/internal/server/errors.js | 48 ++++++++ .../svelte/src/internal/server/hydratable.js | 89 ++++++++++++++ .../src/internal/server/render-context.js | 93 +++++++++++++++ .../svelte/src/internal/server/renderer.js | 103 +++++++++++++--- .../src/internal/server/renderer.test.ts | 39 +++++- .../svelte/src/internal/server/types.d.ts | 12 ++ packages/svelte/src/internal/shared/errors.js | 17 +++ .../svelte/src/internal/shared/types.d.ts | 42 +++++++ packages/svelte/src/internal/shared/utils.js | 2 +- .../samples/hydratables/_config.js | 19 +++ .../samples/hydratables/main.svelte | 9 ++ packages/svelte/types/index.d.ts | 22 ++++ pnpm-lock.yaml | 8 ++ 30 files changed, 821 insertions(+), 38 deletions(-) create mode 100644 packages/svelte/src/internal/client/hydratable.js create mode 100644 packages/svelte/src/internal/server/hydratable.js create mode 100644 packages/svelte/src/internal/server/render-context.js create mode 100644 packages/svelte/tests/runtime-runes/samples/hydratables/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/hydratables/main.svelte diff --git a/documentation/docs/98-reference/.generated/client-errors.md b/documentation/docs/98-reference/.generated/client-errors.md index 3f1cb8f76b..4de99988e9 100644 --- a/documentation/docs/98-reference/.generated/client-errors.md +++ b/documentation/docs/98-reference/.generated/client-errors.md @@ -130,12 +130,6 @@ $effect(() => { Often when encountering this issue, the value in question shouldn't be state (for example, if you are pushing to a `logs` array in an effect, make `logs` a normal array rather than `$state([])`). In the rare cases where you really _do_ need to write to state in an effect — [which you should avoid]($effect#When-not-to-use-$effect) — you can read the state with [untrack](svelte#untrack) to avoid adding it as a dependency. -### experimental_async_fork - -``` -Cannot use `fork(...)` unless the `experimental.async` compiler option is `true` -``` - ### flush_sync_in_effect ``` @@ -146,6 +140,12 @@ The `flushSync()` function can be used to flush any pending effects synchronousl This restriction only applies when using the `experimental.async` option, which will be active by default in Svelte 6. +### fn_unavailable_on_client + +``` +`%name%`(...) is unavailable in the browser. +``` + ### fork_discarded ``` @@ -164,6 +164,23 @@ Cannot create a fork inside an effect or when state changes are pending `getAbortSignal()` can only be called inside an effect or derived ``` +### hydratable_missing_but_expected_e + +``` +Expected to find a hydratable with key `%key%` during hydration, but did not. +This can happen if you render a hydratable on the client that was not rendered on the server, and means that it was forced to fall back to running its function blockingly during hydration. This is bad for performance, as it blocks hydration until the asynchronous work completes. +```svelte + +``` + ### hydration_failed ``` diff --git a/documentation/docs/98-reference/.generated/client-warnings.md b/documentation/docs/98-reference/.generated/client-warnings.md index c95ace2229..d7de5998fa 100644 --- a/documentation/docs/98-reference/.generated/client-warnings.md +++ b/documentation/docs/98-reference/.generated/client-warnings.md @@ -140,6 +140,23 @@ The easiest way to log a value as it changes over time is to use the [`$inspect` %handler% should be a function. Did you mean to %suggestion%? ``` +### hydratable_missing_but_expected_w + +``` +Expected to find a hydratable with key `%key%` during hydration, but did not. +This can happen if you render a hydratable on the client that was not rendered on the server, and means that it was forced to fall back to running its function blockingly during hydration. This is bad for performance, as it blocks hydration until the asynchronous work completes. +```svelte + +``` + ### hydration_attribute_changed ``` diff --git a/documentation/docs/98-reference/.generated/server-errors.md b/documentation/docs/98-reference/.generated/server-errors.md index 6263032212..0630e378f1 100644 --- a/documentation/docs/98-reference/.generated/server-errors.md +++ b/documentation/docs/98-reference/.generated/server-errors.md @@ -8,12 +8,40 @@ Encountered asynchronous work while rendering synchronously. You (or the framework you're using) called [`render(...)`](svelte-server#render) with a component containing an `await` expression. Either `await` the result of `render` or wrap the `await` (or the component containing it) in a [``](svelte-boundary) with a `pending` snippet. +### fn_unavailable_on_server + +``` +`%name%`(...) is unavailable on the server. +``` + ### html_deprecated ``` The `html` property of server render results has been deprecated. Use `body` instead. ``` +### hydratable_clobbering + +``` +Attempted to set hydratable with key `%key%` twice. This behavior is undefined. + +First set occurred at: +%stack% +``` + +This error occurs when using `hydratable` or `hydratable.set` multiple times with the same key. To avoid this, you can combine `hydratable` with `cache`, or check whether the value has already been set with `hydratable.has`. + +```svelte + +``` + ### lifecycle_function_unavailable ``` @@ -21,3 +49,10 @@ The `html` property of server render results has been deprecated. Use `body` ins ``` Certain methods such as `mount` cannot be invoked while running in a server context. Avoid calling them eagerly, i.e. not during render. + +### render_context_unavailable + +``` +Failed to retrieve `render` context. %addendum% +If `AsyncLocalStorage` is available, you're likely calling a function that needs access to the `render` context (`hydratable`, `cache`, or something that depends on these) from outside of `render`. If `AsyncLocalStorage` is not available, these functions must also be called synchronously from within `render` -- i.e. not after any `await`s. +``` diff --git a/documentation/docs/98-reference/.generated/shared-errors.md b/documentation/docs/98-reference/.generated/shared-errors.md index 07e13dea45..136b3f4957 100644 --- a/documentation/docs/98-reference/.generated/shared-errors.md +++ b/documentation/docs/98-reference/.generated/shared-errors.md @@ -1,5 +1,11 @@ +### experimental_async_required + +``` +Cannot use `%name%(...)` unless the `experimental.async` compiler option is `true` +``` + ### invalid_default_snippet ``` diff --git a/packages/svelte/messages/client-errors/errors.md b/packages/svelte/messages/client-errors/errors.md index ae7d811b2e..36d9caf4d4 100644 --- a/packages/svelte/messages/client-errors/errors.md +++ b/packages/svelte/messages/client-errors/errors.md @@ -100,10 +100,6 @@ $effect(() => { Often when encountering this issue, the value in question shouldn't be state (for example, if you are pushing to a `logs` array in an effect, make `logs` a normal array rather than `$state([])`). In the rare cases where you really _do_ need to write to state in an effect — [which you should avoid]($effect#When-not-to-use-$effect) — you can read the state with [untrack](svelte#untrack) to avoid adding it as a dependency. -## experimental_async_fork - -> Cannot use `fork(...)` unless the `experimental.async` compiler option is `true` - ## flush_sync_in_effect > Cannot use `flushSync` inside an effect @@ -112,6 +108,10 @@ The `flushSync()` function can be used to flush any pending effects synchronousl This restriction only applies when using the `experimental.async` option, which will be active by default in Svelte 6. +## fn_unavailable_on_client + +> `%name%`(...) is unavailable in the browser. + ## fork_discarded > Cannot commit a fork that was already discarded @@ -124,6 +124,21 @@ This restriction only applies when using the `experimental.async` option, which > `getAbortSignal()` can only be called inside an effect or derived +## hydratable_missing_but_expected_e + +> Expected to find a hydratable with key `%key%` during hydration, but did not. +This can happen if you render a hydratable on the client that was not rendered on the server, and means that it was forced to fall back to running its function blockingly during hydration. This is bad for performance, as it blocks hydration until the asynchronous work completes. +```svelte + +``` + ## hydration_failed > Failed to hydrate the application diff --git a/packages/svelte/messages/client-warnings/warnings.md b/packages/svelte/messages/client-warnings/warnings.md index 9763c8df1a..f5e8f18005 100644 --- a/packages/svelte/messages/client-warnings/warnings.md +++ b/packages/svelte/messages/client-warnings/warnings.md @@ -124,6 +124,21 @@ The easiest way to log a value as it changes over time is to use the [`$inspect` > %handler% should be a function. Did you mean to %suggestion%? +## hydratable_missing_but_expected_w + +> Expected to find a hydratable with key `%key%` during hydration, but did not. +This can happen if you render a hydratable on the client that was not rendered on the server, and means that it was forced to fall back to running its function blockingly during hydration. This is bad for performance, as it blocks hydration until the asynchronous work completes. +```svelte + +``` + ## hydration_attribute_changed > The `%attribute%` attribute on `%html%` changed its value between server and client renders. The client value, `%value%`, will be ignored in favour of the server value diff --git a/packages/svelte/messages/server-errors/errors.md b/packages/svelte/messages/server-errors/errors.md index 49d2a310f6..1b699f3390 100644 --- a/packages/svelte/messages/server-errors/errors.md +++ b/packages/svelte/messages/server-errors/errors.md @@ -4,12 +4,41 @@ You (or the framework you're using) called [`render(...)`](svelte-server#render) with a component containing an `await` expression. Either `await` the result of `render` or wrap the `await` (or the component containing it) in a [``](svelte-boundary) with a `pending` snippet. +## fn_unavailable_on_server + +> `%name%`(...) is unavailable on the server. + ## html_deprecated > The `html` property of server render results has been deprecated. Use `body` instead. +## hydratable_clobbering + +> Attempted to set hydratable with key `%key%` twice. This behavior is undefined. +> +> First set occurred at: +> %stack% + +This error occurs when using `hydratable` or `hydratable.set` multiple times with the same key. To avoid this, you can combine `hydratable` with `cache`, or check whether the value has already been set with `hydratable.has`. + +```svelte + +``` + ## lifecycle_function_unavailable > `%name%(...)` is not available on the server Certain methods such as `mount` cannot be invoked while running in a server context. Avoid calling them eagerly, i.e. not during render. + +## render_context_unavailable + +> Failed to retrieve `render` context. %addendum% +If `AsyncLocalStorage` is available, you're likely calling a function that needs access to the `render` context (`hydratable`, `cache`, or something that depends on these) from outside of `render`. If `AsyncLocalStorage` is not available, these functions must also be called synchronously from within `render` -- i.e. not after any `await`s. diff --git a/packages/svelte/messages/shared-errors/errors.md b/packages/svelte/messages/shared-errors/errors.md index e3959034a3..bf053283e4 100644 --- a/packages/svelte/messages/shared-errors/errors.md +++ b/packages/svelte/messages/shared-errors/errors.md @@ -1,3 +1,7 @@ +## experimental_async_required + +> Cannot use `%name%(...)` unless the `experimental.async` compiler option is `true` + ## invalid_default_snippet > Cannot use `{@render children(...)}` if the parent component uses `let:` directives. Consider using a named snippet instead diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 731ae6f004..a874bedcf3 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -174,6 +174,7 @@ "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", + "devalue": "^5.5.0", "esm-env": "^1.2.1", "esrap": "^2.1.0", "is-reference": "^3.0.3", diff --git a/packages/svelte/src/index-client.js b/packages/svelte/src/index-client.js index 4fcfff980d..0eb1b80315 100644 --- a/packages/svelte/src/index-client.js +++ b/packages/svelte/src/index-client.js @@ -249,6 +249,7 @@ export { hasContext, setContext } from './internal/client/context.js'; +export { hydratable } from './internal/client/hydratable.js'; export { hydrate, mount, unmount } from './internal/client/render.js'; export { tick, untrack, settled } from './internal/client/runtime.js'; export { createRawSnippet } from './internal/client/dom/blocks/snippet.js'; diff --git a/packages/svelte/src/index-server.js b/packages/svelte/src/index-server.js index 61b0d98c06..9fb810fd9e 100644 --- a/packages/svelte/src/index-server.js +++ b/packages/svelte/src/index-server.js @@ -51,4 +51,6 @@ export { setContext } from './internal/server/context.js'; +export { hydratable } from './internal/server/hydratable.js'; + export { createRawSnippet } from './internal/server/blocks/snippet.js'; diff --git a/packages/svelte/src/internal/client/errors.js b/packages/svelte/src/internal/client/errors.js index 8a5fde4f3b..1b3b4e9772 100644 --- a/packages/svelte/src/internal/client/errors.js +++ b/packages/svelte/src/internal/client/errors.js @@ -230,34 +230,35 @@ export function effect_update_depth_exceeded() { } /** - * Cannot use `fork(...)` unless the `experimental.async` compiler option is `true` + * Cannot use `flushSync` inside an effect * @returns {never} */ -export function experimental_async_fork() { +export function flush_sync_in_effect() { if (DEV) { - const error = new Error(`experimental_async_fork\nCannot use \`fork(...)\` unless the \`experimental.async\` compiler option is \`true\`\nhttps://svelte.dev/e/experimental_async_fork`); + const error = new Error(`flush_sync_in_effect\nCannot use \`flushSync\` inside an effect\nhttps://svelte.dev/e/flush_sync_in_effect`); error.name = 'Svelte error'; throw error; } else { - throw new Error(`https://svelte.dev/e/experimental_async_fork`); + throw new Error(`https://svelte.dev/e/flush_sync_in_effect`); } } /** - * Cannot use `flushSync` inside an effect + * `%name%`(...) is unavailable in the browser. + * @param {string} name * @returns {never} */ -export function flush_sync_in_effect() { +export function fn_unavailable_on_client(name) { if (DEV) { - const error = new Error(`flush_sync_in_effect\nCannot use \`flushSync\` inside an effect\nhttps://svelte.dev/e/flush_sync_in_effect`); + const error = new Error(`fn_unavailable_on_client\n\`${name}\`(...) is unavailable in the browser.\nhttps://svelte.dev/e/fn_unavailable_on_client`); error.name = 'Svelte error'; throw error; } else { - throw new Error(`https://svelte.dev/e/flush_sync_in_effect`); + throw new Error(`https://svelte.dev/e/fn_unavailable_on_client`); } } @@ -309,6 +310,31 @@ export function get_abort_signal_outside_reaction() { } } +/** + * Expected to find a hydratable with key `%key%` during hydration, but did not. + * This can happen if you render a hydratable on the client that was not rendered on the server, and means that it was forced to fall back to running its function blockingly during hydration. This is bad for performance, as it blocks hydration until the asynchronous work completes. + * ```svelte + * `; + } } export class SSRState { @@ -673,3 +716,33 @@ export class SSRState { } } } + +export class MemoizedUneval { + /** @type {Map} */ + #cache = new Map(); + + /** + * @param {unknown} value + * @returns {string} + */ + uneval = (value) => { + return uneval(value, (value, uneval) => { + const cached = this.#cache.get(value); + if (cached) { + // this breaks my brain a bit, but: + // - when the entry is defined but its value is `undefined`, calling `uneval` below will cause the custom replacer to be called again + // - because the custom replacer returns this, which is `undefined`, it will fall back to the default serialization + // - ...which causes it to return a string + // - ...which is then added to this cache before being returned + return cached.value; + } + + const stub = {}; + this.#cache.set(value, stub); + + const result = uneval(value); + stub.value = result; + return result; + }); + }; +} diff --git a/packages/svelte/src/internal/server/renderer.test.ts b/packages/svelte/src/internal/server/renderer.test.ts index 8e9a377a5b..a2a979aeb4 100644 --- a/packages/svelte/src/internal/server/renderer.test.ts +++ b/packages/svelte/src/internal/server/renderer.test.ts @@ -1,7 +1,8 @@ import { afterAll, beforeAll, describe, expect, test } from 'vitest'; -import { Renderer, SSRState } from './renderer.js'; +import { MemoizedUneval, Renderer, SSRState } from './renderer.js'; import type { Component } from 'svelte'; import { disable_async_mode_flag, enable_async_mode_flag } from '../flags/index.js'; +import { uneval } from 'devalue'; test('collects synchronous body content by default', () => { const component = (renderer: Renderer) => { @@ -355,3 +356,39 @@ describe('async', () => { expect(destroyed).toEqual(['c', 'e', 'a', 'b', 'b*', 'd']); }); }); + +describe('MemoizedDevalue', () => { + test.each([ + 1, + 'general kenobi', + { foo: 'bar' }, + [1, 2], + null, + undefined, + new Map([[1, '2']]) + ] as const)('has same behavior as unmemoized devalue for %s', (input) => { + expect(new MemoizedUneval().uneval(input)).toBe(uneval(input)); + }); + + test('caches results', () => { + const memoized = new MemoizedUneval(); + let calls = 0; + + const input = { + get only_once() { + calls++; + return 42; + } + }; + + const first = memoized.uneval(input); + const max_calls = calls; + const second = memoized.uneval(input); + memoized.uneval(input); + + expect(first).toBe(second); + // for reasons I don't quite comprehend, it does get called twice, but both calls happen in the first + // serialization, and don't increase afterwards + expect(calls).toBe(max_calls); + }); +}); diff --git a/packages/svelte/src/internal/server/types.d.ts b/packages/svelte/src/internal/server/types.d.ts index 53cefabc69..6b1cf6e1f2 100644 --- a/packages/svelte/src/internal/server/types.d.ts +++ b/packages/svelte/src/internal/server/types.d.ts @@ -1,3 +1,4 @@ +import type { Encode } from '#shared'; import type { Element } from './dev'; import type { Renderer } from './renderer'; @@ -14,6 +15,17 @@ export interface SSRContext { element?: Element; } +export interface HydratableEntry { + value: unknown; + encode: Encode | undefined; + stack?: string; +} + +export interface RenderContext { + hydratables: Map; + cache: Map>; +} + export interface SyncRenderOutput { /** HTML that goes into the `` */ head: string; diff --git a/packages/svelte/src/internal/shared/errors.js b/packages/svelte/src/internal/shared/errors.js index 669cdd96a7..b13a65b598 100644 --- a/packages/svelte/src/internal/shared/errors.js +++ b/packages/svelte/src/internal/shared/errors.js @@ -2,6 +2,23 @@ import { DEV } from 'esm-env'; +/** + * Cannot use `%name%(...)` unless the `experimental.async` compiler option is `true` + * @param {string} name + * @returns {never} + */ +export function experimental_async_required(name) { + if (DEV) { + const error = new Error(`experimental_async_required\nCannot use \`${name}(...)\` unless the \`experimental.async\` compiler option is \`true\`\nhttps://svelte.dev/e/experimental_async_required`); + + error.name = 'Svelte error'; + + throw error; + } else { + throw new Error(`https://svelte.dev/e/experimental_async_required`); + } +} + /** * Cannot use `{@render children(...)}` if the parent component uses `let:` directives. Consider using a named snippet instead * @returns {never} diff --git a/packages/svelte/src/internal/shared/types.d.ts b/packages/svelte/src/internal/shared/types.d.ts index 4deeb76b2f..1bcdd36f97 100644 --- a/packages/svelte/src/internal/shared/types.d.ts +++ b/packages/svelte/src/internal/shared/types.d.ts @@ -8,3 +8,45 @@ export type Getters = { }; export type Snapshot = ReturnType>; + +export type MaybePromise = T | Promise; + +export type Decode = (value: any) => T; + +export type Encode = (value: T) => string; + +export type Transport = + | { + encode: Encode; + decode?: undefined; + } + | { + encode?: undefined; + decode: Decode; + }; + +export type Hydratable = { + (key: string, fn: () => T, options?: { transport?: Transport }): T; + get: (key: string, options?: { decode?: Decode }) => T | undefined; + has: (key: string) => boolean; + set: (key: string, value: T, options?: { encode?: Encode }) => void; +}; + +export type Resource = { + then: Promise>['then']; + catch: Promise>['catch']; + finally: Promise>['finally']; + refresh: () => Promise; + set: (value: Awaited) => void; + loading: boolean; + error: any; +} & ( + | { + ready: false; + current: undefined; + } + | { + ready: true; + current: Awaited; + } +); diff --git a/packages/svelte/src/internal/shared/utils.js b/packages/svelte/src/internal/shared/utils.js index 10f8597520..659cd10040 100644 --- a/packages/svelte/src/internal/shared/utils.js +++ b/packages/svelte/src/internal/shared/utils.js @@ -48,7 +48,7 @@ export function run_all(arr) { /** * TODO replace with Promise.withResolvers once supported widely enough - * @template T + * @template [T=void] */ export function deferred() { /** @type {(value: T) => void} */ diff --git a/packages/svelte/tests/runtime-runes/samples/hydratables/_config.js b/packages/svelte/tests/runtime-runes/samples/hydratables/_config.js new file mode 100644 index 0000000000..b7622279e3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/hydratables/_config.js @@ -0,0 +1,19 @@ +import { tick } from 'svelte'; +import { ok, test } from '../../test'; + +export default test({ + skip_mode: ['server'], + + server_props: { environment: 'server' }, + ssrHtml: '

The current environment is: server

', + + props: { environment: 'browser' }, + html: '

The current environment is: server

', + + async test({ assert, target }) { + await tick(); + const p = target.querySelector('p'); + ok(p); + assert.htmlEqual(p.outerHTML, '

The current environment is: server

'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/hydratables/main.svelte b/packages/svelte/tests/runtime-runes/samples/hydratables/main.svelte new file mode 100644 index 0000000000..53b9c24f91 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/hydratables/main.svelte @@ -0,0 +1,9 @@ + + +

The current environment is: {value}

diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 5e3ca77eb5..fcefe8e309 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -450,6 +450,7 @@ declare module 'svelte' { * @deprecated Use [`$effect`](https://svelte.dev/docs/svelte/$effect) instead * */ export function afterUpdate(fn: () => void): void; + export const hydratable: Hydratable; /** * Create a snippet programmatically * */ @@ -595,6 +596,27 @@ declare module 'svelte' { [K in keyof T]: () => T[K]; }; + type Decode = (value: any) => T; + + type Encode = (value: T) => string; + + type Transport = + | { + encode: Encode; + decode?: undefined; + } + | { + encode?: undefined; + decode: Decode; + }; + + type Hydratable = { + (key: string, fn: () => T, options?: { transport?: Transport }): T; + get: (key: string, options?: { decode?: Decode }) => T | undefined; + has: (key: string) => boolean; + set: (key: string, value: T, options?: { encode?: Encode }) => void; + }; + export {}; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0afaef0ceb..0b1f57213d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -89,6 +89,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + devalue: + specifier: ^5.5.0 + version: 5.5.0 esm-env: specifier: ^1.2.1 version: 1.2.1 @@ -1515,6 +1518,9 @@ packages: engines: {node: '>=0.10'} hasBin: true + devalue@5.5.0: + resolution: {integrity: sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w==} + dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -3990,6 +3996,8 @@ snapshots: detect-libc@1.0.3: optional: true + devalue@5.5.0: {} + dir-glob@3.0.1: dependencies: path-type: 4.0.0