From 4d23a6ab2a7b74d34b91fa4923090b5ef8367226 Mon Sep 17 00:00:00 2001 From: tanhauhau Date: Sat, 13 Apr 2024 11:46:34 +0800 Subject: [PATCH 01/10] feat: reactive url --- .changeset/tidy-chefs-taste.md | 5 + .../svelte/src/reactivity/index-client.js | 1 + .../svelte/src/reactivity/index-server.js | 2 + packages/svelte/src/reactivity/url.js | 287 ++++++++++++++++++ packages/svelte/src/reactivity/url.test.ts | 78 +++++ .../samples/reactive-url/_config.js | 46 +++ .../samples/reactive-url/main.svelte | 25 ++ 7 files changed, 444 insertions(+) create mode 100644 .changeset/tidy-chefs-taste.md create mode 100644 packages/svelte/src/reactivity/url.js create mode 100644 packages/svelte/src/reactivity/url.test.ts create mode 100644 packages/svelte/tests/runtime-runes/samples/reactive-url/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/reactive-url/main.svelte diff --git a/.changeset/tidy-chefs-taste.md b/.changeset/tidy-chefs-taste.md new file mode 100644 index 000000000000..fc6e39dbd499 --- /dev/null +++ b/.changeset/tidy-chefs-taste.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +feat: reactive url diff --git a/packages/svelte/src/reactivity/index-client.js b/packages/svelte/src/reactivity/index-client.js index 38c7d918850d..9c0b28dd2f34 100644 --- a/packages/svelte/src/reactivity/index-client.js +++ b/packages/svelte/src/reactivity/index-client.js @@ -1,3 +1,4 @@ export { ReactiveDate as Date } from './date.js'; export { ReactiveSet as Set } from './set.js'; export { ReactiveMap as Map } from './map.js'; +export { ReactiveURL as URL, ReactiveURLSearchParams as URLSearchParams } from './url.js'; diff --git a/packages/svelte/src/reactivity/index-server.js b/packages/svelte/src/reactivity/index-server.js index 1821bac2de82..63360a30d4f5 100644 --- a/packages/svelte/src/reactivity/index-server.js +++ b/packages/svelte/src/reactivity/index-server.js @@ -1,3 +1,5 @@ export const Date = globalThis.Date; export const Set = globalThis.Set; export const Map = globalThis.Map; +export const URL = globalThis.URL; +export const URLSearchParams = globalThis.URLSearchParams; diff --git a/packages/svelte/src/reactivity/url.js b/packages/svelte/src/reactivity/url.js new file mode 100644 index 000000000000..58218a73d2a8 --- /dev/null +++ b/packages/svelte/src/reactivity/url.js @@ -0,0 +1,287 @@ +import { source, set } from '../internal/client/reactivity/sources.js'; +import { get } from '../internal/client/runtime.js'; +import { map } from './utils.js'; + +const UPDATE = Symbol.for('UPDATE'); + +export class ReactiveURL extends URL { + #url = { + protocol: source(super.protocol), + username: source(super.username), + password: source(super.password), + hostname: source(super.hostname), + port: source(super.port), + pathname: source(super.pathname), + search: source(super.search), + hash: source(super.hash) + }; + #searchParams = new ReactiveURLSearchParams(super.searchParams, this.#url.search); + + get hash() { + get(this.#url.hash); + return super.hash; + } + + set hash(value) { + super.hash = value; + set(this.#url.hash, super.hash); + } + + get host() { + get(this.#url.hostname); + get(this.#url.port); + return super.host; + } + + set host(value) { + super.host = value; + set(this.#url.hostname, super.hostname); + set(this.#url.port, super.port); + } + + get hostname() { + get(this.#url.hostname); + return super.hostname; + } + + set hostname(value) { + super.hostname = value; + set(this.#url.hostname, super.hostname); + } + + get href() { + get(this.#url.protocol); + get(this.#url.username); + get(this.#url.password); + get(this.#url.hostname); + get(this.#url.port); + get(this.#url.pathname); + get(this.#url.search); + get(this.#url.hash); + return super.href; + } + + set href(value) { + super.href = value; + set(this.#url.protocol, super.protocol); + set(this.#url.username, super.username); + set(this.#url.password, super.password); + set(this.#url.hostname, super.hostname); + set(this.#url.port, super.port); + set(this.#url.pathname, super.pathname); + set(this.#url.search, super.search); + set(this.#url.hash, super.hash); + this.#searchParams[UPDATE](super.searchParams); + } + + get password() { + get(this.#url.password); + return super.password; + } + + set password(value) { + super.password = value; + set(this.#url.password, super.password); + } + + get pathname() { + get(this.#url.pathname); + return super.pathname; + } + + set pathname(value) { + super.pathname = value; + set(this.#url.pathname, super.pathname); + } + + get port() { + get(this.#url.port); + return super.port; + } + + set port(value) { + super.port = value; + set(this.#url.port, super.port); + } + + get protocol() { + get(this.#url.protocol); + return super.protocol; + } + + set protocol(value) { + super.protocol = value; + set(this.#url.protocol, super.protocol); + } + + get search() { + get(this.#url.search); + return super.search; + } + + set search(value) { + super.search = value; + set(this.#url.search, super.search); + this.#searchParams[UPDATE](super.searchParams); + } + + get username() { + get(this.#url.username); + return super.username; + } + + set username(value) { + super.username = value; + set(this.#url.username, super.username); + } + + get origin() { + get(this.#url.protocol); + get(this.#url.hostname); + get(this.#url.port); + return super.origin; + } + + get searchParams() { + return this.#searchParams; + } + + toString() { + this.href; + return super.toString(); + } + + toJSON() { + this.href; + return super.toJSON(); + } + + /** + * @param {string} input + * @param {string=} base + */ + constructor(input, base) { + super(input, base); + } +} + +export class ReactiveURLSearchParams extends URLSearchParams { + #url_search_params; + #search; + #version = source(0); + + #increment_version() { + set(this.#version, this.#version.v + 1); + } + #update_search() { + set(this.#search, '?' + this.#url_search_params.toString()); + } + + /** + * + * @param {URLSearchParams} value + */ + [UPDATE](value) { + this.#url_search_params = value; + this.#increment_version(); + } + /** + * + * @param {URLSearchParams} url_search_params + * @param {import('../internal/client/reactivity/types.js').Source} search + */ + constructor(url_search_params, search) { + super(); + this.#url_search_params = url_search_params; + this.#search = search; + } + + /** + * + * @param {string} name + * @param {string} value + * @returns {void} + */ + append(name, value) { + this.#increment_version(); + this.#update_search(); + return this.#url_search_params.append(name, value); + } + /** + * + * @param {string} name + * @param {string=} value + * @returns {void} + */ + delete(name, value) { + this.#increment_version(); + this.#update_search(); + return this.#url_search_params.delete(name, value); + } + /** + * + * @param {string} name + * @returns {string|null} + */ + get(name) { + get(this.#version); + return this.#url_search_params.get(name); + } + /** + * + * @param {string} name + * @returns {string[]} + */ + getAll(name) { + get(this.#version); + return this.#url_search_params.getAll(name); + } + /** + * + * @param {string} name + * @param {string=} value + * @returns {boolean} + */ + has(name, value) { + get(this.#version); + return this.#url_search_params.has(name, value); + } + keys() { + get(this.#version); + return this.#url_search_params.keys(); + } + /** + * + * @param {string} name + * @param {string} value + * @returns {void} + */ + set(name, value) { + this.#increment_version(); + this.#update_search(); + return this.#url_search_params.set(name, value); + } + sort() { + this.#increment_version(); + this.#update_search(); + return this.#url_search_params.sort(); + } + toString() { + get(this.#version); + return this.#url_search_params.toString(); + } + values() { + get(this.#version); + return this.#url_search_params.values(); + } + entries() { + get(this.#version); + return this.#url_search_params.entries(); + } + [Symbol.iterator]() { + return this.entries(); + } + get size() { + return this.#url_search_params.size; + } +} diff --git a/packages/svelte/src/reactivity/url.test.ts b/packages/svelte/src/reactivity/url.test.ts new file mode 100644 index 000000000000..53f7796bc2ee --- /dev/null +++ b/packages/svelte/src/reactivity/url.test.ts @@ -0,0 +1,78 @@ +import { render_effect, effect_root } from '../internal/client/reactivity/effects.js'; +import { flushSync } from '../index-client.js'; +import { ReactiveURL } from './url.js'; +import { assert, test } from 'vitest'; + +test('url.hash', () => { + const url = new ReactiveURL('http://google.com'); + const log: any = []; + + const cleanup = effect_root(() => { + render_effect(() => { + log.push(url.hash); + }); + }); + + flushSync(() => { + url.hash = 'abc'; + }); + + flushSync(() => { + url.href = 'http://google.com/a/b/c#def'; + }); + + flushSync(() => { + // does not affect hash + url.pathname = 'e/f'; + }); + + assert.deepEqual(log, ['', '#abc', '#def']); + + cleanup(); +}); + +test('url.searchParams', () => { + const url = new ReactiveURL('https://svelte.dev?foo=bar&t=123'); + const log: any = []; + + const cleanup = effect_root(() => { + render_effect(() => { + log.push('search: ' + url.search); + }); + render_effect(() => { + log.push('foo: ' + url.searchParams.get('foo')); + }); + render_effect(() => { + log.push('q: ' + url.searchParams.has('q')); + }); + }); + + flushSync(() => { + url.search = '?q=kit&foo=baz'; + }); + + flushSync(() => { + url.searchParams.append('foo', 'qux'); + }); + + flushSync(() => { + url.searchParams.delete('foo'); + }); + + assert.deepEqual(log, [ + 'search: ?foo=bar&t=123', + 'foo: bar', + 'q: false', + 'search: ?q=kit&foo=baz', + 'foo: baz', + 'q: true', + 'search: ?q=kit&foo=baz&foo=qux', + 'foo: baz', + 'q: true', + 'search: ?q=kit', + 'foo: null', + 'q: true' + ]); + + cleanup(); +}); diff --git a/packages/svelte/tests/runtime-runes/samples/reactive-url/_config.js b/packages/svelte/tests/runtime-runes/samples/reactive-url/_config.js new file mode 100644 index 000000000000..88c036fa823a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/reactive-url/_config.js @@ -0,0 +1,46 @@ +import { flushSync } from '../../../../src/index-client'; +import { test } from '../../test'; + +export default test({ + html: `
href: https://svelte.dev/repl/hello-world?version=5.0
host: svelte.dev
pathname: /repl/hello-world
search: ?version=5.0
version: 5.0
t:
`, + + test({ assert, target }) { + const [btn, btn2, btn3, btn4] = target.querySelectorAll('button'); + + flushSync(() => { + btn?.click(); + }); + + assert.htmlEqual( + target.innerHTML, + `
href: https://kit.svelte.dev/repl/hello-world?version=5.0
host: kit.svelte.dev
pathname: /repl/hello-world
search: ?version=5.0
version: 5.0
t:
` + ); + + flushSync(() => { + btn2?.click(); + }); + + assert.htmlEqual( + target.innerHTML, + `
href: https://kit.svelte.dev/docs/introduction?version=5.0
host: kit.svelte.dev
pathname: /docs/introduction
search: ?version=5.0
version: 5.0
t:
` + ); + + flushSync(() => { + btn3?.click(); + }); + + assert.htmlEqual( + target.innerHTML, + `
href: https://kit.svelte.dev/docs/introduction?t=123
host: kit.svelte.dev
pathname: /docs/introduction
search: ?t=123
version:
t: 123
` + ); + + flushSync(() => { + btn4?.click(); + }); + + assert.htmlEqual( + target.innerHTML, + `
href: https://google.com/search?version=3
host: google.com
pathname: /search
search: ?version=3
version: 3
t:
` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/reactive-url/main.svelte b/packages/svelte/tests/runtime-runes/samples/reactive-url/main.svelte new file mode 100644 index 000000000000..2116b55a405e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/reactive-url/main.svelte @@ -0,0 +1,25 @@ + + +
href: {url.href}
+
host: {url.host}
+
pathname: {url.pathname}
+
search: {url.search}
+
version: {url.searchParams.get('version')}
+
t: {url.searchParams.get('t')}
+ + + + + From 63945ec447ec819c8978f86cb95d4d7c774daec1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 14 Apr 2024 14:03:33 -0400 Subject: [PATCH 02/10] fix --- packages/svelte/src/reactivity/url.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/reactivity/url.js b/packages/svelte/src/reactivity/url.js index 58218a73d2a8..f23646e1024e 100644 --- a/packages/svelte/src/reactivity/url.js +++ b/packages/svelte/src/reactivity/url.js @@ -1,8 +1,8 @@ import { source, set } from '../internal/client/reactivity/sources.js'; import { get } from '../internal/client/runtime.js'; -import { map } from './utils.js'; -const UPDATE = Symbol.for('UPDATE'); +const UPDATE = Symbol('UPDATE'); +const VERSION = Symbol('version'); export class ReactiveURL extends URL { #url = { @@ -116,6 +116,7 @@ export class ReactiveURL extends URL { get search() { get(this.#url.search); + get(this.#searchParams[VERSION]); return super.search; } @@ -169,6 +170,7 @@ export class ReactiveURLSearchParams extends URLSearchParams { #url_search_params; #search; #version = source(0); + [VERSION] = this.#version; #increment_version() { set(this.#version, this.#version.v + 1); From 94108ceb290d73005a305f38dde70be36f1fd499 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 14 Apr 2024 14:10:01 -0400 Subject: [PATCH 03/10] simplify --- packages/svelte/src/reactivity/url.js | 36 ++++++++------------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/packages/svelte/src/reactivity/url.js b/packages/svelte/src/reactivity/url.js index f23646e1024e..28f7c0e5b415 100644 --- a/packages/svelte/src/reactivity/url.js +++ b/packages/svelte/src/reactivity/url.js @@ -15,11 +15,11 @@ export class ReactiveURL extends URL { search: source(super.search), hash: source(super.hash) }; + #searchParams = new ReactiveURLSearchParams(super.searchParams, this.#url.search); get hash() { - get(this.#url.hash); - return super.hash; + return get(this.#url.hash); } set hash(value) { @@ -40,8 +40,7 @@ export class ReactiveURL extends URL { } get hostname() { - get(this.#url.hostname); - return super.hostname; + return get(this.#url.hostname); } set hostname(value) { @@ -75,8 +74,7 @@ export class ReactiveURL extends URL { } get password() { - get(this.#url.password); - return super.password; + return get(this.#url.password); } set password(value) { @@ -85,8 +83,7 @@ export class ReactiveURL extends URL { } get pathname() { - get(this.#url.pathname); - return super.pathname; + return get(this.#url.pathname); } set pathname(value) { @@ -95,8 +92,7 @@ export class ReactiveURL extends URL { } get port() { - get(this.#url.port); - return super.port; + return get(this.#url.port); } set port(value) { @@ -105,8 +101,7 @@ export class ReactiveURL extends URL { } get protocol() { - get(this.#url.protocol); - return super.protocol; + return get(this.#url.protocol); } set protocol(value) { @@ -127,8 +122,7 @@ export class ReactiveURL extends URL { } get username() { - get(this.#url.username); - return super.username; + return get(this.#url.username); } set username(value) { @@ -148,21 +142,11 @@ export class ReactiveURL extends URL { } toString() { - this.href; - return super.toString(); + return this.href; } toJSON() { - this.href; - return super.toJSON(); - } - - /** - * @param {string} input - * @param {string=} base - */ - constructor(input, base) { - super(input, base); + return this.href; } } From a0645dd48620d4971d846184cda5a5255facdf3b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 14 Apr 2024 14:13:42 -0400 Subject: [PATCH 04/10] tidy --- packages/svelte/src/reactivity/url.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/svelte/src/reactivity/url.js b/packages/svelte/src/reactivity/url.js index 28f7c0e5b415..bf5781f73fba 100644 --- a/packages/svelte/src/reactivity/url.js +++ b/packages/svelte/src/reactivity/url.js @@ -159,6 +159,7 @@ export class ReactiveURLSearchParams extends URLSearchParams { #increment_version() { set(this.#version, this.#version.v + 1); } + #update_search() { set(this.#search, '?' + this.#url_search_params.toString()); } @@ -171,8 +172,8 @@ export class ReactiveURLSearchParams extends URLSearchParams { this.#url_search_params = value; this.#increment_version(); } + /** - * * @param {URLSearchParams} url_search_params * @param {import('../internal/client/reactivity/types.js').Source} search */ @@ -183,7 +184,6 @@ export class ReactiveURLSearchParams extends URLSearchParams { } /** - * * @param {string} name * @param {string} value * @returns {void} @@ -193,8 +193,8 @@ export class ReactiveURLSearchParams extends URLSearchParams { this.#update_search(); return this.#url_search_params.append(name, value); } + /** - * * @param {string} name * @param {string=} value * @returns {void} @@ -204,8 +204,8 @@ export class ReactiveURLSearchParams extends URLSearchParams { this.#update_search(); return this.#url_search_params.delete(name, value); } + /** - * * @param {string} name * @returns {string|null} */ @@ -213,8 +213,8 @@ export class ReactiveURLSearchParams extends URLSearchParams { get(this.#version); return this.#url_search_params.get(name); } + /** - * * @param {string} name * @returns {string[]} */ @@ -222,8 +222,8 @@ export class ReactiveURLSearchParams extends URLSearchParams { get(this.#version); return this.#url_search_params.getAll(name); } + /** - * * @param {string} name * @param {string=} value * @returns {boolean} @@ -232,12 +232,13 @@ export class ReactiveURLSearchParams extends URLSearchParams { get(this.#version); return this.#url_search_params.has(name, value); } + keys() { get(this.#version); return this.#url_search_params.keys(); } + /** - * * @param {string} name * @param {string} value * @returns {void} @@ -247,26 +248,32 @@ export class ReactiveURLSearchParams extends URLSearchParams { this.#update_search(); return this.#url_search_params.set(name, value); } + sort() { this.#increment_version(); this.#update_search(); return this.#url_search_params.sort(); } + toString() { get(this.#version); return this.#url_search_params.toString(); } + values() { get(this.#version); return this.#url_search_params.values(); } + entries() { get(this.#version); return this.#url_search_params.entries(); } + [Symbol.iterator]() { return this.entries(); } + get size() { return this.#url_search_params.size; } From ea73ad31e11df9770fa375b308d1d90632efb458 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 14 Apr 2024 14:47:52 -0400 Subject: [PATCH 05/10] simplify, make ReactiveURLSearchParams signature match URLSearchParams --- packages/svelte/src/reactivity/url.js | 75 +++++++++------------- packages/svelte/src/reactivity/url.test.ts | 25 +++++++- 2 files changed, 53 insertions(+), 47 deletions(-) diff --git a/packages/svelte/src/reactivity/url.js b/packages/svelte/src/reactivity/url.js index bf5781f73fba..cb9697e7817e 100644 --- a/packages/svelte/src/reactivity/url.js +++ b/packages/svelte/src/reactivity/url.js @@ -1,8 +1,7 @@ import { source, set } from '../internal/client/reactivity/sources.js'; import { get } from '../internal/client/runtime.js'; -const UPDATE = Symbol('UPDATE'); -const VERSION = Symbol('version'); +const REPLACE = Symbol(); export class ReactiveURL extends URL { #url = { @@ -16,7 +15,7 @@ export class ReactiveURL extends URL { hash: source(super.hash) }; - #searchParams = new ReactiveURLSearchParams(super.searchParams, this.#url.search); + #searchParams = new ReactiveURLSearchParams(super.searchParams); get hash() { return get(this.#url.hash); @@ -70,7 +69,7 @@ export class ReactiveURL extends URL { set(this.#url.pathname, super.pathname); set(this.#url.search, super.search); set(this.#url.hash, super.hash); - this.#searchParams[UPDATE](super.searchParams); + this.#searchParams[REPLACE](super.searchParams); } get password() { @@ -110,15 +109,13 @@ export class ReactiveURL extends URL { } get search() { - get(this.#url.search); - get(this.#searchParams[VERSION]); - return super.search; + const search = this.#searchParams.toString(); + return search ? `?${search}` : ''; } set search(value) { super.search = value; - set(this.#url.search, super.search); - this.#searchParams[UPDATE](super.searchParams); + this.#searchParams[REPLACE](super.searchParams); } get username() { @@ -151,36 +148,25 @@ export class ReactiveURL extends URL { } export class ReactiveURLSearchParams extends URLSearchParams { - #url_search_params; - #search; #version = source(0); - [VERSION] = this.#version; #increment_version() { set(this.#version, this.#version.v + 1); } - #update_search() { - set(this.#search, '?' + this.#url_search_params.toString()); - } - /** - * - * @param {URLSearchParams} value + * @param {URLSearchParams} params */ - [UPDATE](value) { - this.#url_search_params = value; - this.#increment_version(); - } + [REPLACE](params) { + for (const key of [...super.keys()]) { + super.delete(key); + } - /** - * @param {URLSearchParams} url_search_params - * @param {import('../internal/client/reactivity/types.js').Source} search - */ - constructor(url_search_params, search) { - super(); - this.#url_search_params = url_search_params; - this.#search = search; + for (const [key, value] of params) { + super.append(key, value); + } + + this.#increment_version(); } /** @@ -190,8 +176,7 @@ export class ReactiveURLSearchParams extends URLSearchParams { */ append(name, value) { this.#increment_version(); - this.#update_search(); - return this.#url_search_params.append(name, value); + return super.append(name, value); } /** @@ -201,8 +186,7 @@ export class ReactiveURLSearchParams extends URLSearchParams { */ delete(name, value) { this.#increment_version(); - this.#update_search(); - return this.#url_search_params.delete(name, value); + return super.delete(name, value); } /** @@ -211,7 +195,7 @@ export class ReactiveURLSearchParams extends URLSearchParams { */ get(name) { get(this.#version); - return this.#url_search_params.get(name); + return super.get(name); } /** @@ -220,7 +204,7 @@ export class ReactiveURLSearchParams extends URLSearchParams { */ getAll(name) { get(this.#version); - return this.#url_search_params.getAll(name); + return super.getAll(name); } /** @@ -230,12 +214,12 @@ export class ReactiveURLSearchParams extends URLSearchParams { */ has(name, value) { get(this.#version); - return this.#url_search_params.has(name, value); + return super.has(name, value); } keys() { get(this.#version); - return this.#url_search_params.keys(); + return super.keys(); } /** @@ -245,29 +229,27 @@ export class ReactiveURLSearchParams extends URLSearchParams { */ set(name, value) { this.#increment_version(); - this.#update_search(); - return this.#url_search_params.set(name, value); + return super.set(name, value); } sort() { this.#increment_version(); - this.#update_search(); - return this.#url_search_params.sort(); + return super.sort(); } toString() { get(this.#version); - return this.#url_search_params.toString(); + return super.toString(); } values() { get(this.#version); - return this.#url_search_params.values(); + return super.values(); } entries() { get(this.#version); - return this.#url_search_params.entries(); + return super.entries(); } [Symbol.iterator]() { @@ -275,6 +257,7 @@ export class ReactiveURLSearchParams extends URLSearchParams { } get size() { - return this.#url_search_params.size; + get(this.#version); + return super.size; } } diff --git a/packages/svelte/src/reactivity/url.test.ts b/packages/svelte/src/reactivity/url.test.ts index 53f7796bc2ee..910edba5021d 100644 --- a/packages/svelte/src/reactivity/url.test.ts +++ b/packages/svelte/src/reactivity/url.test.ts @@ -1,6 +1,6 @@ import { render_effect, effect_root } from '../internal/client/reactivity/effects.js'; import { flushSync } from '../index-client.js'; -import { ReactiveURL } from './url.js'; +import { ReactiveURL, ReactiveURLSearchParams } from './url.js'; import { assert, test } from 'vitest'; test('url.hash', () => { @@ -76,3 +76,26 @@ test('url.searchParams', () => { cleanup(); }); + +test('URLSearchParams', () => { + const params = new ReactiveURLSearchParams(); + const log: any = []; + + const cleanup = effect_root(() => { + render_effect(() => { + log.push(params.toString()); + }); + }); + + flushSync(() => { + params.set('a', 'b'); + }); + + flushSync(() => { + params.append('a', 'c'); + }); + + assert.deepEqual(log, ['', 'a=b', 'a=b&a=c']); + + cleanup(); +}); From 293c89049e315f4cffa02180508f96b22d7382ba Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 14 Apr 2024 14:52:34 -0400 Subject: [PATCH 06/10] Update .changeset/tidy-chefs-taste.md --- .changeset/tidy-chefs-taste.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/tidy-chefs-taste.md b/.changeset/tidy-chefs-taste.md index fc6e39dbd499..ef87b2fa463b 100644 --- a/.changeset/tidy-chefs-taste.md +++ b/.changeset/tidy-chefs-taste.md @@ -2,4 +2,4 @@ "svelte": patch --- -feat: reactive url +feat: reactive `URL` and `URLSearchParams` classes From c61c946662ee9076ae198fff6c627f42711a5aa6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 14 Apr 2024 15:13:58 -0400 Subject: [PATCH 07/10] fix --- packages/svelte/src/reactivity/url.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/svelte/src/reactivity/url.js b/packages/svelte/src/reactivity/url.js index cb9697e7817e..4fb323f90510 100644 --- a/packages/svelte/src/reactivity/url.js +++ b/packages/svelte/src/reactivity/url.js @@ -11,11 +11,20 @@ export class ReactiveURL extends URL { hostname: source(super.hostname), port: source(super.port), pathname: source(super.pathname), - search: source(super.search), hash: source(super.hash) }; - #searchParams = new ReactiveURLSearchParams(super.searchParams); + #searchParams = new ReactiveURLSearchParams(); + + /** + * @param {string | URL} url + * @param {string | URL} [base] + */ + constructor(url, base) { + url = new URL(url, base); + super(url); + this.#searchParams[REPLACE](url.searchParams); + } get hash() { return get(this.#url.hash); @@ -54,8 +63,8 @@ export class ReactiveURL extends URL { get(this.#url.hostname); get(this.#url.port); get(this.#url.pathname); - get(this.#url.search); get(this.#url.hash); + this.#searchParams.toString(); return super.href; } @@ -67,7 +76,6 @@ export class ReactiveURL extends URL { set(this.#url.hostname, super.hostname); set(this.#url.port, super.port); set(this.#url.pathname, super.pathname); - set(this.#url.search, super.search); set(this.#url.hash, super.hash); this.#searchParams[REPLACE](super.searchParams); } @@ -109,13 +117,12 @@ export class ReactiveURL extends URL { } get search() { - const search = this.#searchParams.toString(); + const search = this.#searchParams?.toString(); return search ? `?${search}` : ''; } set search(value) { - super.search = value; - this.#searchParams[REPLACE](super.searchParams); + this.#searchParams[REPLACE](new URLSearchParams(value.replace(/^\?/, ''))); } get username() { From 099d547ae2ee3798fe718f8397643d6581e9318d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 14 Apr 2024 15:19:01 -0400 Subject: [PATCH 08/10] fix --- packages/svelte/src/reactivity/url.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/svelte/src/reactivity/url.js b/packages/svelte/src/reactivity/url.js index 4fb323f90510..629dfdcca7ec 100644 --- a/packages/svelte/src/reactivity/url.js +++ b/packages/svelte/src/reactivity/url.js @@ -122,6 +122,7 @@ export class ReactiveURL extends URL { } set search(value) { + super.search = value; this.#searchParams[REPLACE](new URLSearchParams(value.replace(/^\?/, ''))); } From 642db07827cb4f777c647769161ff42082c784db Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 14 Apr 2024 15:25:30 -0400 Subject: [PATCH 09/10] regenerate types --- packages/svelte/types/index.d.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index fb90150af75d..43e1a649dd6c 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -1750,6 +1750,8 @@ declare module 'svelte/compiler' { style?: Preprocessor; script?: Preprocessor; } + + export { walk }; } declare module 'svelte/easing' { @@ -1965,12 +1967,12 @@ declare module 'svelte/motion' { } declare module 'svelte/reactivity' { - export class Date extends Date { + class ReactiveDate extends Date { constructor(...values: any[]); #private; } - export class Set extends Set { + class ReactiveSet extends Set { constructor(value?: Iterable | null | undefined); @@ -1985,7 +1987,7 @@ declare module 'svelte/reactivity' { [Symbol.iterator](): IterableIterator; #private; } - export class Map extends Map { + class ReactiveMap extends Map { constructor(value?: Iterable | null | undefined); @@ -2004,6 +2006,18 @@ declare module 'svelte/reactivity' { [Symbol.iterator](): IterableIterator<[K, V]>; #private; } + class ReactiveURL extends URL { + get searchParams(): ReactiveURLSearchParams; + #private; + } + class ReactiveURLSearchParams extends URLSearchParams { + + [REPLACE](params: URLSearchParams): void; + #private; + } + const REPLACE: unique symbol; + + export { ReactiveDate as Date, ReactiveSet as Set, ReactiveMap as Map, ReactiveURL as URL, ReactiveURLSearchParams as URLSearchParams }; } declare module 'svelte/server' { From f55ad91e5e0622171c856f43aef3de89f3bc0bca Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 14 Apr 2024 15:30:38 -0400 Subject: [PATCH 10/10] improve minifiability --- packages/svelte/src/reactivity/url.js | 87 +++++++++++++-------------- 1 file changed, 42 insertions(+), 45 deletions(-) diff --git a/packages/svelte/src/reactivity/url.js b/packages/svelte/src/reactivity/url.js index 629dfdcca7ec..3f11a4af37c7 100644 --- a/packages/svelte/src/reactivity/url.js +++ b/packages/svelte/src/reactivity/url.js @@ -4,16 +4,13 @@ import { get } from '../internal/client/runtime.js'; const REPLACE = Symbol(); export class ReactiveURL extends URL { - #url = { - protocol: source(super.protocol), - username: source(super.username), - password: source(super.password), - hostname: source(super.hostname), - port: source(super.port), - pathname: source(super.pathname), - hash: source(super.hash) - }; - + #protocol = source(super.protocol); + #username = source(super.username); + #password = source(super.password); + #hostname = source(super.hostname); + #port = source(super.port); + #pathname = source(super.pathname); + #hash = source(super.hash); #searchParams = new ReactiveURLSearchParams(); /** @@ -27,93 +24,93 @@ export class ReactiveURL extends URL { } get hash() { - return get(this.#url.hash); + return get(this.#hash); } set hash(value) { super.hash = value; - set(this.#url.hash, super.hash); + set(this.#hash, super.hash); } get host() { - get(this.#url.hostname); - get(this.#url.port); + get(this.#hostname); + get(this.#port); return super.host; } set host(value) { super.host = value; - set(this.#url.hostname, super.hostname); - set(this.#url.port, super.port); + set(this.#hostname, super.hostname); + set(this.#port, super.port); } get hostname() { - return get(this.#url.hostname); + return get(this.#hostname); } set hostname(value) { super.hostname = value; - set(this.#url.hostname, super.hostname); + set(this.#hostname, super.hostname); } get href() { - get(this.#url.protocol); - get(this.#url.username); - get(this.#url.password); - get(this.#url.hostname); - get(this.#url.port); - get(this.#url.pathname); - get(this.#url.hash); + get(this.#protocol); + get(this.#username); + get(this.#password); + get(this.#hostname); + get(this.#port); + get(this.#pathname); + get(this.#hash); this.#searchParams.toString(); return super.href; } set href(value) { super.href = value; - set(this.#url.protocol, super.protocol); - set(this.#url.username, super.username); - set(this.#url.password, super.password); - set(this.#url.hostname, super.hostname); - set(this.#url.port, super.port); - set(this.#url.pathname, super.pathname); - set(this.#url.hash, super.hash); + set(this.#protocol, super.protocol); + set(this.#username, super.username); + set(this.#password, super.password); + set(this.#hostname, super.hostname); + set(this.#port, super.port); + set(this.#pathname, super.pathname); + set(this.#hash, super.hash); this.#searchParams[REPLACE](super.searchParams); } get password() { - return get(this.#url.password); + return get(this.#password); } set password(value) { super.password = value; - set(this.#url.password, super.password); + set(this.#password, super.password); } get pathname() { - return get(this.#url.pathname); + return get(this.#pathname); } set pathname(value) { super.pathname = value; - set(this.#url.pathname, super.pathname); + set(this.#pathname, super.pathname); } get port() { - return get(this.#url.port); + return get(this.#port); } set port(value) { super.port = value; - set(this.#url.port, super.port); + set(this.#port, super.port); } get protocol() { - return get(this.#url.protocol); + return get(this.#protocol); } set protocol(value) { super.protocol = value; - set(this.#url.protocol, super.protocol); + set(this.#protocol, super.protocol); } get search() { @@ -127,18 +124,18 @@ export class ReactiveURL extends URL { } get username() { - return get(this.#url.username); + return get(this.#username); } set username(value) { super.username = value; - set(this.#url.username, super.username); + set(this.#username, super.username); } get origin() { - get(this.#url.protocol); - get(this.#url.hostname); - get(this.#url.port); + get(this.#protocol); + get(this.#hostname); + get(this.#port); return super.origin; }