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 theobserve
method from svelte-extras - Rewrite calls to
component.get('foo')
ascomponent.get().foo
- Return
destroy
from your custom event handlers, rather thanteardown
- 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) => any
d: (a: any
a, b: any
b, c: any
c) => (a: any
a = b: any
b + c: any
c)
}
};
...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: any
a, b: any
b, c: any
c }) => (a: any
a = b: any
b + c: any
c)
}
};
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: any
changed, current: any
current, previous: any
previous }) {
// this fires before oncreate, and
// whenever state changes
},
function onupdate({ changed, current, previous }: {
changed: any;
current: any;
previous: any;
}): void
onupdate({ changed: any
changed, current: any
current, previous: any
previous }) {
// 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: any
changed, current: any
current, previous: any
previous }) => {
// ...
});
component.observe
With the new lifecycle hooks, we no longer need the component.observe(...)
method:
// before
export default {
function oncreate(): void
oncreate() {
this.observe('foo', foo: any
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
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.
log(`foo is now ${foo: any
foo}`);
});
}
};
// after
export default {
function onstate({ changed, current }: {
changed: any;
current: any;
}): void
onstate({ changed: any
changed, current: any
current }) {
if (changed: any
changed.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
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.
log(`foo is now ${current: any
current.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 observe
observe } from 'svelte-extras';
export default {
methods: {
observe: any;
}
methods: {
observe: any
observe
}
};
component.get
This method no longer takes an optional key
argument — instead, it always returns the entire state object:
// before
const const foo: any
foo = this.get('foo');
const const bar: any
bar = this.get('bar');
// after
const { const foo: any
foo, const bar: any
bar } = 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: any
js, const css: any
css, const ast: any
ast, const stats: any
stats } = 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.