Merge branch 'master' into v3-readme

pull/1953/head
Conduitry 7 years ago
commit c92df84e64

@ -33,9 +33,8 @@
"plugin:import/errors", "plugin:import/errors",
"plugin:import/warnings" "plugin:import/warnings"
], ],
"plugins": ["svelte3"],
"parserOptions": { "parserOptions": {
"ecmaVersion": 6, "ecmaVersion": 9,
"sourceType": "module" "sourceType": "module"
}, },
"settings": { "settings": {

1
.gitignore vendored

@ -2,6 +2,7 @@
.nyc_output .nyc_output
node_modules node_modules
*.map *.map
/src/compile/internal-exports.ts
/cli/ /cli/
/compiler.js /compiler.js
/index.js /index.js

@ -3,5 +3,8 @@ export {
onDestroy, onDestroy,
beforeUpdate, beforeUpdate,
afterUpdate, afterUpdate,
setContext,
getContext,
tick,
createEventDispatcher createEventDispatcher
} from './internal'; } from './internal';

3955
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,9 +1,9 @@
{ {
"name": "svelte", "name": "svelte",
"version": "3.0.0-alpha15", "version": "3.0.0-beta.3",
"description": "The magical disappearing UI framework", "description": "The magical disappearing UI framework",
"module": "index.mjs", "module": "index.mjs",
"main": "index.js", "main": "index",
"bin": { "bin": {
"svelte": "svelte" "svelte": "svelte"
}, },
@ -23,18 +23,17 @@
"scripts": { "scripts": {
"test": "mocha --opts mocha.opts", "test": "mocha --opts mocha.opts",
"quicktest": "mocha --opts mocha.opts", "quicktest": "mocha --opts mocha.opts",
"precoverage": "export COVERAGE=true && nyc mocha --opts mocha.coverage.opts", "precoverage": "c8 mocha --opts mocha.coverage.opts",
"coverage": "nyc report --reporter=text-lcov > coverage.lcov", "coverage": "c8 report --reporter=text-lcov > coverage.lcov && c8 report --reporter=html",
"codecov": "codecov", "codecov": "codecov",
"precodecov": "npm run coverage", "precodecov": "npm run coverage",
"lint": "eslint src test/*.js", "lint": "eslint src test/*.js",
"build": "rollup -c", "build": "rollup -c",
"prepare": "npm run build", "prepare": "npm run build",
"dev": "rollup -c -w", "dev": "rollup -cw",
"pretest": "npm run build", "pretest": "npm run build",
"posttest": "agadoo src/internal/index.js", "posttest": "agadoo src/internal/index.js",
"prepublishOnly": "npm run lint && npm test", "prepublishOnly": "export PUBLISH=true && npm run lint && npm test"
"prettier": "prettier --write \"src/**/*.ts\""
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -55,31 +54,31 @@
"devDependencies": { "devDependencies": {
"@types/mocha": "^5.2.0", "@types/mocha": "^5.2.0",
"@types/node": "^10.5.5", "@types/node": "^10.5.5",
"acorn": "^5.4.1", "acorn": "^6.0.5",
"acorn-dynamic-import": "^3.0.0", "acorn-dynamic-import": "^4.0.0",
"agadoo": "^1.0.1", "agadoo": "^1.0.1",
"c8": "^3.4.0",
"codecov": "^3.0.0", "codecov": "^3.0.0",
"css-tree": "1.0.0-alpha22", "css-tree": "1.0.0-alpha22",
"eslint": "^5.3.0", "eslint": "^5.3.0",
"eslint-plugin-html": "^4.0.3", "eslint-plugin-html": "^5.0.0",
"eslint-plugin-import": "^2.11.0", "eslint-plugin-import": "^2.11.0",
"estree-walker": "^0.6.0", "estree-walker": "^0.6.0",
"is-reference": "^1.1.1", "is-reference": "^1.1.1",
"jsdom": "^11.8.0", "jsdom": "^12.2.0",
"kleur": "^3.0.0", "kleur": "^3.0.0",
"locate-character": "^2.0.5", "locate-character": "^2.0.5",
"magic-string": "^0.25.0", "magic-string": "^0.25.2",
"mocha": "^5.2.0", "mocha": "^5.2.0",
"nightmare": "^3.0.1", "nightmare": "^3.0.1",
"node-resolve": "^1.3.3", "node-resolve": "^1.3.3",
"nyc": "^12.0.2", "rollup": "^1.1.2",
"prettier": "^1.12.1",
"rollup": "^0.63.5",
"rollup-plugin-commonjs": "^9.1.0", "rollup-plugin-commonjs": "^9.1.0",
"rollup-plugin-json": "^3.0.0", "rollup-plugin-json": "^3.0.0",
"rollup-plugin-node-resolve": "^3.3.0", "rollup-plugin-node-resolve": "^4.0.0",
"rollup-plugin-replace": "^2.0.0", "rollup-plugin-replace": "^2.0.0",
"rollup-plugin-typescript": "^0.8.1", "rollup-plugin-sucrase": "^2.1.0",
"rollup-plugin-typescript": "^1.0.0",
"rollup-plugin-virtual": "^1.0.1", "rollup-plugin-virtual": "^1.0.1",
"rollup-watch": "^4.3.1", "rollup-watch": "^4.3.1",
"sade": "^1.4.0", "sade": "^1.4.0",
@ -88,7 +87,7 @@
"source-map": "0.6", "source-map": "0.6",
"source-map-support": "^0.5.4", "source-map-support": "^0.5.4",
"tiny-glob": "^0.2.1", "tiny-glob": "^0.2.1",
"ts-node": "^7.0.0", "ts-node": "^8.0.2",
"tslib": "^1.8.0", "tslib": "^1.8.0",
"typescript": "^3.0.1" "typescript": "^3.0.1"
}, },

@ -2,21 +2,21 @@ const fs = require('fs');
const path = require('path'); const path = require('path');
const { compile } = require('./compiler.js'); const { compile } = require('./compiler.js');
let compileOptions = { let extensions = ['.svelte', '.html'];
extensions: ['.html'] let compileOptions = {};
};
function capitalise(name) { function capitalise(name) {
return name[0].toUpperCase() + name.slice(1); return name[0].toUpperCase() + name.slice(1);
} }
function register(options) { function register(options = {}) {
if (options.extensions) { if (options.extensions) {
compileOptions.extensions.forEach(deregisterExtension); extensions.forEach(deregisterExtension);
options.extensions.forEach(registerExtension); options.extensions.forEach(registerExtension);
} }
compileOptions = options; compileOptions = Object.assign({}, options);
delete compileOptions.extensions;
} }
function deregisterExtension(extension) { function deregisterExtension(extension) {
@ -43,6 +43,7 @@ function registerExtension(extension) {
}; };
} }
registerExtension('.svelte');
registerExtension('.html'); registerExtension('.html');
module.exports = register; module.exports = register;

@ -1,11 +1,41 @@
import fs from 'fs';
import replace from 'rollup-plugin-replace'; import replace from 'rollup-plugin-replace';
import resolve from 'rollup-plugin-node-resolve'; import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs'; import commonjs from 'rollup-plugin-commonjs';
import json from 'rollup-plugin-json'; import json from 'rollup-plugin-json';
import sucrase from 'rollup-plugin-sucrase';
import typescript from 'rollup-plugin-typescript'; import typescript from 'rollup-plugin-typescript';
import pkg from './package.json'; import pkg from './package.json';
const is_publish = !!process.env.PUBLISH;
export default [ export default [
/* internal.[m]js */
{
input: `src/internal/index.js`,
output: [
{
file: `internal.mjs`,
format: 'esm',
paths: id => id.startsWith('svelte/') && id.replace('svelte', '.')
},
{
file: `internal.js`,
format: 'cjs',
paths: id => id.startsWith('svelte/') && id.replace('svelte', '.')
}
],
external: id => id.startsWith('svelte/'),
plugins: [{
generateBundle(options, bundle) {
const mod = bundle['internal.mjs'];
if (mod) {
fs.writeFileSync('src/compile/internal-exports.ts', `// This file is automatically generated\nexport default new Set(${JSON.stringify(mod.exports)});`);
}
}
}]
},
/* compiler.js */ /* compiler.js */
{ {
input: 'src/index.ts', input: 'src/index.ts',
@ -14,20 +44,29 @@ export default [
__VERSION__: pkg.version __VERSION__: pkg.version
}), }),
resolve(), resolve(),
commonjs(), commonjs({
include: ['node_modules/**']
}),
json(), json(),
typescript({ is_publish
include: 'src/**', ? typescript({
exclude: 'src/internal/**', include: 'src/**',
typescript: require('typescript') exclude: 'src/internal/**',
}) typescript: require('typescript')
})
: sucrase({
transforms: ['typescript']
})
], ],
output: { output: {
file: 'compiler.js', file: 'compiler.js',
format: 'umd', format: is_publish ? 'umd' : 'cjs',
name: 'svelte', name: 'svelte',
sourcemap: true sourcemap: true,
} },
external: is_publish
? []
: id => id === 'acorn' || id === 'magic-string' || id.startsWith('css-tree')
}, },
/* cli/*.js */ /* cli/*.js */
@ -48,27 +87,26 @@ export default [
typescript({ typescript({
typescript: require('typescript') typescript: require('typescript')
}) })
], ]
experimentalCodeSplitting: true
}, },
/* internal.[m]js, motion.mjs */ /* motion.mjs */
...['internal', 'motion'].map(name => ({ {
input: `src/${name}/index.js`, input: `src/motion/index.js`,
output: [ output: [
{ {
file: `${name}.mjs`, file: `motion.mjs`,
format: 'esm', format: 'esm',
paths: id => id.startsWith('svelte/') && id.replace('svelte', '.') paths: id => id.startsWith('svelte/') && id.replace('svelte', '.')
}, },
{ {
file: `${name}.js`, file: `motion.js`,
format: 'cjs', format: 'cjs',
paths: id => id.startsWith('svelte/') && id.replace('svelte', '.') paths: id => id.startsWith('svelte/') && id.replace('svelte', '.')
} }
], ],
external: id => id.startsWith('svelte/') external: id => id.startsWith('svelte/')
})), },
// everything else // everything else
...['index', 'store', 'easing', 'transition'].map(name => ({ ...['index', 'store', 'easing', 'transition'].map(name => ({

@ -35,7 +35,7 @@
], ],
"plugins": ["svelte3"], "plugins": ["svelte3"],
"parserOptions": { "parserOptions": {
"ecmaVersion": 6, "ecmaVersion": 8,
"sourceType": "module" "sourceType": "module"
}, },
"settings": { "settings": {

@ -1,75 +1,26 @@
# sapper-template-rollup ## Running locally
A version of the default [Sapper](https://github.com/sveltejs/sapper) template that uses Rollup instead of webpack. To clone it and get started: Set up the project:
```bash ```bash
npx degit sveltejs/sapper-template#rollup my-app git clone https://github.com/sveltejs/svelte.git
cd my-app cd svelte/site
npm install # or yarn! npm ci
npm run dev npm run update
``` ```
Open up [localhost:3000](http://localhost:3000) and start clicking around. Start the server with `npm run dev`, and navigate to [localhost:3000](http://localhost:3000).
Consult [sapper.svelte.technology](https://sapper.svelte.technology) for help getting started. ## Using a local copy of Svelte
*[Click here for the webpack version of this template](https://github.com/sveltejs/sapper-template)* By default, the REPL will fetch the most recent version of Svelte from https://unpkg.com/svelte. To use the local copy of the compiler and runtime from this repo, you can navigate to [localhost:3000/repl?version=local](http://localhost:3000/repl?version=local).
## Structure ## REPL GitHub integration
Sapper expects to find three directories in the root of your project — `app`, `assets` and `routes`. In order for the REPL's GitHub integration to work properly when running locally, you will need to create a GitHub OAuth app. Set its authorization callback URL to `http://localhost:3000/auth/callback`, and in this project, create `site/.env` containing:
### app
The [app](app) directory contains the entry points for your app — `client.js`, `server.js` and (optionally) a `service-worker.js` — along with a `template.html` file.
### assets
The [assets](assets) directory contains any static assets that should be available. These are served using [sirv](https://github.com/lukeed/sirv).
In your [service-worker.js](app/service-worker.js) file, you can import these as `assets` from the generated manifest...
```js
import { assets } from './manifest/service-worker.js';
``` ```
GITHUB_CLIENT_ID=[your app's client id]
...so that you can cache them (though you can choose not to, for example if you don't want to cache very large files). GITHUB_CLIENT_SECRET=[your app's client secret]
BASEURL=http://localhost:3000
### routes
This is the heart of your Sapper app. There are two kinds of routes — *pages*, and *server routes*.
**Pages** are Svelte components written in `.html` files. When a user first visits the application, they will be served a server-rendered version of the route in question, plus some JavaScript that 'hydrates' the page and initialises a client-side router. From that point forward, navigating to other pages is handled entirely on the client for a fast, app-like feel. (Sapper will preload and cache the code for these subsequent pages, so that navigation is instantaneous.)
**Server routes** are modules written in `.js` files, that export functions corresponding to HTTP methods. Each function receives Express `request` and `response` objects as arguments, plus a `next` function. This is useful for creating a JSON API, for example.
There are three simple rules for naming the files that define your routes:
* A file called `routes/about.html` corresponds to the `/about` route. A file called `routes/blog/[slug].html` corresponds to the `/blog/:slug` route, in which case `params.slug` is available to the route
* The file `routes/index.html` (or `routes/index.js`) corresponds to the root of your app. `routes/about/index.html` is treated the same as `routes/about.html`.
* Files and directories with a leading underscore do *not* create routes. This allows you to colocate helper modules and components with the routes that depend on them — for example you could have a file called `routes/_helpers/datetime.js` and it would *not* create a `/_helpers/datetime` route
## Rollup config
Sapper uses Rollup to provide code-splitting and dynamic imports, as well as compiling your Svelte components. As long as you don't do anything daft, you can edit the configuration files to add whatever plugins you'd like.
## Production mode and deployment
To start a production version of your app, run `npm run build && npm start`.
You can deploy your application to any environment that supports Node 8 or above. As an example, to deploy to [Now](https://zeit.co/now), run these commands:
```bash
npm install -g now
now
``` ```
## Bugs and feedback
Sapper is in early development, and may have the odd rough edge here and there. Please be vocal over on the [Sapper issue tracker](https://github.com/sveltejs/sapper/issues).

@ -0,0 +1,23 @@
---
title: Svelte on The Changelog
description: Listen to the interview here
pubdate: 2019-01-31
author: Rich Harris
authorURL: https://twitter.com/Rich_Harris
---
Earlier this month, I had the privilege of appearing on [The Changelog](https://changelog.com/podcast), a podcast about software development. We had a fun (for me) and wide-ranging conversation:
* life as a coder inside a newsroom
* the big compilers-as-frameworks trend
* scalability
* the [Great Divide](https://css-tricks.com/the-great-divide/)
...and, most importantly, Svelte 3.
Unless you hang out in our [Discord server](https://discord.gg/yy75DKs) or follow [@sveltejs](https://twitter.com/sveltejs) on Twitter, you might not know that Svelte 3 is just around the corner, and it's going to be a huge release. We've rethought the developer experience from the ground up, and while it *will* be a nuisance if you need to upgrade a Svelte 2 app (more on that soon) we think you're going to love it.
On the podcast [Adam](https://twitter.com/adamstac), [Jerod](https://twitter.com/jerodsanto) and I talk about some of the changes and why we're making them. You can listen here or on the [podcast page](https://changelog.com/podcast/332).
<audio data-theme="night" style="width: 100%" data-src="https://changelog.com/podcast/332/embed" src="https://cdn.changelog.com/uploads/podcast/332/the-changelog-332.mp3" preload="none" class="changelog-episode" controls></audio><p><a href="https://changelog.com/podcast/332">The Changelog 332: A UI framework without the framework</a> Listen on <a href="https://changelog.com/">Changelog.com</a></p><script async src="//cdn.changelog.com/embed.js"></script>

@ -1,17 +1,24 @@
<!-- https://eugenkiss.github.io/7guis/tasks#crud --> <!-- https://eugenkiss.github.io/7guis/tasks#crud -->
<script> <script>
import { beforeUpdate } from 'svelte';
export let people = []; export let people = [];
let filteredPeople;
let selected;
let prefix = ''; let prefix = '';
let first = ''; let first = '';
let last = ''; let last = '';
let i = 0; let i = 0;
$: filteredPeople = prefix
? people.filter(person => {
const name = `${person.last}, ${person.first}`;
return name.toLowerCase().startsWith(prefix.toLowerCase());
})
: people;
$: selected = filteredPeople[i];
$: reset_inputs(selected);
function create() { function create() {
people = people.concat({ first, last }); people = people.concat({ first, last });
i = people.length - 1; i = people.length - 1;
@ -29,18 +36,8 @@
i = Math.min(i, people.length - 1); i = Math.min(i, people.length - 1);
} }
$: filteredPeople = prefix function reset_inputs(person) {
? people.filter(person => { ({ first, last } = person);
const name = `${person.last}, ${person.first}`;
return name.toLowerCase().startsWith(prefix.toLowerCase());
})
: people;
$: selected = filteredPeople[i];
$: if (selected) {
first = selected.first;
last = selected.last;
} }
</script> </script>

@ -1,33 +1,31 @@
<script> <script>
const tomorrow = new Date(Date.now() + 86400000); const tomorrow = new Date(Date.now() + 86400000);
const tomorrowAsString = [ let start = [
tomorrow.getFullYear(), tomorrow.getFullYear(),
pad(tomorrow.getMonth() + 1, 2), pad(tomorrow.getMonth() + 1, 2),
pad(tomorrow.getDate(), 2) pad(tomorrow.getDate(), 2)
].join('-'); ].join('-');
let start = tomorrowAsString; let end = start;
let end = tomorrowAsString;
let isReturn = false; let isReturn = false;
const startDate = () => convertToDate(start); $: startDate = convertToDate(start);
const endDate = () => convertToDate(end); $: endDate = convertToDate(end);
function bookFlight() { function bookFlight() {
const type = isReturn ? 'return' : 'one-way'; const type = isReturn ? 'return' : 'one-way';
let message = `You have booked a ${type} flight, leaving ${startDate().toDateString()}`; let message = `You have booked a ${type} flight, leaving ${startDate.toDateString()}`;
if (type === 'return') { if (type === 'return') {
message += ` and returning ${endDate().toDateString()}`; message += ` and returning ${endDate.toDateString()}`;
} }
alert(message); alert(message);
} }
function convertToDate(str) { function convertToDate(str) {
var split = str.split('-'); const split = str.split('-');
return new Date(+split[0], +split[1] - 1, +split[2]); return new Date(+split[0], +split[1] - 1, +split[2]);
} }
@ -57,5 +55,5 @@
<button <button
on:click={bookFlight} on:click={bookFlight}
disabled="{isReturn && (startDate() >= endDate())}" disabled="{isReturn && (startDate >= endDate)}"
>book</button> >book</button>

@ -1,13 +1,11 @@
<script> <script>
export let promise; let promise;
// [svelte-upgrade suggestion] function findAnswer() {
// review these functions and remove unnecessary 'export' keywords
export function findAnswer() {
promise = new Promise(fulfil => { promise = new Promise(fulfil => {
const delay = 1000 + Math.random() * 3000; const delay = 1000 + Math.random() * 3000;
setTimeout(() => fulfil(42), delay); setTimeout(() => fulfil(42), delay);
}); });
} }
</script> </script>

@ -1,5 +1,4 @@
<script> <script>
import { onMount } from 'svelte';
import { scaleLinear } from 'd3-scale'; import { scaleLinear } from 'd3-scale';
export let points; export let points;

@ -1,16 +1,16 @@
<script> <script>
export let paused = true; let paused = true;
export let t = 0; let t = 0;
export let d; let d;
let icon, bg; let icon, bg;
$: icon = `https://icon.now.sh/${paused ? 'play' : 'pause'}_circle_filled`; $: icon = `https://icon.now.sh/${paused ? 'play' : 'pause'}_circle_filled`;
$: { $: {
var p = d ? t / d : 0; const p = d ? t / d : 0;
var h = 90 + 90 * p; const h = 90 + 90 * p;
var l = 10 + p * 30; const l = 10 + p * 30;
bg = `hsl(${h},50%,${l}%)`; bg = `hsl(${h},50%,${l}%)`;
} }
@ -20,8 +20,8 @@
const format = time => { const format = time => {
if (isNaN(time)) return '--:--.-'; if (isNaN(time)) return '--:--.-';
var minutes = Math.floor(time / 60); const minutes = Math.floor(time / 60);
var seconds = (time % 60).toFixed(1); const seconds = (time % 60).toFixed(1);
return minutes + ':' + pad(seconds) return minutes + ':' + pad(seconds)
}; };
@ -29,7 +29,7 @@
function seek(event) { function seek(event) {
if (event.buttons === 1) { if (event.buttons === 1) {
event.preventDefault(); event.preventDefault();
var p = event.clientX / window.innerWidth; const p = event.clientX / window.innerWidth;
t = p * d; t = p * d;
} }
} }

@ -1,7 +1,7 @@
<script> <script>
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import List from './List.html'; import List from './List.svelte';
import Item from './Item.html'; import Item from './Item.svelte';
let item; let item;
let page; let page;

@ -1,5 +1,5 @@
<script> <script>
import Foo from './Foo.html'; import Foo from './Foo.svelte';
</script> </script>
<style> <style>

@ -1,8 +1,8 @@
<svelte:meta immutable/> <svelte:options immutable/>
<script> <script>
import ImmutableTodo from './ImmutableTodo.html'; import ImmutableTodo from './ImmutableTodo.svelte';
import MutableTodo from './MutableTodo.html'; import MutableTodo from './MutableTodo.svelte';
export let todos; export let todos;

@ -1,4 +1,4 @@
<svelte:meta immutable/> <svelte:options immutable/>
<script> <script>
import { afterUpdate } from 'svelte'; import { afterUpdate } from 'svelte';

@ -8,7 +8,6 @@
const xTicks = [1980, 1990, 2000, 2010]; const xTicks = [1980, 1990, 2000, 2010];
const padding = { top: 20, right: 15, bottom: 20, left: 25 }; const padding = { top: 20, right: 15, bottom: 20, left: 25 };
let svg;
let width = 500; let width = 500;
let height = 200; let height = 200;
@ -36,10 +35,9 @@
return "'" + tick % 100; return "'" + tick % 100;
} }
let svg;
function resize() { function resize() {
const bcr = svg.getBoundingClientRect(); ({ width, height } = svg.getBoundingClientRect());
width = bcr.width;
height = bcr.height;
} }
onMount(resize); onMount(resize);

@ -1,5 +1,5 @@
<script> <script>
import Modal from './Modal.html'; import Modal from './Modal.svelte';
export let showModal; export let showModal;
</script> </script>

@ -1,5 +1,5 @@
<script> <script>
import Nested from './Nested.html'; import Nested from './Nested.svelte';
</script> </script>
<p>This is a top-level element.</p> <p>This is a top-level element.</p>

@ -1,5 +1,5 @@
<script> <script>
import Scatterplot from './Scatterplot.html'; import Scatterplot from './Scatterplot.svelte';
export let a; export let a;
export let b; export let b;

@ -1,111 +0,0 @@
<script>
// TODO this example needs updating
import { onMount } from 'svelte';
import { scaleLinear } from 'd3-scale';
export let svg;
const xScale = scaleLinear();
const yScale = scaleLinear();
export let width = 500;
export let height = 200;
export let padding = { top: 20, right: 40, bottom: 40, left: 25 };
export let points;
export let xTicks = [0, 4, 8, 12, 16, 20];
export let yTicks = [0, 2, 4, 6, 8, 10, 12];
function xTicks() {
return width > 180 ?
[0, 4, 8, 12, 16, 20] :
[0, 10, 20];
}
function yTicks() {
return height > 180 ?
[0, 2, 4, 6, 8, 10, 12] :
[0, 4, 8, 12];
}
function xScale() {
return xScale()
.domain([0, 20])
.range([padding.left, width - padding.right]);
}
function yScale() {
return yScale()
.domain([0, 12])
.range([height - padding.bottom, padding.top]);
}
onMount(() => {
resize();
});
function resize() {
const { width, height } = svg.getBoundingClientRect();
width = width, height = height;
}
</script>
<svelte:window on:resize='{resize}'/>
<svg ref:svg>
<!-- y axis -->
<g class='axis y-axis'>
{#each yTicks as tick}
<g class='tick tick-{tick}' transform='translate(0, {yScale()(tick)})'>
<line x1='{padding.left}' x2='{xScale()(22)}'/>
<text x='{padding.left - 8}' y='+4'>{tick}</text>
</g>
{/each}
</g>
<!-- x axis -->
<g class='axis x-axis'>
{#each xTicks as tick}
<g class='tick' transform='translate({xScale()(tick)},0)'>
<line y1='{yScale()(0)}' y2='{yScale()(13)}'/>
<text y='{height - padding.bottom + 16}'>{tick}</text>
</g>
{/each}
</g>
<!-- data -->
{#each points as point}
<circle cx='{xScale()(point.x)}' cy='{yScale()(point.y)}' r='5'/>
{/each}
</svg>
<style>
svg {
width: 50%;
height: 50%;
float: left;
}
circle {
fill: orange;
fill-opacity: 0.6;
stroke: rgba(0,0,0,0.5);
}
.tick line {
stroke: #ddd;
stroke-dasharray: 2;
}
text {
font-size: 12px;
fill: #999;
}
.x-axis text {
text-anchor: middle;
}
.y-axis text {
text-anchor: end;
}
</style>

@ -0,0 +1,96 @@
<script>
import { onMount } from 'svelte';
import { scaleLinear } from 'd3-scale';
export let points;
const padding = { top: 20, right: 40, bottom: 40, left: 25 };
let xScale;
$: xScale = scaleLinear()
.domain([0, 20])
.range([padding.left, width - padding.right]);
let yScale;
$: yScale = scaleLinear()
.domain([0, 12])
.range([height - padding.bottom, padding.top]);
let width = 500;
let height = 200;
let xTicks;
$: xTicks = width > 180 ?
[0, 4, 8, 12, 16, 20] :
[0, 10, 20];
let yTicks;
$: yTicks = height > 180 ?
[0, 2, 4, 6, 8, 10, 12] :
[0, 4, 8, 12];
onMount(resize);
let svg;
function resize() {
({ width, height } = svg.getBoundingClientRect());
}
</script>
<svelte:window on:resize='{resize}'/>
<svg bind:this={svg}>
<!-- y axis -->
<g class='axis y-axis'>
{#each yTicks as tick}
<g class='tick tick-{tick}' transform='translate(0, {yScale(tick)})'>
<line x1='{padding.left}' x2='{xScale(22)}'/>
<text x='{padding.left - 8}' y='+4'>{tick}</text>
</g>
{/each}
</g>
<!-- x axis -->
<g class='axis x-axis'>
{#each xTicks as tick}
<g class='tick' transform='translate({xScale(tick)},0)'>
<line y1='{yScale(0)}' y2='{yScale(13)}'/>
<text y='{height - padding.bottom + 16}'>{tick}</text>
</g>
{/each}
</g>
<!-- data -->
{#each points as point}
<circle cx='{xScale(point.x)}' cy='{yScale(point.y)}' r='5'/>
{/each}
</svg>
<style>
svg {
width: 50%;
height: 50%;
float: left;
}
circle {
fill: orange;
fill-opacity: 0.6;
stroke: rgba(0,0,0,0.5);
}
.tick line {
stroke: #ddd;
stroke-dasharray: 2;
}
text {
font-size: 12px;
fill: #999;
}
.x-axis text {
text-anchor: middle;
}
.y-axis text {
text-anchor: end;
}
</style>

@ -2,7 +2,7 @@
import * as eases from 'svelte/easing'; import * as eases from 'svelte/easing';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
export let visible; let visible;
const wheee = (node, params) => { const wheee = (node, params) => {
return { return {

@ -1,7 +1,7 @@
<script> <script>
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
export let visible; let visible;
</script> </script>
<input type=checkbox bind:checked={visible}> visible <input type=checkbox bind:checked={visible}> visible

@ -1,7 +1,7 @@
<script> <script>
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
export let visible; let visible;
</script> </script>
<input type=checkbox bind:checked={visible}> visible <input type=checkbox bind:checked={visible}> visible

@ -1,7 +1,7 @@
<script> <script>
import { fade, fly } from 'svelte/transition'; import { fade, fly } from 'svelte/transition';
export let visible; let visible;
</script> </script>
<input type=checkbox bind:checked={visible}> visible <input type=checkbox bind:checked={visible}> visible

@ -114,11 +114,9 @@ You can also bind to certain values — so far `innerWidth`, `outerWidth`, `inne
``` ```
### `<svelte:document>` ### `<svelte:body>`
TODO REPLACE THIS WITH svelte:body The `<svelte:body>` tag, just like `<svelte:window>`, gives you a convenient way to declaratively add event listeners to the `document.body` object. This is useful for listening to events that don't fire on `window`, such as `mouseenter` and `mouseleave`.
The `<svelte:document>` tag, just like `<svelte:window>`, gives you a convenient way to declaratively add event listeners to the `document` object. This is useful for listening to events that don't fire on `window`, such as `mouseenter` and `mouseleave`.
### `<svelte:head>` ### `<svelte:head>`

@ -36,7 +36,7 @@ Element bindings make it easy to respond to user interactions:
```html ```html
<!-- { title: 'Element bindings' } --> <!-- { title: 'Element bindings' } -->
<h1>Hello {name}!</h1> <h1>Hello {name}!</h1>
<input bind:value=name> <input bind:value={name}>
``` ```
```json ```json

@ -18,7 +18,7 @@ It's easier to show the effect of this than to describe it. Open the following e
```html ```html
<!-- { title: 'Keyed each blocks' } --> <!-- { title: 'Keyed each blocks' } -->
<button on:click="update()">update</button> <button on:click="{update}">update</button>
<section> <section>
<h2>Keyed</h2> <h2>Keyed</h2>
@ -46,29 +46,21 @@ It's easier to show the effect of this than to describe it. Open the following e
</style> </style>
<script> <script>
import { slide } from 'svelte-transitions'; import { slide } from 'svelte/transition';
var people = ['Alice', 'Barry', 'Cecilia', 'Douglas', 'Eleanor', 'Felix', 'Grace', 'Horatio', 'Isabelle']; const names = ['Alice', 'Barry', 'Cecilia', 'Douglas', 'Eleanor', 'Felix', 'Grace', 'Horatio', 'Isabelle'];
function random() { function random() {
return people return names
.filter(() => Math.random() < 0.5) .filter(() => Math.random() < 0.5)
.map(name => ({ name })) .map(name => ({ name }));
} }
export default { let people = random();
data() {
return { people: random() };
},
methods: {
update() {
this.set({ people: random() });
}
},
transitions: { slide } function update() {
}; people = random();
}
</script> </script>
``` ```

1024
site/package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,57 +1,58 @@
{ {
"name": "TODO", "name": "svelte.technology",
"description": "TODO", "version": "1.0.0",
"version": "0.0.1", "description": "Docs and examples for Svelte",
"scripts": { "scripts": {
"dev": "sapper dev", "dev": "sapper dev",
"sapper": "sapper build --legacy", "sapper": "sapper build --legacy",
"update_template": "sh ./scripts/update_template.sh", "update": "node scripts/update_template.js && node scripts/get-contributors.js",
"start": "node __sapper__/build", "start": "node __sapper__/build",
"cy:run": "cypress run", "cy:run": "cypress run",
"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 && node scripts/get-contributors.js && npm run sapper", "prestage": "npm run update && npm run sapper",
"stage": "now" "stage": "now"
}, },
"dependencies": { "dependencies": {
"codemirror": "^5.42.0", "codemirror": "^5.43.0",
"compression": "^1.7.1", "compression": "^1.7.3",
"devalue": "^1.1.0", "devalue": "^1.1.0",
"do-not-zip": "^1.0.0", "do-not-zip": "^1.0.0",
"dotenv": "^6.2.0", "dotenv": "^6.2.0",
"express": "^4.16.4", "express": "^4.16.4",
"express-session": "^1.15.6", "express-session": "^1.15.6",
"golden-fleece": "^1.0.9", "golden-fleece": "^1.0.9",
"marked": "^0.5.2", "marked": "^0.6.0",
"node-fetch": "^2.3.0", "node-fetch": "^2.3.0",
"passport": "^0.4.0", "passport": "^0.4.0",
"passport-github": "^1.1.0", "passport-github": "^1.1.0",
"prismjs": "^1.15.0", "prismjs": "^1.15.0",
"session-file-store": "^1.2.0", "session-file-store": "^1.2.0",
"sirv": "^0.2.0", "shelljs": "^0.8.3",
"sirv": "^0.2.2",
"yootils": "0.0.14" "yootils": "0.0.14"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.0.0", "@babel/core": "^7.2.2",
"@babel/plugin-syntax-dynamic-import": "^7.0.0", "@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-transform-runtime": "^7.0.0", "@babel/plugin-transform-runtime": "^7.2.0",
"@babel/preset-env": "^7.0.0", "@babel/preset-env": "^7.3.1",
"@babel/runtime": "^7.0.0", "@babel/runtime": "^7.3.1",
"chokidar": "^2.0.4", "chokidar": "^2.1.0",
"degit": "^2.1.3", "degit": "^2.1.3",
"eslint-plugin-svelte3": "git+https://github.com/sveltejs/eslint-plugin-svelte3.git", "eslint-plugin-svelte3": "git+https://github.com/sveltejs/eslint-plugin-svelte3.git#semver:*",
"now": "^12.1.14", "now": "^13.1.3",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"rollup": "^0.68.0", "rollup": "^1.1.2",
"rollup-plugin-babel": "^4.0.2", "rollup-plugin-babel": "^4.3.2",
"rollup-plugin-commonjs": "^9.1.6", "rollup-plugin-commonjs": "^9.2.0",
"rollup-plugin-json": "^3.1.0", "rollup-plugin-json": "^3.1.0",
"rollup-plugin-node-resolve": "^3.3.0", "rollup-plugin-node-resolve": "^4.0.0",
"rollup-plugin-replace": "^2.0.0", "rollup-plugin-replace": "^2.1.0",
"rollup-plugin-svelte": "^4.2.1", "rollup-plugin-svelte": "^5.0.1",
"rollup-plugin-terser": "^1.0.1", "rollup-plugin-terser": "^4.0.4",
"sapper": "^0.25.0-alpha2", "sapper": "^0.26.0-alpha.8",
"svelte": "^3.0.0-alpha10" "svelte": "^3.0.0-beta.3"
} }
} }

@ -0,0 +1,26 @@
const sh = require('shelljs');
const fs = require('fs')
sh.cd(__dirname+'/../')
// fetch svelte app
sh.rm('-rf','scripts/svelte-app')
sh.exec('npx degit sveltejs/template scripts/svelte-app')
// update repl-viewer.css based on template
sh.cp('scripts/svelte-app/public/global.css', 'static/repl-viewer.css')
// remove src (will be recreated client-side) and node_modules
sh.rm('-rf', 'scripts/svelte-app/src')
sh.rm('-rf', 'scripts/svelte-app/node_modules')
// build svelte-app.json
const appPath = 'scripts/svelte-app'
let files = []
for (const path of sh.find(appPath).filter(p => fs.lstatSync(p).isFile()) ) {
files.push({ path: path.slice(appPath.length + 1), data: fs.readFileSync(path).toString() });
}
fs.writeFileSync('static/svelte-app.json', JSON.stringify(files));

@ -1,15 +0,0 @@
cd `dirname $0`/..
# fetch svelte-app
rm -rf scripts/svelte-app
node_modules/.bin/degit sveltejs/template scripts/svelte-app
# update repl-viewer.css based on template
cp scripts/svelte-app/public/global.css static/repl-viewer.css
# remove src (will be recreated client-side) and node_modules
rm -rf scripts/svelte-app/src
rm -rf scripts/svelte-app/node_modules
# build svelte-app.json
node scripts/build-svelte-app-json.js `find scripts/svelte-app -type f`

@ -1,4 +1,4 @@
import * as sapper from '../__sapper__/client.js'; import * as sapper from '@sapper/app';
sapper.start({ sapper.start({
target: document.querySelector('#sapper') target: document.querySelector('#sapper')

Before

Width:  |  Height:  |  Size: 828 B

After

Width:  |  Height:  |  Size: 828 B

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

@ -1,7 +1,7 @@
<script> <script>
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import Icon from './Icon.html'; import Icon from './Icon.svelte';
import Logo from './Logo.html'; import Logo from './Logo.svelte';
export let segment; export let segment;

@ -1,6 +1,6 @@
<script> <script>
import InlineSvg from '../components/InlineSvg.html'; import InlineSvg from '../components/InlineSvg.svelte';
import Nav from '../components/TopNav.html'; import Nav from '../components/TopNav.svelte';
export let child; export let child;
export let path; export let path;

@ -3,58 +3,53 @@ import path from 'path';
import process_markdown from '../../../utils/_process_markdown.js'; import process_markdown from '../../../utils/_process_markdown.js';
import marked from 'marked'; import marked from 'marked';
// import hljs from 'highlight.js'; import PrismJS from 'prismjs';
import prismjs from 'prismjs'; // prism-highlighter smaller footprint [hljs: 192.5k] import 'prismjs/components/prism-bash';
require('prismjs/components/prism-bash');
// map lang to prism-language-attr // map lang to prism-language-attr
const prismLang = { const prismLang = {
bash: 'bash', bash: 'bash',
html: 'markup', html: 'markup',
js: 'javascript', js: 'javascript',
css: 'css', css: 'css',
}; };
export default function() { export default function() {
return fs return fs
.readdirSync('content/blog') .readdirSync('content/blog')
.map(file => { .map(file => {
if (path.extname(file) !== '.md') return; if (path.extname(file) !== '.md') return;
const markdown = fs.readFileSync(`content/blog/${file}`, 'utf-8'); const markdown = fs.readFileSync(`content/blog/${file}`, 'utf-8');
const { content, metadata } = process_markdown(markdown); const { content, metadata } = process_markdown(markdown);
const date = new Date(`${metadata.pubdate} EDT`); // cheeky hack const date = new Date(`${metadata.pubdate} EDT`); // cheeky hack
metadata.dateString = date.toDateString(); metadata.dateString = date.toDateString();
const renderer = new marked.Renderer(); const renderer = new marked.Renderer();
renderer.code = (source, lang) => { renderer.code = (source, lang) => {
let plang = prismLang[lang]; const plang = prismLang[lang];
const highlighted = Prism.highlight( const highlighted = PrismJS.highlight(
source, source,
Prism.languages[plang], PrismJS.languages[plang],
lang, lang,
); );
return `<pre class='language-${plang}'><code>${highlighted}</code></pre>`; return `<pre class='language-${plang}'><code>${highlighted}</code></pre>`;
}; };
const html = marked( const html = marked(
content.replace(/^\t+/gm, match => match.split('\t').join(' ')), content.replace(/^\t+/gm, match => match.split('\t').join(' ')),
{ { renderer }
renderer, );
},
); return {
html,
return { metadata,
html, slug: file.replace(/^[\d-]+/, '').replace(/\.md$/, ''),
metadata, };
slug: file.replace(/^[\d-]+/, '').replace(/\.md$/, ''), })
}; .sort((a, b) => a.metadata.pubdate < b.metadata.pubdate ? 1 : -1);
})
.sort((a, b) => {
return a.metadata.pubdate < b.metadata.pubdate ? 1 : -1;
});
} }

@ -19,7 +19,7 @@ function createExample(slug) {
const components = files const components = files
.map(file => { .map(file => {
const ext = path.extname(file); const ext = path.extname(file);
if (ext !== '.html' && ext !== '.js') return null; if (ext !== '.svelte' && ext !== '.js') return null;
const source = fs.readFileSync(`content/examples/${slug}/${file}`, 'utf-8'); const source = fs.readFileSync(`content/examples/${slug}/${file}`, 'utf-8');
@ -31,10 +31,10 @@ function createExample(slug) {
}) })
.filter(Boolean) .filter(Boolean)
.sort((a, b) => { .sort((a, b) => {
if (a.name === 'App' && a.type === 'html') return -1; if (a.name === 'App' && a.type === 'svelte') return -1;
if (b.name === 'App' && b.type === 'html') return 1; if (b.name === 'App' && b.type === 'svelte') return 1;
if (a.type !== b.type) return a.type === 'html' ? -1 : 1; if (a.type !== b.type) return a.type === 'svelte' ? -1 : 1;
return a.name < b.name ? -1 : 1; return a.name < b.name ? -1 : 1;
}); });

@ -1,6 +1,6 @@
export function get(req, res) { export function get(req, res) {
if (!req.session.passport || !req.session.passport.user) { if (!req.session || !req.session.passport || !req.session.passport.user) {
res.send(null); res.send('null');
return; return;
} }

@ -11,6 +11,7 @@
<svelte:head> <svelte:head>
<title>Svelte • The magical disappearing UI framework</title> <title>Svelte • The magical disappearing UI framework</title>
<link rel="alternate" type="application/rss+xml" title="Svelte blog" href="blog/rss.xml">
</svelte:head> </svelte:head>
<div class='posts stretch'> <div class='posts stretch'>

@ -1,10 +1,10 @@
import get_posts from '../api/blog/_posts.js'; import get_posts from '../api/blog/_posts.js';
const months = ',Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split( ',' ); const months = ',Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(',');
function formatPubdate ( str ) { function formatPubdate(str) {
const [ y, m, d ] = str.split( '-' ); const [y, m, d] = str.split('-');
return `${d} ${months[+m]} ${y}`; return `${d} ${months[+m]} ${y} 12:00 +0000`;
} }
const rss = ` const rss = `
@ -20,18 +20,18 @@ const rss = `
<title>Svelte</title> <title>Svelte</title>
<link>https://svelte.technology/blog</link> <link>https://svelte.technology/blog</link>
</image> </image>
${get_posts().map( post => ` ${get_posts().map(post => `
<item> <item>
<title>${post.metadata.title}</title> <title>${post.metadata.title}</title>
<link>https://svelte.technology/blog/${post.slug}</link> <link>https://svelte.technology/blog/${post.slug}</link>
<description>${post.metadata.description}</description> <description>${post.metadata.description}</description>
<pubDate>${formatPubdate(post.metadata.pubdate)}</pubDate> <pubDate>${formatPubdate(post.metadata.pubdate)}</pubDate>
</item> </item>
` )} `).join('')}
</channel> </channel>
</rss> </rss>
`.replace( />[^\S]+/gm, '>' ).replace( /[^\S]+</gm, '<' ).trim(); `.replace(/>[^\S]+/gm, '>').replace(/[^\S]+</gm, '<').trim();
export function get(req, res) { export function get(req, res) {
res.set({ res.set({

@ -5,7 +5,7 @@ export async function get(req, res) {
const { id } = req.params; const { id } = req.params;
const headers = {}; const headers = {};
const user = req.session.passport && req.session.passport.user; const user = req.session && req.session.passport && req.session.passport.user;
if (user) { if (user) {
headers.Authorization = `token ${user.token}`; headers.Authorization = `token ${user.token}`;
} }
@ -34,7 +34,7 @@ export async function get(req, res) {
} }
export async function patch(req, res) { export async function patch(req, res) {
const user = req.session.passport && req.session.passport.user; const user = req.session && req.session.passport && req.session.passport.user;
if (!user) { if (!user) {
res.writeHead(403, { res.writeHead(403, {

@ -1,6 +1,6 @@
<script> <script>
import { onMount, afterUpdate } from 'svelte'; import { onMount, afterUpdate } from 'svelte';
import Icon from '../../components/Icon.html'; import Icon from '../../components/Icon.svelte';
export let sections = []; export let sections = [];
export let active_section = null; export let active_section = null;

@ -4,13 +4,8 @@ import * as fleece from 'golden-fleece';
import process_markdown from '../../utils/_process_markdown.js'; import process_markdown from '../../utils/_process_markdown.js';
import marked from 'marked'; import marked from 'marked';
import Prism from 'prismjs'; // prism-highlighter smaller footprint [hljs: 192.5k] import PrismJS from 'prismjs';
require('prismjs/components/prism-bash'); import 'prismjs/components/prism-bash';
const langs = {
'hidden-data': 'json',
'html-no-repl': 'html',
};
// map lang to prism-language-attr // map lang to prism-language-attr
const prismLang = { const prismLang = {
@ -20,10 +15,6 @@ const prismLang = {
css: 'css', css: 'css',
}; };
function btoa(str) {
return new Buffer(str).toString('base64');
}
const escaped = { const escaped = {
'"': '&quot;', '"': '&quot;',
"'": '&#39;', "'": '&#39;',
@ -112,13 +103,6 @@ export default function() {
let className = 'code-block'; let className = 'code-block';
if (lang === 'html' && !group) { if (lang === 'html' && !group) {
/* prettier-ignore
-----------------------------------------------
edit vedam
don't know how to access components here
so i hardcoded icon here
-----------------------------------------------
*/
if (!meta || meta.repl !== false) { if (!meta || meta.repl !== false) {
prefix = `<a class='open-in-repl' href='repl?demo=@@${uid}' title='open in REPL'><svg class='icon'><use xlink:href='#maximize-2' /></svg></a>`; prefix = `<a class='open-in-repl' href='repl?demo=@@${uid}' title='open in REPL'><svg class='icon'><use xlink:href='#maximize-2' /></svg></a>`;
} }
@ -129,7 +113,7 @@ export default function() {
if (meta) { if (meta) {
source = lines.slice(1).join('\n'); source = lines.slice(1).join('\n');
const filename = meta.filename || (lang === 'html' && 'App.html'); const filename = meta.filename || (lang === 'html' && 'App.svelte');
if (filename) { if (filename) {
prefix = `<span class='filename'>${prefix} ${filename}</span>`; prefix = `<span class='filename'>${prefix} ${filename}</span>`;
className += ' named'; className += ' named';
@ -140,16 +124,10 @@ export default function() {
if (meta && meta.hidden) return ''; if (meta && meta.hidden) return '';
/* prettier-ignore const plang = prismLang[lang];
----------------------------------------------- const highlighted = PrismJS.highlight(
design-edit vedam
insert prism-highlighter
-----------------------------------------------
*/
let plang = prismLang[lang];
const highlighted = Prism.highlight(
source, source,
Prism.languages[plang], PrismJS.languages[plang],
lang lang
); );

@ -7,8 +7,8 @@
<script> <script>
import { onMount, afterUpdate } from 'svelte'; import { onMount, afterUpdate } from 'svelte';
import GuideContents from './_GuideContents.html'; import GuideContents from './_GuideContents.svelte';
import Icon from '../../components/Icon.html'; import Icon from '../../components/Icon.svelte';
export let sections; export let sections;
let active_section; let active_section;

@ -1,6 +1,6 @@
<script> <script>
import Icon from '../components/Icon.html'; import Icon from '../components/Icon.svelte';
import Logo from '../components/Logo.html'; import Logo from '../components/Logo.svelte';
import contributors from './_contributors.js'; import contributors from './_contributors.js';
let sy = 0; let sy = 0;

@ -1,8 +1,8 @@
<script> <script>
import * as fleece from 'golden-fleece'; import * as fleece from 'golden-fleece';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import UserMenu from './UserMenu.html'; import UserMenu from './UserMenu.svelte';
import Icon from '../../../../components/Icon.html'; import Icon from '../../../../components/Icon.svelte';
import * as doNotZip from 'do-not-zip'; import * as doNotZip from 'do-not-zip';
import downloadBlob from '../../_utils/downloadBlob.js'; import downloadBlob from '../../_utils/downloadBlob.js';
import { user } from '../../../../user.js'; import { user } from '../../../../user.js';
@ -115,7 +115,7 @@
// null out any deleted files // null out any deleted files
const set = new Set(components.map(m => `${m.name}.${m.type}`)); const set = new Set(components.map(m => `${m.name}.${m.type}`));
Object.keys(gist.files).forEach(file => { Object.keys(gist.files).forEach(file => {
if (/\.(html|js)$/.test(file)) { if (/\.(svelte|html|js)$/.test(file)) {
if (!set.has(file)) files[file] = null; if (!set.has(file)) files[file] = null;
} }
}); });
@ -126,7 +126,7 @@
throw new Error(`GitHub does not allow saving gists with empty files - ${file}`); throw new Error(`GitHub does not allow saving gists with empty files - ${file}`);
} }
if (!gist.files[files] || module.source !== gist.files[file].content) { if (!gist.files[file] || module.source !== gist.files[file].content) {
files[file] = { content: module.source }; files[file] = { content: module.source };
} }
}); });
@ -189,7 +189,7 @@
files.push(...components.map(component => ({ path: `src/${component.name}.${component.type}`, data: component.source }))); files.push(...components.map(component => ({ path: `src/${component.name}.${component.type}`, data: component.source })));
files.push({ files.push({
path: `src/main.js`, data: `import App from './App.html'; path: `src/main.js`, data: `import App from './App.svelte';
var app = new App({ var app = new App({
target: document.body, target: document.body,

@ -1,6 +1,6 @@
<script> <script>
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import Icon from '../../../../components/Icon.html'; import Icon from '../../../../components/Icon.svelte';
import { enter } from '../events.js'; import { enter } from '../events.js';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
@ -25,7 +25,7 @@
} }
function closeEdit() { function closeEdit() {
const match = /(.+)\.(html|js)$/.exec($selected_store.name); const match = /(.+)\.(svelte|js)$/.exec($selected_store.name);
$selected_store.name = match ? match[1] : $selected_store.name; $selected_store.name = match ? match[1] : $selected_store.name;
if (match && match[2]) $selected_store.type = match[2]; if (match && match[2]) $selected_store.type = match[2];
editing = null; editing = null;
@ -49,7 +49,7 @@
function addNew() { function addNew() {
const component = { const component = {
name: uid++ ? `Component${uid}` : 'Component1', name: uid++ ? `Component${uid}` : 'Component1',
type: 'html', type: 'svelte',
source: '' source: ''
}; };
@ -181,7 +181,7 @@
> >
{#if component.name == 'App'} {#if component.name == 'App'}
<div class="uneditable"> <div class="uneditable">
App.html App.svelte
</div> </div>
{:else} {:else}
{#if component === editing} {#if component === editing}

@ -1,5 +1,5 @@
<script> <script>
import CodeMirror from '../CodeMirror.html'; import CodeMirror from '../CodeMirror.svelte';
export let component; export let component;
export let error; export let error;

@ -1,6 +1,6 @@
<script> <script>
import ComponentSelector from './ComponentSelector.html'; import ComponentSelector from './ComponentSelector.svelte';
import ModuleEditor from './ModuleEditor.html'; import ModuleEditor from './ModuleEditor.svelte';
export let component_store; export let component_store;
export let selected_store; export let selected_store;

@ -1,7 +1,7 @@
<script> <script>
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import * as fleece from 'golden-fleece'; import * as fleece from 'golden-fleece';
import CodeMirror from '../CodeMirror.html'; import CodeMirror from '../CodeMirror.svelte';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();

@ -1,6 +1,7 @@
<script> <script>
import { onMount, createEventDispatcher } from 'svelte'; import { onMount, onDestroy, createEventDispatcher } from 'svelte';
import getLocationFromStack from '../../_utils/getLocationFromStack.js'; import getLocationFromStack from '../../_utils/getLocationFromStack.js';
import ReplProxy from '../../_utils/replProxy.js';
import { decode } from 'sourcemap-codec'; import { decode } from 'sourcemap-codec';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
@ -14,29 +15,18 @@
export let error; export let error;
export function setProp(prop, value) { export function setProp(prop, value) {
if (component) { if (!replProxy) return;
component[prop] = value; replProxy.setProp(prop, value);
}
} }
let component; let hasComponent = false;
const refs = {}; const refs = {};
const importCache = {};
let pendingImports = 0; let pendingImports = 0;
let pending = false; let pending = false;
function fetchImport(id, curl) { let replProxy = null;
return new Promise((fulfil, reject) => {
curl([`https://bundle.run/${id}`]).then(module => {
importCache[id] = module;
fulfil(module);
}, err => {
console.error(err.stack);
reject(new Error(`Error loading ${id} from bundle.run`));
});
});
}
const namespaceSpecifier = /\*\s+as\s+(\w+)/; const namespaceSpecifier = /\*\s+as\s+(\w+)/;
const namedSpecifiers = /\{(.+)\}/; const namedSpecifiers = /\{(.+)\}/;
@ -77,38 +67,28 @@
let createComponent; let createComponent;
let init; let init;
onDestroy(() => {
if (replProxy) {
replProxy.destroy();
}
});
onMount(() => { onMount(() => {
replProxy = new ReplProxy(refs.child);
refs.child.addEventListener('load', () => { refs.child.addEventListener('load', () => {
const iframe = refs.child;
const body = iframe.contentDocument.body;
const evalInIframe = iframe.contentWindow.eval;
// intercept links, so that we can use #hashes inside the iframe
body.addEventListener('click', event => {
if (event.which !== 1) return;
if (event.metaKey || event.ctrlKey || event.shiftKey) return;
if (event.defaultPrevented) return;
// ensure target is a link
let el = event.target;
while (el && el.nodeName !== 'A') el = el.parentNode;
if (!el || el.nodeName !== 'A') return;
if (el.hasAttribute('download') || el.getAttribute('rel') === 'external' || el.target) return;
event.preventDefault();
if (el.href.startsWith(top.location.origin)) {
const url = new URL(el.href);
if (url.hash[0] === '#') {
iframe.contentWindow.location.hash = url.hash;
return;
}
}
window.open(el.href, '_blank'); replProxy.onPropUpdate = (prop, value) => {
}); dispatch('binding', { prop, value });
values_store.update(values => Object.assign({}, values, {
[prop]: value
}));
};
replProxy.onFetchProgress = (progress) => {
pendingImports = progress
}
replProxy.handleLinks();
let promise = null; let promise = null;
let updating = false; let updating = false;
@ -118,14 +98,18 @@
const init = () => { const init = () => {
if (sourceError) return; if (sourceError) return;
const imports = [];
const missingImports = bundle.imports.filter(x => !importCache[x]);
const removeStyles = () => { const removeStyles = () => {
const styles = iframe.contentDocument.querySelectorAll('style.svelte'); replProxy.eval(`
let i = styles.length; const styles = document.querySelectorAll('style.svelte');
while (i--) styles[i].parentNode.removeChild(styles[i]); let i = styles.length;
while (i--) styles[i].parentNode.removeChild(styles[i]);
`)
};
const destroyComponent = () => {
replProxy.eval(`if (window.component)
window.component.\$destroy();
window.component = null`);
}; };
const ready = () => { const ready = () => {
@ -133,18 +117,10 @@
if (toDestroy) { if (toDestroy) {
removeStyles(); removeStyles();
destroyComponent();
toDestroy.$destroy();
toDestroy = null; toDestroy = null;
} }
bundle.imports.forEach(x => {
const module = importCache[x];
const name = bundle.importMap.get(x);
iframe.contentWindow[name] = module;
});
if (ssr) { // this only gets generated if component uses lifecycle hooks if (ssr) { // this only gets generated if component uses lifecycle hooks
pending = true; pending = true;
createHtml(); createHtml();
@ -152,23 +128,22 @@
pending = false; pending = false;
createComponent(); createComponent();
} }
}; }
const createHtml = () => { const createHtml = () => {
try { replProxy.eval(`${ssr.code}
evalInIframe(`${ssr.code} var rendered = SvelteComponent.render(${JSON.stringify($values_store)});
var rendered = SvelteComponent.render(${JSON.stringify($values_store)});
if (rendered.css.code) {
if (rendered.css.code) { var style = document.createElement('style');
var style = document.createElement('style'); style.className = 'svelte';
style.className = 'svelte'; style.textContent = rendered.css.code;
style.textContent = rendered.css.code; document.head.appendChild(style);
document.head.appendChild(style); }
}
document.body.innerHTML = rendered.html;
document.body.innerHTML = rendered.html; `)
`) .catch( e => {
} catch (e) {
const loc = getLocationFromStack(e.stack, ssr.map); const loc = getLocationFromStack(e.stack, ssr.map);
if (loc) { if (loc) {
e.filename = loc.source; e.filename = loc.source;
@ -176,35 +151,29 @@
} }
error = e; error = e;
} });
}; };
const createComponent = () => { const createComponent = () => {
// remove leftover styles from SSR renderer // remove leftover styles from SSR renderer
if (ssr) removeStyles(); if (ssr) removeStyles();
try { replProxy.eval(`${dom.code}
evalInIframe(`${dom.code} document.body.innerHTML = '';
document.body.innerHTML = ''; window.location.hash = '';
window.location.hash = ''; window._svelteTransitionManager = null;
window._svelteTransitionManager = null;
window.component = new SvelteComponent({
var component = new SvelteComponent({ target: document.body,
target: document.body, props: ${JSON.stringify($values_store)}
props: ${JSON.stringify($values_store)} });
});`); `)
.then(()=> {
component = window.app = window.component = iframe.contentWindow.component; replProxy.bindProps(props);
})
// component.on('state', ({ current }) => { .catch(e => {
// if (updating) return;
// updating = true;
// this.fire('data', { current });
// updating = false;
// });
} catch (e) {
// TODO show in UI // TODO show in UI
component = null; hasComponent = false;
const loc = getLocationFromStack(e.stack, dom.map); const loc = getLocationFromStack(e.stack, dom.map);
if (loc) { if (loc) {
@ -213,33 +182,21 @@
} }
error = e; error = e;
} });
}; };
pendingImports = missingImports.length; // Download the imports (sets them on iframe window when complete)
{
if (missingImports.length) {
let cancelled = false; let cancelled = false;
promise = replProxy.fetchImports(bundle);
promise = Promise.all( promise.cancel = () => { cancelled = true };
missingImports.map(id => fetchImport(id, iframe.contentWindow.curl).then(module => { promise.then(() => {
pendingImports -= 1;
return module;
}))
);
promise.cancel = () => cancelled = true;
promise
.then(() => {
if (cancelled) return; if (cancelled) return;
ready(); ready()
}) }).catch(e => {
.catch(e => {
if (cancelled) return; if (cancelled) return;
error = e; error = e;
}); });
} else {
ready();
} }
run = () => { run = () => {
@ -247,6 +204,10 @@
// TODO do we need to clear out SSR HTML? // TODO do we need to clear out SSR HTML?
createComponent(); createComponent();
props_handler = props => {
replProxy.bindProps(props)
};
replProxy.bindProps(props);
}; };
} }
@ -254,31 +215,13 @@
if (!bundle) return; // TODO can this ever happen? if (!bundle) return; // TODO can this ever happen?
if (promise) promise.cancel(); if (promise) promise.cancel();
toDestroy = component; toDestroy = hasComponent;
component = null; hasComponent = false;
init(); init();
}; };
props_handler = props => {
if (!component) {
// TODO can this happen?
console.error(`no component to bind to`);
return;
}
props.forEach(prop => {
// TODO should there be a public API for binding?
// e.g. `component.$watch(prop, handler)`?
// (answer: probably)
component.$$.bound[prop] = value => {
dispatch('binding', { prop, value });
values_store.update(values => Object.assign({}, values, {
[prop]: value
}));
};
});
};
}); });
}); });
@ -346,7 +289,7 @@
</style> </style>
<div class="iframe-container"> <div class="iframe-container">
<iframe title="Result" bind:this={refs.child} class="{error || pending || pendingImports ? 'greyed-out' : ''}" srcdoc=' <iframe title="Result" bind:this={refs.child} sandbox="allow-scripts allow-popups allow-forms allow-pointer-lock allow-top-navigation allow-modals" class="{error || pending || pendingImports ? 'greyed-out' : ''}" srcdoc='
<!doctype html> <!doctype html>
<html> <html>
<head> <head>
@ -355,6 +298,7 @@
<body> <body>
<script src="/curl.js"></script> <script src="/curl.js"></script>
<script>curl.config(&#123; dontAddFileExt: /./ });</script> <script>curl.config(&#123; dontAddFileExt: /./ });</script>
<script src="/repl-runner.js"></script>
</body> </body>
</html> </html>
'></iframe> '></iframe>

@ -1,9 +1,9 @@
<script> <script>
import SplitPane from '../SplitPane.html'; import SplitPane from '../SplitPane.svelte';
import Viewer from './Viewer.html'; import Viewer from './Viewer.svelte';
import CompilerOptions from './CompilerOptions.html'; import CompilerOptions from './CompilerOptions.svelte';
import PropEditor from './PropEditor.html'; import PropEditor from './PropEditor.svelte';
import CodeMirror from '../CodeMirror.html'; import CodeMirror from '../CodeMirror.svelte';
export let bundle; export let bundle;
export let js; export let js;

@ -2,13 +2,13 @@
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import * as fleece from 'golden-fleece'; import * as fleece from 'golden-fleece';
import SplitPane from './SplitPane.html'; import SplitPane from './SplitPane.svelte';
import CodeMirror from './CodeMirror.html'; import CodeMirror from './CodeMirror.svelte';
import Input from './Input/index.html'; import Input from './Input/index.svelte';
import Output from './Output/index.html'; import Output from './Output/index.svelte';
import InputOutputToggle from './InputOutputToggle.html'; import InputOutputToggle from './InputOutputToggle.svelte';
export let version = 'alpha'; // TODO change this to latest when the time comes export let version = 'beta'; // TODO change this to latest when the time comes
export let app; export let app;
export let embedded = false; export let embedded = false;
@ -53,7 +53,7 @@
let js = ''; let js = '';
let css = ''; let css = '';
let uid = 0; let uid = 0;
let props = null; let props = [];
let sourceErrorLoc; let sourceErrorLoc;
let runtimeErrorLoc; let runtimeErrorLoc;
@ -100,7 +100,7 @@
const selected = $selected_store; const selected = $selected_store;
if (selected.name === 'App') { if (selected.name === 'App') {
// App.html can't be removed // App.svelte can't be removed
selected.source = ''; selected.source = '';
// $selected_store.set(selected); // $selected_store.set(selected);
} else { } else {
@ -118,13 +118,13 @@
} }
function compile(component, options) { function compile(component, options) {
if (component.type === 'html') { if (component.type === 'svelte') {
workers.compiler.postMessage({ workers.compiler.postMessage({
type: 'compile', type: 'compile',
source: component.source, source: component.source,
options: Object.assign({ options: Object.assign({
name: component.name, name: component.name,
filename: `${component.name}.html` filename: `${component.name}.svelte`
}, options), }, options),
entry: component === $component_store[0] entry: component === $component_store[0]
}); });
@ -154,7 +154,7 @@
} }
function navigate(filename) { function navigate(filename) {
const name = filename.replace(/\.html$/, ''); const name = filename.replace(/\.svelte$/, '');
console.error(`TODO navigate`); console.error(`TODO navigate`);

@ -0,0 +1,90 @@
export default class ReplProxy {
constructor(iframe) {
this.iframe = iframe;
this.cmdId = 1;
this.pendingCmds = new Map();
this.onPropUpdate = null;
this.onFetchProgress = null;
this.handle_event = (ev) => this.handleReplMessage(ev);
window.addEventListener("message", this.handle_event, false);
}
destroy() {
window.removeEventListener("message", this.handle_event);
}
iframeCommand(command, args) {
return new Promise( (resolve, reject) => {
this.cmdId = this.cmdId + 1;
this.pendingCmds.set(this.cmdId, { resolve: resolve, reject: reject });
this.iframe.contentWindow.postMessage({
action: command,
cmdId: this.cmdId,
args: args
}, '*')
});
}
handleCommandMessage(cmdData) {
let action = cmdData.action;
let id = cmdData.cmdId;
let handler = this.pendingCmds.get(id);
if (handler) {
this.pendingCmds.delete(id);
if (action == "cmdError") {
let { message, stack } = cmdData;
let e = new Error(message);
e.stack = stack;
console.log("repl cmd fail");
handler.reject(e)
}
if (action == "cmdOk") {
handler.resolve(cmdData.args)
}
} else {
console.error("command not found", id, cmdData, [...this.pendingCmds.keys()]);
}
}
handleReplMessage(ev) {
let action = ev.data.action;
if ( action == "cmdError" || action == "cmdOk" ) {
this.handleCommandMessage(ev.data);
}
if (action == "prop_update") {
let { prop, value } = ev.data.args;
if (this.onPropUpdate)
this.onPropUpdate(prop, value)
}
if (action == "fetch_progress") {
if (this.onFetchProgress)
this.onFetchProgress(ev.data.args.remaining)
}
}
eval(script) {
return this.iframeCommand("eval", { script: script });
}
setProp(prop, value) {
return this.iframeCommand("set_prop", {prop, value})
}
bindProps(props) {
return this.iframeCommand("bind_props", { props: props })
}
handleLinks() {
return this.iframeCommand("catch_clicks", {});
}
fetchImports(bundle) {
return this.iframeCommand("fetch_imports", { bundle })
}
}

@ -1,10 +1,19 @@
<script context="module">
export function preload({ query }) {
return {
version: query.version || 'beta',
gist: query.gist,
demo: query.demo
};
}
</script>
<script> <script>
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import * as fleece from 'golden-fleece'; import * as fleece from 'golden-fleece';
import Repl from './_components/Repl.html'; import Repl from './_components/Repl.svelte';
export let query; export let version, gist, demo;
let version = query.version || 'alpha';
let app = { let app = {
components: [], components: [],
@ -14,14 +23,16 @@
let name = 'loading...'; let name = 'loading...';
onMount(() => { onMount(() => {
fetch(`https://unpkg.com/svelte@${version}/package.json`) if (version !== 'local') {
.then(r => r.json()) fetch(`https://unpkg.com/svelte@${version}/package.json`)
.then(pkg => { .then(r => r.json())
version = pkg.version; .then(pkg => {
}); version = pkg.version;
});
}
if (query.gist) { if (gist) {
fetch(`gist/${query.gist}`).then(r => r.json()).then(data => { fetch(`gist/${gist}`).then(r => r.json()).then(data => {
const { id, description, files } = data; const { id, description, files } = data;
name = description; name = description;
@ -46,20 +57,20 @@
source source
}; };
}) })
.filter(x => x.type === 'html' || x.type === 'js') .filter(x => x.type === 'svelte' || x.type === 'js')
.sort((a, b) => { .sort((a, b) => {
if (a.name === 'App' && a.type === 'html') return -1; if (a.name === 'App' && a.type === 'svelte') return -1;
if (b.name === 'App' && b.type === 'html') return 1; if (b.name === 'App' && b.type === 'svelte') return 1;
if (a.type !== b.type) return a.type === 'html' ? -1 : 1; if (a.type !== b.type) return a.type === 'svelte' ? -1 : 1;
return a.name < b.name ? -1 : 1; return a.name < b.name ? -1 : 1;
}); });
app = { components, values }; app = { components, values };
}); });
} else if (query.demo) { } else if (demo) {
const url = `api/examples/${query.demo}`; const url = `api/examples/${demo}`;
fetch(url).then(async response => { fetch(url).then(async response => {
if (response.ok) { if (response.ok) {

@ -1,16 +1,22 @@
<script context="module">
export function preload({ query }) {
return {
version: query.version || 'beta',
gist_id: query.gist,
demo: query.demo || 'hello-world'
};
}
</script>
<script> <script>
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { locate } from 'locate-character'; import { locate } from 'locate-character';
import * as fleece from 'golden-fleece'; import * as fleece from 'golden-fleece';
import AppControls from './_components/AppControls/index.html'; import AppControls from './_components/AppControls/index.svelte';
import Repl from './_components/Repl.html'; import Repl from './_components/Repl.svelte';
import examples from '../../../content/examples/manifest.json'; import examples from '../../../content/examples/manifest.json';
export let query; export let version, demo, gist_id;
let version = query.version;
let demo = query.demo || 'hello-world';
let gist_id = query.gist;
let gist; let gist;
@ -45,11 +51,13 @@
}); });
onMount(() => { onMount(() => {
fetch(`https://unpkg.com/svelte@${version || 'alpha'}/package.json`) if (version !== 'local') {
.then(r => r.json()) fetch(`https://unpkg.com/svelte@${version || 'beta'}/package.json`)
.then(pkg => { .then(r => r.json())
version = pkg.version; .then(pkg => {
}); version = pkg.version;
});
}
if (gist_id) { if (gist_id) {
fetch(`gist/${gist_id}`).then(r => r.json()).then(data => { fetch(`gist/${gist_id}`).then(r => r.json()).then(data => {
@ -72,18 +80,21 @@
values = tryParseData(source) || {}; values = tryParseData(source) || {};
} }
let type = file.slice(dot + 1);
if (type === 'html') type = 'svelte';
return { return {
name: file.slice(0, dot), name: file.slice(0, dot),
type: file.slice(dot + 1), type,
source source
}; };
}) })
.filter(x => x.type === 'html' || x.type === 'js') .filter(x => x.type === 'svelte' || x.type === 'js')
.sort((a, b) => { .sort((a, b) => {
if (a.name === 'App' && a.type === 'html') return -1; if (a.name === 'App' && a.type === 'svelte') return -1;
if (b.name === 'App' && b.type === 'html') return 1; if (b.name === 'App' && b.type === 'svelte') return 1;
if (a.type !== b.type) return a.type === 'html' ? -1 : 1; if (a.type !== b.type) return a.type === 'svelte' ? -1 : 1;
return a.name < b.name ? -1 : 1; return a.name < b.name ? -1 : 1;
}); });

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save