Skip to content

Commit 4a85b7f

Browse files
elliott-with-the-longest-name-on-githubbenmccannRich Harris
authored
feat: unshadow form and data in enhance (#9902)
* feat: Un-shadow `data` and `form` in `enhance`, warn about future deprecation in dev * changeset * snek * Update .changeset/odd-crews-own.md Co-authored-by: Ben McCann <[email protected]> * Update packages/kit/test/apps/dev-only/package.json Co-authored-by: Ben McCann <[email protected]> * am not smart * still not smart * oops * oof * add deprecation notice --------- Co-authored-by: Ben McCann <[email protected]> Co-authored-by: Rich Harris <[email protected]>
1 parent 7042766 commit 4a85b7f

File tree

7 files changed

+197
-16
lines changed

7 files changed

+197
-16
lines changed

.changeset/odd-crews-own.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': minor
3+
---
4+
5+
feat: unshadow `data` and `form` in `enhance` and warn about future deprecation when used in `dev` mode

packages/kit/src/runtime/app/forms.js

+46-14
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,26 @@ export function deserialize(result) {
1414
return parsed;
1515
}
1616

17+
/**
18+
* @param {string} old_name
19+
* @param {string} new_name
20+
* @param {string} call_location
21+
* @returns void
22+
*/
23+
function warn_on_access(old_name, new_name, call_location) {
24+
if (!DEV) return;
25+
// TODO 2.0: Remove this code
26+
console.warn(
27+
`\`${old_name}\` has been deprecated in favor of \`${new_name}\`. \`${old_name}\` will be removed in a future version. (Called from ${call_location})`
28+
);
29+
}
30+
1731
/** @type {import('$app/forms').enhance} */
18-
export function enhance(form, submit = () => {}) {
32+
export function enhance(form_element, submit = () => {}) {
1933
if (
2034
DEV &&
21-
/** @type {HTMLFormElement} */ (HTMLFormElement.prototype.cloneNode.call(form)).method !==
22-
'post'
35+
/** @type {HTMLFormElement} */ (HTMLFormElement.prototype.cloneNode.call(form_element))
36+
.method !== 'post'
2337
) {
2438
throw new Error('use:enhance can only be used on <form> fields with method="POST"');
2539
}
@@ -35,7 +49,7 @@ export function enhance(form, submit = () => {}) {
3549
if (result.type === 'success') {
3650
if (reset !== false) {
3751
// We call reset from the prototype to avoid DOM clobbering
38-
HTMLFormElement.prototype.reset.call(form);
52+
HTMLFormElement.prototype.reset.call(form_element);
3953
}
4054
await invalidateAll();
4155
}
@@ -60,28 +74,38 @@ export function enhance(form, submit = () => {}) {
6074
// We do cloneNode for avoid DOM clobbering - https://github.com/sveltejs/kit/issues/7593
6175
event.submitter?.hasAttribute('formaction')
6276
? /** @type {HTMLButtonElement | HTMLInputElement} */ (event.submitter).formAction
63-
: /** @type {HTMLFormElement} */ (HTMLFormElement.prototype.cloneNode.call(form)).action
77+
: /** @type {HTMLFormElement} */ (HTMLFormElement.prototype.cloneNode.call(form_element))
78+
.action
6479
);
6580

66-
const data = new FormData(form);
81+
const form_data = new FormData(form_element);
6782

6883
const submitter_name = event.submitter?.getAttribute('name');
6984
if (submitter_name) {
70-
data.append(submitter_name, event.submitter?.getAttribute('value') ?? '');
85+
form_data.append(submitter_name, event.submitter?.getAttribute('value') ?? '');
7186
}
7287

7388
const controller = new AbortController();
7489

7590
let cancelled = false;
7691
const cancel = () => (cancelled = true);
7792

93+
// TODO 2.0: Remove `data` and `form`
7894
const callback =
7995
(await submit({
8096
action,
8197
cancel,
8298
controller,
83-
data,
84-
form,
99+
get data() {
100+
warn_on_access('data', 'formData', 'use:enhance submit function');
101+
return form_data;
102+
},
103+
formData: form_data,
104+
get form() {
105+
warn_on_access('form', 'formElement', 'use:enhance submit function');
106+
return form_element;
107+
},
108+
formElement: form_element,
85109
submitter: event.submitter
86110
})) ?? fallback_callback;
87111
if (cancelled) return;
@@ -97,7 +121,7 @@ export function enhance(form, submit = () => {}) {
97121
'x-sveltekit-action': 'true'
98122
},
99123
cache: 'no-store',
100-
body: data,
124+
body: form_data,
101125
signal: controller.signal
102126
});
103127

@@ -110,21 +134,29 @@ export function enhance(form, submit = () => {}) {
110134

111135
callback({
112136
action,
113-
data,
114-
form,
137+
get data() {
138+
warn_on_access('data', 'formData', 'callback returned from use:enhance submit function');
139+
return form_data;
140+
},
141+
formData: form_data,
142+
get form() {
143+
warn_on_access('form', 'formElement', 'callback returned from use:enhance submit function');
144+
return form_element;
145+
},
146+
formElement: form_element,
115147
update: (opts) => fallback_callback({ action, result, reset: opts?.reset }),
116148
// @ts-expect-error generic constraints stuff we don't care about
117149
result
118150
});
119151
}
120152

121153
// @ts-expect-error
122-
HTMLFormElement.prototype.addEventListener.call(form, 'submit', handle_submit);
154+
HTMLFormElement.prototype.addEventListener.call(form_element, 'submit', handle_submit);
123155

124156
return {
125157
destroy() {
126158
// @ts-expect-error
127-
HTMLFormElement.prototype.removeEventListener.call(form, 'submit', handle_submit);
159+
HTMLFormElement.prototype.removeEventListener.call(form_element, 'submit', handle_submit);
128160
}
129161
};
130162
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// TODO 2.0: Remove this code and corresponding tests
2+
export const actions = {
3+
form_submit: () => {
4+
return {
5+
form_submit: true
6+
};
7+
},
8+
9+
form_callback: () => {
10+
return {
11+
form_callback: true
12+
};
13+
},
14+
15+
data_submit: () => {
16+
return {
17+
data_submit: true
18+
};
19+
},
20+
21+
data_callback: () => {
22+
return {
23+
data_callback: true
24+
};
25+
}
26+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<script>
2+
import { enhance } from '$app/forms';
3+
4+
export let form;
5+
6+
const access_form_submit = (node) => {
7+
return enhance(node, ({ form }) => {});
8+
};
9+
const access_data_submit = (node) => {
10+
return enhance(node, ({ data }) => {});
11+
};
12+
const access_form_callback = (node) => {
13+
return enhance(
14+
node,
15+
() =>
16+
({ form, update }) =>
17+
update()
18+
);
19+
};
20+
const access_data_callback = (node) => {
21+
return enhance(
22+
node,
23+
() =>
24+
({ data, update }) =>
25+
update()
26+
);
27+
};
28+
</script>
29+
30+
<form method="POST" action="?/form_submit" use:access_form_submit>
31+
<button id="access-form-in-submit" type="submit"
32+
>{form?.form_submit ? 'processed' : 'not processed'}</button
33+
>
34+
</form>
35+
36+
<form method="POST" action="?/data_submit" use:access_data_submit>
37+
<button id="access-data-in-submit" type="submit"
38+
>{form?.data_submit ? 'processed' : 'not processed'}</button
39+
>
40+
</form>
41+
42+
<form method="POST" action="?/form_callback" use:access_form_callback>
43+
<button id="access-form-in-callback" type="submit"
44+
>{form?.form_callback ? 'processed' : 'not processed'}</button
45+
>
46+
</form>
47+
48+
<form method="POST" action="?/data_callback" use:access_data_callback>
49+
<button id="access-data-in-callback" type="submit"
50+
>{form?.data_callback ? 'processed' : 'not processed'}</button
51+
>
52+
</form>

packages/kit/test/apps/basics/test/test.js

+45
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,51 @@ test.describe('Matchers', () => {
828828
});
829829

830830
test.describe('Actions', () => {
831+
for (const { id, old_name, new_name, call_location } of [
832+
{
833+
id: 'access-form-in-submit',
834+
old_name: 'form',
835+
new_name: 'formElement',
836+
call_location: 'use:enhance submit function'
837+
},
838+
{
839+
id: 'access-form-in-callback',
840+
old_name: 'form',
841+
new_name: 'formElement',
842+
call_location: 'callback returned from use:enhance submit function'
843+
},
844+
{
845+
id: 'access-data-in-submit',
846+
old_name: 'data',
847+
new_name: 'formData',
848+
call_location: 'use:enhance submit function'
849+
},
850+
{
851+
id: 'access-data-in-callback',
852+
old_name: 'data',
853+
new_name: 'formData',
854+
call_location: 'callback returned from use:enhance submit function'
855+
}
856+
]) {
857+
test(`Accessing v2 deprecated properties results in a warning log, type: ${id}`, async ({
858+
page,
859+
javaScriptEnabled
860+
}) => {
861+
test.skip(!javaScriptEnabled, 'skip when js is disabled');
862+
test.skip(!process.env.DEV, 'skip when not in dev mode');
863+
await page.goto('/actions/enhance/old-property-access');
864+
const log_promise = page.waitForEvent('console');
865+
const button = page.locator(`#${id}`);
866+
await button.click();
867+
expect(await button.textContent()).toBe('processed'); // needed to make sure action completes
868+
const log = await log_promise;
869+
expect(log.text()).toBe(
870+
`\`${old_name}\` has been deprecated in favor of \`${new_name}\`. \`${old_name}\` will be removed in a future version. (Called from ${call_location})`
871+
);
872+
expect(log.type()).toBe('warning');
873+
});
874+
}
875+
831876
test('Error props are returned', async ({ page, javaScriptEnabled }) => {
832877
await page.goto('/actions/form-errors');
833878
await page.click('button');

packages/kit/test/apps/dev-only/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "test-basics",
2+
"name": "test-dev-only",
33
"private": true,
44
"version": "0.0.2-next.0",
55
"scripts": {

packages/kit/types/ambient.d.ts

+22-1
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,36 @@ declare module '$app/forms' {
8080
Invalid extends Record<string, unknown> | undefined = Record<string, any>
8181
> = (input: {
8282
action: URL;
83+
/**
84+
* use `formData` instead of `data`
85+
* @deprecated
86+
*/
8387
data: FormData;
88+
formData: FormData;
89+
/**
90+
* use `formElement` instead of `form`
91+
* @deprecated
92+
*/
8493
form: HTMLFormElement;
94+
formElement: HTMLFormElement;
8595
controller: AbortController;
8696
cancel(): void;
8797
submitter: HTMLElement | null;
8898
}) => MaybePromise<
8999
| void
90100
| ((opts: {
101+
/**
102+
* use `formData` instead of `data`
103+
* @deprecated
104+
*/
105+
data: FormData;
106+
formData: FormData;
107+
/**
108+
* use `formElement` instead of `form`
109+
* @deprecated
110+
*/
91111
form: HTMLFormElement;
112+
formElement: HTMLFormElement;
92113
action: URL;
93114
result: ActionResult<Success, Invalid>;
94115
/**
@@ -108,7 +129,7 @@ declare module '$app/forms' {
108129
Success extends Record<string, unknown> | undefined = Record<string, any>,
109130
Invalid extends Record<string, unknown> | undefined = Record<string, any>
110131
>(
111-
form: HTMLFormElement,
132+
formElement: HTMLFormElement,
112133
/**
113134
* Called upon submission with the given FormData and the `action` that should be triggered.
114135
* If `cancel` is called, the form will not be submitted.

0 commit comments

Comments
 (0)