Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/neat-melons-cheer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': minor
---

feat: add `createContext` utility for type-safe context
8 changes: 8 additions & 0 deletions documentation/docs/98-reference/.generated/shared-errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ Certain lifecycle methods can only be used during component initialisation. To f
<button onclick={handleClick}>click me</button>
```

### missing_context

```
Context was not set in a parent component
```

The [`createContext()`](svelte#createContext) utility returns a `[get, set]` pair of functions. `get` will throw an error if `set` was not used to set the context in a parent component.

### snippet_without_render_tag

```
Expand Down
6 changes: 6 additions & 0 deletions packages/svelte/messages/shared-errors/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ Certain lifecycle methods can only be used during component initialisation. To f
<button onclick={handleClick}>click me</button>
```

## missing_context

> Context was not set in a parent component

The [`createContext()`](svelte#createContext) utility returns a `[get, set]` pair of functions. `get` will throw an error if `set` was not used to set the context in a parent component.

## snippet_without_render_tag

> Attempted to render a snippet without a `{@render}` block. This would cause the snippet code to be stringified instead of its content being rendered to the DOM. To fix this, change `{snippet}` to `{@render snippet()}`.
Expand Down
8 changes: 7 additions & 1 deletion packages/svelte/src/index-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,13 @@ function init_update_callbacks(context) {
}

export { flushSync } from './internal/client/reactivity/batch.js';
export { getContext, getAllContexts, hasContext, setContext } from './internal/client/context.js';
export {
createContext,
getContext,
getAllContexts,
hasContext,
setContext
} from './internal/client/context.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';
8 changes: 7 additions & 1 deletion packages/svelte/src/index-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ export async function settled() {}

export { getAbortSignal } from './internal/server/abort-signal.js';

export { getAllContexts, getContext, hasContext, setContext } from './internal/server/context.js';
export {
createContext,
getAllContexts,
getContext,
hasContext,
setContext
} from './internal/server/context.js';

export { createRawSnippet } from './internal/server/blocks/snippet.js';
20 changes: 20 additions & 0 deletions packages/svelte/src/internal/client/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,26 @@ export function set_dev_current_component_function(fn) {
dev_current_component_function = fn;
}

/**
* Returns a `[get, set]` pair of functions for working with context in a type-safe way.
* @template T
* @returns {[() => T, (context: T) => T]}
*/
export function createContext() {
const key = {};

return [
() => {
if (!hasContext(key)) {
e.missing_context();
}

return getContext(key);
},
(context) => setContext(key, context)
];
}

/**
* Retrieves the context that belongs to the closest parent component with the specified `key`.
* Must be called during component initialisation.
Expand Down
9 changes: 9 additions & 0 deletions packages/svelte/src/internal/server/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ export function set_ssr_context(v) {
ssr_context = v;
}

/**
* @template T
* @returns {[() => T, (context: T) => T]}
*/
export function createContext() {
const key = {};
return [() => getContext(key), (context) => setContext(key, context)];
}

/**
* @template T
* @param {any} key
Expand Down
16 changes: 16 additions & 0 deletions packages/svelte/src/internal/shared/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,20 @@ export function svelte_element_invalid_this_value() {
} else {
throw new Error(`https://svelte.dev/e/svelte_element_invalid_this_value`);
}
}

/**
* Context was not set in a parent component
* @returns {never}
*/
export function missing_context() {
if (DEV) {
const error = new Error(`missing_context\nContext was not set in a parent component\nhttps://svelte.dev/e/missing_context`);

error.name = 'Svelte error';

throw error;
} else {
throw new Error(`https://svelte.dev/e/missing_context`);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script>
import { get } from './main.svelte';
const message = get();
</script>

<h1>{message}</h1>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { test } from '../../test';

export default test({
html: `<h1>hello</h1>`
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script module>
import { createContext } from 'svelte';

/** @type {ReturnType<typeof createContext<string>>} */
const [get, set] = createContext();

export { get };
</script>

<script>
import Child from './Child.svelte';

set('hello');
</script>

<Child />
1 change: 1 addition & 0 deletions packages/svelte/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ declare module 'svelte' {
}): Snippet<Params>;
/** Anything except a function */
type NotFunction<T> = T extends Function ? never : T;
export function createContext<T>(): [() => T, (context: T) => T];
/**
* Retrieves the context that belongs to the closest parent component with the specified `key`.
* Must be called during component initialisation.
Expand Down
Loading