diff --git a/.gitignore b/.gitignore
index 8a4d1d070e..cc2e05ea5f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
.idea
.DS_Store
-.vscode
+.vscode/*
+!.vscode/launch.json
node_modules
.eslintcache
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000000..2e22a5ab9b
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,27 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "chrome",
+ "request": "launch",
+ "name": "Playground: Browser",
+ "url": "http://localhost:10001"
+ },
+ {
+ "type": "node",
+ "request": "launch",
+ "runtimeArgs": ["--watch"],
+ "name": "Playground: Server",
+ "outputCapture": "std",
+ "program": "start.js",
+ "cwd": "${workspaceFolder}/packages/playground",
+ "cascadeTerminateToConfigurations": ["Playground: Browser"]
+ }
+ ],
+ "compounds": [
+ {
+ "name": "Playground: Full",
+ "configurations": ["Playground: Server", "Playground: Browser"]
+ }
+ ]
+}
diff --git a/packages/playground/.gitignore b/packages/playground/.gitignore
new file mode 100644
index 0000000000..0212d8b6ce
--- /dev/null
+++ b/packages/playground/.gitignore
@@ -0,0 +1,3 @@
+dist
+dist-ssr
+*.local
diff --git a/packages/playground/README.md b/packages/playground/README.md
new file mode 100644
index 0000000000..6ac0393720
--- /dev/null
+++ b/packages/playground/README.md
@@ -0,0 +1,7 @@
+You may use this package to experiment with your changes to Svelte.
+
+To prevent any changes you make in this directory from being accidentally committed, run `git update-index --skip-worktree ./**/*.*` in this directory.
+
+If you would actually like to make some changes to the files here for everyone then run `git update-index --no-skip-worktree ./**/*.*` before committing.
+
+If you're using VS Code, you can use the "Playground: Full" launch configuration to run the playground and attach the debugger to both the compiler and the browser.
diff --git a/packages/playground/jsconfig.json b/packages/playground/jsconfig.json
new file mode 100644
index 0000000000..ba142a7d31
--- /dev/null
+++ b/packages/playground/jsconfig.json
@@ -0,0 +1,33 @@
+{
+ "compilerOptions": {
+ "moduleResolution": "Node",
+ "target": "ESNext",
+ "module": "ESNext",
+ /**
+ * svelte-preprocess cannot figure out whether you have
+ * a value or a type, so tell TypeScript to enforce using
+ * `import type` instead of `import` for Types.
+ */
+ "verbatimModuleSyntax": true,
+ "isolatedModules": true,
+ "resolveJsonModule": true,
+ /**
+ * To have warnings / errors of the Svelte compiler at the
+ * correct position, enable source maps by default.
+ */
+ "sourceMap": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ /**
+ * Typecheck JS in `.svelte` and `.js` files by default.
+ * Disable this if you'd like to use dynamic types.
+ */
+ "checkJs": true
+ },
+ /**
+ * Use global.d.ts instead of compilerOptions.types
+ * to avoid limiting type declarations.
+ */
+ "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
+}
diff --git a/packages/playground/package.json b/packages/playground/package.json
new file mode 100644
index 0000000000..9c1f03db20
--- /dev/null
+++ b/packages/playground/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "playground",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "node --watch start.js"
+ },
+ "devDependencies": {
+ "rollup": "^3.20.2",
+ "rollup-plugin-serve": "^2.0.2",
+ "svelte": "workspace:*"
+ }
+}
diff --git a/packages/playground/src/App.svelte b/packages/playground/src/App.svelte
new file mode 100644
index 0000000000..ab87de6d96
--- /dev/null
+++ b/packages/playground/src/App.svelte
@@ -0,0 +1,3 @@
+
+ Hello world!
+
\ No newline at end of file
diff --git a/packages/playground/src/entry-client.js b/packages/playground/src/entry-client.js
new file mode 100644
index 0000000000..e62bbf2eb7
--- /dev/null
+++ b/packages/playground/src/entry-client.js
@@ -0,0 +1,24 @@
+import App from './App.svelte';
+
+new App({
+ target: document.getElementById('app'),
+ hydrate: true
+});
+
+function get_version() {
+ return fetch('/version.json').then((r) => r.json());
+}
+
+let prev = await get_version();
+
+// Mom: We have live reloading at home
+// Live reloading at home:
+while (true) {
+ await new Promise((r) => setTimeout(r, 2500));
+ try {
+ const version = await get_version();
+ if (prev !== version) {
+ window.location.reload();
+ }
+ } catch {}
+}
diff --git a/packages/playground/src/entry-server.js b/packages/playground/src/entry-server.js
new file mode 100644
index 0000000000..7402e1e9ad
--- /dev/null
+++ b/packages/playground/src/entry-server.js
@@ -0,0 +1,6 @@
+import App from './App.svelte';
+
+export function render() {
+ // @ts-ignore
+ return App.render();
+}
diff --git a/packages/playground/src/lib/Counter.svelte b/packages/playground/src/lib/Counter.svelte
new file mode 100644
index 0000000000..e45f903109
--- /dev/null
+++ b/packages/playground/src/lib/Counter.svelte
@@ -0,0 +1,10 @@
+
+
+
diff --git a/packages/playground/src/template.html b/packages/playground/src/template.html
new file mode 100644
index 0000000000..2cc688066f
--- /dev/null
+++ b/packages/playground/src/template.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/playground/start.js b/packages/playground/start.js
new file mode 100644
index 0000000000..4144ba97ac
--- /dev/null
+++ b/packages/playground/start.js
@@ -0,0 +1,100 @@
+import { readFileSync, writeFileSync } from 'node:fs';
+import path from 'node:path';
+import { watch } from 'rollup';
+import serve from 'rollup-plugin-serve';
+import * as svelte from '../svelte/src/compiler/index.js';
+
+const __dirname = new URL('.', import.meta.url).pathname;
+
+/** @returns {import('rollup').Plugin}*/
+function create_plugin(ssr = false) {
+ return {
+ name: 'custom-svelte-ssr-' + ssr,
+ resolveId(id) {
+ if (id === 'svelte') {
+ return path.resolve(
+ __dirname,
+ ssr ? '../svelte/src/runtime/ssr.js' : '../svelte/src/runtime/index.js'
+ );
+ } else if (id.startsWith('svelte/')) {
+ return path.resolve(__dirname, `../svelte/src/runtime/${id.slice(7)}/index.js`);
+ }
+ },
+ transform(code, id) {
+ code = code.replaceAll('import.meta.env.SSR', ssr);
+
+ if (!id.endsWith('.svelte')) {
+ return {
+ code,
+ map: null
+ };
+ }
+
+ const compiled = svelte.compile(code, {
+ filename: id,
+ generate: ssr ? 'ssr' : 'dom',
+ hydratable: true,
+ css: 'injected'
+ });
+
+ return compiled.js;
+ }
+ };
+}
+
+const client_plugin = create_plugin();
+const ssr_plugin = create_plugin(true);
+
+const watcher = watch([
+ {
+ input: 'src/entry-client.js',
+ output: {
+ dir: 'dist',
+ format: 'esm',
+ sourcemap: true
+ },
+ plugins: [client_plugin, serve('dist')]
+ },
+ {
+ input: 'src/entry-server.js',
+ output: {
+ dir: 'dist-ssr',
+ format: 'iife',
+ indent: false
+ },
+ plugins: [
+ ssr_plugin,
+ {
+ async generateBundle(_, bundle) {
+ const result = bundle['entry-server.js'];
+ const mod = (0, eval)(result.code);
+ const { html } = mod.render();
+
+ writeFileSync(
+ 'dist/index.html',
+ readFileSync('src/template.html', 'utf-8')
+ .replace('', html)
+ .replace('', svelte.VERSION)
+ );
+ writeFileSync('dist/version.json', Date.now().toString());
+ }
+ }
+ ],
+ onwarn(warning, handler) {
+ if (warning.code === 'MISSING_NAME_OPTION_FOR_IIFE_EXPORT') return;
+ handler(warning);
+ }
+ }
+]);
+
+watcher
+ .on('change', (id) => {
+ console.log(`changed ${id}`);
+ })
+ .on('event', (event) => {
+ if (event.code === 'ERROR') {
+ console.error(event.error);
+ } else if (event.code === 'BUNDLE_END') {
+ console.log(`Generated ${event.output} in ${event.duration}ms`);
+ }
+ });
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index dcf3298935..cbec4fcac6 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -36,6 +36,18 @@ importers:
specifier: ^2.10.0
version: 2.10.0(prettier@2.8.8)(svelte@3.59.1)
+ packages/playground:
+ devDependencies:
+ rollup:
+ specifier: ^3.20.2
+ version: 3.23.0
+ rollup-plugin-serve:
+ specifier: ^2.0.2
+ version: 2.0.2
+ svelte:
+ specifier: workspace:*
+ version: link:../svelte
+
packages/svelte:
dependencies:
'@ampproject/remapping':
@@ -3012,6 +3024,12 @@ packages:
mime-db: 1.52.0
dev: true
+ /mime@3.0.0:
+ resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
+ engines: {node: '>=10.0.0'}
+ hasBin: true
+ dev: true
+
/min-indent@1.0.1:
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
engines: {node: '>=4'}
@@ -3143,6 +3161,11 @@ packages:
wrappy: 1.0.2
dev: true
+ /opener@1.5.2:
+ resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==}
+ hasBin: true
+ dev: true
+
/optionator@0.8.3:
resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==}
engines: {node: '>= 0.8.0'}
@@ -3560,6 +3583,13 @@ packages:
glob: 7.2.3
dev: true
+ /rollup-plugin-serve@2.0.2:
+ resolution: {integrity: sha512-ALqyTbPhlf7FZ5RzlbDvMYvbKuCHWginJkTo6dMsbgji/a78IbsXox+pC83HENdkTRz8OXrTj+aShp3+3ratpg==}
+ dependencies:
+ mime: 3.0.0
+ opener: 1.5.2
+ dev: true
+
/rollup@3.23.0:
resolution: {integrity: sha512-h31UlwEi7FHihLe1zbk+3Q7z1k/84rb9BSwmBSr/XjOCEaBJ2YyedQDuM0t/kfOS0IxM+vk1/zI9XxYj9V+NJQ==}
engines: {node: '>=14.18.0', npm: '>=8.0.0'}