From 0f4d9c69aa31fb8a2eb000663d5c2e59884740b0 Mon Sep 17 00:00:00 2001 From: jycouet Date: Fri, 3 Oct 2025 23:26:41 +0200 Subject: [PATCH] `--from-playground` & PlaygroundLayout --- .changeset/tasty-friends-think.md | 5 + packages/cli/commands/create.ts | 2 +- packages/create/playground.ts | 241 ++++++++++++++++++++++++++++- packages/create/test/playground.ts | 21 ++- 4 files changed, 263 insertions(+), 6 deletions(-) create mode 100644 .changeset/tasty-friends-think.md diff --git a/.changeset/tasty-friends-think.md b/.changeset/tasty-friends-think.md new file mode 100644 index 000000000..7127bf4db --- /dev/null +++ b/.changeset/tasty-friends-think.md @@ -0,0 +1,5 @@ +--- +'sv': patch +--- + +feat(cli): `--from-playground` will now bring a PlaygroundLayout to get a more consistent experience with the online playground diff --git a/packages/cli/commands/create.ts b/packages/cli/commands/create.ts index cf39b4ff0..5f630a6b1 100644 --- a/packages/cli/commands/create.ts +++ b/packages/cli/commands/create.ts @@ -244,7 +244,7 @@ async function createProjectFromPlayground(url: string, cwd: string): Promise { diff --git a/packages/create/playground.ts b/packages/create/playground.ts index 242353670..bb15e1dd9 100644 --- a/packages/create/playground.ts +++ b/packages/create/playground.ts @@ -154,6 +154,7 @@ function extractPackageVersion(pkgName: string) { } export function setupPlaygroundProject( + url: string, playground: PlaygroundData, cwd: string, installDependencies: boolean @@ -171,17 +172,251 @@ export function setupPlaygroundProject( } // write file to disk - const filePath = path.join(cwd, 'src', 'routes', file.name); + const filePath = path.join(cwd, 'src', 'lib', 'playground', file.name); fs.mkdirSync(path.dirname(filePath), { recursive: true }); fs.writeFileSync(filePath, file.content, 'utf8'); } + // add playground layout to lib + { + const playgroundLayoutPath = path.join(cwd, 'src', 'lib', 'PlaygroundLayout.svelte'); + const { generateCode } = parseSvelte(''); + const newContent = generateCode({ + script: `import favicon from "$lib/assets/favicon.svg"; + + let { children } = $props(); + + const title = "${playground.name}"; + const href = "${url}"; + + let prefersDark = $state(true); + let isDark = $state(true); + + function setTheme(value) { + isDark = value === "dark"; + localStorage.setItem("sv:theme", isDark === prefersDark ? "system" : value); + } + + $effect(() => { + document.documentElement.classList.remove("light", "dark"); + + prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches; + const theme = localStorage.getItem("sv:theme") + isDark = theme === "dark" || (theme === "system" && prefersDark); + + document.documentElement.classList.add(isDark ? "dark" : "light"); + });`, + template: ` + --from-playground {title} + + + +
+ + +
+ {@render children?.()} +
+
+ +` + }); + fs.writeFileSync(playgroundLayoutPath, newContent, 'utf-8'); + } + // add app import to +page.svelte const filePath = path.join(cwd, 'src/routes/+page.svelte'); const content = fs.readFileSync(filePath, 'utf-8'); const { script, generateCode } = parseSvelte(content); - js.imports.addDefault(script.ast, { from: `./${mainFile.name}`, as: 'App' }); - const newContent = generateCode({ script: script.generateCode(), template: `` }); + js.imports.addDefault(script.ast, { as: 'App', from: `$lib/playground/${mainFile.name}` }); + js.imports.addDefault(script.ast, { + as: 'PlaygroundLayout', + from: `$lib/PlaygroundLayout.svelte` + }); + const newContent = generateCode({ + script: script.generateCode(), + template: ` + +` + }); fs.writeFileSync(filePath, newContent, 'utf-8'); // add packages as dependencies to package.json if requested diff --git a/packages/create/test/playground.ts b/packages/create/test/playground.ts index 81fd4e0ea..226aef8ad 100644 --- a/packages/create/test/playground.ts +++ b/packages/create/test/playground.ts @@ -165,11 +165,23 @@ test('real world download and convert playground async', async () => { svelteVersion: '5.38.7' }); - setupPlaygroundProject(playground, directory, true); + setupPlaygroundProject( + 'https://svelte.dev/playground/770bbef086034b9f8e337bab57efe8d8', + playground, + directory, + true + ); const pageFilePath = path.join(directory, 'src/routes/+page.svelte'); const pageContent = fs.readFileSync(pageFilePath, 'utf-8'); expect(pageContent).toContain(''); + expect(pageContent).toContain(''); + + const playgroundLayoutPath = path.join(directory, 'src/lib/PlaygroundLayout.svelte'); + const playgroundLayoutContent = fs.readFileSync(playgroundLayoutPath, 'utf-8'); + expect(playgroundLayoutContent).toContain('localStorage.getItem'); + expect(playgroundLayoutContent).toContain('sv:theme'); + expect(playgroundLayoutContent).toContain('770bbef086034b9f8e337bab57efe8d8'); const packageJsonPath = path.join(directory, 'package.json'); const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf-8'); @@ -199,7 +211,12 @@ test('real world download and convert playground without async', async () => { svelteVersion: '5.0.5' }); - setupPlaygroundProject(playground, directory, true); + setupPlaygroundProject( + 'https://svelte.dev/playground/770bbef086034b9f8e337bab57efe8d8', + playground, + directory, + true + ); const packageJsonPath = path.join(directory, 'package.json'); const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf-8');