Merge branch 'master' into embedded-repl-hide-compiler-options

pull/1923/head
Richard Harris 6 years ago
commit ff2a21e63a

1
.gitignore vendored

@ -26,3 +26,4 @@ _actual*.*
/site/.sessions /site/.sessions
/site/static/svelte-app.json /site/static/svelte-app.json
/site/scripts/svelte-app /site/scripts/svelte-app
/site/src/routes/_contributors.js

2
package-lock.json generated

@ -1,6 +1,6 @@
{ {
"name": "svelte", "name": "svelte",
"version": "3.0.0-alpha6", "version": "3.0.0-alpha12",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

@ -15,25 +15,12 @@ Almost a year after we first started talking about version 2 on the Svelte issue
Each of these items is described in more depth below. If you get stuck, ask for help in our friendly [Gitter chatroom](https://gitter.im/sveltejs/svelte). Each of these items is described in more depth below. If you get stuck, ask for help in our friendly [Gitter chatroom](https://gitter.im/sveltejs/svelte).
<style> - Install Svelte v2 from npm
ul { - Upgrade your templates with [svelte-upgrade](https://github.com/sveltejs/svelte-upgrade)
position: relative; - Remove calls to `component.observe`, or add the `observe` method from [svelte-extras](https://github.com/sveltejs/svelte-extras)
list-style: none; - 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
li input {
position: absolute;
left: -2.5em;
top: 0.3em;
}
</style>
- <input type=checkbox> Install Svelte v2 from npm
- <input type=checkbox> Upgrade your templates with [svelte-upgrade](https://github.com/sveltejs/svelte-upgrade)
- <input type=checkbox> Remove calls to `component.observe`, or add the `observe` method from [svelte-extras](https://github.com/sveltejs/svelte-extras)
- <input type=checkbox> Rewrite calls to `component.get('foo')` as `component.get().foo`
- <input type=checkbox> Return `destroy` from your custom event handlers, rather than `teardown`
- <input type=checkbox> Make sure you're not passing numeric string props to components
## New template syntax ## New template syntax

@ -0,0 +1,29 @@
---
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
---
CSS is a core part of any web app. By extension, a UI framework that doesn't have a built-in way to add styles to your components is unfinished.
That's why Svelte allows you to add CSS in a component's `<style>` tag. Co-locating your CSS with your markup means we can [solve the biggest problems developers face when writing CSS](/blog/the-zen-of-just-writing-css) without introducing new ones, all while providing a rather nice development experience.
But Svelte's style handling does have some limitations. It's too difficult to share styles between components, or apply app-level optimisations. These are areas we plan to address in future versions, but in the meantime if you need those things you can use any framework-agnostic CSS-in-JS library.
## For example
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>
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!
Note that you can mix-and-match — you can still use Svelte's built-in CSS handling alongside a CSS-in-JS library.

@ -0,0 +1,148 @@
---
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
---
If you've used JavaScript frameworks in the last few years, you've probably heard the phrase 'the virtual DOM is fast', often said to mean that it's faster than the *real* DOM. It's a surprisingly resilient meme — for example people have asked how Svelte can be fast when it doesn't use a virtual DOM.
It's time to take a closer look.
## What is the virtual DOM?
In many frameworks, you build an app by creating `render()` functions, like this simple [React](https://reactjs.org/) component:
```js
function HelloMessage(props) {
return (
<div className="greeting">
Hello {props.name}
</div>
);
}
```
You can do the same thing without JSX...
```js
function HelloMessage(props) {
return React.createElement(
'div',
{ className: 'greeting' },
'Hello ',
props.name
);
}
```
...but the result is the same — an object representing how the page should now look. That object is the virtual DOM. Every time your app's state updates (for example when the `name` prop changes), you create a new one. The framework's job is to *reconcile* the new one against the old one, to figure out what changes are necessary and apply them to the real DOM.
## How did the meme start?
Misunderstood claims about virtual DOM performance date back to the launch of React. In [Rethinking Best Practices](https://www.youtube.com/watch?v=x7cQ3mrcKaY), a seminal 2013 talk by former React core team member Pete Hunt, we learned the following:
> This is actually extremely fast, primarily because most DOM operations tend to be slow. There's been a lot of performance work on the DOM, but most DOM operations tend to drop frames.
<figure>
<img alt="Pete Hunt at JSConfEU 2013" src="media/rethinking-best-practices.jpg">
<figcaption>Screenshot from <a href="https://www.youtube.com/watch?v=x7cQ3mrcKaY">Rethinking Best Practices</a> at JSConfEU 2013</figcaption>
</figure>
But hang on a minute! The virtual DOM operations are *in addition to* the eventual operations on the real DOM. The only way it could be faster is if we were comparing it to a less efficient framework (there were plenty to go around back in 2013!), or arguing against a straw man — that the alternative is to do something no-one actually does:
```js
onEveryStateChange(() => {
document.body.innerHTML = renderMyApp();
});
```
Pete clarifies soon after...
> React is not magic. Just like you can drop into assembler with C and beat the C compiler, you can drop into raw DOM operations and DOM API calls and beat React if you wanted to. However, using C or Java or JavaScript is an order of magnitude performance improvement because you don't have to worry...about the specifics of the platform. With React you can build applications without even thinking about performance and the default state is fast.
...but that's not the part that stuck.
## So... is the virtual DOM *slow*?
Not exactly. It's more like 'the virtual DOM is usually fast enough', but with certain caveats.
The original promise of React was that you could re-render your entire app on every single state change without worrying about performance. In practice, I don't think that's turned out to be accurate. If it was, there'd be no need for optimisations like `shouldComponentUpdate` (which is a way of telling React when it can safely skip a component).
Even with `shouldComponentUpdate`, updating your entire app's virtual DOM in one go is a lot of work. A while back, the React team introduced something called React Fiber which allows the update to be broken into smaller chunks. This means (among other things) that updates don't block the main thread for long periods of time, though it doesn't reduce the total amount of work or the time an update takes.
## Where does the overhead come from?
Most obviously, [diffing isn't free](https://twitter.com/pcwalton/status/1015694528857047040). You can't apply changes to the real DOM without first comparing the new virtual DOM with the previous snapshot. To take the earlier `HelloMessage` example, suppose the `name` prop changed from 'world' to 'everybody'.
1. Both snapshots contain a single element. In both cases it's a `<div>`, which means we can keep the same DOM node
2. We enumerate all the attributes on the old `<div>` and the new one to see if any need to be changed, added or removed. In both cases we have a single attribute — a `className` with a value of `"greeting"`
3. Descending into the element, we see that the text has changed, so we'll need to update the real DOM
Of these three steps, only the third has value in this case, since — as is the case in the vast majority of updates — the basic structure of the app is unchanged. It would be much more efficient if we could skip straight to step 3:
```js
if (changed.name) {
text.data = name;
}
```
(This is almost exactly the update code that Svelte generates. Unlike traditional UI frameworks, Svelte is a compiler that knows at *build time* how things could change in your app, rather than waiting to do the work at *run time*.)
## It's not just the diffing though
The diffing algorithms used by React and other virtual DOM frameworks are fast. Arguably, the greater overhead is in the components themselves. You wouldn't write code like this...
```js
function StrawManComponent(props) {
const value = expensivelyCalculateValue(props.foo);
return (
<p>the value is {value}</p>
);
}
```
...because you'd be carelessly recalculating `value` on every update, regardless of whether `props.foo` had changed. But it's extremely common to do unnecessary computation and allocation in ways that seem much more benign:
```js
function MoreRealisticComponent(props) {
const [selected, setSelected] = useState(null);
return (
<div>
<p>Selected {selected ? selected.name : 'nothing'}</p>
<ul>
${props.items.map(item =>
<li>
<button onClick={() => setSelected(item)}>
{item.name}
</button>
</li>
)}
</ul>
</div>
);
}
```
Here, we're generating a new array of virtual `<li>` elements — each with their own inline event handler — on every state change, regardless of whether `props.items` has changed. Unless you're unhealthily obsessed with performance, you're not going to optimise that. There's no point. It's plenty fast enough. But you know what would be even faster? *Not doing that.*
The danger of defaulting to doing unnecessary work, even if that work is trivial, is that your app will eventually succumb to 'death by a thousand cuts' with no clear bottleneck to aim at once it's time to optimise.
Svelte is explicitly designed to prevent you from ending up in that situation.
## Why do frameworks use the virtual DOM then?
It's important to understand that virtual DOM *isn't a feature*. It's a means to an end, the end being declarative, state-driven UI development. Virtual DOM is valuable because it allows you to build apps without thinking about state transitions, with performance that is *generally good enough*. That means less buggy code, and more time spent on creative tasks instead of tedious ones.
But it turns out that we can achieve a similar programming model without using virtual DOM — and that's where Svelte comes in.

@ -0,0 +1,7 @@
---
title: Important note
---
### Read the RFCs
Much of the documentation below is out of date! For more accurate and current information on how Svelte 3 works in the meantime, check out the RFCS on [reactive assignments](https://github.com/sveltejs/rfcs/blob/master/text/0001-reactive-assignments.md), [reactive stores](https://github.com/sveltejs/rfcs/blob/master/text/0002-reactive-stores.md) and [reactive declarations](https://github.com/sveltejs/rfcs/blob/master/text/0003-reactive-declarations.md).

@ -15,7 +15,7 @@ You can build your entire app with Svelte, or you can add it incrementally to an
[Read the introductory blog post](/blog/frameworks-without-the-framework) to learn more about Svelte's goals and philosophy. [Read the introductory blog post](/blog/frameworks-without-the-framework) to learn more about Svelte's goals and philosophy.
### Understanding Svelte components ### Understanding components
In Svelte, an application is composed from one or more *components*. A component is a reusable self-contained block of code that encapsulates markup, styles and behaviours that belong together, written into an `.html` file. Here's a simple example: In Svelte, an application is composed from one or more *components*. A component is a reusable self-contained block of code that encapsulates markup, styles and behaviours that belong together, written into an `.html` file. Here's a simple example:

@ -92,7 +92,7 @@ Here is a complete example of using two way bindings with a form:
} }
``` ```
> 'two way' bindings allow you to update a value in a nested property as seen in [checkbox input](repl?demo=binding-input-checkbox). > 'two way' bindings allow you to update a value in a nested property as seen in [this checkbox input example](repl?demo=binding-input-checkbox).
### bind:this ### bind:this

@ -11,7 +11,7 @@
"cy:open": "cypress open", "cy:open": "cypress open",
"test": "run-p --race dev cy:run", "test": "run-p --race dev cy:run",
"deploy": "npm run stage && now alias", "deploy": "npm run stage && now alias",
"prestage": "npm run update_template && npm run sapper", "prestage": "npm run update_template && node scripts/get-contributors.js && npm run sapper",
"stage": "now" "stage": "now"
}, },
"dependencies": { "dependencies": {

@ -0,0 +1,16 @@
const fs = require('fs');
const fetch = require('node-fetch');
process.chdir(__dirname);
fetch(`https://api.github.com/repos/sveltejs/svelte/stats/contributors`)
.then(r => r.json())
.then(contributors => {
const munged = contributors
.sort((a, b) => b.total - a.total)
.map(({ author }) => ({ name: author.login, src: author.avatar_url }));
const str = `[\n\t${munged.map(c => `{ name: '${c.name}', src: '${c.src}' }`).join(',\n\t')}\n]`;
fs.writeFileSync(`../src/routes/_contributors.js`, `export default ${str};`);
});

@ -108,4 +108,8 @@
<path d="M15,7L18,7A2 2 0 0 1 18,17L15,17"/> <path d="M15,7L18,7A2 2 0 0 1 18,17L15,17"/>
<path d="M7,12L17,12"/> <path d="M7,12L17,12"/>
</symbol> </symbol>
<symbol id="chevron" class="icon" viewBox="0 0 24 24">
<path d="M2,7 L12,17 L20,7"/>
</symbol>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

@ -1,57 +1,45 @@
<script> <script>
import { onMount } from 'svelte';
import Icon from './Icon.html'; import Icon from './Icon.html';
import Logo from './Logo.html'; import Logo from './Logo.html';
export let segment; export let segment;
let open = false; let open = false;
let nav; let visible = true;
function toggleOpen() { // TODO remove this post-https://github.com/sveltejs/svelte/issues/1914
// if the menu is closing, scroll back to the top *after* it let ul;
// shuts. otherwise, scroll back to the top immediately onMount(() => {
// (just in case the user reopened before it happened). function handler(event) {
// The reason we don't just do it when the menu opens is
// that the scrollbar visibly flashes
if (open) {
setTimeout(() => {
if (!open) { if (!open) {
nav.scrollTop = 0; event.preventDefault();
event.stopPropagation();
open = true;
} }
}, 350);
} else {
nav.scrollTop = 0;
} }
open = !open; ul.addEventListener('touchstart', handler, {
} capture: true
</script> });
<style> return () => {
/* Unfortunately I'm not able to understand mousecatcher */ ul.removeEventListener('touchstart', handler, {
.mousecatcher { capture: true
position: fixed; });
left: 0; };
top: 0; });
width: 120vw;
height: 100vh;
background-color: black;
pointer-events: none;
opacity: 0;
transition: opacity .4s;
z-index: 3;
}
.mousecatcher.open { let last_scroll = 0;
pointer-events: all; function handle_scroll() {
opacity: .3; const scroll = window.pageYOffset;
} visible = (scroll < 50 || scroll < last_scroll);
@keyframes fadein { last_scroll = scroll;
from { opacity: 0 }
to { opacity: 1 }
} }
</script>
<style>
header { header {
position: fixed; position: fixed;
display: flex; display: flex;
@ -65,51 +53,104 @@
box-shadow: 0 -0.4rem 0.9rem 0.2rem rgba(0,0,0,.5); box-shadow: 0 -0.4rem 0.9rem 0.2rem rgba(0,0,0,.5);
font-family: var(--font); font-family: var(--font);
z-index: 10; z-index: 10;
user-select: none;
transform: translate(0,calc(-100% - 1rem));
transition: transform 0.2s;
}
header.visible {
transform: none;
} }
nav { nav {
position: fixed; position: fixed;
width: calc(var(--sidebar-w) + 6rem); top: 0;
height: calc(100vh - var(--nav-h)); left: 0;
top: var(--nav-h); width: 100vw;
padding: 6rem 3rem 6rem 6rem; height: var(--nav-h);
background-color: hsla(240, 8%, 15%, .9); padding: 0 var(--side-nav) 0 var(--side-nav);
box-shadow: .3rem .3rem .6rem -.2rem rgba(0,0,0,.7); display: flex;
transform: translate(-120vw, 0); align-items: center;
transition: transform .1s var(--in-cubic); justify-content: space-between;
z-index: 5; background-color: transparent;
user-select: none; transform: none;
transition: none;
box-shadow: none;
} }
h2 { h2 {
display: block;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.1em;
font-weight: 300; font-weight: 300;
} font-size: 2.8rem;
letter-spacing: .12em;
.open { line-height: 1;
transform: translate(-5rem,0); margin: 0;
transition: transform .2s var(--out-cubic); top: .1rem;
overflow-y: auto;
} }
.primary { .primary {
list-style: none; list-style: none;
font-family: var(--font); font-family: var(--font);
margin: 0; margin: 0;
line-height: 1;
} }
.primary li { display: inline } li {
display: block;
display: none;
}
.primary:first-of-type li { li.active {
display: block; display: block;
} }
.primary li a { ul {
position: relative;
padding: 0 2em 0 0;
background: url(/icons/chevron.svg) calc(100% - 1em) 0.05em no-repeat;
background-size: 1em 1em;
}
ul::after {
/* prevent clicks from registering if nav is closed */
position: absolute;
content: '';
width: 100%;
height: 100%;
left: 0;
top: 0;
}
ul.open {
padding: 0 2em 1em 2em;
background: white;
border-left: 1px solid #eee;
border-right: 1px solid #eee;
border-bottom: 1px solid #eee;
border-radius: 0 0 var(--border-r) var(--border-r);
align-self: start;
}
ul.open li {
display: block;
text-align: right
}
ul.open::after {
display: none;
}
ul li a {
font-size: var(--h6); font-size: var(--h6);
padding: 0 .8rem; padding: 0 .8rem;
} }
ul.open li a {
padding: 2.3rem .7rem 0 .8rem;
display: block;
}
.primary :global(svg) { .primary :global(svg) {
width: 2rem; width: 2rem;
height: 2rem; height: 2rem;
@ -132,14 +173,6 @@
padding: 0.5em 0; padding: 0.5em 0;
} }
.logotype {
display: none;
font-size: 2.8rem;
letter-spacing: .12em;
line-height: 1;
margin: 0;
}
.home { .home {
position: relative; position: relative;
top: 0; top: 0;
@ -157,73 +190,76 @@
color: var(--prime) color: var(--prime)
} }
/* media-queries -------------------------- */
@media (min-width: 768px) { @media (min-width: 768px) {
.mousecatcher, ul {
.menu-link, padding: 0;
.secondary { display: none } background: none;
}
nav { ul.open {
position: fixed; padding: 0;
top: 0; background: white;
left: 0; border: none;
width: 100vw; align-self: initial;
height: var(--nav-h);
padding: 0 var(--side-nav) 0 var(--side-nav);
display: flex;
align-items: center;
justify-content: space-between;
background-color: transparent;
transform: none;
transition: none;
box-shadow: none;
} }
.primary:first-of-type li { ul.open li {
display: inline; display: inline;
text-align: left;
} }
.logotype { display: block } ul.open li a {
font-size: var(--h6);
padding: 0 .8rem;
display: inline;
} }
</style>
<div class='{open ? "open": "closed"} mousecatcher' on:click="{() => open = false}" /> ul::after {
display: none;
}
<header> li {
<span class='menu-link' on:click="{toggleOpen}"> display: inline !important;
{#if open} }
<Icon name='close' />
{:else}
<Icon name='menu' />
{/if}
</span>
<nav bind:this={nav} class='{open ? "open": "closed"}' on:click="{() => open = false}" > .hide-if-desktop {
display: none !important;
}
}
</style>
<svelte:window on:click="{() => open = false}" on:scroll={handle_scroll}/>
<header class:visible="{visible || open}">
<nav>
<a rel="prefetch" href='.' class="home" title='Homepage'> <a rel="prefetch" href='.' class="home" title='Homepage'>
<h2 class='logotype'>Svelte</h2> <h2>Svelte</h2>
</a> </a>
<ul class='primary'> <ul
<li><a rel='prefetch' class='{segment === "guide" ? "active": ""}' href='guide'>Guide</a></li> bind:this={ul}
<li><a rel='prefetch' class='{segment === "repl" ? "active": ""}' href='repl'>REPL</a></li> class="primary"
<li><a rel='prefetch' class='{segment === "blog" ? "active": ""}' href='blog'>Blog</a></li> class:open
<li><a href='https://sapper-redesign.now.sh'>Sapper</a></li> on:mouseenter="{() => open = true}"
on:mouseleave="{() => open = false}"
>
<li class="hide-if-desktop" class:active="{!segment}"><a rel="prefetch" href=".">Home</a></li>
<li class:active="{segment === 'guide'}"><a rel="prefetch" href="guide">Guide</a></li>
<li class:active="{segment === 'repl'}"><a rel="prefetch" href="repl">REPL</a></li>
<li class:active="{segment === 'blog'}"><a rel="prefetch" href="blog">Blog</a></li>
<li><a href="https://sapper.svelte.technology">Sapper</a></li>
<li> <li>
<a href='https://discord.gg/yy75DKs' title='Discord Chat'> <a href="https://discord.gg/yy75DKs" title="Discord Chat">
<Icon name='message-square' /> <Icon name="message-square" />
</a> </a>
</li> </li>
<li> <li>
<a href='https://github.com/sveltejs/svelte' title='Github Repo'> <a href="https://github.com/sveltejs/svelte" title="Github Repo">
<Icon name='github' /> <Icon name="github" />
</a> </a>
</li> </li>
</ul> </ul>
<!-- <ul class='secondary'>
</ul> -->
</nav> </nav>
</header> </header>

@ -24,7 +24,9 @@
<style> <style>
.post { .post {
padding: var(--top-offset) var(--side-page); padding: var(--top-offset) 0;
max-width: var(--main-width);
margin: 0 auto;
} }
.byline { .byline {
@ -44,13 +46,17 @@
color: var(--second); color: var(--second);
} }
.post p, /* .post p,
.post :global(p) { .post :global(p) {
max-width: var(--linemax) max-width: var(--linemax)
} } */
.post :global(figure) { .post :global(figure) {
margin-top: 1.6rem margin: 1.6rem 0 3.2rem 0;
}
.post :global(figure) :global(img) {
max-width: 100%;
} }
.post :global(figcaption) { .post :global(figcaption) {
@ -62,11 +68,26 @@
} }
.post :global(blockquote) { .post :global(blockquote) {
max-width: none;
border-left: 4px solid #eee; border-left: 4px solid #eee;
background: #f9f9f9; background: #f9f9f9;
border-radius: 0 var(--border-r) var(--border-r) 0; border-radius: 0 var(--border-r) var(--border-r) 0;
} }
.post :global(code) {
padding: .3rem .8rem .3rem;
margin: 0 0.2rem;
top: -.1rem;
background: #f4f4f4;
}
.post :global(pre) :global(code) {
padding: 0;
margin: 0;
top: 0;
background: transparent;
}
.standfirst { .standfirst {
font-size: var(--h4); font-size: var(--h4);
color: var(--second); color: var(--second);
@ -80,4 +101,32 @@
color: var(--second); color: var(--second);
z-index: 2; z-index: 2;
} }
.post :global(iframe) {
width: 100%;
height: 420px;
margin: 2em 0;
border-radius: var(--border-r);
border: 0.8rem solid var(--second);
}
@media (min-width: 910px) {
.post :global(iframe) {
width: calc(100vw - 2 * var(--side-nav));
margin: 2em calc(400px + var(--side-nav) - 50vw);
}
}
@media (min-width: 1460px) {
.post :global(iframe) {
width: 1360px;
margin: 2em -280px;
}
}
@media (min-height: 800px) {
.post :global(iframe) {
height: 640px;
}
}
</style> </style>

@ -26,24 +26,12 @@
<style> <style>
.posts { .posts {
/* display: grid; */
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
grid-gap: 1em; grid-gap: 1em;
/* prevent scroll bar pop-in when navigating to post */
/* yes this is hacktacular */
min-height: calc(100vh - var(--nav-h)); min-height: calc(100vh - var(--nav-h));
padding: var(--top-offset) 0;
padding: 10rem var(--side-page); max-width: var(--main-width);
/* background-color: var(--back-light) */ margin: 0 auto;
}
.post {
/* display: flex;
flex-direction: column;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
padding: 4rem 3.2rem 2.4rem; */
} }
h2 { h2 {
@ -83,9 +71,6 @@
color: var(--second); color: var(--second);
} }
/* fast link-reset */
/* .posts a { all: unset; cursor: pointer } */
.post > a { .post > a {
display: block; display: block;
} }

@ -4,6 +4,7 @@
export let sections = []; export let sections = [];
export let active_section = null; export let active_section = null;
export let show_contents;
onMount(() => { onMount(() => {
// ------------------------------------------ // ------------------------------------------
@ -40,6 +41,10 @@
let ul; let ul;
afterUpdate(() => { afterUpdate(() => {
// bit of a hack — prevent sidebar scrolling if
// TOC is open on mobile
if (show_contents && window.innerWidth < 832) return;
const active = ul.querySelector('.active'); const active = ul.querySelector('.active');
if (active) { if (active) {
@ -65,39 +70,10 @@
}); });
</script> </script>
<ul bind:this={ul} class="guide-toc">
{#each sections as section}
<li>
<a class="section" class:active="{section.slug === active_section}" href="guide#{section.slug}">
{section.metadata.title}
{#if section.slug === active_section}
<Icon name="arrow-right" />
{/if}
</a>
{#each section.subsections as subsection}
<!-- see <script> below: on:click='scrollTo(event, subsection.slug)' -->
<a class="subsection" class:active="{subsection.slug === active_section}" href="guide#{subsection.slug}">
{subsection.title}
{#if subsection.slug === active_section}
<Icon name="arrow-right" />
{/if}
</a>
{/each}
</li>
{/each}
</ul>
<style> <style>
.guide-toc {
--section-clr: white;
}
.guide-toc li { .guide-toc li {
display: block; display: block;
line-height: 2; line-height: 1.2;
margin: 0 0 4.8rem 0; margin: 0 0 4.8rem 0;
} }
@ -105,7 +81,6 @@
display: block; display: block;
padding: 0 0 .8rem 0; padding: 0 0 .8rem 0;
font: 400 var(--h6) var(--font); font: 400 var(--h6) var(--font);
color: var(--section-clr);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.12em; letter-spacing: 0.12em;
} }
@ -114,15 +89,35 @@
display: block; display: block;
font-size: 1.6rem; font-size: 1.6rem;
font-family: var(--font); font-family: var(--font);
padding: 0.3em 0;
} }
.section:hover, .section:hover,
.subsection:hover { color: var(--flash) } .subsection:hover { color: var(--flash) }
.active { color: var(--prime) } .active { color: var(--prime) }
@media screen and (min-width: 768px) {
.guide-toc {
--section-clr: black;
}
}
</style> </style>
<ul bind:this={ul} class="guide-toc">
{#each sections as section}
<li>
<a class="section" class:active="{section.slug === active_section}" href="guide#{section.slug}">
{section.metadata.title}
{#if section.slug === active_section}
<Icon name="arrow-right" />
{/if}
</a>
{#each section.subsections as subsection}
<!-- see <script> below: on:click='scrollTo(event, subsection.slug)' -->
<a class="subsection" class:active="{subsection.slug === active_section}" href="guide#{subsection.slug}">
{subsection.title}
{#if subsection.slug === active_section}
<Icon name="arrow-right" />
{/if}
</a>
{/each}
</li>
{/each}
</ul>

@ -10,15 +10,12 @@
import GuideContents from './_GuideContents.html'; import GuideContents from './_GuideContents.html';
import Icon from '../../components/Icon.html'; import Icon from '../../components/Icon.html';
// NOT YET
// get offset from css-vars (for smooth-scroll)
// import CustomProperties from '../../utils/css-custom-properties.js'
export let sections; export let sections;
let active_section; let active_section;
let container; let container;
let aside; let aside;
let show_contents = false;
onMount(() => { onMount(() => {
const anchors = container.querySelectorAll('[id]'); const anchors = container.querySelectorAll('[id]');
@ -75,65 +72,130 @@
<style> <style>
aside { aside {
display: none; /* display: none; */
position: fixed; position: fixed;
left: 0; background-color: white;
top: var(--nav-h); left: 0.8rem;
width: var(--sidebar-w); bottom: 0.8rem;
height: calc(100vh - var(--nav-h)); width: 2em;
height: 2em;
overflow: hidden; overflow: hidden;
border: 1px solid #eee;
box-shadow: 1px 1px 6px rgba(0,0,0,0.1);
border-radius: var(--border-r);
padding: 1.6rem;
transition: width 0.2s, height 0.2s;
}
aside button {
position: absolute;
bottom: 0;
left: 0;
width: 3.4rem;
height: 3.4rem;
}
aside.open {
width: calc(100vw - 1.6rem);
height: calc(100vh - var(--nav-h) - 7rem);
}
aside.open::before {
content: '';
position: absolute;
left: 0;
bottom: calc(100vh - var(--nav-h) - 10.8rem);
width: 100%;
height: 2em;
background: linear-gradient(to top, rgba(255,255,255,0) 0%, rgba(255,255,255,0.7) 50%, rgba(255,255,255,1) 100%);
pointer-events: none;
z-index: 2;
} }
aside::after { aside::after {
content: ''; content: '';
position: absolute; position: absolute;
left: 0; left: 0;
bottom: 0; bottom: 1.9em;
width: 100%; width: 100%;
height: var(--top-offset); height: 2em;
background: linear-gradient(to bottom, rgba(255,255,255,0) 0%, rgba(255,255,255,0.7) 50%, rgba(255,255,255,1) 100%); background: linear-gradient(to bottom, rgba(255,255,255,0) 0%, rgba(255,255,255,0.7) 50%, rgba(255,255,255,1) 100%);
pointer-events: none; pointer-events: none;
} }
.sidebar { .sidebar {
padding: var(--top-offset) 3.2rem var(--top-offset) 0; position: absolute;
font-family: var(--font); font-family: var(--font);
overflow-y: auto; overflow-y: auto;
height: 100%; width: calc(100vw - 4.8rem);
width: calc(var(--sidebar-w) + 5rem); height: calc(100vh - var(--nav-h) - 10.8rem);
padding: 2em 0;
bottom: 2em;
} }
.content { .content {
width: 100%; width: 100%;
max-width: calc(var(--main-width) + 2 * var(--side-nav)); max-width: calc(var(--main-width) + var(--side-nav));
margin: 0 auto; margin: 0 auto;
-moz-tab-size: 2; -moz-tab-size: 2;
padding: var(--top-offset) var(--side-nav); padding: var(--top-offset) 0;
tab-size: 2; tab-size: 2;
} }
@media (min-width: 52em) { /* can't use vars in @media :( */ @media (min-width: 832px) { /* can't use vars in @media :( */
aside { aside {
display: block; display: block;
width: var(--sidebar-w);
height: calc(100vh - var(--nav-h));
top: var(--nav-h);
left: var(--side-nav); left: var(--side-nav);
overflow: hidden;
box-shadow: none;
border: none;
overflow: hidden;
padding: 0;
}
aside.open::before {
display: none;
}
aside::after {
content: '';
bottom: 0;
height: var(--top-offset);
}
aside button {
display: none;
}
.sidebar {
padding: var(--top-offset) 3.2rem var(--top-offset) 0;
font-family: var(--font);
overflow-y: auto;
height: 100%;
bottom: auto;
width: calc(var(--sidebar-w) + 5rem);
} }
.content { .content {
max-width: none; max-width: none;
padding-left: 32rem; padding-left: 28rem;
} }
} }
@media (min-width: 89.6rem) { /* can't use vars in @media :( */ @media (min-width: 1200px) { /* can't use vars in @media :( */
aside { aside {
display: block; display: block;
left: calc(50% - 60rem); left: calc(50vw - (60rem - var(--side-nav)));
} }
.content { .content {
max-width: none; width: 80rem;
padding-left: calc(50% - 30rem); padding-left: calc(50vw - 32rem);
padding-right: calc(50% - 50rem); box-sizing: content-box;
/* padding-right: calc(50% - 50rem); */
} }
} }
@ -170,10 +232,12 @@
border: none !important; /* TODO get rid of linkify */ border: none !important; /* TODO get rid of linkify */
} }
@media (min-width: 768px) {
.content :global(h2):hover :global(.anchor), .content :global(h2):hover :global(.anchor),
.content :global(h3):hover :global(.anchor) { .content :global(h3):hover :global(.anchor) {
opacity: 1; opacity: 1;
} }
}
.content :global(h3), .content :global(h3),
.content :global(h3 > code) { .content :global(h3 > code) {
@ -272,8 +336,12 @@
{/each} {/each}
</div> </div>
<aside bind:this={aside} class="sidebar-container"> <aside bind:this={aside} class="sidebar-container" class:open={show_contents}>
<div class="sidebar"> <div class="sidebar" on:click="{() => show_contents = false}"> <!-- scroll container -->
<GuideContents {sections} {active_section} /> <GuideContents {sections} {active_section} {show_contents} />
</div> </div>
<button on:click="{() => show_contents = !show_contents}">
<Icon name="{show_contents? 'close' : 'menu'}"/>
</button>
</aside> </aside>

@ -1,6 +1,7 @@
<script> <script>
import Icon from '../components/Icon.html'; import Icon from '../components/Icon.html';
import Logo from '../components/Logo.html'; import Logo from '../components/Logo.html';
import contributors from './_contributors.js';
let sy = 0; let sy = 0;
</script> </script>
@ -138,6 +139,17 @@
position: relative; position: relative;
} }
.contributor {
width: 2.4em;
height: 2.4em;
border-radius: 50%;
text-indent: -9999px;
display: inline-block;
background-size: 100% 100%;
margin: 0 0.5em 0.5em 0;
border: 1px solid var(--second);
}
@media (min-width: 920px) { @media (min-width: 920px) {
.example { .example {
display: grid; display: grid;
@ -267,8 +279,21 @@ npm run dev & open http://localhost:5000
></iframe> ></iframe>
</section> </section>
<section class="container grid half"> <section class="container linkify">
<p>TODO finish building this page. Ideas: Who's using Svelte? Example code (interactive, ideally). What else?</p> <h3>Who's using Svelte?</h3>
<p>TODO. See <a href="https://github.com/sveltejs/svelte.technology/issues/379">this issue</a></p>
</section> </section>
<!-- TODO example code transformation (interactive REPL?) --> <section class="container">
<h3>Contributors</h3>
<p class="linkify">Svelte is free and open source software, made possible by the work of dozens of volunteers. <a href="https://github.com/sveltejs/svelte">Join us!</a></p>
{#each contributors as contributor}
<a
class="contributor"
style="background-image: url({contributor.src})"
href="https://github.com/{contributor.name}"
>{contributor.name}</a>
{/each}
</section>

@ -120,7 +120,7 @@
editor.toTextArea(); editor.toTextArea();
} }
editor = CodeMirror.fromTextArea(refs.editor, { const opts = {
lineNumbers, lineNumbers,
lineWrapping: true, lineWrapping: true,
indentWithTabs: true, indentWithTabs: true,
@ -130,12 +130,15 @@
mode: modes[mode] || { mode: modes[mode] || {
name: mode name: mode
}, },
readOnly: readonly, readOnly: readonly
extraKeys: { };
if (!tab) opts.extraKeys = {
Tab: tab, Tab: tab,
'Shift-Tab': tab 'Shift-Tab': tab
} };
});
editor = CodeMirror.fromTextArea(refs.editor, opts);
editor.on('change', instance => { editor.on('change', instance => {
if (!updating) { if (!updating) {

@ -123,7 +123,7 @@
const missingImports = bundle.imports.filter(x => !importCache[x]); const missingImports = bundle.imports.filter(x => !importCache[x]);
const removeStyles = () => { const removeStyles = () => {
const styles = iframe.contentDocument.querySelectorAll('style'); const styles = iframe.contentDocument.querySelectorAll('style.svelte');
let i = styles.length; let i = styles.length;
while (i--) styles[i].parentNode.removeChild(styles[i]); while (i--) styles[i].parentNode.removeChild(styles[i]);
}; };
@ -161,6 +161,7 @@
if (rendered.css.code) { if (rendered.css.code) {
var style = document.createElement('style'); var style = document.createElement('style');
style.className = 'svelte';
style.textContent = rendered.css.code; style.textContent = rendered.css.code;
document.head.appendChild(style); document.head.appendChild(style);
} }

@ -143,7 +143,7 @@
overflow: hidden; overflow: hidden;
background-color: var(--back); background-color: var(--back);
padding: var(--app-controls-h) 0 0 0; padding: var(--app-controls-h) 0 0 0;
margin: 0 -0.8rem; margin: 0 calc(var(--side-nav) * -1);
box-sizing: border-box; box-sizing: border-box;
} }
@ -157,12 +157,6 @@
.pane { width: 100%; height: 100% } .pane { width: 100%; height: 100% }
@media (min-width: 600px) {
.repl-outer {
margin: 0 -4.8rem; /* can't do calc(0 - var(--side-nav)) */
}
}
.loading { .loading {
text-align: center; text-align: center;
color: var(--second); color: var(--second);

@ -12,8 +12,6 @@
<link rel='manifest' href='manifest.json'> <link rel='manifest' href='manifest.json'>
<link rel='icon' type='image/png' href='favicon.png'> <link rel='icon' type='image/png' href='favicon.png'>
<link href="https://fonts.googleapis.com/css?family=IBM+Plex+Mono|Roboto:100,100i,300,300i,400,400i,700,700i" rel="stylesheet">
<!-- Sapper generates a <style> tag containing critical CSS <!-- Sapper generates a <style> tag containing critical CSS
for the current page. CSS for the rest of the app is for the current page. CSS for the rest of the app is
lazily loaded when it precaches secondary pages --> lazily loaded when it precaches secondary pages -->

@ -169,7 +169,7 @@ a:focus {
--sidebar-w: 24rem; --sidebar-w: 24rem;
--main-width: 80rem; --main-width: 80rem;
--code-w: 72em; --code-w: 72em;
--side-nav: .8rem; --side-nav: 1.6rem;
--side-page: var(--side-nav); --side-page: var(--side-nav);
/* easings */ /* easings */

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" viewBox="0 0 24 24">
<path style="stroke: #676778; stroke-width: 2; fill: none" d="M2,8 L12,16 L22,8"/>
</svg>

After

Width:  |  Height:  |  Size: 221 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

@ -12,7 +12,7 @@ self.addEventListener('message', async event => {
importScripts( importScripts(
`https://unpkg.com/svelte@${version}/compiler.js`, `https://unpkg.com/svelte@${version}/compiler.js`,
`https://unpkg.com/rollup/dist/rollup.browser.js` `https://unpkg.com/rollup@0.68/dist/rollup.browser.js`
); );
fulfil(); fulfil();

Loading…
Cancel
Save