Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9747ac8
move reusable stuff into utils
dummdidumm Dec 5, 2023
8d9449c
start of kit v2 migration - remove throw from redirect/error
dummdidumm Dec 5, 2023
eaa7ce2
docs (WIP)
dummdidumm Dec 5, 2023
bae661f
migrate tsconfig
dummdidumm Dec 5, 2023
78e41f1
Update documentation/docs/60-appendix/11-v2-migration-guide.md
Rich-Harris Dec 8, 2023
86056df
add goto to migration guide
Rich-Harris Dec 8, 2023
101d344
prettier
Rich-Harris Dec 8, 2023
ec31489
mentiond `dangerZone.trackServerFetches`
dummdidumm Dec 9, 2023
a405a1f
update migration docs
Rich-Harris Dec 11, 2023
34fc736
Merge branch 'version-2' into v2-migration
Rich-Harris Dec 11, 2023
340ff47
shuffle files around
Rich-Harris Dec 11, 2023
7c3e876
oops
Rich-Harris Dec 11, 2023
d4cc843
tweak docs
Rich-Harris Dec 11, 2023
251ddf3
tweak some wording
Rich-Harris Dec 11, 2023
54fbbc5
fix semver comparison
Rich-Harris Dec 11, 2023
c8c6c63
more docs
dummdidumm Dec 11, 2023
8351751
tsconfig and package.json changes
dummdidumm Dec 11, 2023
a7082a0
add migration note on cookies
Rich-Harris Dec 11, 2023
31ba34f
svelte config migration
dummdidumm Dec 11, 2023
c1bbb9e
failed attempt to inject cookie comment
Rich-Harris Dec 11, 2023
e18b746
Merge branch 'v2-migration' of github.com:sveltejs/kit into v2-migration
Rich-Harris Dec 11, 2023
792f997
cookie note
dummdidumm Dec 11, 2023
5bbb832
Merge branch 'v2-migration' of https://github.com/sveltejs/kit into v…
dummdidumm Dec 11, 2023
3849bfc
adjust note
dummdidumm Dec 11, 2023
5f6b078
lint
dummdidumm Dec 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions documentation/docs/60-appendix/11-v2-migration-guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
title: Migration to SvelteKit v2
---

Bumping from SvelteKit version 1 to version 2 should be mostly seamless. There are a few breaking changes to note, which are listed here. You can use `npx svelte-migrate sveltekit-2` to migrate some of these changes automatically.

## `redirect` and `error` are no longer thrown by you

Previously, you had to `throw` the `error` and `redirect` methods yourself. In SvelteKit 2, these methods themselves throw, so you no longer need to do it yourself.

```diff
import { error } from '@sveltejs/kit'

...
- throw error(500, 'something went wrong');
+ error(500, 'something went wrong');
```

`svelte-migrate` will do these changes automatically for you.

## Top-level promises are no longer awaited

