Skip to main content

Svelte v2 is out!

Here’s what you need to know

Almost a year after we first started talking about version 2 on the Svelte issue tracker, it’s finally time to make some breaking changes. This blog post will explain what changed, why it changed, and what you need to do to bring your apps up to date.

tl;dr

Each of these items is described in more depth below. If you get stuck, ask for help in our friendly Discord chatroom.

  • Install Svelte v2 from npm
  • Upgrade your templates with svelte-upgrade
  • Remove calls to component.observe, or add the observe method from svelte-extras
  • Rewrite calls to component.get('foo') as component.get().foo
  • Return destroy from your custom event handlers, rather than teardown
  • Make sure you’re not passing numeric string props to components

New template syntax

The most visible change: we’ve made some improvements to the template syntax.

A common piece of feedback we heard was ‘ewww, Mustache’ or ‘ewww, Handlebars’. A lot of people who used string-based templating systems in a previous era of web development really dislike them. Because Svelte adopted the {{curlies}} from those languages, a lot of people assumed that we somehow shared the limitations of those tools, such as weird scoping rules or an inability to use arbitrary JavaScript expressions.

Beyond that, JSX proved that double curlies are unnecessary. So we’ve made our templates more... svelte, by adopting single curlies. The result feels much lighter to look at and is more pleasant to type:

<h1>Hello {name}!</h1>

There are a few other updates. But you don’t need to make them manually — just run svelte-upgrade on your codebase:

npx svelte-upgrade v2 src

This assumes any .html files in src are Svelte components. You can specify whichever directory you like, or target a different directory — for example, you’d do npx svelte-upgrade v2 routes to update a Sapper app.

To see the full set of changes, consult the svelte-upgrade README.

Computed properties

Another thing that people often found confusing about Svelte is the way computed properties work. To recap, if you had a component with this...

export default {
	
computed: {
    d: (a: any, b: any, c: any) => any;
}
computed
: {
d: (a: any, b: any, c: any) => anyd: (a: anya, b: anyb, c: anyc) => (a: anya = b: anyb + c: anyc) } };

...then Svelte would first look at the function arguments to see which values d depended on, and then it would write code that updated d whenever those values changed, by injecting them into the function. That’s cool, because it allows you to derive complex values from your component’s inputs without worrying about when they need to recomputed, but it’s also... weird. JavaScript doesn’t work that way!

In v2, we use destructuring instead:

export default {
	
computed: {
    d: ({ a, b, c }: {
        a: any;
        b: any;
        c: any;
    }) => any;
}
computed
: {
d: ({ a, b, c }: {
    a: any;
    b: any;
    c: any;
}) => any
d
: ({ a: anya, b: anyb, c: anyc }) => (a: anya = b: anyb + c: anyc)
} };

The Svelte compiler can still see which values d depends on, but it’s no longer injecting values — it just passes the component state object into each computed property.

Again, you don’t need to make this change manually — just run svelte-upgrade on your components, as shown above.

Sorry, IE11. It’s not you, it’s... well actually, yeah. It’s you

Svelte v1 was careful to only emit ES5 code, so that you wouldn’t be forced to faff around with transpilers in order to use it. But it’s 2018 now, and almost all browsers support modern JavaScript. By ditching the ES5 constraint, we can generate leaner code.

If you need to support IE11 and friends, you will need to use a transpiler like Babel or Bublé.

New lifecycle hooks

In addition to oncreate and ondestroy, Svelte v2 adds two more lifecycle hooks for responding to state changes:

export default {
	
function onstate({ changed, current, previous }: {
    changed: any;
    current: any;
    previous: any;
}): void
onstate
({ changed: anychanged, current: anycurrent, previous: anyprevious }) {
// this fires before oncreate, and // whenever state changes },
function onupdate({ changed, current, previous }: {
    changed: any;
    current: any;
    previous: any;
}): void
onupdate
({ changed: anychanged, current: anycurrent, previous: anyprevious }) {
// this fires after oncreate, and // whenever the DOM has been updated // following a state change } };

You can also listen to those events programmatically:

component.on('state', ({ changed: anychanged, current: anycurrent, previous: anyprevious }) => {
	// ...
});

component.observe

With the new lifecycle hooks, we no longer need the component.observe(...) method:

// before
export default {
	function oncreate(): voidoncreate() {
		this.observe('foo', foo: anyfoo => {
			var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without calling require('console').

Warning: The global console object’s methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
//   Error: Whoops, something bad happened
//     at [eval]:5:15
//     at Script.runInThisContext (node:vm:132:18)
//     at Object.runInThisContext (node:vm:309:38)
//     at node:internal/process/execution:77:19
//     at [eval]-wrapper:6:22
//     at evalScript (node:internal/process/execution:76:60)
//     at node:internal/main/eval_string:23:3

const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);

myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err

const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
@seesource
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100
log
(`foo is now ${foo: anyfoo}`);
}); } }; // after export default {
function onstate({ changed, current }: {
    changed: any;
    current: any;
}): void
onstate
({ changed: anychanged, current: anycurrent }) {
if (changed: anychanged.foo) { var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without calling require('console').

Warning: The global console object’s methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
//   Error: Whoops, something bad happened
//     at [eval]:5:15
//     at Script.runInThisContext (node:vm:132:18)
//     at Object.runInThisContext (node:vm:309:38)
//     at node:internal/process/execution:77:19
//     at [eval]-wrapper:6:22
//     at evalScript (node:internal/process/execution:76:60)
//     at node:internal/main/eval_string:23:3

const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);

myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err

const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
@seesource
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100
log
(`foo is now ${current: anycurrent.foo}`);
} } };

This shrinks the amount of code Svelte needs to generate, and gives you more flexibility. For example, it’s now very easy to take action when any one of several properties have changed, such as redrawing a canvas without debouncing several observers.

However, if you prefer to use component.observe(...), then you can install it from svelte-extras:

import { import observeobserve } from 'svelte-extras';

export default {
	
methods: {
    observe: any;
}
methods
: {
observe: anyobserve } };

component.get

This method no longer takes an optional key argument — instead, it always returns the entire state object:

// before
const const foo: anyfoo = this.get('foo');
const const bar: anybar = this.get('bar');

// after
const { const foo: anyfoo, const bar: anybar } = this.get();

This change might seem annoying initially, but it’s the right move: among other things, it’s likely to play better with type systems as we explore that space more fully in future.

event_handler.destroy

If your app has custom event handlers, they must return an object with a destroy method, not a teardown method (this aligns event handlers with the component API).

No more type coercion

Previously, numeric values passed to components were treated as numbers:

<Counter start="1" />

That causes unexpected behaviour, and has been changed: if you need to pass a literal number, do so as an expression:

<Counter start={1} />

Compiler changes

In most cases you’ll never need to deal with the compiler directly, so this shouldn’t require any action on your part. It’s worth noting anyway: the compiler API has changed. Instead of an object with a mish-mash of properties, the compiler now returns js, css, ast and stats:

const { const js: anyjs, const css: anycss, const ast: anyast, const stats: anystats } = svelte.compile(source, options);

js and css are both { code, map } objects, where code is a string and map is a sourcemap. The ast is an abstract syntax tree of your component, and the stats object contains metadata about the component, and information about the compilation.

Before, there was a svelte.validate method which checked your component was valid. That’s been removed — if you want to check a component without actually compiling it, just pass the generate: false option.

My app is broken! Help!

Hopefully this covers everything, and the update should be easier for you than it was for us. But if you find bugs, or discover things that aren’t mentioned here, swing by Discord chatroom or raise an issue on the tracker.