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 06533b46f..93ba0e43a 100644 --- a/packages/cli/commands/create.ts +++ b/packages/cli/commands/create.ts @@ -194,7 +194,11 @@ async function createProject(cwd: ProjectPath, options: Options) { }); if (options.fromPlayground) { - await createProjectFromPlayground(options.fromPlayground, projectPath); + await createProjectFromPlayground( + options.fromPlayground, + projectPath, + language === 'typescript' + ); } p.log.success('Project created'); @@ -236,7 +240,11 @@ async function createProject(cwd: ProjectPath, options: Options) { return { directory: projectPath, addOnNextSteps, packageManager }; } -async function createProjectFromPlayground(url: string, cwd: string): Promise { +async function createProjectFromPlayground( + url: string, + cwd: string, + typescript: boolean +): Promise { const urlData = parsePlaygroundUrl(url); const playground = await downloadPlaygroundData(urlData); @@ -244,7 +252,7 @@ async function createProjectFromPlayground(url: string, cwd: string): Promise { diff --git a/packages/create/index.ts b/packages/create/index.ts index 7e92162fe..b415953ae 100644 --- a/packages/create/index.ts +++ b/packages/create/index.ts @@ -19,7 +19,7 @@ export type File = { contents: string; }; -export type Condition = TemplateType | LanguageType; +export type Condition = TemplateType | LanguageType | 'playground'; export type Common = { files: Array<{ diff --git a/packages/create/playground.ts b/packages/create/playground.ts index 242353670..cdf1a4340 100644 --- a/packages/create/playground.ts +++ b/packages/create/playground.ts @@ -3,6 +3,8 @@ import path from 'node:path'; import * as js from '@sveltejs/cli-core/js'; import { parseJson, parseScript, parseSvelte } from '@sveltejs/cli-core/parsers'; import { isVersionUnsupportedBelow } from '@sveltejs/cli-core'; +import { dist } from './utils.ts'; +import type { Common } from './index.ts'; export function validatePlaygroundUrl(link: string): boolean { try { @@ -154,9 +156,11 @@ function extractPackageVersion(pkgName: string) { } export function setupPlaygroundProject( + url: string, playground: PlaygroundData, cwd: string, - installDependencies: boolean + installDependencies: boolean, + typescript: boolean ): void { const mainFile = playground.files.find((file) => file.name === 'App.svelte'); if (!mainFile) throw new Error('Failed to find `App.svelte` entrypoint.'); @@ -171,17 +175,53 @@ 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 shared files + { + const shared = dist('shared.json'); + const { files } = JSON.parse(fs.readFileSync(shared, 'utf-8')) as Common; + const playgroundFiles = files.filter((file) => file.include.includes('playground')); + + for (const file of playgroundFiles) { + let contentToWrite = file.contents; + + if (file.name === 'src/lib/PlaygroundLayout.svelte') { + // getting raw content + const { script, template } = parseSvelte(file.contents); + // generating new content with the right language style + const { generateCode } = parseSvelte('', { typescript }); + contentToWrite = generateCode({ + script: script + .generateCode() + .replaceAll('$sv-title-$sv', playground.name) + .replaceAll('$sv-url-$sv', url), + template: template.generateCode() + }); + } + + fs.writeFileSync(path.join(cwd, file.name), contentToWrite, '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: `` }); + const { script, generateCode } = parseSvelte(content, { typescript }); + 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/shared/+playground/src/lib/PlaygroundLayout.svelte b/packages/create/shared/+playground/src/lib/PlaygroundLayout.svelte new file mode 100644 index 000000000..9cc6dd861 --- /dev/null +++ b/packages/create/shared/+playground/src/lib/PlaygroundLayout.svelte @@ -0,0 +1,220 @@ + + + + --from-playground {title} + + + +
+ + +
+ {@render children?.()} +
+
+ + diff --git a/packages/create/test/playground.ts b/packages/create/test/playground.ts index 81fd4e0ea..e76376c52 100644 --- a/packages/create/test/playground.ts +++ b/packages/create/test/playground.ts @@ -165,11 +165,24 @@ 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, + 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 +212,13 @@ 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, + true + ); const packageJsonPath = path.join(directory, 'package.json'); const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf-8');