In SvelteKit version 1, promises returned from the top level of a `load` function are automatically awaited. Very few people actually took advantage of this, and with the introduction of [streaming load functions](https://svelte.dev/blog/streaming-snapshots-sveltekit) this behavior became a bit awkward as it forces you to nest your streamed data one level deep. Therefore, SvelteKit no longer awaits top level promises returned from a `load` function and instead streams these, too. To get back the blocking behavior, wrap your top level promises with `Promise.all(...)`, or if you only have one promise, `await` it.

```diff
export function load({ fetch }) {
- const a = fetch(...).then(r => r.json());
- const b = fetch(...).then(r => r.json());
+ const [a, b] = Promise.all([
+ fetch(...).then(r => r.json()),
+ fetch(...).then(r => r.json()),
+ ]);
return { a, b };
}

// Or if you only have a single promise
export function load({ fetch }) {
- const response = fetch(...).then(r => r.json());
+ const response = await fetch(...).then(r => r.json());
return { response }
}
```

## goto(...) no longer accepts external URLs

To navigate to an external URL, use `window.location = url`.

## paths are now relative by default

TODO explain

## Server fetches are not trackable anymore

Previously it was possible to track URLs from `fetch`es on the server in order to rerun load functions. This poses a possible security risk (private URLs leaking), and as such it was behind the `dangerZone.trackServerFetches` setting, which is now removed.

## Updated dependency requirements

SvelteKit requires Node `18.13` or higher, Vite `^5.0`, vite-plugin-svelte `^3.0`, TypeScript `^5.0` and Svelte version 4 or higher. `svelte-migrate` will do the `package.json` bumps for you.

As part of the TypeScript dependency bump, the generated `tsconfig.json` (the one your `tsconfig.json` extends from), it changed the `moduleResolution` to `bundler` (the new recommended default by the TypeScript team, which properly resolves types from packages using the now common exports map feature) and using `verbatimModuleSyntax` which replaces the existing `importsNotUsedAsValues ` and `preserveValueImports` flags (if you have those in your `tsconfig.json`, remove them - `svelte-migrate` will do this for you).
10 changes: 6 additions & 4 deletions packages/migrate/migrations/svelte-4/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import colors from 'kleur';
import fs from 'node:fs';
import prompts from 'prompts';
import glob from 'tiny-glob/sync.js';
import { bail, check_git } from '../../utils.js';
import { update_js_file, update_pkg_json, update_svelte_file } from './migrate.js';
import { bail, check_git, update_js_file, update_svelte_file } from '../../utils.js';
import { transform_code, transform_svelte_code, update_pkg_json } from './migrate.js';

export async function migrate() {
if (!fs.existsSync('package.json')) {
Expand Down Expand Up @@ -78,9 +78,11 @@ export async function migrate() {
for (const file of files) {
if (extensions.some((ext) => file.endsWith(ext))) {
if (svelte_extensions.some((ext) => file.endsWith(ext))) {
update_svelte_file(file, migrate_transition.value);
update_svelte_file(file, transform_code, (code) =>
transform_svelte_code(code, migrate_transition.value)
);
} else {
update_js_file(file);
update_js_file(file, transform_code);
}
}
}
Expand Down
139 changes: 26 additions & 113 deletions packages/migrate/migrations/svelte-4/migrate.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fs from 'node:fs';
import { Project, ts, Node } from 'ts-morph';
import semver from 'semver';
import { log_migration, log_on_ts_modification, update_pkg } from '../../utils.js';

export function update_pkg_json() {
fs.writeFileSync(
Expand All @@ -13,93 +13,31 @@ export function update_pkg_json() {
* @param {string} content
*/
export function update_pkg_json_content(content) {
const indent = content.split('\n')[1].match(/^\s+/)?.[0] || ' ';
const pkg = JSON.parse(content);

/**
* @param {string} name
* @param {string} version
* @param {string} [additional]
*/
function update_pkg(name, version, additional = '') {
if (pkg.dependencies?.[name]) {
const existing_range = pkg.dependencies[name];

if (semver.validRange(existing_range) && !semver.subset(existing_range, version)) {
log_migration(`Updated ${name} to ${version} ${additional}`);
pkg.dependencies[name] = version;
}
}

if (pkg.devDependencies?.[name]) {
const existing_range = pkg.devDependencies[name];

if (semver.validRange(existing_range) && !semver.subset(existing_range, version)) {
log_migration(`Updated ${name} to ${version} ${additional}`);
pkg.devDependencies[name] = version;
}
}
}

update_pkg('svelte', '^4.0.0');
update_pkg('svelte-check', '^3.4.3');
update_pkg('svelte-preprocess', '^5.0.3');
update_pkg('@sveltejs/kit', '^1.20.4');
update_pkg('@sveltejs/vite-plugin-svelte', '^2.4.1');
update_pkg(
'svelte-loader',
'^3.1.8',
' (if you are still on webpack 4, you need to update to webpack 5)'
);
update_pkg('rollup-plugin-svelte', '^7.1.5');
update_pkg('prettier-plugin-svelte', '^2.10.1');
update_pkg('eslint-plugin-svelte', '^2.30.0');
update_pkg(
'eslint-plugin-svelte3',
'^4.0.0',
' (this package is deprecated, use eslint-plugin-svelte instead. More info: https://svelte.dev/docs/v4-migration-guide#new-eslint-package)'
);
update_pkg(
'typescript',
'^5.0.0',
' (this might introduce new type errors due to breaking changes within TypeScript)'
);

return JSON.stringify(pkg, null, indent);
}

/**
* @param {string} file_path
* @param {boolean} migrate_transition
*/
export function update_svelte_file(file_path, migrate_transition) {
try {
const content = fs.readFileSync(file_path, 'utf-8');
const updated = content.replace(
/<script([^]*?)>([^]+?)<\/script>(\n*)/g,
(_match, attrs, contents, whitespace) => {
return `<script${attrs}>${transform_code(
contents,
(attrs.includes('lang=') || attrs.includes('type=')) &&
(attrs.includes('ts') || attrs.includes('typescript'))
)}</script>${whitespace}`;
}
);
fs.writeFileSync(file_path, transform_svelte_code(updated, migrate_transition), 'utf-8');
} catch (e) {
console.error(`Error updating ${file_path}:`, e);
}
}

/** @param {string} file_path */
export function update_js_file(file_path) {
try {
const content = fs.readFileSync(file_path, 'utf-8');
const updated = transform_code(content, file_path.endsWith('.ts'));
fs.writeFileSync(file_path, updated, 'utf-8');
} catch (e) {
console.error(`Error updating ${file_path}:`, e);
}
return update_pkg(content, [
['svelte', '^4.0.0'],
['svelte-check', '^3.4.3'],
['svelte-preprocess', '^5.0.3'],
['@sveltejs/kit', '^1.20.4'],
['@sveltejs/vite-plugin-svelte', '^2.4.1'],
[
'svelte-loader',
'^3.1.8',
' (if you are still on webpack 4, you need to update to webpack 5)'
],
['rollup-plugin-svelte', '^7.1.5'],
['prettier-plugin-svelte', '^2.10.1'],
['eslint-plugin-svelte', '^2.30.0'],
[
'eslint-plugin-svelte3',
'^4.0.0',
' (this package is deprecated, use eslint-plugin-svelte instead. More info: https://svelte.dev/docs/v4-migration-guide#new-eslint-package)'
],
[
'typescript',
'^5.0.0',
' (this might introduce new type errors due to breaking changes within TypeScript)'
]
]);
}

/**
Expand Down Expand Up @@ -401,28 +339,3 @@ function replaceInJsDoc(source, replacer) {
}
});
}

const logged_migrations = new Set();

/**
* @param {import('ts-morph').SourceFile} source
* @param {string} text
*/
function log_on_ts_modification(source, text) {
let logged = false;
const log = () => {
if (!logged) {
logged = true;
log_migration(text);
}
};
source.onModified(log);
return () => source.onModified(log, false);
}

/** @param {string} text */
function log_migration(text) {
if (logged_migrations.has(text)) return;
console.log(text);
logged_migrations.add(text);
}
154 changes: 154 additions & 0 deletions packages/migrate/migrations/sveltekit-2/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import colors from 'kleur';
import fs from 'node:fs';
import prompts from 'prompts';
import semver from 'semver';
import glob from 'tiny-glob/sync.js';
import { bail, check_git, update_js_file, update_svelte_file } from '../../utils.js';
import { transform_code, update_pkg_json, update_tsconfig } from './migrate.js';
import { migrate as migrate_svelte_4 } from '../svelte-4/index.js';

export async function migrate() {
if (!fs.existsSync('package.json')) {
bail('Please re-run this script in a directory with a package.json');
}

if (!fs.existsSync('svelte.config.js')) {
bail('Please re-run this script in a directory with a svelte.config.js');
}

console.log(
colors
.bold()
.yellow(
'\nThis will update files in the current directory\n' +
"If you're inside a monorepo, don't run this in the root directory, rather run it in all projects independently.\n"
)
);

const use_git = check_git();

const response = await prompts({
type: 'confirm',
name: 'value',
message: 'Continue?',
initial: false
});

if (!response.value) {
process.exit(1);
}

const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
const svelte_dep = pkg.devDependencies?.svelte ?? pkg.dependencies?.svelte;
if (svelte_dep === undefined) {
bail('Please install Svelte first');
}

if (semver.lt(svelte_dep, '4.0.0')) {
console.log(
colors
.bold()
.yellow(
'\nSveltKit 2 requires a minimum version of Svelte 4. We recommend updating to Svelte 4 first ' +
'(using `npx svelte-migrate svelte-4`), and then do the SvelteKit 2 migration.\n'
)
);
const response = await prompts({
type: 'confirm',
name: 'value',
message: 'Upgrade to Svelte 4 as part of the SvelteKit 2 migration?',
initial: false
});
if (!response.value) {
process.exit(1);
} else {
await migrate_svelte_4();
console.log(
colors
.bold()
.green('\nMigration to Svelte 4 completed. Continue with the SvelteKit 2 migration?\n')
);
const response = await prompts({
type: 'confirm',
name: 'value',
message: 'Continue?',
initial: false
});
if (!response.value) {
process.exit(1);
}
}
}

const folders = await prompts({
type: 'multiselect',
name: 'value',
message: 'Which folders should be migrated?',
choices: fs
.readdirSync('.')
.filter(
(dir) =>
fs.statSync(dir).isDirectory() &&
dir !== 'node_modules' &&
dir !== 'dist' &&
!dir.startsWith('.')
)
.map((dir) => ({ title: dir, value: dir, selected: dir === 'src' }))
});

if (!folders.value?.length) {
process.exit(1);
}

update_pkg_json();
update_tsconfig();

// const { default: config } = fs.existsSync('svelte.config.js')
// ? await import(pathToFileURL(path.resolve('svelte.config.js')).href)
// : { default: {} };

/** @type {string[]} */
const svelte_extensions = /* config.extensions ?? - disabled because it would break .svx */ [
'.svelte'
];
const extensions = [...svelte_extensions, '.ts', '.js'];
// For some reason {folders.value.join(',')} as part of the glob doesn't work and returns less files
const files = folders.value.flatMap(
/** @param {string} folder */ (folder) =>
glob(`${folder}/**`, { filesOnly: true, dot: true })
.map((file) => file.replace(/\\/g, '/'))
.filter((file) => !file.includes('/node_modules/'))
);

for (const file of files) {
if (extensions.some((ext) => file.endsWith(ext))) {
if (svelte_extensions.some((ext) => file.endsWith(ext))) {
update_svelte_file(file, transform_code, (code) => code);
} else {
update_js_file(file, transform_code);
}
}
}

console.log(colors.bold().green('✔ Your project has been migrated'));

console.log('\nRecommended next steps:\n');

const cyan = colors.bold().cyan;

const tasks = [
use_git && cyan('git commit -m "migration to SvelteKit 2"'),
'Review the migration guide at https://kit.svelte.dev/docs/v2-migration-guide',
'Read the updated docs at https://kit.svelte.dev/docs'
].filter(Boolean);

tasks.forEach((task, i) => {
console.log(` ${i + 1}: ${task}`);
});

console.log('');

if (use_git) {
console.log(`Run ${cyan('git diff')} to review changes.\n`);
}
}
Loading