8000 breaking: change `$inspect` API by dummdidumm · Pull Request #9838 · sveltejs/svelte · GitHub
[go: up one dir, main page]

Skip to content

breaking: change $inspect API #9838

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 7, 2023
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/wise-donkeys-marry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

breaking: change `$inspect` API
2 changes: 1 addition & 1 deletion packages/svelte/src/compiler/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ const runes = {
'invalid-derived-binding': () => `Invalid binding to derived state`,
/**
* @param {string} rune
* @param {number[]} args
* @param {Array<number | string>} args
*/
'invalid-rune-args-length': (rune, args) =>
`${rune} can only be called with ${list(args, 'or')} ${
Expand Down
10 changes: 8 additions & 2 deletions packages/svelte/src/compiler/phases/2-analyze/validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -711,8 +711,14 @@ function validate_call_expression(node, scope, path) {
}

if (rune === '$inspect') {
if (node.arguments.length < 1 || node.arguments.length > 2) {
error(node, 'invalid-rune-args-length', rune, [1, 2]);
if (node.arguments.length < 1) {
error(node, 'invalid-rune-args-length', rune, [1, 'more']);
}
}

if (rune === '$inspect().with') {
if (node.arguments.length !== 1) {
error(node, 'invalid-rune-args-length', rune, [1]);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { get_rune } from '../../../scope.js';
import { is_hoistable_function } from '../../utils.js';
import { is_hoistable_function, transform_inspect_rune } from '../../utils.js';
import * as b from '../../../../utils/builders.js';
import * as assert from '../../../../utils/assert.js';
import { create_state_declarators, get_prop_source, should_proxy } from '../utils.js';
Expand Down Expand Up @@ -301,33 +301,24 @@ export const javascript_visitors_runes = {

context.next();
},
CallExpression(node, { state, next, visit }) {
const rune = get_rune(node, state.scope);
CallExpression(node, context) {
const rune = get_rune(node, context.state.scope);

if (rune === '$effect.active') {
return b.call('$.effect_active');
}

if (rune === '$effect.root') {
const args = /** @type {import('estree').Expression[]} */ (
node.arguments.map((arg) => visit(arg))
node.arguments.map((arg) => context.visit(arg))
);
return b.call('$.user_root_effect', ...args);
}

if (rune === '$inspect') {
if (state.options.dev) {
const arg = /** @type {import('estree').Expression} */ (visit(node.arguments[0]));
const fn =
node.arguments[1] &&
/** @type {import('estree').Expression} */ (visit(node.arguments[1]));

return b.call('$.inspect', b.thunk(arg), fn);
}

return b.unary('void', b.literal(0));
if (rune === '$inspect' || rune === '$inspect().with') {
return transform_inspect_rune(node, context);
}

next();
context.next();
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import {
clean_nodes,
determine_element_namespace,
escape_html,
infer_namespace
infer_namespace,
transform_inspect_rune
} from '../utils.js';
import { create_attribute, is_custom_element_node, is_element_node } from '../../nodes.js';
import { error } from '../../../errors.js';
Expand Down Expand Up @@ -630,26 +631,18 @@ const javascript_visitors_runes = {
}
context.next();
},
CallExpression(node, { state, next, visit }) {
const rune = get_rune(node, state.scope);
CallExpression(node, context) {
const rune = get_rune(node, context.state.scope);

if (rune === '$effect.active') {
return b.literal(false);
}

if (rune === '$inspect') {
if (state.options.dev) {
const args = /** @type {import('estree').Expression[]} */ (
node.arguments.map((arg) => visit(arg))
);

return b.call('console.log', ...args);
}

return b.unary('void', b.literal(0));
if (rune === '$inspect' || rune === '$inspect().with') {
return transform_inspect_rune(node, context);
}

next();
context.next();
}
};

Expand Down
32 changes: 32 additions & 0 deletions packages/svelte/src/compiler/phases/3-transform/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -271,3 +271,35 @@ export function determine_element_namespace(node, namespace, path) {

return namespace;
}

/**
* @template {import('./types.js').TransformState} T
* @param {import('estree').CallExpression} node
* @param {import('zimmerframe').Context<any, T>} context
*/
export function transform_inspect_rune(node, context) {
const { state, visit } = context;
const as_fn = state.options.generate === 'client';

if (!state.options.dev) return b.unary('void', b.literal(0));

if (node.callee.type === 'MemberExpression') {
const raw_inspect_args = /** @type {import('estree').CallExpression} */ (node.callee.object)
.arguments;
const inspect_args =
/** @type {Array<import('estree').Expression>} */
(raw_inspect_args.map((arg) => visit(arg)));
const with_arg = /** @type {import('estree').Expression} */ (visit(node.arguments[0]));

return b.call(
'$.inspect',
as_fn ? b.thunk(b.array(inspect_args)) : b.array(inspect_args),
with_arg
);
} else {
const arg = node.arguments.map(
(arg) => /** @type {import('estree').Expression} */ (visit(arg))
);
return b.call('$.inspect', as_fn ? b.thunk(b.array(arg)) : b.array(arg));
}
}
3 changes: 2 additions & 1 deletion packages/svelte/src/compiler/phases/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ export const Runes = /** @type {const} */ ([
'$effect.pre',
'$effect.active',
'$effect.root',
'$inspect'
'$inspect',
'$inspect().with'
]);

/**
Expand Down
5 changes: 5 additions & 0 deletions packages/svelte/src/compiler/phases/scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,11 @@ export function get_rune(node, scope) {
n = n.object;
}

if (n.type === 'CallExpression' && n.callee.type === 'Identifier') {
joined = '()' + joined;
n = n.callee;
}

if (n.type !== 'Identifier') return null;

joined = n.name + joined;
Expand Down
21 changes: 15 additions & 6 deletions packages/svelte/src/internal/client/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
PROPS_IS_UPDATED
} from '../../constants.js';
import { readonly } from './proxy/readonly.js';
import { proxy } from './proxy/proxy.js';
import { proxy, unstate } from './proxy/proxy.js';

export const SOURCE = 1;
export const DERIVED = 1 << 1;
Expand Down Expand Up @@ -1771,19 +1771,28 @@ function deep_read(value, visited = new Set()) {
}
}

// TODO remove in a few versions, before 5.0 at the latest
let warned_inspect_changed = false;

/**
* @param {() => any} get_value
* @param {Function} inspect
* @returns {void}
* @param {() => any[]} get_value
* @param {Function} [inspect]
*/
// eslint-disable-next-line no-console
export function inspect(get_value, inspect = console.log) {
let initial = true;

pre_effect(() => {
const fn = () => {
const value = get_value();
inspect(value, initial ? 'init' : 'update');
const value = get_value().map(unstate);
if (value.length === 2 && typeof value[1] === 'function' && !warned_inspect_changed) {
// eslint-disable-next-line no-console
console.warn(
'$inspect() API has changed. See https://svelte-5-preview.vercel.app/docs/runes#$inspect for more information.'
);
warned_inspect_changed = true;
}
inspect(initial ? 'init' : 'update', ...value);
};

inspect_fn = fn;
Expand Down
9 changes: 9 additions & 0 deletions packages/svelte/src/internal/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -552,3 +552,12 @@ export function loop_guard(timeout) {
}
};
}

/**
* @param {any[]} args
* @param {Function} [inspect]
*/
// eslint-disable-next-line no-console
export function inspect(args, inspect = console.log) {
inspect('init', ...args);
}
20 changes: 10 additions & 10 deletions packages/svelte/src/main/ambient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,23 +132,23 @@ declare namespace $effect {
declare function $props<T>(): T;

/**
* Inspects a value whenever it, or the properties it contains, change. Example:
* Inspects one or more values whenever they, or the properties they contain, change. Example:
*
* ```ts
* $inspect({ someValue, someOtherValue })
* $inspect(someValue, someOtherValue)
* ```
*
* If a second argument is provided, it will be called with the value and the event type
* (`'init'` or `'update'`), otherwise the value will be logged to the console.
* `$inspect` returns a `with` function, which you can invoke with a callback function that
* will be called with the value and the event type (`'init'` or `'update'`) on every change.
* By default, the values will be logged to the console.
*
* ```ts
* $inspect(x, console.trace);
* $inspect(y, (y) => { debugger; });
* $inspect(x).with(console.trace);
* $inspect(x, y).with(() => { debugger; });
* ```
*
* https://svelte-5-preview.vercel.app/docs/runes#$inspect
*/
declare function $inspect<T>(
value: T,
callback?: (value: T, type: 'init' | 'update') => void
): void;
declare function $inspect<T extends any[]>(
...values: T
): { with: (type: 'init' | 'update', ...values: T) => void };
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ export default test({
button?.click();
await Promise.resolve();

assert.deepEqual(log, ['X', 'init', 'XX', 'update', 'XXX', 'update']);
assert.deepEqual(log, ['init', 'X', 'update', 'XX', 'update', 'XXX']);
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
let x = $state('x');
let y = $derived(x.toUpperCase());

$inspect(y, push);
$inspect(y).with(push);
</script>

<button on:click={() => x += 'x'}>{x}</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { test } from '../../test';

/**
* @type {any[]}
*/
let log;
/**
* @type {typeof console.log}}
*/
let original_log;

export default test({
compileOptions: {
dev: true
},
before_test() {
log = [];
original_log = console.log;
console.log = (...v) => {
log.push(...v);
};
},
after_test() {
console.log = original_log;
},
async test({ assert, target }) {
const [b1, b2] = target.querySelectorAll('button');
b1.click();
b2.click();
await Promise.resolve();

assert.deepEqual(log, ['init', 0, 0, 'update', 1, 0, 'update', 1, 1]);
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script>
let x = $state(0);
let y = $state(0);

$inspect(x, y);
</script>

<button on:click={() => x++}>{x}</button>
<button on:click={() => y++}>{y}</button>
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
let x = $state(0);
let y = $state(0);

$inspect(x, (x, type) => {
$inspect(x).with((type, x) => {
if (type === 'update') console.log(new Error(), x);
});
</script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ export default test({
after_test() {
console.log = original_log;
},
async test({ assert, target, component }) {
async test({ assert, target }) {
const [b1, b2] = target.querySelectorAll('button');
b1.click();
b2.click();
await Promise.resolve();

assert.deepEqual(log, [0, 'init', 1, 'update']);
assert.deepEqual(log, ['init', 0, 'update', 1]);
}
});
Loading
0