Repository: sveltejs/kit
Pull request #7014 (2022-09-24 17:53:15)

[docs] $page.form and Page interface docs
### Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
- [ ] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
- [x] This message body should clearly illustrate what problems it solves.
- [ ] Ideally, include a test that fails without this PR but passes with it.

### Tests
- [ ] Run the tests with `pnpm test` and lint the project with `pnpm lint` and `pnpm check`

### Changesets
- [ ] If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running `pnpm changeset` and following the prompts. All changesets should be `patch` until SvelteKit 1.0


changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: 94fd819dcbd8ef92bb583d0168e7d369da607c7f

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/master/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/sveltejs/kit/new/page-form-docs?filename=.changeset/rich-guests-relate.md&value=---%0A%22%40sveltejs%2Fkit%22%3A%20patch%0A---%0A%0A%5Bdocs%5D%20%24page.form%20and%20Page%20interface%20docs%0A)



Patch:
diff --git a/.changeset/proud-dingos-sing.md b/.changeset/proud-dingos-sing.md
new file mode 100644
index 000000000..04e46028a
--- /dev/null
+++ b/.changeset/proud-dingos-sing.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/adapter-node': patch
+---
+
+Disable builtin modules warning
diff --git a/.changeset/seven-roses-float.md b/.changeset/seven-roses-float.md
new file mode 100644
index 000000000..dc70f52ca
--- /dev/null
+++ b/.changeset/seven-roses-float.md
@@ -0,0 +1,5 @@
+---
+'svelte-migrate': patch
+---
+
+[fix] more robust uppercase migration
diff --git a/.changeset/sharp-beers-learn.md b/.changeset/sharp-beers-learn.md
new file mode 100644
index 000000000..77545786a
--- /dev/null
+++ b/.changeset/sharp-beers-learn.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Add the submitter value with name to formData for use:enhance
diff --git a/documentation/docs/06-form-actions.md b/documentation/docs/06-form-actions.md
index b11a2f74e..a6ed05aa1 100644
--- a/documentation/docs/06-form-actions.md
+++ b/documentation/docs/06-form-actions.md
@@ -92,7 +92,7 @@ As well as the `action` attribute, we can use the `formaction` attribute on a bu
 
 ### Anatomy of an action
 
