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