Merge branch 'design-tweaks' of github.com:sveltejs/svelte into design-tweaks

pull/2441/head
Richard Harris 6 years ago
commit 7b40c23e0e

@ -1,7 +1,6 @@
---
title: Frameworks without the framework: why didn't we think of this sooner?
description: You can't write serious applications in vanilla JavaScript without hitting a complexity wall. But a compiler can do it for you.
pubdate: 2016-11-26
author: Rich Harris
authorURL: https://twitter.com/Rich_Harris
---

@ -1,7 +1,6 @@
---
title: The easiest way to get started with Svelte
description: This'll only take a minute.
pubdate: 2017-08-07
author: Rich Harris
authorURL: https://twitter.com/Rich_Harris
---

@ -1,7 +1,6 @@
---
title: The zen of Just Writing CSS
description: I would say this is the future, but we're already doing it.
pubdate: 2017-09-06
author: Rich Harris
authorURL: https://twitter.com/Rich_Harris
---

@ -1,7 +1,6 @@
---
title: Sapper: Towards the ideal web app framework
description: Taking the next-plus-one step
pubdate: 2017-12-31
author: Rich Harris
authorURL: https://twitter.com/Rich_Harris
---

@ -1,7 +1,6 @@
---
title: Svelte v2 is out!
description: Here's what you need to know
pubdate: 2018-04-18
author: Rich Harris
authorURL: https://twitter.com/Rich_Harris
---

@ -1,7 +1,6 @@
---
title: Using CSS-in-JS with Svelte
description: You don't need to, but you can
pubdate: 2018-12-26
author: Rich Harris
authorURL: https://twitter.com/Rich_Harris
---
@ -17,11 +16,13 @@ But Svelte's style handling does have some limitations. It's too difficult to sh
Here, we're using [Emotion](https://emotion.sh) to generate scoped class names that can be used across multiple components:
<iframe
title="Aphrodite example"
src="/repl/embed?gist=ad495ff5ba9ceefe5984fe62c1f15e19"
scrolling="no"
></iframe>
<div class="max">
<iframe
title="Aphrodite example"
src="/repl/embed?gist=ad495ff5ba9ceefe5984fe62c1f15e19"
scrolling="no"
></iframe>
</div>
It's important to note that most CSS-in-JS libraries have a runtime library, and many don't support statically extracting styles out into a separate <code>.css</code> file at build time (which is essential for the best performance). You should therefore only use CSS-in-JS if it's necessary for your application!

@ -1,7 +1,6 @@
---
title: Virtual DOM is pure overhead
description: Let's retire the 'virtual DOM is fast' myth once and for all
pubdate: 2018-12-27
author: Rich Harris
authorURL: https://twitter.com/Rich_Harris
---

@ -1,7 +1,6 @@
---
title: Svelte on The Changelog
description: Listen to the interview here
pubdate: 2019-01-31
author: Rich Harris
authorURL: https://twitter.com/Rich_Harris
---

@ -0,0 +1,13 @@
---
title: Setting up your editor
description: Instructions for configuring linting and syntax highlighting
author: Rich Harris
authorURL: https://twitter.com/Rich_Harris
draft: true
---
*Coming soon* This post will walk you through setting up your editor so that recognises Svelte files:
* eslint-plugin-svelte3
* svelte-vscode
* associating .svelte files with HTML in VSCode, Sublime, Atom, etc etc etc

@ -0,0 +1,9 @@
---
title: Svelte for new developers
description: Never used Node.js or the command line? No problem
author: Rich Harris
authorURL: https://twitter.com/Rich_Harris
draft: true
---
*Coming soon* This blog post will walk you through installing Node.js and git and using Terminal.app to clone a project template and start developing with Svelte

