Repository: sveltejs/kit (js)

Issue:
Modifying the `app.html` HMR does not trigger a reload
### Describe the bug

Hi, I'm the creator of [Master CSS](https://css.master.co).

Master CSS is using a special way to match the build tool to scan JS and HTML to extract class names and generate corresponding CSS rules.

Something like this:

```js
import { sveltekit } from '@sveltejs/kit/vite'
import MasterCSSVitePlugin from '@master/css.vite'

/** @type {import('vite').UserConfig} */
const config = {
    plugins: [
        sveltekit(),
        MasterCSSVitePlugin({
            output: {
                dir: 'src/routes'
            }
        })
    ]
}

export default config
```

I just started using Svelte Kit before integrating Master CSS and encountered the following problems that need to be fixed:

### Modifying `app.html` and archiving did not trigger HMR or even browser reload

![image](https://user-images.githubusercontent.com/33840671/205565080-883e0acb-8374-4847-9e1f-da8f59f20660.png)

You can observe that vite prints `2:32:42 PM [vite] page reload src/app.html` but actually the browser does not reload.

### SvelteKit doesn't hook into VitePlugin `transformIndexHTML`

You can see that Master CSS is integrating AOT compilation through the Vite Plugin standard API:

```js
export default async function MasterCSSVitePlugin(options?: CompilerOptions): Promise<Plugin> {
    return {
        ...,
        transform(code, id) {
            extract(id, code)
        },
        transformIndexHtml(html, { path: filePath }) {
            html = html.replace(/(<head>)/, `$1<link rel="stylesheet" href="${linkHref}">`)
            /**
             * Extract class names from `index.html`
             */
            extract(filePath, html)
            return html
        },
        ...
    }
}
```

Svelte Kit does not hook `transformIndexHtml` so that we can't read the `app.html` context when dev server starts, which completely blocks the integration of third-party packages with Svelte Kit, and there seems to be no other solution.

https://vitejs.dev/guide/api-plugin.html#transformindexhtml


### Reproduction

I didn't actually do anything, just started.
```bash
npm create svelte@latest my-app
cd my-app
npm install
npm run dev -- --open
```
https://github.com/master-co/css/tree/beta/examples/svelte-kit

### Logs

_No response_

### System Info

```Shell
System:
    OS: macOS 13.0.1
    CPU: (8) arm64 Apple M2
    Memory: 33.48 MB / 24.00 GB
    Shell: 5.8.1 - /bin/zsh
  Binaries:
    Node: 18.12.1 - ~/.nvm/versions/node/v18.12.1/bin/node
    npm: 8.19.3 - ~/master/css/node_modules/.bin/npm
  Browsers:
    Chrome: 107.0.5304.121
    Firefox: 106.0.2
    Safari: 16.1
  npmPackages:
    @sveltejs/kit: next => 1.0.0-next.571 
    svelte: ^3.53.1 => 3.53.1 
    vite: ^3.2.4 => 3.2.4 
```
```


### Severity

blocking all usage of SvelteKit

### Additional Information

_No response_

Pull request description:
[fix] ensure that editing app.html reloads open dev page
fixes #7942 

the vite client assumes that changes to html files should only trigger a full reload if they are named index.html and the path to the file matches the browser path. kit uses `src/app.html` by default which fails that check.

so we send another full-reload event to the client without a path - this forces it's hand.

### Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
- [x] 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
- [x] Run the tests with `pnpm test` and lint the project with `pnpm lint` and `pnpm check`

### Changesets
- [x] 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


Patch:
diff --git a/.changeset/chatty-mice-tan.md b/.changeset/chatty-mice-tan.md
new file mode 100644
index 000000000..49e6b85ce
--- /dev/null
+++ b/.changeset/chatty-mice-tan.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': minor
+---
+
+Checks that element is not null before reading getAttribute
diff --git a/.changeset/healthy-readers-camp.md b/.changeset/healthy-readers-camp.md
new file mode 100644
index 000000000..6e95d9145
--- /dev/null
+++ b/.changeset/healthy-readers-camp.md
@@ -0,0 +1,5 @@
+---
+"@sveltejs/kit": patch
+---
+
+[chore] update `magic-string`
diff --git a/.changeset/hot-news-bake.md b/.changeset/hot-news-bake.md
new file mode 100644
index 000000000..ab6178a23
--- /dev/null
+++ b/.changeset/hot-news-bake.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+reload dev page on change of app.html
diff --git a/package.json b/package.json
index c5f41dbdd..f2eaf8ca1 100644
--- a/package.json
+++ b/package.json
@@ -35,7 +35,7 @@
 		"turbo": "^1.6.3",
 		"typescript": "^4.9.3"
 	},
-	"packageManager": "pnpm@7.17.1",
+	"packageManager": "pnpm@7.18.0",
 	"engines": {
 		"pnpm": "^7.0.0"
 	},
diff --git a/packages/kit/package.json b/packages/kit/package.json
index d13bc7aaa..8e98ae9c6 100644
--- a/packages/kit/package.json
+++ b/packages/kit/package.json
@@ -15,7 +15,7 @@
 		"cookie": "^0.5.0",
 		"devalue": "^4.2.0",
 		"kleur": "^4.1.5",
-		"magic-string": "^0.26.7",
+		"magic-string": "^0.27.0",
 		"mime": "^3.0.0",
 		"sade": "^1.8.1",
 		"set-cookie-parser": "^2.5.1",
diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js
index 778b369de..02056f176 100644
--- a/packages/kit/src/exports/vite/dev/index.js
+++ b/packages/kit/src/exports/vite/dev/index.js
@@ -235,6 +235,18 @@ export async function dev(vite, vite_config, svelte_config) {
 		sync.update(svelte_config, manifest_data, file);
 	});
 
+	const { appTemplate } = svelte_config.kit.files;
+	// vite client only executes a full reload if the triggering html file path is index.html
+	// kit defaults to src/app.html, so unless user changed that to index.html
+	// send the vite client a full-reload event without path being set
+	if (appTemplate !== 'index.html') {
+		vite.watcher.on('change', (file) => {
+			if (file === appTemplate) {
+				vite.ws.send({ type: 'full-reload' });
+			}
+		});
+	}
+
 	const assets = svelte_config.kit.paths.assets ? SVELTE_KIT_ASSETS : svelte_config.kit.paths.base;
 	const asset_server = sirv(svelte_config.kit.files.assets, {
 		dev: true,
diff --git a/packages/kit/src/runtime/client/utils.js b/packages/kit/src/runtime/client/utils.js
index 8cdd0146f..7f953d1f4 100644
--- a/packages/kit/src/runtime/client/utils.js
+++ b/packages/kit/src/runtime/client/utils.js
@@ -153,7 +153,7 @@ export function get_router_options(element) {
 	/** @type {Element} */
 	let el = element;
 
-	while (el !== document.documentElement) {
+	while (el && el !== document.documentElement) {
 		if (preload_code === null) preload_code = link_option(el, 'preload-code');
 		if (preload_data === null) preload_data = link_option(el, 'preload-data');
 		if (noscroll === null) noscroll = link_option(el, 'noscroll');
diff --git a/packages/migrate/package.json b/packages/migrate/package.json
index 63a3632e6..e8a12ee82 100644
--- a/packages/migrate/package.json
+++ b/packages/migrate/package.json
@@ -21,7 +21,7 @@
 	],
 	"dependencies": {
 		"kleur": "^4.1.5",
-		"magic-string": "^0.26.7",
+		"magic-string": "^0.27.0",
 		"prompts": "^2.4.2",
 		"tiny-glob": "^0.2.9",
 		"typescript": "^4.9.3"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4d8432f5e..bdcb20a3f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -275,7 +275,7 @@ importers:
       cookie: ^0.5.0
       devalue: ^4.2.0
       kleur: ^4.1.5
-      magic-string: ^0.26.7
+      magic-string: ^0.27.0
       marked: ^4.2.3
       mime: ^3.0.0
       rollup: ^2.79.1
@@ -295,7 +295,7 @@ importers:
       cookie: 0.5.0
       devalue: 4.2.0
       kleur: 4.1.5
-      magic-string: 0.26.7
+      magic-string: 0.27.0
       mime: 3.0.0
       sade: 1.8.1
       set-cookie-parser: 2.5.1
@@ -630,7 +630,7 @@ importers:
     specifiers:
       '@types/prompts': ^2.4.1
       kleur: ^4.1.5
-      magic-string: ^0.26.7
+      magic-string: ^0.27.0
       prettier: ^2.8.0
       prompts: ^2.4.2
       tiny-glob: ^0.2.9
@@ -638,7 +638,7 @@ importers:
       uvu: ^0.5.6
     dependencies:
       kleur: 4.1.5
-      magic-string: 0.26.7
+      magic-string: 0.27.0
       prompts: 2.4.2
       tiny-glob: 0.2.9
       typescript: 4.9.3
@@ -679,7 +679,7 @@ importers:
       '@sveltejs/site-kit': ^2.1.4
       '@types/node': ^16.18.3
       flexsearch: ^0.7.31
-      magic-string: ^0.26.7
+      magic-string: ^0.27.0
       marked: ^4.2.3
       prism-svelte: ^0.5.0
       prismjs: ^1.29.0
@@ -698,7 +698,7 @@ importers:
       '@sveltejs/site-kit': link:../site-kit
       '@types/node': 16.18.3
       flexsearch: 0.7.31
-      magic-string: 0.26.7
+      magic-string: 0.27.0
       marked: 4.2.3
       prism-svelte: 0.5.0
       prismjs: 1.29.0
@@ -1017,7 +1017,6 @@ packages:
 
   /@jridgewell/sourcemap-codec/1.4.14:
     resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
-    dev: true
 
   /@jridgewell/trace-mapping/0.3.17:
     resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==}
@@ -3212,6 +3211,12 @@ packages:
     dependencies:
       sourcemap-codec: 1.4.8
 
+  /magic-string/0.27.0:
+    resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==}
+    engines: {node: '>=12'}
+    dependencies:
+      '@jridgewell/sourcemap-codec': 1.4.14
+
   /make-dir/3.1.0:
     resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
     engines: {node: '>=8'}
diff --git a/sites/kit.svelte.dev/package.json b/sites/kit.svelte.dev/package.json
index 54c044344..b7a586e38 100644
--- a/sites/kit.svelte.dev/package.json
+++ b/sites/kit.svelte.dev/package.json
@@ -17,7 +17,7 @@
 		"@sveltejs/site-kit": "^2.1.4",
 		"@types/node": "^16.18.3",
 		"flexsearch": "^0.7.31",
-		"magic-string": "^0.26.7",
+		"magic-string": "^0.27.0",
 		"marked": "^4.2.3",
 		"prism-svelte": "^0.5.0",
 		"prismjs": "^1.29.0",


Test patch:
diff --git a/packages/kit/test/apps/basics/src/routes/interactivity/toggle-element/+page.svelte b/packages/kit/test/apps/basics/src/routes/interactivity/toggle-element/+page.svelte
index 449acb986..cc0dcf6b0 100644
--- a/packages/kit/test/apps/basics/src/routes/interactivity/toggle-element/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/interactivity/toggle-element/+page.svelte
@@ -8,6 +8,8 @@
 
 {#if visible}
 	<button on:click={toggle}>remove</button>
+	<a on:click={toggle}>remove</a>
 {:else}
 	<button on:click={toggle}>add</button>
+	<a on:click={toggle}>add</a>
 {/if}
diff --git a/packages/kit/test/apps/basics/test/client.test.js b/packages/kit/test/apps/basics/test/client.test.js
index ed5270f28..b81213171 100644
--- a/packages/kit/test/apps/basics/test/client.test.js
+++ b/packages/kit/test/apps/basics/test/client.test.js
@@ -1200,6 +1200,10 @@ test.describe('Interactivity', () => {
 
 		await page.click('button');
 		expect(await page.textContent('button')).toBe('add');
+		expect(await page.textContent('a')).toBe('add');
+
+		await page.click('a');
+		expect(await page.textContent('a')).toBe('remove');
 
 		expect(errored).toBe(false);
 	});