-Each action receives a `RequestEvent` object, allowing you to read the data with `request.formData()`. After processing the request (for example, logging the user in by setting a cookie), the action can respond with data that will be available as `form` until the next update.
+Each action receives a `RequestEvent` object, allowing you to read the data with `request.formData()`. After processing the request (for example, logging the user in by setting a cookie), the action can respond with data that will be available through the `form` property on the corresponding page and through `$page.form` app-wide until the next update.
 
 ```js
 // @errors: 2339 2304
@@ -249,13 +249,13 @@ The easiest way to progressively enhance a form is to add the `use:enhance` acti
 
 Without an argument, `use:enhance` will emulate the browser-native behaviour, just without the full-page reloads. It will:
 
-- update the `form` property and invalidate all data on a successful response
-- update the `form` property on a invalid response
+- update the `form` property and `$page.form` and invalidate all data on a successful response
+- update the `form` property and `$page.form` on a invalid response
 - update `$page.status` on a successful or invalid response
 - call `goto` on a redirect response
 - render the nearest `+error` boundary if an error occurs
 
-> By default the `form` property is only updated for actions that are in a `+page.server.js` alongside the `+page.svelte` because in the native form submission case you would be redirected to the page the action is on
+> By default the `form` property and `$page.form` is only updated for actions that are in a `+page.server.js` alongside the `+page.svelte` because in the native form submission case you would be redirected to the page the action is on
 
 To customise the behaviour, you can provide a function that runs immediately before the form is submitted, and (optionally) returns a callback that runs with the `ActionResult`.
 
@@ -307,7 +307,7 @@ If you provide your own callbacks, you may need to reproduce part of the default
 
 The behaviour of `applyAction(result)` depends on `result.type`:
 
-- `success`, `invalid` — sets `$page.status` to `result.status` and updates `form` to `result.data`
+- `success`, `invalid` — sets `$page.status` to `result.status` and updates `form` and `$page.form` to `result.data`
 - `redirect` — calls `goto(result.location)`
 - `error` — renders the nearest `+error` boundary with `result.error`
 
diff --git a/packages/adapter-netlify/rollup.config.js b/packages/adapter-netlify/rollup.config.js
index 7e4e1a1ba..254881caf 100644
--- a/packages/adapter-netlify/rollup.config.js
+++ b/packages/adapter-netlify/rollup.config.js
@@ -18,7 +18,7 @@ const config = {
 			format: 'esm'
 		}
 	],
-	plugins: [nodeResolve(), commonjs(), json()],
+	plugins: [nodeResolve({ preferBuiltins: true }), commonjs(), json()],
 	external: (id) => id === '0SERVER' || id.startsWith('node:'),
 	preserveEntrySignatures: 'exports-only'
 };
diff --git a/packages/adapter-node/index.js b/packages/adapter-node/index.js
index 17bb5310c..3e4b2df13 100644
--- a/packages/adapter-node/index.js
+++ b/packages/adapter-node/index.js
@@ -61,7 +61,7 @@ export default function (opts = {}) {
 					manifest: `${tmp}/manifest.js`
 				},
 				external: [...Object.keys(pkg.dependencies || {})],
-				plugins: [nodeResolve(), commonjs(), json()]
+				plugins: [nodeResolve({ preferBuiltins: true }), commonjs(), json()]
 			});
 
 			await bundle.write({
diff --git a/packages/adapter-node/rollup.config.js b/packages/adapter-node/rollup.config.js
index 6384234b2..93c9b3502 100644
--- a/packages/adapter-node/rollup.config.js
+++ b/packages/adapter-node/rollup.config.js
@@ -10,7 +10,7 @@ export default [
 			file: 'files/index.js',
 			format: 'esm'
 		},
-		plugins: [nodeResolve(), commonjs(), json()],
+		plugins: [nodeResolve({ preferBuiltins: true }), commonjs(), json()],
 		external: ['ENV', 'HANDLER', ...builtinModules]
 	},
 	{
diff --git a/packages/kit/src/runtime/app/forms.js b/packages/kit/src/runtime/app/forms.js
index f55f3c837..738ad9d36 100644
--- a/packages/kit/src/runtime/app/forms.js
+++ b/packages/kit/src/runtime/app/forms.js
@@ -51,6 +51,12 @@ export function enhance(form, submit = () => {}) {
 		);
 
 		const data = new FormData(form);
+
+		const submitter_name = event.submitter?.getAttribute('name');
+		if (submitter_name) {
+			data.append(submitter_name, event.submitter?.getAttribute('value') ?? '');
+		}
+
 		const controller = new AbortController();
 
 		let cancelled = false;
diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts
index 5e1f49e4f..f3aecfbcb 100644
--- a/packages/kit/types/index.d.ts
+++ b/packages/kit/types/index.d.ts
@@ -295,13 +295,37 @@ export interface Navigation {
 	delta?: number;
 }
 
+/**
+ * The shape of the `$page` store
+ */
 export interface Page<Params extends Record<string, string> = Record<string, string>> {
+	/**
+	 * The URL of the current page
+	 */
 	url: URL;
+	/**
+	 * The parameters of the current page - e.g. for a route like `/blog/[slug]`, the `slug` parameter
+	 */
 	params: Params;
+	/**
+	 * The route ID of the current page - e.g. for `src/routes/blog/[slug]`, it would be `blog/[slug]`
+	 */
 	routeId: string | null;
+	/**
+	 * Http status code of the current page
+	 */
 	status: number;
+	/**
+	 * The error object of the current page, if any. Filled from the `handleError` hooks.
+	 */
 	error: App.Error | null;
+	/**
+	 * The merged result of all data from all `load` functions on the current page. You can type a common denominator through `App.PageData`.
+	 */
 	data: App.PageData & Record<string, any>;
+	/**
+	 * Filled only after a form submission. See [form actions](https://kit.svelte.dev/docs/form-actions) for more info.
+	 */
 	form: any;
 }
 
diff --git a/packages/migrate/migrations/routes/migrate_page_server/samples.md b/packages/migrate/migrations/routes/migrate_page_server/samples.md
index f265fceaf..c6cf6266f 100644
--- a/packages/migrate/migrations/routes/migrate_page_server/samples.md
+++ b/packages/migrate/migrations/routes/migrate_page_server/samples.md
@@ -157,21 +157,25 @@ export function load() {
 ## A get function that only returns body
 
 ```js before