@ -0,0 +1,165 @@
---
title: Write less code
description: The most important metric you're not paying attention to
author: Rich Harris
authorURL: https://twitter.com/Rich_Harris
draft: true
---
All code is buggy. It stands to reason, therefore, that the more code you have to write the buggier your apps will be.
Writing more code also takes more time, leaving less time for other things like optimisation, nice-to-have features, or being outdoors instead of hunched over a laptop.
In fact it's widely acknowledged that [project development time](https://blog.codinghorror.com/diseconomies-of-scale-and-lines-of-code/) and [bug count](https://www.mayerdan.com/ruby/2012/11/11/bugs-per-line-of-code-ratio) grow *quadratically*, not linearly, with the size of a codebase. That tracks with our intuitions: a ten-line pull request will get a level of scrutiny rarely applied to a 100-line one. And once a given module becomes too big to fit on a single screen, the cognitive effort required to understand it increases significantly. We compensate by refactoring and adding comments — activities that almost always result in *more* code. It's a vicious cycle.
Yet while we obsess — rightly! — over performance numbers, bundle size and anything else we can measure, we rarely pay attention to the amount of code we're writing.
## Readability is important
I'm certainly not claiming that we should use clever tricks to scrunch our code into the most compact form possible at the expense of readability. Nor am I claiming that reducing *lines* of code is necessarily a worthwhile goal, since it encourages turning readable code like this...
```js
for (let i = 0; i <= 100; i += 1) {
if (i % 2 === 0) {
console.log(`${i} is even`);
}
}
```
...into something much harder to parse:
```js
for (let i = 0; i <= 100; i += 1) if (i % 2 === 0) console.log(`${i} is even`);
```
Instead, I'm claiming that we should favour languages and patterns that allow us to naturally write less code.
## Yes, I'm talking about Svelte
Reducing the amount of code you have to write is an explicit goal of Svelte. To illustrate, let's look at a very simple component implemented in React, Vue and Svelte. First, the Svelte version:
<div class="max">
<iframe
title="Simple component example"
src="/repl/embed?gist=6b573f1819d12defc441098236fb9abe"
scrolling="no"
></iframe>
</div>
How would we build this in React? It would probably look something like this:
```js
import React, { useState } from 'react';
export default () => {
const [a, setA] = useState(1);
const [b, setB] = useState(2);
function handleInputA(event) {
setA(+event.target.value);
}
function handleInputB(event) {
setB(+event.target.value);
}
return (
<div>
<input type="number" value={a} onInput={handleInputA}/>
<input type="number" value={b} onInput={handleInputB}/>
<p>{a} + {b} = {a + b}</p>
</div>
);
};
```
Here's an equivalent component in Vue:
```html
<template>
<div>
<input type="number" v-model.number="a">
<input type="number" v-model.number="b">
<p>{{a}} + {{b}} = {{a + b}}</p>
</div>
</template>
<script>
export default {
data: function() {
return {
a: 1,
b: 2
};
}
};
</script>
```
<aside>
<p>I'm counting by copying the source code to the clipboard and running `pbpaste | wc -c` in my terminal</p>
</aside>
In other words, it takes 442 characters in React, and 263 characters in Vue, to achieve something that takes 145 characters in Svelte. The React version is literally three times larger!
It's unusual for the difference to be *quite* so obvious — in my experience, a React component is typically around 40% larger than its Svelte equivalent. Let's look at the features of Svelte's design that enable you to express ideas more concisely:
### Top-level elements
In Svelte, a component can have as many top-level elements as you like. In React and Vue, a component must have a single top-level element — in React's case, trying to return two top-level elements from a component function would result in syntactically invalid code. (You can use a fragment — `<>` instead of a `<div>`, but it's the same basic idea).
In Vue, your markup must be wrapped in a `<template>` element, which I'd argue is redundant.
### Bindings
In React, we have to respond to input events ourselves:
```js
function handleInputA(event) {
setA(+event.target.value);
}
```
This isn't just boring plumbing that takes up extra space on the screen, it's also extra surface area for bugs. Conceptually, the value of the input is bound to the value of `a` and vice versa, but that relationship isn't cleanly expressed — instead we have two tightly-coupled but physically separate chunks of code (the event handler and the `value={a}` prop). Not only that, but we have to remember to coerce the string value with the `+` operator, otherwise `2 + 2` will equal `22` instead of `4`.
Like Svelte, Vue does have a way of expressing the binding — the `v-model` attribute, though again we have to be careful to use `v-model.number` even though it's a numeric input.
### State
In Svelte, you update local component state with an assignment operator:
```js
let count = 0;
function increment() {
count += 1;
}
```
In React, we use the `useState` hook:
```js
const [count, setCount] = useState(0);
function increment() {
setCount(count + 1);
}
```
This is much *noisier* — it expresses the exact same concept but with over 60% more characters. As you're reading the code, you have to do that much more work to understand the author's intent.
In Vue, meanwhile, we have a default export with a `data` function that returns an object literal with properties corresponding to our local state. Things like helper functions and child components can't simply be imported and used in the template, but must instead be 'registered' by attaching them to the correct part of the default export.
## Death to boilerplate
These are just some of the ways that Svelte helps you build user interfaces with a minimum of fuss. There are plenty of others — for example, [reactive declarations](https://svelte.dev/tutorial/reactive-declarations) essentially do the work of React's `useMemo`, `useCallback` and `useEffect` without the boilerplate (or indeed the garbage collection overhead of creating inline functions and arrays on each state change).
How? By choosing a different set of constraints. Because [Svelte is a compiler](blog/frameworks-without-the-framework), we're not bound to the peculiarities of JavaScript: we can *design* a component authoring experience, rather than having to fit it around the semantics of the language. Paradoxically, this results in *more* idiomatic code — for example using variables naturally rather than via proxies or hooks — while delivering significantly more performant apps.

@ -17,7 +17,7 @@ But there's a more *idiomatic* solution:
```js
function addNumber() {
numbers = [...numbers, numbers.length + 1;]
numbers = [...numbers, numbers.length + 1];
}
```

@ -16,5 +16,8 @@ export function get(req, res) {
'Cache-Control': `max-age=${5 * 60 * 1e3}` // 5 minutes
});
res.end(lookup.get(req.params.slug));
} else {
res.statusCode = 404;
res.end(JSON.stringify({ message: 'not found' }));
}
}

