Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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 @@ -51,6 +51,22 @@ export function lifecycle_outside_component(name) {
}
}

/**
* 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`);
}
}

/**
* 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()}`.
* @returns {never}
Expand Down
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 />
4 changes: 4 additions & 0 deletions packages/svelte/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,10 @@ declare module 'svelte' {
}): Snippet<Params>;
/** Anything except a function */
type NotFunction<T> = T extends Function ? never : T;
/**
* Returns a `[get, set]` pair of functions for working with context in a type-safe way.
* */
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