Why another framework?
the ability of computer systems or software to exchange and make use of information.
Question
How to use an `Angular` component in `React`applications?
Imagine One Day
we use a new Framework X
Imagine One Day
There's an awesome component on Framework X
State?
Lifecycle?
CSS in JS?
Biggest Issue
Framework Runtime
Size Comparison
There's no way we can deliver a fast web experience
with two huge framework runtimes
New Trend
Framework as compiler
Stencil.JS
Stencil is a compiler that generates Web Components (more specifically, Custom Elements)
Svelte.JS
Svelte compiles your code to tiny, framework-less vanilla JS — your app starts fast and stays fast
Spoiler
Framework-less; compiler
Component bundled with tiny run time
Experiment
A `react` App running two `todo-mvc`s built with `stencil` and `svelte`
- react-app
- node_modules
- stencil-todo-mvc
- svelte-todo-mvc
Demo
Stencil
"compiled" to web components
// ...
import { defineCustomElements } from 'stencil-todo-mvc/dist/esm/loader';
// customElements.define('todo-mvc', TodoMVC)
defineCustomElements(window);
class App extends React.Component {
render() {
return (
<div className="App">
<todo-mvc message={'Hi Stencil'} />
</div>
);
}
}
Output
- stencil-todo-mvc
- dist
- loader.js (305 bytes)
- runtime.js (34.64 kb)
- todo-mvc.entry.js (1.63 kb)
- // browser polyfills loaded on demand
- // ...
loader.js
import { a as patchEsm, b as bootstrapLazy } from './runtime.js';
// bundle manifest information
const defineCustomElements = (win, options) => {
return patchEsm().then(() => {
bootstrapLazy([["todo-mvc",[[1,"todo-mvc",{"message":[1],"todos":[32],"todoText":[32]}]]]], options);
});
};
export { defineCustomElements };
import { r as registerInstance, h } from './runtime.js';
class TodoMVC {
constructor(hostRef) {
registerInstance(this, hostRef);
this.message = '';
this.todos = [];
this.todoText = '';
this.addNewTODO = () => {
this.todos = [
...this.todos,
{
text: this.todoText,
completed: false,
id: Date.now()
}
];
this.todoText = '';
};
this.toggleDone = (id) => {
this.todos = this.todos.map(todo => {
if (todo.id === id) {
return Object.assign({}, todo, { completed: !todo.completed });
}
return todo;
});
};
this.delete = (id) => {
this.todos = this.todos.filter(todo => todo.id !== id);
console.log(this.todos);
};
}
render() {
return (h("div", null, h("h2", null, "message from \u269B\uFE0F: ", this.message), h("ul", null, this.todos.map(todo => {
return (h("li", null, h("label", null, h("input", { type: "checkbox", checked: todo.completed, onChange: () => { this.toggleDone(todo.id); } }), h("span", { class: todo.completed ? 'done' : '' }, todo.text)), h("button", { onClick: () => { this.delete(todo.id); } }, "Delete")));
})), h("div", null, h("input", { type: "text", ref: (el) => { this.input = el; }, onInput: (e) => { this.todoText = e.target.value; }, value: this.todoText }), h("button", { onClick: this.addNewTODO }, "Add"))));
}
}
export { TodoMVC as todo_mvc };
todo-mvc.entry.js
runtime.js
- Virtual DOM
- Module loader
- Lazy load
- Web Components
Svelte
// ...
import SvelteTodoMVC from 'svelte-todo-mvc/public/bundle.js';
class App extends React.Component {
mountSvelteTodoMVC = (ref) => {
new SvelteTodoMVC({
target: ref,
props: {
message: 'Hi Svelte',
}
})
}
render() {
return (
<div className="App">
<div ref={this.mountSvelteTodoMVC}></div>
</div>
);
}
}
Output
- bundle.js (15.9 Kb, 560 locs)
- runtime (~8 kb, 300 locs)
- todo-mvc (~7 kb, 260 locs)
Input
Output
<script>
let count = 0;
function handleClick() {
// event handler code goes here
count += 1;
}
</script>
<div>
Clicked {count} {count === 1 ? 'time' : 'times'}
</div>
<button on:click={handleClick}>Click</button>
import {
SvelteComponent,
} from "svelte/internal";
function create_fragment(ctx) {
// render logic here
// ...
}
function instance($$self, $$props, $$invalidate) {
let count = 0;
function handleClick() {
// event handler code goes here
$$invalidate('count', count += 1);
}
return {
count,
handleClick
};
}
class App extends SvelteComponent {
constructor(options) {
super();
init(this, options, instance, create_fragment, safe_not_equal, []);
}
}
export default App;
import {
SvelteComponent,
append,
detach,
element,
init,
insert,
listen,
noop,
safe_not_equal,
set_data,
space,
text
} from "svelte/internal";
function create_fragment(ctx) {
var div, t0, t1, t2, t3_value = ctx.count === 1 ? 'time' : 'times',
t3, t4, button, dispose;
return {
c() {
div = element("div");
t0 = text("Clicked ");
t1 = text(ctx.count);
t2 = space();
t3 = text(t3_value);
t4 = space();
button = element("button");
button.textContent = "Click";
dispose = listen(button, "click", ctx.handleClick);
},
m(target, anchor) {
insert(target, div, anchor);
append(div, t0);
append(div, t1);
append(div, t2);
append(div, t3);
insert(target, t4, anchor);
insert(target, button, anchor);
},
p(changed, ctx) {
if (changed.count) {
set_data(t1, ctx.count);
}
if ((changed.count) && t3_value !== (t3_value = ctx.count === 1 ? 'time' : 'times')) {
set_data(t3, t3_value);
}
},
i: noop,
o: noop,
d(detaching) {
if (detaching) {
detach(div);
detach(t4);
detach(button);
}
dispose();
}
};
}
Stencil Todo MVC
import { Component, h, State, Prop } from '@stencil/core';
type TODO = {
text: string,
completed: boolean,
id: number,
}
@Component({
tag: 'todo-mvc',
shadow: true
})
export class TodoMVC {
@Prop() message: string = '';
@State() todos: TODO[] = [];
@State() todoText: string = '';
addNewTODO = () => {
this.todos = [
...this.todos,
{
text: this.todoText,
completed: false,
id: Date.now()
}
]
this.todoText = '';
}
toggleDone = (id) => {
this.todos = this.todos.map(todo => {
if (todo.id === id) {
return {...todo, completed: !todo.completed};
}
return todo;
});
}
delete = (id) => {
this.todos = this.todos.filter(todo => todo.id !== id);
}
render() {
return (
<div>
<h2>message:{this.message}</h2>
<ul>
{
this.todos.map(todo => {
return (
<li>
<label>
<input type="checkbox" checked={todo.completed} onChange={() => { this.toggleDone(todo.id); }} />
<span class={todo.completed ? 'done' : ''}>{todo.text}</span>
</label>
<button onClick={() => { this.delete(todo.id); }}>Delete</button>
</li>
)
})
}
</ul>
<div>
<input type="text" onInput={(e) => {this.todoText = (e.target as HTMLInputElement).value;}} value={this.todoText} />
<button onClick={this.addNewTODO}>Add</button>
</div>
</div>
);
}
}
Similar to React
Svelte Todo MVC
<script>
import todos from './store';
let newTODO = '';
export let message = '';
</script>
<style>
.done {
text-decoration: line-through;
}
</style>
<h2>message: {message}</h2>
<ul>
{#each $todos as todo}
<li>
<label>
<input type=checkbox on:input={()=> { todos.toggleTODO(todo.id) }} checked={todo.done} />
<span class={todo.done ? 'done' : ''}>{todo.text}</span>
</label>
<button on:click={()=>{todos.deleteTODO(todo.id)}}>delete</button>
</li>
{/each}
</ul>
<div>
<input type=text bind:value={newTODO}/>
<button on:click={()=> {todos.addNewTodo(newTODO); newTODO = ''}}>Add</button>
</div>
import {
writable
} from 'svelte/store';
const todos = writable([]);
export default {
subscribe: todos.subscribe,
addNewTodo: (text) => {
todos.update(todos => {
return [
...todos,
{
text: text,
done: false,
id: Date.now()
}
]
})
},
deleteTODO: (id) => {
todos.update(todos => todos.filter(todo => todo.id !== id));
},
toggleTODO: (id) => {
todos.update(todos => todos.map(todo => {
if (todo.id === id) {
return { ...todo,
done: !todo.done
}
}
return todo;
}))
}
}
Stencil vs Svelte
Renderer
Stencil
Virtual DOM
Svelte
Instructions compiled to component code
Data Management
Stencil
- Props
- @Prop
- State
- @State
- Custom Event
- Context (State Tunnel)?
- Store?
Svelte
- Props
- export let xx;
- State
- let xx;
- Reactive statement: $, {}
- Data binding:
<input bind:value={xx}/> - Writable/Readable Store
- Custom Event
Template Language
Stencil
- JSX
- Full JS expressiveness
- Harder to optimize
Svelte
- Traditional templating languages
- Limited expressiveness
- Custom directives
- bind:value
- on:click
- Able to parse and compile to intructions
Type Support
Stencil
Out-of-box TypeScript support
Svelte
CSS
Stencil
- CSS in shadow DOM
- Scoped by browser
- write CSS in separate files
- Full CSS expressiveness
Svelte
- CSS Scope by hashing
-
p.svelte-ouwt3e
-
- write CSS in style block
- Full CSS expressiveness
Code Splitting
Stencil
- Builtin support
- Lazy Load
- Zero config automagic
Svelte
- No official support
Final Thoughts
Interoperable?
Kind of.
But deeper problems remain unsolved
Cooperative Render Scheduling
- React Async Rendering
- Lazy Loading
- Different framework lifecycle abstraction
State Management
- Keep data in sync between multiple frameworks
- One way data flow vs data binding
Stencil
What I like
- Use of Web component
What I dislike
- Virtual DOM
- React like concepts
Svelte
What I like
- Compiling UI logic into instructions is mind changing
What I dislike
- Very opinionated design choices
The END
stencil vs svelte
By Chenchen Sun
stencil vs svelte
- 3,270