+import something from 'somewhere';
+
 /** @type {import('./$types').RequestHandler} */
 export function get() {
 	return {
 		body: {
-			a: 1
+			a: something
 		}
 	};
 }
 ```
 
 ```js after
+import something from 'somewhere';
+
 /** @type {import('./$types').PageServerLoad} */
 export function load() {
 	return {
-		a: 1
+		a: something
 	};
 }
 ```
diff --git a/packages/migrate/migrations/routes/utils.js b/packages/migrate/migrations/routes/utils.js
index 356df0172..bad68a0d4 100644
--- a/packages/migrate/migrations/routes/utils.js
+++ b/packages/migrate/migrations/routes/utils.js
@@ -504,11 +504,11 @@ export function rewrite_type(node, code, old_type, new_type) {
  * @param {NonNullable<ReturnType<typeof parse>>} file
  */
 export function uppercase_migration(methods, file) {
-	const old_methods = ['get', 'post', 'put', 'patch', 'del'].filter((name) =>
-		file.exports.map.has(name)
+	const old_methods = new Set(
+		['get', 'post', 'put', 'patch', 'del'].filter((name) => file.exports.map.has(name))
 	);
 
-	if (old_methods.length && !methods.length) {
+	if (old_methods.size && !methods.length) {
 		for (const statement of file.ast.statements) {
 			for (const method of old_methods) {
 				const fn = get_function_node(
@@ -516,16 +516,17 @@ export function uppercase_migration(methods, file) {
 					/** @type{string} */ (file.exports.map.get(method))
 				);
 				if (!fn?.name) {
-					return;
+					continue;
 				}
 				file.code.overwrite(
 					fn.name.getStart(),
 					fn.name.getEnd(),
 					method === 'del' ? 'DELETE' : method.toUpperCase()
 				);
+				old_methods.delete(method);
 			}
 		}
 	}
 
-	return file.code.toString();
+	return old_methods.size ? undefined : file.code.toString();
 }


Test patch:
diff --git a/packages/kit/test/apps/basics/src/routes/actions/enhance/+page.server.js b/packages/kit/test/apps/basics/src/routes/actions/enhance/+page.server.js
index 7a97721c2..b77d167ae 100644
--- a/packages/kit/test/apps/basics/src/routes/actions/enhance/+page.server.js
+++ b/packages/kit/test/apps/basics/src/routes/actions/enhance/+page.server.js
@@ -21,5 +21,11 @@ export const actions = {
 	},
 	slow: async () => {
 		await new Promise((resolve) => setTimeout(resolve, 500));
+	},
+	submitter: async ({ request }) => {
+		const fields = await request.formData();
+		return {
+			result: 'submitter: ' + fields.get('submitter')
+		};
 	}
 };
diff --git a/packages/kit/test/apps/basics/src/routes/actions/enhance/+page.svelte b/packages/kit/test/apps/basics/src/routes/actions/enhance/+page.svelte
index 28ddf39f9..032b7ca9c 100644
--- a/packages/kit/test/apps/basics/src/routes/actions/enhance/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/actions/enhance/+page.svelte
@@ -17,6 +17,7 @@
 	<input name="username" type="text" />
 	<button class="form1">Submit</button>
 	<button class="form1-register" formAction="?/register">Submit</button>
+	<button class="form1-submitter" formAction="?/submitter" name="submitter" value="foo">Submit</button>
 </form>
 
 <span class="count">{count}</span>
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js
index 5d0574498..a66e6e147 100644
--- a/packages/kit/test/apps/basics/test/test.js
+++ b/packages/kit/test/apps/basics/test/test.js
@@ -1865,6 +1865,21 @@ test.describe('Actions', () => {
 		);
 	});
 
+	test('use:enhance button with name', async ({ page, app }) => {
+		await page.goto('/actions/enhance');
+
+		expect(await page.textContent('pre.formdata1')).toBe(JSON.stringify(null));
+
+		await Promise.all([
+			page.waitForRequest((request) => request.url().includes('/actions/enhance')),
+			page.click('button.form1-submitter')
+		]);
+
+		await expect(page.locator('pre.formdata1')).toHaveText(
+			JSON.stringify({ result: 'submitter: foo' })
+		);
+	});
+
 	test('redirect', async ({ page }) => {
 		await page.goto('/actions/redirect');