diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 046ad335f3ea..51408fc8cc64 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,6 +60,23 @@ jobs: env: CI: true SVELTE_NO_ASYNC: true + TSGo: + permissions: {} + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: 24 + cache: pnpm + - name: install + run: pnpm install --frozen-lockfile + - name: install tsgo + run: cd packages/svelte && pnpm i -D @typescript/native-preview + - name: type check + run: cd packages/svelte && pnpm check:tsgo Lint: permissions: {} runs-on: ubuntu-latest diff --git a/documentation/docs/03-template-syntax/18-class.md b/documentation/docs/03-template-syntax/18-class.md index 1ea4a208dfdc..db85db4b37f4 100644 --- a/documentation/docs/03-template-syntax/18-class.md +++ b/documentation/docs/03-template-syntax/18-class.md @@ -71,7 +71,7 @@ The user of this component has the same flexibility to use a mixture of objects, ``` -Svelte also exposes the `ClassValue` type, which is the type of value that the `class` attribute on elements accept. This is useful if you want to use a type-safe class name in component props: +Since Svelte 5.19, Svelte also exposes the `ClassValue` type, which is the type of value that the `class` attribute on elements accept. This is useful if you want to use a type-safe class name in component props: ```svelte + +{der} + + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/Comp-1.svelte b/packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/Comp-1.svelte new file mode 100644 index 000000000000..fdafa27c3cd0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/Comp-1.svelte @@ -0,0 +1,7 @@ + + +{#each data.obj.arr as i} +

{i}

+{/each} \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/Comp-2.svelte b/packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/Comp-2.svelte new file mode 100644 index 000000000000..e345a7697c6e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/Comp-2.svelte @@ -0,0 +1 @@ +

Comp 2

\ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/_config.js b/packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/_config.js new file mode 100644 index 000000000000..ff5ca12dbf75 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/_config.js @@ -0,0 +1,11 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + const [btn] = target.querySelectorAll('button'); + btn.click(); + flushSync(); + assert.htmlEqual(target.innerHTML, `

Comp 2

`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/main.svelte b/packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/main.svelte new file mode 100644 index 000000000000..abd785fff376 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/main.svelte @@ -0,0 +1,16 @@ + + + + + diff --git a/playgrounds/sandbox/package.json b/playgrounds/sandbox/package.json index 3ab65ac4b50f..f11da983f60e 100644 --- a/playgrounds/sandbox/package.json +++ b/playgrounds/sandbox/package.json @@ -11,7 +11,8 @@ "prod": "npm run build && node dist/server/ssr-prod", "preview": "vite preview", "download": "node scripts/download.js", - "hash": "node scripts/hash.js" + "hash": "node scripts/hash.js", + "create-test": "node scripts/create-test.js" }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^4.0.0-next.6", diff --git a/playgrounds/sandbox/scripts/create-test.js b/playgrounds/sandbox/scripts/create-test.js new file mode 100644 index 000000000000..c733f194195b --- /dev/null +++ b/playgrounds/sandbox/scripts/create-test.js @@ -0,0 +1,155 @@ +// Creates a test from the existing playground. cwd needs to be playground/sandbox +import fs from 'fs'; +import path from 'path'; + +// Get target folder from command line arguments +let target_folder = process.argv[2]; +if (!target_folder) { + console.error( + 'Please provide a target folder as an argument. Example: node create-test.js runtime-runes/my-test' + ); + process.exit(1); +} +if (!target_folder.includes('/')) { + target_folder = 'runtime-runes/' + target_folder; +} +if (!target_folder.startsWith('runtime-')) { + console.error( + 'Target folder must start with "runtime-" (can only convert to these kinds of tests)' + ); + process.exit(1); +} +target_folder = path.join( + path.resolve('../../packages/svelte/tests', target_folder.split('/')[0]), + 'samples', + target_folder.split('/')[1] +); + +const exists = fs.existsSync(target_folder); + +// Check if target folder already exists and ask for confirmation +if (exists) { + console.log(`Target folder "${target_folder}" already exists.`); + process.stdout.write('Do you want to override the existing test? (Y/n): '); + + // Read user input synchronously + const stdin = process.stdin; + stdin.setRawMode(true); + stdin.resume(); + stdin.setEncoding('utf8'); + + const response = await new Promise((resolve) => { + stdin.on('data', (key) => { + stdin.setRawMode(false); + stdin.pause(); + process.stdout.write('\n'); + resolve(key); + }); + }); + + if (response.toLowerCase() === 'n') { + console.log('Operation cancelled.'); + process.exit(0); + } + + // Clear the existing target folder except for _config.js + const files = fs.readdirSync(target_folder); + for (const file of files) { + if (file !== '_config.js') { + const filePath = path.join(target_folder, file); + fs.rmSync(filePath, { recursive: true, force: true }); + } + } +} else { + fs.mkdirSync(target_folder, { recursive: true }); +} + +// Starting file +const app_svelte_path = path.resolve('./src/App.svelte'); +const collected_files = new Set(); +const processed_files = new Set(); + +function collect_imports(file_path) { + if (processed_files.has(file_path) || !fs.existsSync(file_path)) { + return; + } + + processed_files.add(file_path); + collected_files.add(file_path); + + const content = fs.readFileSync(file_path, 'utf8'); + + // Regex to match import statements + const import_regex = /import\s+(?:[^'"]*\s+from\s+)?['"]([^'"]+)['"]/g; + let match; + + while ((match = import_regex.exec(content)) !== null) { + const import_path = match[1]; + + // Skip node_modules imports + if (!import_path.startsWith('.')) { + continue; + } + + // Resolve relative import path + const resolved_path = path.resolve(path.dirname(file_path), import_path); + + // Try different extensions if file doesn't exist + const extensions = ['', '.svelte', '.js', '.ts']; + let actual_path = null; + + for (const ext of extensions) { + const test_path = resolved_path + ext; + if (fs.existsSync(test_path)) { + actual_path = test_path; + break; + } + } + + if (actual_path) { + collect_imports(actual_path); + } + } +} + +// Start collecting from App.svelte +collect_imports(app_svelte_path); + +// Copy collected files to target folder +for (const file_path of collected_files) { + const relative_path = path.relative(path.resolve('./src'), file_path); + let target_path = path.join(target_folder, relative_path); + + // Rename App.svelte to main.svelte + if (path.basename(file_path) === 'App.svelte') { + target_path = path.join(target_folder, path.dirname(relative_path), 'main.svelte'); + } + + // Ensure target directory exists + const target_dir = path.dirname(target_path); + if (!fs.existsSync(target_dir)) { + fs.mkdirSync(target_dir, { recursive: true }); + } + + // Copy file + fs.copyFileSync(file_path, target_path); + console.log(`Copied: ${file_path} -> ${target_path}`); +} + +// Create empty _config.js +if (!exists) { + const config_path = path.join(target_folder, '_config.js'); + fs.writeFileSync( + config_path, + `import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + } +}); +` + ); + console.log(`Created: ${config_path}`); +} + +console.log(`\nTest files created in: ${target_folder}`); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 315d699e25ac..cad5fefdf8c6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,9 +62,9 @@ importers: packages/svelte: dependencies: - '@ampproject/remapping': - specifier: ^2.3.0 - version: 2.3.0 + '@jridgewell/remapping': + specifier: ^2.3.4 + version: 2.3.4 '@jridgewell/sourcemap-codec': specifier: ^1.5.0 version: 1.5.0 @@ -457,6 +457,9 @@ packages: resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} engines: {node: '>=6.0.0'} + '@jridgewell/remapping@2.3.4': + resolution: {integrity: sha512-aG+WvAz17rhbzhKNkSeMLgbkPPK82ovXdONvmucbGhUqcroRFLLVhoGAk4xEI17gHpXgNX3sr0/B1ybRUsbEWw==} + '@jridgewell/resolve-uri@3.1.1': resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} engines: {node: '>=6.0.0'} @@ -2777,6 +2780,11 @@ snapshots: '@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/remapping@2.3.4': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/resolve-uri@3.1.1': {} '@jridgewell/set-array@1.2.1': {}