@ -1,7 +1,7 @@
<script context="module">
export async function preload({ params }) {
const post = await this.fetch(`blog/${params.slug}.json`).then(r => r.json());
return { post };
const res = await this.fetch(`blog/${params.slug}.json`);
return res.ok ? { post: await res.json() } : this.error(404, 'Not found');
}
</script>
@ -110,6 +110,10 @@
z-index: 2;
}
.post :global(.max) {
width: 100%;
}
.post :global(iframe) {
width: 100%;
height: 420px;
@ -119,13 +123,20 @@
}
@media (min-width: 910px) {
.post :global(iframe) {
.post :global(.max) {
width: calc(100vw - 2 * var(--side-nav));
margin: 2em calc(400px + var(--side-nav) - 50vw);
margin: 0 calc(var(--main-width) / 2 - 50vw);
text-align: center;
}
.post :global(iframe) {
width: 100%;
max-width: 1100px;
margin: 2em auto;
}
}
@media (min-width: 1460px) {
/* @media (min-width: 1460px) {
.post :global(iframe) {
width: 1360px;
margin: 2em -280px;
@ -136,5 +147,5 @@
.post :global(iframe) {
height: 640px;
}
}
} */
</style>

@ -5,17 +5,23 @@ import marked from 'marked';
import PrismJS from 'prismjs';
import 'prismjs/components/prism-bash';
export default function() {
export default function get_posts() {
return fs
.readdirSync('content/blog')
.map(file => {
if (path.extname(file) !== '.md') return;
const match = /^(\d+-\d+-\d+)-(.+)\.md$/.exec(file);
if (!match) throw new Error(`Invalid filename '${file}'`);
const [, pubdate, slug] = match;
const markdown = fs.readFileSync(`content/blog/${file}`, 'utf-8');
const { content, metadata } = extract_frontmatter(markdown);
const date = new Date(`${metadata.pubdate} EDT`); // cheeky hack
const date = new Date(`${pubdate} EDT`); // cheeky hack
metadata.pubdate = pubdate;
metadata.dateString = date.toDateString();
const renderer = new marked.Renderer();
@ -41,7 +47,7 @@ export default function() {
return {
html,
metadata,
slug: file.replace(/^[\d-]+/, '').replace(/\.md$/, ''),
slug
};
})
.sort((a, b) => a.metadata.pubdate < b.metadata.pubdate ? 1 : -1);

@ -4,12 +4,16 @@ let json;
export function get(req, res) {
if (!json || process.env.NODE_ENV !== 'production') {
json = JSON.stringify(get_posts().map(post => {
return {
slug: post.slug,
metadata: post.metadata
};
}));
const posts = get_posts()
.filter(post => !post.metadata.draft)
.map(post => {
return {
slug: post.slug,
metadata: post.metadata
};
});
json = JSON.stringify(posts);
}
res.set({

@ -3,7 +3,7 @@
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width'>
<meta name='theme-color' content='#aa1e1e'>
<meta name='theme-color' content='#ff3e00'>
%sapper.base%

Loading…
Cancel
Save