diff --git a/.eslintrc.json b/.eslintrc.json index 340b9a0e12..c5da7150a7 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,46 +1,45 @@ { - "root": true, - "rules": { - "indent": [ 2, "tab", { "SwitchCase": 1 } ], - "semi": [ 2, "always" ], - "keyword-spacing": [ 2, { "before": true, "after": true } ], - "space-before-blocks": [ 2, "always" ], - "no-mixed-spaces-and-tabs": [ 2, "smart-tabs" ], - "no-cond-assign": 0, - "no-unused-vars": 2, - "object-shorthand": [ 2, "always" ], - "no-const-assign": 2, - "no-class-assign": 2, - "no-this-before-super": 2, - "no-var": 2, - "no-unreachable": 2, - "valid-typeof": 2, - "quote-props": [ 2, "as-needed" ], - "one-var": [ 2, "never" ], - "prefer-arrow-callback": 2, - "prefer-const": [ 2, { "destructuring": "all" } ], - "arrow-spacing": 2, - "no-inner-declarations": 0 - }, - "env": { - "es6": true, - "browser": true, - "node": true, - "mocha": true - }, - "extends": [ - "eslint:recommended", - "plugin:import/errors", - "plugin:import/warnings" - ], - "plugins": [ - "html" - ], - "parserOptions": { - "ecmaVersion": 6, - "sourceType": "module" - }, - "settings": { - "import/core-modules": [ "svelte" ] - } + "root": true, + "rules": { + "indent": [2, "tab", { "SwitchCase": 1 }], + "semi": [2, "always"], + "keyword-spacing": [2, { "before": true, "after": true }], + "space-before-blocks": [2, "always"], + "no-mixed-spaces-and-tabs": [2, "smart-tabs"], + "no-cond-assign": 0, + "no-unused-vars": 2, + "object-shorthand": [2, "always"], + "no-const-assign": 2, + "no-class-assign": 2, + "no-this-before-super": 2, + "no-var": 2, + "no-unreachable": 2, + "valid-typeof": 2, + "quote-props": [2, "as-needed"], + "one-var": [2, "never"], + "prefer-arrow-callback": 2, + "prefer-const": [2, { "destructuring": "all" }], + "arrow-spacing": 2, + "no-inner-declarations": 0 + }, + "env": { + "es6": true, + "browser": true, + "node": true, + "mocha": true + }, + "extends": [ + "eslint:recommended", + "plugin:import/errors", + "plugin:import/warnings" + ], + "plugins": ["svelte3"], + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module" + }, + "settings": { + "import/core-modules": ["svelte"], + "svelte3/extensions": ["html"] + } } diff --git a/.gitignore b/.gitignore index dbed468f15..1499a0e697 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,8 @@ node_modules /test/sourcemaps/samples/*/output.css.map /yarn-error.log _actual*.* -_*/ + +/site/cypress/screenshots/ +/site/__sapper__/ +/site/.env +/site/.sessions \ No newline at end of file diff --git a/package.json b/package.json index 9cac947be5..0f43ed0019 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.0.0-alpha6", + "version": "3.0.0-alpha8", "description": "The magical disappearing UI framework", "module": "index.mjs", "main": "index.js", diff --git a/site/.eslintrc.json b/site/.eslintrc.json new file mode 100644 index 0000000000..cb0d06a2ec --- /dev/null +++ b/site/.eslintrc.json @@ -0,0 +1,45 @@ +{ + "root": true, + "rules": { + "indent": [2, "tab", { "SwitchCase": 1 }], + "semi": [2, "always"], + "keyword-spacing": [2, { "before": true, "after": true }], + "space-before-blocks": [2, "always"], + "no-mixed-spaces-and-tabs": [2, "smart-tabs"], + "no-cond-assign": 0, + "no-unused-vars": 2, + "object-shorthand": [2, "always"], + "no-const-assign": 2, + "no-class-assign": 2, + "no-this-before-super": 2, + "no-var": 2, + "no-unreachable": 2, + "valid-typeof": 2, + "quote-props": [2, "as-needed"], + "one-var": [2, "never"], + "prefer-arrow-callback": 2, + "prefer-const": [2, { "destructuring": "all" }], + "arrow-spacing": 2, + "no-inner-declarations": 0 + }, + "env": { + "es6": true, + "browser": true, + "node": true, + "mocha": true + }, + "extends": [ + "eslint:recommended", + "plugin:import/errors", + "plugin:import/warnings" + ], + "plugins": ["svelte3"], + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module" + }, + "settings": { + "import/core-modules": ["svelte"], + "svelte3/extensions": [".html"] + } +} diff --git a/site/.travis.yml b/site/.travis.yml new file mode 100644 index 0000000000..8ad1fc38c8 --- /dev/null +++ b/site/.travis.yml @@ -0,0 +1,11 @@ +sudo: false +language: node_js +node_js: + - "stable" +env: + global: + - BUILD_TIMEOUT=10000 +install: + - npm install + - npm install cypress + diff --git a/site/README.md b/site/README.md new file mode 100644 index 0000000000..eb3c7013ce --- /dev/null +++ b/site/README.md @@ -0,0 +1,75 @@ +# sapper-template-rollup + +A version of the default [Sapper](https://github.com/sveltejs/sapper) template that uses Rollup instead of webpack. To clone it and get started: + +```bash +npx degit sveltejs/sapper-template#rollup my-app +cd my-app +npm install # or yarn! +npm run dev +``` + +Open up [localhost:3000](http://localhost:3000) and start clicking around. + +Consult [sapper.svelte.technology](https://sapper.svelte.technology) for help getting started. + +*[Click here for the webpack version of this template](https://github.com/sveltejs/sapper-template)* + +## Structure + +Sapper expects to find three directories in the root of your project — `app`, `assets` and `routes`. + + +### 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'; +``` + +...so that you can cache them (though you can choose not to, for example if you don't want to cache very large files). + + +### 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). diff --git a/site/appveyor.yml b/site/appveyor.yml new file mode 100644 index 0000000000..e75da3bfae --- /dev/null +++ b/site/appveyor.yml @@ -0,0 +1,18 @@ +version: "{build}" + +shallow_clone: true + +init: + - git config --global core.autocrlf false + +build: off + +environment: + matrix: + # node.js + - nodejs_version: stable + +install: + - ps: Install-Product node $env:nodejs_version + - npm install cypress + - npm install diff --git a/site/content/blog/2016-11-26-frameworks-without-the-framework.md b/site/content/blog/2016-11-26-frameworks-without-the-framework.md new file mode 100644 index 0000000000..9c663a01ba --- /dev/null +++ b/site/content/blog/2016-11-26-frameworks-without-the-framework.md @@ -0,0 +1,56 @@ +--- +title: Frameworks without the framework: why didn't we think of this sooner? +description: You can't write serious applications in vanilla JavaScript without hitting a complexity wall. But a compiler can do it for you. +pubdate: 2016-11-26 +author: Rich Harris +authorURL: https://twitter.com/Rich_Harris +--- + +> Wait, this new framework has a *runtime*? Ugh. Thanks, I'll pass. +> **– front end developers in 2018** + +We're shipping too much code to our users. Like a lot of front end developers, I've been in denial about that fact, thinking that it was fine to serve 100kb of JavaScript on page load – just use [one less .jpg!](https://twitter.com/miketaylr/status/227056824275333120) – and that what *really* mattered was performance once your app was already interactive. + +But I was wrong. 100kb of .js isn't equivalent to 100kb of .jpg. It's not just the network time that'll kill your app's startup performance, but the time spent parsing and evaluating your script, during which time the browser becomes completely unresponsive. On mobile, those milliseconds rack up very quickly. + +If you're not convinced that this is a problem, follow [Alex Russell](https://twitter.com/slightlylate) on Twitter. Alex [hasn't been making many friends in the framework community lately](https://twitter.com/slightlylate/status/728355959022587905), but he's not wrong. But the proposed alternative to using frameworks like Angular, React and Ember – [Polymer](https://www.polymer-project.org/1.0/) – hasn't yet gained traction in the front end world, and it's certainly not for a lack of marketing. + +Perhaps we need to rethink the whole thing. + + +## What problem do frameworks *really* solve? + +The common view is that frameworks make it easier to manage the complexity of your code: the framework abstracts away all the fussy implementation details with techniques like virtual DOM diffing. But that's not really true. At best, frameworks *move the complexity around*, away from code that you had to write and into code you didn't. + +Instead, the reason that ideas like React are so wildly and deservedly successful is that they make it easier to manage the complexity of your *concepts*. Frameworks are primarily a tool for structuring your thoughts, not your code. + +Given that, what if the framework *didn't actually run in the browser*? What if, instead, it converted your application into pure vanilla JavaScript, just like Babel converts ES2016+ to ES5? You'd pay no upfront cost of shipping a hefty runtime, and your app would get seriously fast, because there'd be no layers of abstraction between your app and the browser. + + +## Introducing Svelte + +Svelte is a new framework that does exactly that. You write your components using HTML, CSS and JavaScript (plus a few extra bits you can [learn in under 5 minutes](/guide)), and during your build process Svelte compiles them into tiny standalone JavaScript modules. By statically analysing the component template, we can make sure that the browser does as little work as possible. + +The [Svelte implementation of TodoMVC](http://svelte-todomvc.surge.sh/) weighs 3.6kb zipped. For comparison, React plus ReactDOM *without any app code* weighs about 45kb zipped. It takes about 10x as long for the browser just to evaluate React as it does for Svelte to be up and running with an interactive TodoMVC. + +And once your app *is* up and running, according to [js-framework-benchmark](https://github.com/krausest/js-framework-benchmark) **Svelte is fast as heck**. It's faster than React. It's faster than Vue. It's faster than Angular, or Ember, or Ractive, or Preact, or Riot, or Mithril. It's competitive with Inferno, which is probably the fastest UI framework in the world, for now, because [Dominic Gannaway](https://twitter.com/trueadm) is a wizard. (Svelte is slower at removing elements. We're [working on it](https://github.com/sveltejs/svelte/issues/26).) + +It's basically as fast as vanilla JS, which makes sense because it *is* vanilla JS – just vanilla JS that you didn't have to write. + + +## But that's not the important thing + +Well, it *is* important – performance matters a great deal. What's really exciting about this approach, though, is that we can finally solve some of the thorniest problems in web development. + +Consider interoperability. Want to `npm install cool-calendar-widget` and use it in your app? Previously, you could only do that if you were already using (a correct version of) the framework that the widget was designed for – if `cool-calendar-widget` was built in React and you're using Angular then, well, hard cheese. But if the widget author used Svelte, apps that use it can be built using whatever technology you like. (On the TODO list: a way to convert Svelte components into web components.) + +Or [code splitting](https://twitter.com/samccone/status/797528710085652480). It's a great idea (only load the code the user needs for the initial view, then get the rest later), but there's a problem – even if you only initially serve one React component instead of 100, *you still have to serve React itself*. With Svelte, code splitting can be much more effective, because the framework is embedded in the component, and the component is tiny. + +Finally, something I've wrestled with a great deal as an open source maintainer: your users always want *their* features prioritised, and underestimate the cost of those features to people who don't need them. A framework author must always balance the long-term health of the project with the desire to meet their users' needs. That's incredibly difficult, because it's hard to anticipate – much less articulate – the consequences of incremental bloat, and it takes serious soft skills to tell people (who may have been enthusiastically evangelising your tool up to that point) that their feature isn't important enough. But with an approach like Svelte's, many features can be added with absolutely no cost to people who don't use them, because the code that implements those features just doesn't get generated by the compiler if it's unnecessary. + + +## We're just getting started + +Svelte is very new. There's a lot of work still left to do – creating build tool integrations, adding a server-side renderer, hot reloading, transitions, more documentation and examples, starter kits, and so on. + +But you can already build rich components with it, which is why we've gone straight to a stable 1.0.0 release. [Read the guide](/guide), [try it out in the REPL](/repl), and head over to [GitHub](https://github.com/sveltejs/svelte) to help kickstart the next era of front end development. diff --git a/site/content/blog/2017-08-07-the-easiest-way-to-get-started.md b/site/content/blog/2017-08-07-the-easiest-way-to-get-started.md new file mode 100644 index 0000000000..08f5b1bf02 --- /dev/null +++ b/site/content/blog/2017-08-07-the-easiest-way-to-get-started.md @@ -0,0 +1,66 @@ +--- +title: The easiest way to get started with Svelte +description: This'll only take a minute. +pubdate: 2017-08-07 +author: Rich Harris +authorURL: https://twitter.com/Rich_Harris +--- + +Svelte is a [new kind of framework](/blog/frameworks-without-the-framework). Rather than putting a ` + + + +
+ + +
+ + + {#each circles as circle} + + {/each} + + +{#if adjusting} +
+

adjust diameter of circle at {selected.cx}, {selected.cy}

+ +
+{/if} \ No newline at end of file diff --git a/site/content/examples/7guis-circles/data.json5 b/site/content/examples/7guis-circles/data.json5 new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/site/content/examples/7guis-circles/data.json5 @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/site/content/examples/7guis-counter/App.html b/site/content/examples/7guis-counter/App.html new file mode 100644 index 0000000000..385a3c9749 --- /dev/null +++ b/site/content/examples/7guis-counter/App.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/site/content/examples/7guis-counter/data.json5 b/site/content/examples/7guis-counter/data.json5 new file mode 100644 index 0000000000..f30dc46c11 --- /dev/null +++ b/site/content/examples/7guis-counter/data.json5 @@ -0,0 +1,3 @@ +{ + "count": 0 +} \ No newline at end of file diff --git a/site/content/examples/7guis-crud/App.html b/site/content/examples/7guis-crud/App.html new file mode 100644 index 0000000000..82ce893aac --- /dev/null +++ b/site/content/examples/7guis-crud/App.html @@ -0,0 +1,84 @@ + + + + + + + + + + + + + +
+ + + +
\ No newline at end of file diff --git a/site/content/examples/7guis-crud/data.json5 b/site/content/examples/7guis-crud/data.json5 new file mode 100644 index 0000000000..c3e6dc3e67 --- /dev/null +++ b/site/content/examples/7guis-crud/data.json5 @@ -0,0 +1,16 @@ +{ + "people": [ + { + "first": "Hans", + "last": "Emil" + }, + { + "first": "Max", + "last": "Mustermann" + }, + { + "first": "Roman", + "last": "Tisch" + } + ] +} \ No newline at end of file diff --git a/site/content/examples/7guis-flight-booker/App.html b/site/content/examples/7guis-flight-booker/App.html new file mode 100644 index 0000000000..1cb45371e1 --- /dev/null +++ b/site/content/examples/7guis-flight-booker/App.html @@ -0,0 +1,61 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/site/content/examples/7guis-flight-booker/data.json5 b/site/content/examples/7guis-flight-booker/data.json5 new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/site/content/examples/7guis-flight-booker/data.json5 @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/site/content/examples/7guis-temperature/App.html b/site/content/examples/7guis-temperature/App.html new file mode 100644 index 0000000000..216c3461eb --- /dev/null +++ b/site/content/examples/7guis-temperature/App.html @@ -0,0 +1,24 @@ + + °c = + °f + + + + diff --git a/site/content/examples/7guis-temperature/data.json5 b/site/content/examples/7guis-temperature/data.json5 new file mode 100644 index 0000000000..21950c8dfb --- /dev/null +++ b/site/content/examples/7guis-temperature/data.json5 @@ -0,0 +1,3 @@ +{ + "celsius": 0 +} \ No newline at end of file diff --git a/site/content/examples/7guis-timer/App.html b/site/content/examples/7guis-timer/App.html new file mode 100644 index 0000000000..47afd6c48b --- /dev/null +++ b/site/content/examples/7guis-timer/App.html @@ -0,0 +1,41 @@ + + + + + + +
{(elapsed / 1000).toFixed(1)}s
+ + + + \ No newline at end of file diff --git a/site/content/examples/7guis-timer/data.json5 b/site/content/examples/7guis-timer/data.json5 new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/site/content/examples/7guis-timer/data.json5 @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/site/content/examples/await-block/App.html b/site/content/examples/await-block/App.html new file mode 100644 index 0000000000..dd3ef25bfc --- /dev/null +++ b/site/content/examples/await-block/App.html @@ -0,0 +1,24 @@ + + + + +{#if promise} + {#await promise} +

wait for it...

+ {:then answer} +

the answer is {answer}!

+ {:catch error} +

well that's odd

+ {/await} +{/if} \ No newline at end of file diff --git a/site/content/examples/await-block/data.json5 b/site/content/examples/await-block/data.json5 new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/site/content/examples/await-block/data.json5 @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/site/content/examples/bar-chart/App.html b/site/content/examples/bar-chart/App.html new file mode 100644 index 0000000000..0098454e91 --- /dev/null +++ b/site/content/examples/bar-chart/App.html @@ -0,0 +1,112 @@ + + + + +
+

US birthrate by year {width}/{height}

+ + + + {#each yTicks as tick} + + + {tick} {tick === 20 ? ' per 1,000 population' : ''} + + {/each} + + + + + {#each points as point, i} + + {width > 380 ? point.year : formatMobile(point.year)} + + {/each} + + + + {#each points as point, i} + + {/each} + + +
\ No newline at end of file diff --git a/site/content/examples/bar-chart/data.json5 b/site/content/examples/bar-chart/data.json5 new file mode 100644 index 0000000000..389c90a7d1 --- /dev/null +++ b/site/content/examples/bar-chart/data.json5 @@ -0,0 +1,28 @@ +{ + "points": [ + { + "year": 1990, + "birthrate": 16.7 + }, + { + "year": 1995, + "birthrate": 14.6 + }, + { + "year": 2000, + "birthrate": 14.4 + }, + { + "year": 2005, + "birthrate": 14 + }, + { + "year": 2010, + "birthrate": 13 + }, + { + "year": 2015, + "birthrate": 12.4 + } + ] +} \ No newline at end of file diff --git a/site/content/examples/binding-input-checkbox-group/App.html b/site/content/examples/binding-input-checkbox-group/App.html new file mode 100644 index 0000000000..617e71e17d --- /dev/null +++ b/site/content/examples/binding-input-checkbox-group/App.html @@ -0,0 +1,16 @@ + + + + + + +

{selected.join(', ') || 'nothing'} selected

\ No newline at end of file diff --git a/site/content/examples/binding-input-checkbox-group/data.json5 b/site/content/examples/binding-input-checkbox-group/data.json5 new file mode 100644 index 0000000000..2285e3abfa --- /dev/null +++ b/site/content/examples/binding-input-checkbox-group/data.json5 @@ -0,0 +1,3 @@ +{ + "selected": ["blue"] +} \ No newline at end of file diff --git a/site/content/examples/binding-input-checkbox/App.html b/site/content/examples/binding-input-checkbox/App.html new file mode 100644 index 0000000000..b8facdd53b --- /dev/null +++ b/site/content/examples/binding-input-checkbox/App.html @@ -0,0 +1,17 @@ +{#each todos as todo} +
+ + +
+{/each} + + \ No newline at end of file diff --git a/site/content/examples/binding-input-checkbox/data.json5 b/site/content/examples/binding-input-checkbox/data.json5 new file mode 100644 index 0000000000..4afee559a5 --- /dev/null +++ b/site/content/examples/binding-input-checkbox/data.json5 @@ -0,0 +1,16 @@ +{ + "todos": [ + { + "description": "Buy some milk", + "done": true + }, + { + "description": "Do the laundry", + "done": true + }, + { + "description": "Find life's true purpose", + "done": false + } + ] +} \ No newline at end of file diff --git a/site/content/examples/binding-input-numeric/App.html b/site/content/examples/binding-input-numeric/App.html new file mode 100644 index 0000000000..566c16006c --- /dev/null +++ b/site/content/examples/binding-input-numeric/App.html @@ -0,0 +1,12 @@ + + + + +

{a} * {b} = {a * b}

+ + \ No newline at end of file diff --git a/site/content/examples/binding-input-numeric/data.json5 b/site/content/examples/binding-input-numeric/data.json5 new file mode 100644 index 0000000000..1aa99a76cd --- /dev/null +++ b/site/content/examples/binding-input-numeric/data.json5 @@ -0,0 +1,4 @@ +{ + "a": 5, + "b": 5 +} \ No newline at end of file diff --git a/site/content/examples/binding-input-radio/App.html b/site/content/examples/binding-input-radio/App.html new file mode 100644 index 0000000000..f83813754f --- /dev/null +++ b/site/content/examples/binding-input-radio/App.html @@ -0,0 +1,16 @@ + + + + + + +

selected {selected}

\ No newline at end of file diff --git a/site/content/examples/binding-input-radio/data.json5 b/site/content/examples/binding-input-radio/data.json5 new file mode 100644 index 0000000000..961184190e --- /dev/null +++ b/site/content/examples/binding-input-radio/data.json5 @@ -0,0 +1,3 @@ +{ + "selected": "blue" +} \ No newline at end of file diff --git a/site/content/examples/binding-input-text/App.html b/site/content/examples/binding-input-text/App.html new file mode 100644 index 0000000000..5d81d1e564 --- /dev/null +++ b/site/content/examples/binding-input-text/App.html @@ -0,0 +1,2 @@ + +

Hello {name || 'stranger'}!

\ No newline at end of file diff --git a/site/content/examples/binding-input-text/data.json5 b/site/content/examples/binding-input-text/data.json5 new file mode 100644 index 0000000000..393554903d --- /dev/null +++ b/site/content/examples/binding-input-text/data.json5 @@ -0,0 +1,3 @@ +{ + "name": "" +} \ No newline at end of file diff --git a/site/content/examples/binding-media-elements/App.html b/site/content/examples/binding-media-elements/App.html new file mode 100644 index 0000000000..a8fcb64f64 --- /dev/null +++ b/site/content/examples/binding-media-elements/App.html @@ -0,0 +1,96 @@ + + + + + + +

THX Deep Note

+
+ play/pause button + {format(t)} + {format(d)} +
+ +
+

THX Deep Note

+
+ play/pause button + {format(t)} + {format(d)} +
+
+ + diff --git a/site/content/examples/binding-media-elements/data.json5 b/site/content/examples/binding-media-elements/data.json5 new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/site/content/examples/binding-media-elements/data.json5 @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/site/content/examples/binding-textarea/App.html b/site/content/examples/binding-textarea/App.html new file mode 100644 index 0000000000..4fd46f2625 --- /dev/null +++ b/site/content/examples/binding-textarea/App.html @@ -0,0 +1,15 @@ + + + +
{@html marked(markdown)}
+ + \ No newline at end of file diff --git a/site/content/examples/binding-textarea/data.json5 b/site/content/examples/binding-textarea/data.json5 new file mode 100644 index 0000000000..12268060fb --- /dev/null +++ b/site/content/examples/binding-textarea/data.json5 @@ -0,0 +1,3 @@ +{ + "markdown": "# Markdown editor\n\nTODOs:\n\n* make a Svelte app\n* think of a third item for this list" +} \ No newline at end of file diff --git a/site/content/examples/each-blocks/App.html b/site/content/examples/each-blocks/App.html new file mode 100644 index 0000000000..b8ba07a5e9 --- /dev/null +++ b/site/content/examples/each-blocks/App.html @@ -0,0 +1,7 @@ +

Cats of YouTube

+ + \ No newline at end of file diff --git a/site/content/examples/each-blocks/data.json5 b/site/content/examples/each-blocks/data.json5 new file mode 100644 index 0000000000..41eff03354 --- /dev/null +++ b/site/content/examples/each-blocks/data.json5 @@ -0,0 +1,16 @@ +{ + "cats": [ + { + "name": "Keyboard Cat", + "video": "https://www.youtube.com/watch?v=J---aiyznGQ" + }, + { + "name": "Maru", + "video": "https://www.youtube.com/watch?v=z_AbfPXTKms" + }, + { + "name": "Henri The Existential Cat", + "video": "https://www.youtube.com/watch?v=OUtn3pvWmpg" + } + ] +} \ No newline at end of file diff --git a/site/content/examples/hacker-news/App.html b/site/content/examples/hacker-news/App.html new file mode 100644 index 0000000000..2f8283fdf2 --- /dev/null +++ b/site/content/examples/hacker-news/App.html @@ -0,0 +1,59 @@ + + + + + + +
+ {#if item} + + {:elseif page} + + {/if} +
\ No newline at end of file diff --git a/site/content/examples/hacker-news/Comment.html b/site/content/examples/hacker-news/Comment.html new file mode 100644 index 0000000000..241499dcb3 --- /dev/null +++ b/site/content/examples/hacker-news/Comment.html @@ -0,0 +1,28 @@ + + +
+

{comment.user} {comment.time_ago}

+ + {@html comment.content} + +
+ {#each comment.comments as child} + + {/each} +
+
\ No newline at end of file diff --git a/site/content/examples/hacker-news/Item.html b/site/content/examples/hacker-news/Item.html new file mode 100644 index 0000000000..9ce2f739f8 --- /dev/null +++ b/site/content/examples/hacker-news/Item.html @@ -0,0 +1,43 @@ + + + + +« back + + + +
+ {#each item.comments as comment} + + {/each} +
\ No newline at end of file diff --git a/site/content/examples/hacker-news/List.html b/site/content/examples/hacker-news/List.html new file mode 100644 index 0000000000..361db9905d --- /dev/null +++ b/site/content/examples/hacker-news/List.html @@ -0,0 +1,49 @@ + + + + +{#if items} + {#each items as item, i} + + {/each} + + page {page + 1} +{:else} +

loading...

+{/if} \ No newline at end of file diff --git a/site/content/examples/hacker-news/Summary.html b/site/content/examples/hacker-news/Summary.html new file mode 100644 index 0000000000..0ff25964f7 --- /dev/null +++ b/site/content/examples/hacker-news/Summary.html @@ -0,0 +1,38 @@ + + + + + \ No newline at end of file diff --git a/site/content/examples/hacker-news/data.json5 b/site/content/examples/hacker-news/data.json5 new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/site/content/examples/hacker-news/data.json5 @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/site/content/examples/hello-world/App.html b/site/content/examples/hello-world/App.html new file mode 100644 index 0000000000..f6882abc09 --- /dev/null +++ b/site/content/examples/hello-world/App.html @@ -0,0 +1,14 @@ +

Hello {name}!

+ + \ No newline at end of file diff --git a/site/content/examples/hello-world/data.json5 b/site/content/examples/hello-world/data.json5 new file mode 100644 index 0000000000..3949aecc6a --- /dev/null +++ b/site/content/examples/hello-world/data.json5 @@ -0,0 +1,3 @@ +{ + "name": "world" +} \ No newline at end of file diff --git a/site/content/examples/if-blocks/App.html b/site/content/examples/if-blocks/App.html new file mode 100644 index 0000000000..57de219159 --- /dev/null +++ b/site/content/examples/if-blocks/App.html @@ -0,0 +1,5 @@ +{#if foo} +

foo!

+{:else} +

not foo!

+{/if} \ No newline at end of file diff --git a/site/content/examples/if-blocks/data.json5 b/site/content/examples/if-blocks/data.json5 new file mode 100644 index 0000000000..c803db4749 --- /dev/null +++ b/site/content/examples/if-blocks/data.json5 @@ -0,0 +1,3 @@ +{ + "foo": true +} \ No newline at end of file diff --git a/site/content/examples/immutable/App.html b/site/content/examples/immutable/App.html new file mode 100644 index 0000000000..db7795f43f --- /dev/null +++ b/site/content/examples/immutable/App.html @@ -0,0 +1,40 @@ + + + + +

Immutable

+{#each todos as todo} + +{/each} + +

Mutable

+{#each todos as todo} + +{/each} diff --git a/site/content/examples/immutable/ImmutableTodo.html b/site/content/examples/immutable/ImmutableTodo.html new file mode 100644 index 0000000000..bc2403f048 --- /dev/null +++ b/site/content/examples/immutable/ImmutableTodo.html @@ -0,0 +1,15 @@ + + + + + +{todo.text} diff --git a/site/content/examples/immutable/MutableTodo.html b/site/content/examples/immutable/MutableTodo.html new file mode 100644 index 0000000000..948aaea805 --- /dev/null +++ b/site/content/examples/immutable/MutableTodo.html @@ -0,0 +1,13 @@ + + + +{todo.text} diff --git a/site/content/examples/immutable/data.json5 b/site/content/examples/immutable/data.json5 new file mode 100644 index 0000000000..a0ed59d82b --- /dev/null +++ b/site/content/examples/immutable/data.json5 @@ -0,0 +1,7 @@ +{ + todos: [ + { id: 1, done: true, text: "wash the car" }, + { id: 2, done: false, text: "take the dog for a walk" }, + { id: 3, done: false, text: "mow the lawn" } + ] +} diff --git a/site/content/examples/immutable/flash.js b/site/content/examples/immutable/flash.js new file mode 100644 index 0000000000..d8c251f32a --- /dev/null +++ b/site/content/examples/immutable/flash.js @@ -0,0 +1,13 @@ +import { afterUpdate } from 'svelte'; + +export default function flash(fn) { + afterUpdate(() => { + const span = fn(); + + span.style.color = 'red'; + + setTimeout(() => { + span.style.color = 'black'; + }, 400); + }); +} \ No newline at end of file diff --git a/site/content/examples/line-chart/App.html b/site/content/examples/line-chart/App.html new file mode 100644 index 0000000000..41881f8a8f --- /dev/null +++ b/site/content/examples/line-chart/App.html @@ -0,0 +1,129 @@ + + + + +
+

Arctic sea ice minimum

+ + + + + {#each yTicks as tick} + + + {tick} {tick === 8 ? ' million sq km' : ''} + + {/each} + + + + + {#each xTicks as tick} + + + {width > 380 ? tick : formatMobile(tick)} + + {/each} + + + + + + + +

Average September extent. Source: NSIDC/NASA +

+ + \ No newline at end of file diff --git a/site/content/examples/line-chart/data.json5 b/site/content/examples/line-chart/data.json5 new file mode 100644 index 0000000000..88e7f02f35 --- /dev/null +++ b/site/content/examples/line-chart/data.json5 @@ -0,0 +1,156 @@ +{ + "points": [ + { + "x": 1979, + "y": 7.19 + }, + { + "x": 1980, + "y": 7.83 + }, + { + "x": 1981, + "y": 7.24 + }, + { + "x": 1982, + "y": 7.44 + }, + { + "x": 1983, + "y": 7.51 + }, + { + "x": 1984, + "y": 7.1 + }, + { + "x": 1985, + "y": 6.91 + }, + { + "x": 1986, + "y": 7.53 + }, + { + "x": 1987, + "y": 7.47 + }, + { + "x": 1988, + "y": 7.48 + }, + { + "x": 1989, + "y": 7.03 + }, + { + "x": 1990, + "y": 6.23 + }, + { + "x": 1991, + "y": 6.54 + }, + { + "x": 1992, + "y": 7.54 + }, + { + "x": 1993, + "y": 6.5 + }, + { + "x": 1994, + "y": 7.18 + }, + { + "x": 1995, + "y": 6.12 + }, + { + "x": 1996, + "y": 7.87 + }, + { + "x": 1997, + "y": 6.73 + }, + { + "x": 1998, + "y": 6.55 + }, + { + "x": 1999, + "y": 6.23 + }, + { + "x": 2000, + "y": 6.31 + }, + { + "x": 2001, + "y": 6.74 + }, + { + "x": 2002, + "y": 5.95 + }, + { + "x": 2003, + "y": 6.13 + }, + { + "x": 2004, + "y": 6.04 + }, + { + "x": 2005, + "y": 5.56 + }, + { + "x": 2006, + "y": 5.91 + }, + { + "x": 2007, + "y": 4.29 + }, + { + "x": 2008, + "y": 4.72 + }, + { + "x": 2009, + "y": 5.38 + }, + { + "x": 2010, + "y": 4.92 + }, + { + "x": 2011, + "y": 4.61 + }, + { + "x": 2012, + "y": 3.62 + }, + { + "x": 2013, + "y": 5.35 + }, + { + "x": 2014, + "y": 5.28 + }, + { + "x": 2015, + "y": 4.63 + }, + { + "x": 2016, + "y": 4.72 + } + ] +} \ No newline at end of file diff --git a/site/content/examples/manifest.json b/site/content/examples/manifest.json new file mode 100644 index 0000000000..f14f803ea8 --- /dev/null +++ b/site/content/examples/manifest.json @@ -0,0 +1,175 @@ +[ + { + "name": "Basics", + "examples": [ + { + "slug": "hello-world", + "title": "Hello World!" + }, + { + "slug": "if-blocks", + "title": "If blocks" + }, + { + "slug": "each-blocks", + "title": "Each blocks" + }, + { + "slug": "scoped-styles", + "title": "Scoped styles" + } + ] + }, + { + "name": "Two-way bindings", + "examples": [ + { + "slug": "binding-input-text", + "title": "Text input" + }, + { + "slug": "binding-input-numeric", + "title": "Numeric input" + }, + { + "slug": "binding-textarea", + "title": "Textarea" + }, + { + "slug": "binding-input-checkbox", + "title": "Checkbox input" + }, + { + "slug": "binding-input-checkbox-group", + "title": "Checkbox input (grouped)" + }, + { + "slug": "binding-input-radio", + "title": "Radio input" + }, + { + "slug": "binding-media-elements", + "title": "Media elements" + } + ] + }, + { + "name": "Nested components", + "examples": [ + { + "slug": "nested-components", + "title": "Nested components" + }, + { + "slug": "modal-with-slot", + "title": "Modal with " + }, + { + "slug": "self-references", + "title": "Self-references" + } + ] + }, + { + "name": "SVG and dataviz", + "examples": [ + { + "slug": "svg-clock", + "title": "SVG Clock" + }, + { + "slug": "line-chart", + "title": "Line/area chart" + }, + { + "slug": "bar-chart", + "title": "Bar chart" + }, + { + "slug": "scatterplot", + "title": "Scatterplot" + } + ] + }, + { + "name": "Transitions", + "examples": [ + { + "slug": "transitions-fade", + "title": "Simple fade" + }, + { + "slug": "transitions-fly", + "title": "Parameterised" + }, + { + "slug": "transitions-in-out", + "title": "In and out" + }, + { + "slug": "transitions-custom", + "title": "Custom CSS" + } + ] + }, + { + "name": "Async data", + "examples": [ + { + "slug": "await-block", + "title": "Await block" + } + ] + }, + { + "name": "7guis", + "examples": [ + { + "slug": "7guis-counter", + "title": "Counter" + }, + { + "slug": "7guis-temperature", + "title": "Temperature converter" + }, + { + "slug": "7guis-flight-booker", + "title": "Flight booker" + }, + { + "slug": "7guis-timer", + "title": "Timer" + }, + { + "slug": "7guis-crud", + "title": "CRUD" + }, + { + "slug": "7guis-circles", + "title": "Circles" + } + ] + }, + { + "name": "<:Window>", + "examples": [ + { + "slug": "parallax", + "title": "Parallax" + } + ] + }, + { + "name": "Miscellaneous", + "examples": [ + { + "slug": "hacker-news", + "title": "Hacker News" + }, + { + "slug": "immutable", + "title": "Immutable data" + } + ] + } +] diff --git a/site/content/examples/modal-with-slot/App.html b/site/content/examples/modal-with-slot/App.html new file mode 100644 index 0000000000..621dfdd203 --- /dev/null +++ b/site/content/examples/modal-with-slot/App.html @@ -0,0 +1,29 @@ + + +{#if showModal} + +

+ modal + adjective mod·al \ˈmō-dəl\ +

+ +
    +
  1. of or relating to modality in logic
  2. +
  3. containing provisions as to the mode of procedure or the manner of taking effect —used of a contract or legacy
  4. +
  5. of or relating to a musical mode
  6. +
  7. of or relating to structure as opposed to substance
  8. +
  9. of, relating to, or constituting a grammatical form or category characteristically indicating predication
  10. +
  11. of or relating to a statistical mode
  12. +
+ + merriam-webster.com +
+{:else} + +{/if} \ No newline at end of file diff --git a/site/content/examples/modal-with-slot/Modal.html b/site/content/examples/modal-with-slot/Modal.html new file mode 100644 index 0000000000..87f1b02299 --- /dev/null +++ b/site/content/examples/modal-with-slot/Modal.html @@ -0,0 +1,45 @@ + + + + + + + diff --git a/site/content/examples/modal-with-slot/data.json5 b/site/content/examples/modal-with-slot/data.json5 new file mode 100644 index 0000000000..18cc32884c --- /dev/null +++ b/site/content/examples/modal-with-slot/data.json5 @@ -0,0 +1,3 @@ +{ + "showModal": true +} \ No newline at end of file diff --git a/site/content/examples/nested-components/App.html b/site/content/examples/nested-components/App.html new file mode 100644 index 0000000000..a00cb18a41 --- /dev/null +++ b/site/content/examples/nested-components/App.html @@ -0,0 +1,6 @@ + + +

This is a top-level element.

+ \ No newline at end of file diff --git a/site/content/examples/nested-components/Nested.html b/site/content/examples/nested-components/Nested.html new file mode 100644 index 0000000000..74ee56a5ed --- /dev/null +++ b/site/content/examples/nested-components/Nested.html @@ -0,0 +1 @@ +

And this is a nested component.

\ No newline at end of file diff --git a/site/content/examples/nested-components/data.json5 b/site/content/examples/nested-components/data.json5 new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/site/content/examples/nested-components/data.json5 @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/site/content/examples/parallax/App.html b/site/content/examples/parallax/App.html new file mode 100644 index 0000000000..c7781e7a3f --- /dev/null +++ b/site/content/examples/parallax/App.html @@ -0,0 +1,76 @@ + + + + + + +
+ + + + + +
+ +
+ (scroll down) + parallax has never been this easy +
+ + \ No newline at end of file diff --git a/site/content/examples/parallax/data.json5 b/site/content/examples/parallax/data.json5 new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/site/content/examples/parallax/data.json5 @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/site/content/examples/scatterplot/App.html b/site/content/examples/scatterplot/App.html new file mode 100644 index 0000000000..e45d6f8f7f --- /dev/null +++ b/site/content/examples/scatterplot/App.html @@ -0,0 +1,28 @@ + + +
+

Anscombe's quartet

+ + + + + +
+ + \ No newline at end of file diff --git a/site/content/examples/scatterplot/Scatterplot.html b/site/content/examples/scatterplot/Scatterplot.html new file mode 100644 index 0000000000..6f62bc9c6f --- /dev/null +++ b/site/content/examples/scatterplot/Scatterplot.html @@ -0,0 +1,111 @@ + + + + + + + + {#each yTicks as tick} + + + {tick} + + {/each} + + + + + {#each xTicks as tick} + + + {tick} + + {/each} + + + + {#each points as point} + + {/each} + + + \ No newline at end of file diff --git a/site/content/examples/scatterplot/data.json5 b/site/content/examples/scatterplot/data.json5 new file mode 100644 index 0000000000..8d879e1edd --- /dev/null +++ b/site/content/examples/scatterplot/data.json5 @@ -0,0 +1,186 @@ +{ + "a": [ + { + "x": 10, + "y": 8.04 + }, + { + "x": 8, + "y": 6.95 + }, + { + "x": 13, + "y": 7.58 + }, + { + "x": 9, + "y": 8.81 + }, + { + "x": 11, + "y": 8.33 + }, + { + "x": 14, + "y": 9.96 + }, + { + "x": 6, + "y": 7.24 + }, + { + "x": 4, + "y": 4.26 + }, + { + "x": 12, + "y": 10.84 + }, + { + "x": 7, + "y": 4.82 + }, + { + "x": 5, + "y": 5.68 + } + ], + "b": [ + { + "x": 10, + "y": 9.14 + }, + { + "x": 8, + "y": 8.14 + }, + { + "x": 13, + "y": 8.74 + }, + { + "x": 9, + "y": 8.77 + }, + { + "x": 11, + "y": 9.26 + }, + { + "x": 14, + "y": 8.1 + }, + { + "x": 6, + "y": 6.13 + }, + { + "x": 4, + "y": 3.1 + }, + { + "x": 12, + "y": 9.13 + }, + { + "x": 7, + "y": 7.26 + }, + { + "x": 5, + "y": 4.74 + } + ], + "c": [ + { + "x": 10, + "y": 7.46 + }, + { + "x": 8, + "y": 6.77 + }, + { + "x": 13, + "y": 12.74 + }, + { + "x": 9, + "y": 7.11 + }, + { + "x": 11, + "y": 7.81 + }, + { + "x": 14, + "y": 8.84 + }, + { + "x": 6, + "y": 6.08 + }, + { + "x": 4, + "y": 5.39 + }, + { + "x": 12, + "y": 8.15 + }, + { + "x": 7, + "y": 6.42 + }, + { + "x": 5, + "y": 5.73 + } + ], + "d": [ + { + "x": 8, + "y": 6.58 + }, + { + "x": 8, + "y": 5.76 + }, + { + "x": 8, + "y": 7.71 + }, + { + "x": 8, + "y": 8.84 + }, + { + "x": 8, + "y": 8.47 + }, + { + "x": 8, + "y": 7.04 + }, + { + "x": 8, + "y": 5.25 + }, + { + "x": 19, + "y": 12.5 + }, + { + "x": 8, + "y": 5.56 + }, + { + "x": 8, + "y": 7.91 + }, + { + "x": 8, + "y": 6.89 + } + ] +} \ No newline at end of file diff --git a/site/content/examples/scoped-styles/App.html b/site/content/examples/scoped-styles/App.html new file mode 100644 index 0000000000..c4d73176f0 --- /dev/null +++ b/site/content/examples/scoped-styles/App.html @@ -0,0 +1,11 @@ +
+ Big red Comic Sans +
+ + \ No newline at end of file diff --git a/site/content/examples/scoped-styles/data.json5 b/site/content/examples/scoped-styles/data.json5 new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/site/content/examples/scoped-styles/data.json5 @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/site/content/examples/self-references/App.html b/site/content/examples/self-references/App.html new file mode 100644 index 0000000000..bd4475e206 --- /dev/null +++ b/site/content/examples/self-references/App.html @@ -0,0 +1,9 @@ +
    +
  • {node.name} + {#if node.children} + {#each node.children as child} + + {/each} + {/if} +
  • +
\ No newline at end of file diff --git a/site/content/examples/self-references/data.json5 b/site/content/examples/self-references/data.json5 new file mode 100644 index 0000000000..de0cfbd194 --- /dev/null +++ b/site/content/examples/self-references/data.json5 @@ -0,0 +1,32 @@ +{ + "node": { + "name": "Fruit", + "children": [ + { + "name": "Red", + "children": [ + { + "name": "Cherry" + }, + { + "name": "Strawberry" + } + ] + }, + { + "name": "Green", + "children": [ + { + "name": "Apple" + }, + { + "name": "Pear" + }, + { + "name": "Lime" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/site/content/examples/svg-clock/App.html b/site/content/examples/svg-clock/App.html new file mode 100644 index 0000000000..3e406b5d65 --- /dev/null +++ b/site/content/examples/svg-clock/App.html @@ -0,0 +1,100 @@ + + + + + + + {#each [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55] as minute} + + + {#each [1, 2, 3, 4] as offset} + + {/each} + {/each} + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/content/examples/svg-clock/data.json5 b/site/content/examples/svg-clock/data.json5 new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/site/content/examples/svg-clock/data.json5 @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/site/content/examples/transitions-custom/App.html b/site/content/examples/transitions-custom/App.html new file mode 100644 index 0000000000..66d6918895 --- /dev/null +++ b/site/content/examples/transitions-custom/App.html @@ -0,0 +1,46 @@ + + + visible + +{#if visible} +
+ wheeee!!!!! +
+{/if} + + diff --git a/site/content/examples/transitions-custom/data.json5 b/site/content/examples/transitions-custom/data.json5 new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/site/content/examples/transitions-custom/data.json5 @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/site/content/examples/transitions-fade/App.html b/site/content/examples/transitions-fade/App.html new file mode 100644 index 0000000000..ef424447de --- /dev/null +++ b/site/content/examples/transitions-fade/App.html @@ -0,0 +1,11 @@ + + + visible + +{#if visible} +

fades in and out

+{/if} \ No newline at end of file diff --git a/site/content/examples/transitions-fade/data.json5 b/site/content/examples/transitions-fade/data.json5 new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/site/content/examples/transitions-fade/data.json5 @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/site/content/examples/transitions-fly/App.html b/site/content/examples/transitions-fly/App.html new file mode 100644 index 0000000000..2568480bbf --- /dev/null +++ b/site/content/examples/transitions-fly/App.html @@ -0,0 +1,11 @@ + + + visible + +{#if visible} +

flies 200 pixels up, slowly

+{/if} \ No newline at end of file diff --git a/site/content/examples/transitions-fly/data.json5 b/site/content/examples/transitions-fly/data.json5 new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/site/content/examples/transitions-fly/data.json5 @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/site/content/examples/transitions-in-out/App.html b/site/content/examples/transitions-in-out/App.html new file mode 100644 index 0000000000..940377ea3d --- /dev/null +++ b/site/content/examples/transitions-in-out/App.html @@ -0,0 +1,11 @@ + + + visible + +{#if visible} +

flies up, fades out

+{/if} \ No newline at end of file diff --git a/site/content/examples/transitions-in-out/data.json5 b/site/content/examples/transitions-in-out/data.json5 new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/site/content/examples/transitions-in-out/data.json5 @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/site/content/guide/00-introduction.md b/site/content/guide/00-introduction.md new file mode 100644 index 0000000000..f193f32217 --- /dev/null +++ b/site/content/guide/00-introduction.md @@ -0,0 +1,112 @@ +--- +title: Introduction +--- + +### What is Svelte? + +Svelte is a tool for building fast web applications. + +It is similar to JavaScript frameworks such as React and Vue, which share a goal of making it easy to build slick interactive user interfaces. + +But there's a crucial difference: Svelte converts your app into ideal JavaScript at *build time*, rather than interpreting your application code at *run time*. This means you don't pay the performance cost of the framework's abstractions, and you don't incur a penalty when your app first loads. + +You can build your entire app with Svelte, or you can add it incrementally to an existing codebase. You can also ship components as standalone packages that work anywhere, without the overhead of a dependency on a conventional framework. + +[Read the introductory blog post](/blog/frameworks-without-the-framework) to learn more about Svelte's goals and philosophy. + + +### Understanding Svelte 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: + +```html + +

Hello {name}!

+``` + +```json +/* { hidden: true } */ +{ + name: 'world' +} +``` + +> Wherever you see REPL links, click through for an interactive example + +Svelte turns this into a JavaScript module that you can import into your app: + +```js +/* { filename: 'main.js' } */ +import App from './App.html'; + +const app = new App({ + target: document.querySelector('main'), + props: { name: 'world' }, +}); + +// change the component's "name" prop. We'll learn about props (aka properties) below +app.name = 'everybody'; + +// detach the component and clean everything up +app.$destroy(); +``` + +Congratulations, you've just learned about half of Svelte's API! + + +### Getting started + +Normally, this is the part where the instructions might tell you to add the framework to your page as a ` + +{#await promise} +

wait for it...

+{:then answer} +

the answer is {answer}!

+{:catch error} +

well that's odd

+{/await} +``` + +If the expression in `{#await expression}` *isn't* a promise, Svelte skips ahead to the `then` section. + + +### Directives + +Directives allow you to add special instructions for adding [event handlers](guide#event-handlers), [bindings](guide#bindings), [transitions](guide#transitions) and so on. We'll cover each of those in later stages of this guide – for now, all you need to know is that directives can be identified by the `:` character: + +```html + +

Count: {count}

+ +``` + +```json +/* { hidden: true } */ +{ + count: 0 +} +``` + +> Technically, the `:` character is used to denote namespaced attributes in HTML. These will *not* be treated as directives, if encountered. + + +### Debug tags + +To inspect data as it changes and flows through your app, use a `{@debug ...}` tag: + +```html + + + +{@debug name} +

Hello {name}!

+``` + +```json +/* { hidden: true } */ +{ + name: 'world' +} +``` + +This will log the value of `name` whenever it changes. If your devtools are open, changing `name` will pause execution and open the debugger. + +You can debug multiple values simultaneously (`{@debug foo, bar, baz}`), or use `{@debug}` to pause execution whenever the surrounding markup is updated. + +> Debug tags only have an effect when compiling with the `dev: true` compiler option. diff --git a/site/content/guide/03-scoped-styles.md b/site/content/guide/03-scoped-styles.md new file mode 100644 index 0000000000..9a96da84db --- /dev/null +++ b/site/content/guide/03-scoped-styles.md @@ -0,0 +1,118 @@ +--- +title: Scoped styles +--- + +One of Svelte's key tenets is that components should be self-contained and reusable in different contexts. Because of that, it has a mechanism for *scoping* your CSS, so that you don't accidentally clobber other selectors on the page. + +### Adding styles + +Your component template can have a ` + +
+ Big red Comic Sans +
+``` + + +### How it works + +Open the example above in the REPL and inspect the element to see what has happened – Svelte has added a `svelte-[uniqueid]` class to the element, and transformed the CSS selector accordingly. Since no other element on the page can share that selector, anything else on the page with `class="foo"` will be unaffected by our styles. + +This is vastly simpler than achieving the same effect via [Shadow DOM](http://caniuse.com/#search=shadow%20dom) and works everywhere without polyfills. + +> Svelte will add a ` + +
+ +
+``` + +> Scoped styles are *not* dynamic – they are shared between all instances of a component. In other words you can't use `{tags}` inside your CSS. + + +### Unused style removal + +Svelte will identify and remove styles that are not being used in your app. It will also emit a warning so that you can remove them from the source. + +For rules *not* to be removed, they must apply to the component's markup. As far as Svelte is concerned `.bold` is unused in the following code and should be removed: + +```html + +
+

this text is not bold

+
+ + + + +``` + +Instead of manually manipulating the DOM, you should always use the `class` attribute (or the [class directive](https://svelte.technology/guide#classes)): + +```html + +
+

this text is bold

+
+``` + +If that's impossible for some reason, you can use `:global(...)`: + +```html + + +``` + +The same applies to the contents of `{@html ...}` tags. \ No newline at end of file diff --git a/site/content/guide/04-behaviour.md b/site/content/guide/04-behaviour.md new file mode 100644 index 0000000000..008a6e4556 --- /dev/null +++ b/site/content/guide/04-behaviour.md @@ -0,0 +1,121 @@ +--- +title: Behaviours +--- + +As well as scoped styles and a template, components can encapsulate *behaviours*. For that, we add a ` + +
+ +
+``` + + +### Internal state + +Often, it makes sense for a component to have internal state that isn't visible to the outside world. + +```html + + + +

Count: {count}

+ +``` + + +### External properties + +On the other hand, for the component to form part of a system, it needs to expose certain values so that they can be set from outside. These are called *props*, and we use the `export` keyword to differentiate them from internal state: + +```html + + + +

Count: {count}

+ +``` + +> Effectively, we're exporting a *contract* with the outside world. The `export` keyword normally means something different in JavaScript, so you might be surprised to see it used like this. Just roll with it for now! + +The `= 0` sets a default value for `count`, if none is provided. + +```js +const counter = new Counter({ + target: document.body, + props: { + count: 99 + } +}); + +counter.count; // 99 +counter.count += 1; // 100 +``` + +Props declared with `const` or `function` are *read-only* — they cannot be set from outside. This allows you to, for example, attach custom methods to your component: + +```js +component.doSomethingFun(); +``` + + +### Lifecycle hooks + +There are four 'hooks' provided by Svelte for adding control logic — `onMount`, `beforeUpdate`, `afterUpdate` and `onDestroy`. Import them directly from `svelte`: + +```html + + + + + +> Lifecycle hooks do *not* run in server-side rendering (SSR) mode, with the exception of `onDestroy`. More on SSR later. diff --git a/site/content/guide/05-nested-components.md b/site/content/guide/05-nested-components.md new file mode 100644 index 0000000000..be114b49eb --- /dev/null +++ b/site/content/guide/05-nested-components.md @@ -0,0 +1,147 @@ +--- +title: Nested components +--- + +As well as containing elements (and `if` blocks and `each` blocks), Svelte components can contain *other* Svelte components. + +```html + + + +
+ +
+``` + +```html + +

I am a nested component. The answer is {answer}

+``` + +That's similar to doing this... + +```js +import Widget from './Widget.html'; + +const widget = new Widget({ + target: document.querySelector('.widget-container'), + props: { + answer: 42 + } +}); +``` + +...except that Svelte takes care of destroying the child component when the parent is destroyed, and keeps props in sync if they change. + +> Component names must be capitalised, following the widely-used JavaScript convention of capitalising constructor names. It's also an easy way to distinguish components from elements in your template. + + +### Props + +Props, short for 'properties', are the means by which you pass data down from a parent to a child component — in other words, they're just like attributes on an element. As with element attributes, prop values can contain any valid JavaScript expression. + +Often, the name of the property will be the same as the value, in which case we can use a shorthand: + +```html + + + + +``` + +> Note that props are *one-way* — to get data from a child component into a parent component, use [bindings](guide#bindings). + + +### Composing with `` + +A component can contain a `` element, which allows the parent component to inject content: + +```html + + + + +

Hello!

+

This is a box. It can contain anything.

+
+``` + +```html + + + +
+ +
+``` + +The `` element can contain 'fallback content', which will be used if no children are provided for the component: + +```html + + + + +``` + +```html + + + +
+ +

the box is empty!

+
+
+``` + +You can also have *named* slots. Any elements with a corresponding `slot` attribute will fill these slots: + +```html + + + + + P. Sherman + 42 Wallaby Way, Sydney + +``` + +```html + + + +
+

+ Unknown address +
+ Unknown email +
+``` \ No newline at end of file diff --git a/site/content/guide/06-special-components.md b/site/content/guide/06-special-components.md new file mode 100644 index 0000000000..47c31c3733 --- /dev/null +++ b/site/content/guide/06-special-components.md @@ -0,0 +1,137 @@ +--- +title: Special elements +--- + +Svelte includes a handful of built-in elements with special behaviour. + + +### `` + +Sometimes, a component needs to embed itself recursively — for example if you have a tree-like data structure. In Svelte, that's accomplished with the `` tag: + +```html + +{#if countdown > 0} +

{countdown}

+ +{:else} +

liftoff!

+{/if} +``` + +```json +/* { hidden: true } */ +{ + countdown: 5 +} +``` + + +### `` + +If you don't know what kind of component to render until the app runs — in other words, it's driven by state (aka a dynamic component) — you can use ``: + +```html + + + + foo + +``` + +```html + +

Red {name}

+``` + +```html + +

Blue {name}

+``` + +The expression inside the `this="{...}"` can be any valid JavaScript expression. + + +### `` + +The `` tag gives you a convenient way to declaratively add event listeners to `window`. Event listeners are automatically removed when the component is destroyed. + +```html + + + + + +{#if key} +

{key === ' ' ? 'Space' : key} (code {keyCode})

+{:else} +

click in this window and press any key

+{/if} +``` + +You can also bind to certain values — so far `innerWidth`, `outerWidth`, `innerHeight`, `outerHeight`, `scrollX`, `scrollY` and `online`: + +```html + + + + + +
+

user has scrolled {y} pixels

+``` + + +### `` + +TODO REPLACE THIS WITH svelte:body + +The `` tag, just like ``, 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`. + + +### `` + +If you're building an application with Svelte — particularly if you're using [Sapper](https://sapper.svelte.technology) — then it's likely you'll need to add some content to the `` of your page, such as adding a `` element. + +You can do that with the `<svelte:head>` tag: + +```html +<!-- { title: '<svelte:head> tags' } --> +<svelte:head> + <title>{post.title} • My blog + +``` + +When [server rendering](guide#server-side-rendering), the `` contents can be extracted separately to the rest of the markup. diff --git a/site/content/guide/07-events.md b/site/content/guide/07-events.md new file mode 100644 index 0000000000..6cf6b98a23 --- /dev/null +++ b/site/content/guide/07-events.md @@ -0,0 +1,180 @@ +--- +title: Events +--- + +In most applications, you'll need to respond to the user's actions. In Svelte, this is done with the `on:[event]` directive. + +### Element events + +When used on an element, `on:click={handler}` is equivalent to calling `element.addEventListener('click', handler)`. When the element is removed, Svelte calls `removeEventListener` automatically. + +```html + +

Count: {count}

+ +``` + +```json +/* { hidden: true } */ +{ + count: 0 +} +``` + +For more complicated behaviours, you'll probably want to declare an event handler in your ` + +

Count: {count}

+ +``` + +```json +/* { hidden: true } */ +{ + count: 0 +} +``` + + +### Event handler modifiers + +While you can invoke methods like `event.stopPropagation` directly... + +```html + +
...
+``` + +...it gets annoying if you want to combine that with some other behaviour: + +```html + + + +
...
+``` + +For that reason, Svelte lets you use *event modifiers*: + +- [`preventDefault`](https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault) +- [`stopPropagation`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation) +- [`passive`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Parameters) — improves scrolling performance on touch/wheel events (Svelte will add it automatically where it's safe to do so) +- [`once`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Parameters) — removes the listener after the first invocation +- [`capture`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Parameter) + +> `passive` and `once` are not implemented in `legacy` mode + +The example above can be achieved with modifiers — no need for a separate event handler: + +```html + +
...
+``` + + +### Component events + +Events are an excellent way for [nested components](guide#nested-components) to communicate with their parents. Let's revisit our earlier example, but turn it into a `` component: + +```html + +

Select a category:

+ +{#each categories as category} + +{/each} + + +``` + +When the user clicks a button, the component will fire a `select` event, where the `event` object has a `category` property. Any component that nests `` can listen for events like so: + +```html + + + + +``` + +```html + +

Select a category:

+ +{#each categories as category} + +{/each} + + +``` + +Just as `this` in an element's event handler refers to the element itself, in a component event handler `this` refers to the component firing the event. + +There is also a shorthand for listening for and re-firing an event unchanged. + +```html + + + + +``` + +Since component events do not propagate as DOM events do, this can be used to pass events through intermediate components. This shorthand technique also applies to element events (`on:click` is equivalent to `on:click="fire('click', event)"`). diff --git a/site/content/guide/08-bindings.md b/site/content/guide/08-bindings.md new file mode 100644 index 0000000000..b899ea2376 --- /dev/null +++ b/site/content/guide/08-bindings.md @@ -0,0 +1,161 @@ +--- +title: Bindings +--- + + +### Bindings + +As we've seen, data can be passed down to elements and components with attributes and [props](guide#props). Occasionally, you need to get data back *up*; for that we use bindings. + + +#### Component bindings + +Component bindings keep values in sync between a parent and a child: + +```html + + +``` + +Whenever `childValue` changes in the child component, `parentValue` will be updated in the parent component and vice versa. + +If the names are the same, you can shorten the declaration: + +```html + + +``` + +> Use component bindings judiciously. They can save you a lot of boilerplate, but will make it harder to reason about data flow within your application if you overuse them. + + +#### Element bindings + +Element bindings make it easy to respond to user interactions: + +```html + +

Hello {name}!

+ +``` + +```json +/* { hidden: true } */ +{ + name: 'world' +} +``` + +Some bindings are *one-way*, meaning that the values are read-only. Most are *two-way* — changing the data programmatically will update the DOM. The following bindings are available: + +| Name | Applies to | Kind | +|-----------------------------------------------------------------|----------------------------------------------|----------------------| +| `value` | `` ` + + {#if error} +

+ {#if error.loc} + + {#if error.filename} + {error.filename} + {/if} + + ({error.loc.line}:{error.loc.column}) + + {/if} + + {error.message} +

+ {:elseif warningCount > 0} +

+ Compiled, but with {warningCount} {warningCount === 1 ? 'warning' : 'warnings'} — check the console for details +

+ {/if} + + +{#if !CodeMirror} +

loading editor...

+{/if} + + + \ No newline at end of file diff --git a/site/src/routes/repl/_components/Input/ComponentSelector.html b/site/src/routes/repl/_components/Input/ComponentSelector.html new file mode 100644 index 0000000000..32be6074b1 --- /dev/null +++ b/site/src/routes/repl/_components/Input/ComponentSelector.html @@ -0,0 +1,217 @@ + + + + +
+
+ {#each $component_store as component} + + {/each} +
+ + +
diff --git a/site/src/routes/repl/_components/Input/ModuleEditor.html b/site/src/routes/repl/_components/Input/ModuleEditor.html new file mode 100644 index 0000000000..783dc02dc9 --- /dev/null +++ b/site/src/routes/repl/_components/Input/ModuleEditor.html @@ -0,0 +1,38 @@ + + + + +
+ {#if component} + + {/if} +
\ No newline at end of file diff --git a/site/src/routes/repl/_components/Input/index.html b/site/src/routes/repl/_components/Input/index.html new file mode 100644 index 0000000000..50460af098 --- /dev/null +++ b/site/src/routes/repl/_components/Input/index.html @@ -0,0 +1,29 @@ + + + + + + \ No newline at end of file diff --git a/site/src/routes/repl/_components/Output/PropEditor.html b/site/src/routes/repl/_components/Output/PropEditor.html new file mode 100644 index 0000000000..a42612fb2d --- /dev/null +++ b/site/src/routes/repl/_components/Output/PropEditor.html @@ -0,0 +1,70 @@ + + + + +
+ +
\ No newline at end of file diff --git a/site/src/routes/repl/_components/Output/Viewer.html b/site/src/routes/repl/_components/Output/Viewer.html new file mode 100644 index 0000000000..aeeb49060a --- /dev/null +++ b/site/src/routes/repl/_components/Output/Viewer.html @@ -0,0 +1,395 @@ + + + + +
+ +
+ +
+ {#if error} +

+ {#if error.loc} + + {#if error.filename} + {error.filename} + {/if} + + ({error.loc.line}:{error.loc.column}) + + {/if} + + {error.message} +

+ {:elseif pending} +
+ +
+ {:elseif pendingImports} +

loading {pendingImports} {pendingImports === 1 ? 'dependency' : 'dependencies'} from + https://bundle.run

+ {/if} +
\ No newline at end of file diff --git a/site/src/routes/repl/_components/Output/index.html b/site/src/routes/repl/_components/Output/index.html new file mode 100644 index 0000000000..80bd6c777d --- /dev/null +++ b/site/src/routes/repl/_components/Output/index.html @@ -0,0 +1,173 @@ + + + + +
+ + + +
+ + +{#if view === 'result'} + +
+ {#if bundle} + + {:else} + +

loading Svelte compiler...

+ {/if} +
+ +
+

Props editor

+ + {#if props} + {#if props.length > 0} +
+ {#each props.sort() as prop (prop)} + {prop} + + + + {/each} +
+ {:else} +
+ + +

This component has no props — declare props with the export keyword

+
+ {/if} + {/if} +
+
+{:else} + +
+ +
+ +
+

Compiler options

+ +
TODO
+
+
+{/if} \ No newline at end of file diff --git a/site/src/routes/repl/_components/Repl.html b/site/src/routes/repl/_components/Repl.html new file mode 100644 index 0000000000..4e0bec168c --- /dev/null +++ b/site/src/routes/repl/_components/Repl.html @@ -0,0 +1,278 @@ + + + + +
+ +
+ +
+ +
+ +
+
+
\ No newline at end of file diff --git a/site/src/routes/repl/_components/SplitPane.html b/site/src/routes/repl/_components/SplitPane.html new file mode 100644 index 0000000000..f4843c24e1 --- /dev/null +++ b/site/src/routes/repl/_components/SplitPane.html @@ -0,0 +1,168 @@ + + + + +
+
+ +
+ +
+ +
+ +
+
+ +{#if dragging} +
+{/if} \ No newline at end of file diff --git a/site/src/routes/repl/_components/_codemirror.js b/site/src/routes/repl/_components/_codemirror.js new file mode 100644 index 0000000000..1619ab760c --- /dev/null +++ b/site/src/routes/repl/_components/_codemirror.js @@ -0,0 +1,10 @@ +const CodeMirror = require('codemirror'); +require('./codemirror.css'); +require('codemirror/mode/javascript/javascript.js'); +require('codemirror/mode/shell/shell.js'); +require('codemirror/mode/handlebars/handlebars.js'); +require('codemirror/mode/htmlmixed/htmlmixed.js'); +require('codemirror/mode/xml/xml.js'); +require('codemirror/mode/css/css.js'); + +module.exports = CodeMirror; diff --git a/site/src/routes/repl/_components/codemirror.css b/site/src/routes/repl/_components/codemirror.css new file mode 100644 index 0000000000..e573122457 --- /dev/null +++ b/site/src/routes/repl/_components/codemirror.css @@ -0,0 +1,350 @@ +/* BASICS */ + +.CodeMirror { + /* copied colors over from prism */ + --background: var(--back-light); + --base: hsl(45, 7%, 45%); + --comment: hsl(210, 25%, 60%); + --keyword: hsl(204, 58%, 45%); + --function: hsl(19, 67%, 45%); + --string: hsl(41, 37%, 45%); + --number: hsl(102, 27%, 50%); + --tags: var(--function); + --important: var(--string); + + /* Set height, width, borders, and global font properties here */ + /* see prism.css */ + height: 300px; + direction: ltr; +} + +/* PADDING */ + +.CodeMirror-lines { + padding: 4px 0; /* Vertical padding around content */ +} +.CodeMirror pre { + padding: 0 4px; /* Horizontal padding of content */ +} + +.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + background-color: white; /* The little square between H and V scrollbars */ +} + +/* GUTTER */ + +.CodeMirror-gutters { + border-right: 1px solid #ddd; + background-color: var(--back-light); + white-space: nowrap; +} +.CodeMirror-linenumber { + padding: 0 3px 0 5px; + min-width: 20px; + text-align: right; + color: var(--comment); + white-space: nowrap; + opacity: .6; +} + +.CodeMirror-guttermarker { color: black; } +.CodeMirror-guttermarker-subtle { color: #999; } + +/* CURSOR */ + +.CodeMirror-cursor { + border-left: 1px solid black; + border-right: none; + width: 0; +} +/* Shown when moving in bi-directional text */ +.CodeMirror div.CodeMirror-secondarycursor { + border-left: 1px solid silver; +} +.cm-fat-cursor .CodeMirror-cursor { + width: auto; + border: 0 !important; + background: #7e7; +} +.cm-fat-cursor div.CodeMirror-cursors { + z-index: 1; +} +.cm-fat-cursor-mark { + background-color: rgba(20, 255, 20, .5); + -webkit-animation: blink 1.06s steps(1) infinite; + -moz-animation: blink 1.06s steps(1) infinite; + animation: blink 1.06s steps(1) infinite; +} +.cm-animate-fat-cursor { + width: auto; + border: 0; + -webkit-animation: blink 1.06s steps(1) infinite; + -moz-animation: blink 1.06s steps(1) infinite; + animation: blink 1.06s steps(1) infinite; + background-color: #7e7; +} +@-moz-keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} +@-webkit-keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} +@keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} + +.cm-tab { display: inline-block; text-decoration: inherit; } + +.CodeMirror-rulers { + position: absolute; + left: 0; right: 0; top: -50px; bottom: -20px; + overflow: hidden; +} +.CodeMirror-ruler { + border-left: 1px solid #ccc; + top: 0; bottom: 0; + position: absolute; +} + +/* DEFAULT THEME */ +.cm-s-default .cm-header {color: blue} +.cm-s-default .cm-quote {color: #090} +.cm-negative {color: #d44} +.cm-positive {color: #292} +.cm-header, .cm-strong {font-weight: bold} +.cm-em {font-style: italic} +.cm-link {text-decoration: underline} +.cm-strikethrough {text-decoration: line-through} + +.cm-s-default .cm-atom, +.cm-s-default .cm-def, +.cm-s-default .cm-property, +.cm-s-default .cm-variable-2, +.cm-s-default .cm-variable-3, +.cm-s-default .cm-punctuation {color: var(--base)} +.cm-s-default .cm-hr, +.cm-s-default .cm-comment {color: var(--comment)} +.cm-s-default .cm-attribute, +.cm-s-default .cm-keyword {color: var(--keyword)} +.cm-s-default .cm-variable, +.cm-s-default .cm-bracket, +.cm-s-default .cm-tag {color: var(--tags)} +.cm-s-default .cm-number {color: var(--number)} +.cm-s-default .cm-string {color: var(--string)} + +.cm-s-default .cm-string-2 {color: #f50} +.cm-s-default .cm-type {color: #085} +.cm-s-default .cm-meta {color: #555} +.cm-s-default .cm-qualifier {color: #555} +.cm-s-default .cm-builtin {color: #30a} +.cm-s-default .cm-link {color: var(--flash)} +.cm-s-default .cm-error {color: #ff008c} +.cm-invalidchar {color: #ff008c} + +.CodeMirror-composing { border-bottom: 2px solid; } + +/* Default styles for common addons */ + +div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;} +div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;} +.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } +.CodeMirror-activeline-background {background: #e8f2ff;} + +/* STOP */ + +/* The rest of this file contains styles related to the mechanics of + the editor. You probably shouldn't touch them. */ + +.CodeMirror { + position: relative; + overflow: hidden; + background: white; +} + +.CodeMirror-scroll { + overflow: scroll !important; /* Things will break if this is overridden */ + /* 30px is the magic margin used to hide the element's real scrollbars */ + /* See overflow: hidden in .CodeMirror */ + margin-bottom: -30px; margin-right: -30px; + padding-bottom: 30px; + height: 100%; + outline: none; /* Prevent dragging from highlighting the element */ + position: relative; +} +.CodeMirror-sizer { + position: relative; + border-right: 30px solid transparent; +} + +/* The fake, visible scrollbars. Used to force redraw during scrolling + before actual scrolling happens, thus preventing shaking and + flickering artifacts. */ +.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + position: absolute; + z-index: 6; + display: none; +} +.CodeMirror-vscrollbar { + right: 0; top: 0; + overflow-x: hidden; + overflow-y: scroll; +} +.CodeMirror-hscrollbar { + bottom: 0; left: 0; + overflow-y: hidden; + overflow-x: scroll; +} +.CodeMirror-scrollbar-filler { + right: 0; bottom: 0; +} +.CodeMirror-gutter-filler { + left: 0; bottom: 0; +} + +.CodeMirror-gutters { + position: absolute; left: 0; top: 0; + min-height: 100%; + z-index: 3; +} +.CodeMirror-gutter { + white-space: normal; + height: 100%; + display: inline-block; + vertical-align: top; + margin-bottom: -30px; +} +.CodeMirror-gutter-wrapper { + position: absolute; + z-index: 4; + background: none !important; + border: none !important; +} +.CodeMirror-gutter-background { + position: absolute; + top: 0; bottom: 0; + z-index: 4; +} +.CodeMirror-gutter-elt { + position: absolute; + cursor: default; + z-index: 4; +} +.CodeMirror-gutter-wrapper ::selection { background-color: transparent } +.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent } + +.CodeMirror-lines { + cursor: text; + min-height: 1px; /* prevents collapsing before first draw */ +} +.CodeMirror pre { + /* Reset some styles that the rest of the page might have set */ + -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; + border-width: 0; + background: transparent; + font-family: inherit; + font-size: inherit; + margin: 0; + white-space: pre; + word-wrap: normal; + line-height: inherit; + color: inherit; + z-index: 2; + position: relative; + overflow: visible; + -webkit-tap-highlight-color: transparent; + -webkit-font-variant-ligatures: contextual; + font-variant-ligatures: contextual; +} +.CodeMirror-wrap pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: normal; +} + +.CodeMirror-linebackground { + position: absolute; + left: 0; right: 0; top: 0; bottom: 0; + z-index: 0; +} + +.CodeMirror-linewidget { + position: relative; + z-index: 2; + padding: .1px; /* Force widget margins to stay inside of the container */ +} + +.CodeMirror-rtl pre { direction: rtl; } + +.CodeMirror-code { + outline: none; +} + +/* Force content-box sizing for the elements where we expect it */ +.CodeMirror-scroll, +.CodeMirror-sizer, +.CodeMirror-gutter, +.CodeMirror-gutters, +.CodeMirror-linenumber { + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +.CodeMirror-measure { + position: absolute; + width: 100%; + height: 0; + overflow: hidden; + visibility: hidden; +} + +.CodeMirror-cursor { + position: absolute; + pointer-events: none; +} +.CodeMirror-measure pre { position: static; } + +div.CodeMirror-cursors { + visibility: hidden; + position: relative; + z-index: 3; +} +div.CodeMirror-dragcursors { + visibility: visible; +} + +.CodeMirror-focused div.CodeMirror-cursors { + visibility: visible; +} + +.CodeMirror-selected { background: #d9d9d9; } +.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } +.CodeMirror-crosshair { cursor: crosshair; } +.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } +.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } + +.cm-searching { + background-color: #ffa; + background-color: rgba(255, 255, 0, .4); +} + +/* Used to force a border model for a node */ +.cm-force-border { padding-right: .1px; } + +@media print { + /* Hide the cursor when printing */ + .CodeMirror div.CodeMirror-cursors { + visibility: hidden; + } +} + +/* See issue #2901 */ +.cm-tab-wrap-hack:after { content: ''; } + +/* Help users use markselection to safely style text background */ +span.CodeMirror-selectedtext { background: none; } diff --git a/site/src/routes/repl/_components/events.js b/site/src/routes/repl/_components/events.js new file mode 100644 index 0000000000..c1d2e84199 --- /dev/null +++ b/site/src/routes/repl/_components/events.js @@ -0,0 +1,19 @@ +export function keyEvent(code) { + return function (node, callback) { + node.addEventListener('keydown', handleKeydown); + + function handleKeydown(event) { + if (event.keyCode === code) { + callback.call(this, event); + } + } + + return { + destroy() { + node.removeEventListener('keydown', handleKeydown); + } + }; + } +} + +export const enter = keyEvent(13); \ No newline at end of file diff --git a/site/src/routes/repl/_utils/downloadBlob.js b/site/src/routes/repl/_utils/downloadBlob.js new file mode 100644 index 0000000000..d90ed40530 --- /dev/null +++ b/site/src/routes/repl/_utils/downloadBlob.js @@ -0,0 +1,11 @@ +export default (blob, filename) => { + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = filename; + link.style.display = 'none'; + document.body.appendChild(link); + link.click(); + URL.revokeObjectURL(url); + link.remove(); +}; diff --git a/site/src/routes/repl/_utils/getLocationFromStack.js b/site/src/routes/repl/_utils/getLocationFromStack.js new file mode 100644 index 0000000000..3d10fdee98 --- /dev/null +++ b/site/src/routes/repl/_utils/getLocationFromStack.js @@ -0,0 +1,31 @@ +import { decode } from 'sourcemap-codec'; + +export default function getLocationFromStack(stack, map) { + if (!stack) return; + const last = stack.split('\n')[1]; + const match = /:(\d+):(\d+)\)$/.exec(last); + + if (!match) return null; + + const line = +match[1]; + const column = +match[2]; + + return trace({ line, column }, map); +} + +function trace(loc, map) { + const mappings = decode(map.mappings); + const segments = mappings[loc.line - 1]; + + for (let i = 0; i < segments.length; i += 1) { + const segment = segments[i]; + if (segment[0] === loc.column) { + const [, sourceIndex, line, column] = segment; + const source = map.sources[sourceIndex].slice(2); + + return { source, line: line + 1, column }; + } + } + + return null; +} diff --git a/site/src/routes/repl/index.html b/site/src/routes/repl/index.html new file mode 100644 index 0000000000..112088adb4 --- /dev/null +++ b/site/src/routes/repl/index.html @@ -0,0 +1,212 @@ + + + + + + Svelte REPL + + +
+ + + {#if process.browser} + + {/if} +
diff --git a/site/src/server.js b/site/src/server.js new file mode 100644 index 0000000000..e0a78b072a --- /dev/null +++ b/site/src/server.js @@ -0,0 +1,114 @@ +import 'dotenv/config'; +import express from 'express'; +import compression from 'compression'; +import session from 'express-session'; +import passport from 'passport'; +import { Strategy } from 'passport-github'; +import sessionFileStore from 'session-file-store'; +import serve from 'serve-static'; +import devalue from 'devalue'; +import * as sapper from '../__sapper__/server.js'; + +const app = express(); + +if (process.env.GITHUB_CLIENT_ID) { + const FileStore = sessionFileStore(session); + + passport.use(new Strategy({ + clientID: process.env.GITHUB_CLIENT_ID, + clientSecret: process.env.GITHUB_CLIENT_SECRET, + callbackURL: `${process.env.BASEURL}/auth/callback`, + userAgent: 'svelte.technology' + }, (accessToken, refreshToken, profile, callback) => { + return callback(null, { + token: accessToken, + id: profile.id, + username: profile.username, + displayName: profile.displayName, + photo: profile.photos && profile.photos[0] && profile.photos[0].value + }); + })); + + passport.serializeUser((user, cb) => { + cb(null, user); + }); + + passport.deserializeUser((obj, cb) => { + cb(null, obj); + }); + + app + .use(session({ + secret: 'svelte', + resave: true, + saveUninitialized: true, + cookie: { + maxAge: 31536000 + }, + store: new FileStore({ + path: process.env.NOW ? `/tmp/sessions` : `.sessions` + }) + })) + + .use(passport.initialize()) + .use(passport.session()) + + .get('/auth/login', (req, res, next) => { + const { returnTo } = req.query; + req.session.returnTo = returnTo ? decodeURIComponent(returnTo) : '/'; + next(); + }, passport.authenticate('github', { scope: ['gist', 'read:user'] })) + + .post('/auth/logout', (req, res) => { + req.logout(); + res.end('ok'); + }) + + .get('/auth/callback', passport.authenticate('github', { failureRedirect: '/auth/error' }), (req, res) => { + const { id, username, displayName, photo } = req.session.passport && req.session.passport.user; + + res.set({ 'Content-Type': 'text/html; charset=utf-8' }); + res.end(` + + `); + }); +} else { + app.get('/auth/login', (req, res) => { + res.writeHead(500); + res.end(` + +

Missing .env file

+

In order to use GitHub authentication, you will need to register an OAuth application with gist and read:user scopes, and create a .env file:

+ +
GITHUB_CLIENT_ID=[YOUR_APP_ID]\nGITHUB_CLIENT_SECRET=[YOUR_APP_SECRET]\nBASEURL=http://localhost:3000
+ +

The BASEURL variable should match the callback URL specified for your app.

+ + `); + }); +} + +app.use( + compression({ threshold: 0 }), + serve('static'), + sapper.middleware({ + // TODO update Sapper so that we can pass props to the client + props: req => { + const user = req.session && req.session.passport && req.session.passport.user; + + return { + user: user && { + // strip access token + id: user.id, + username: user.username, + displayName: user.displayName, + photo: user.photo + } + }; + } + }) +).listen(process.env.PORT); \ No newline at end of file diff --git a/site/src/service-worker.js b/site/src/service-worker.js new file mode 100644 index 0000000000..d175aa9a76 --- /dev/null +++ b/site/src/service-worker.js @@ -0,0 +1,82 @@ +import { timestamp, files, shell, routes } from '../__sapper__/service-worker.js'; + +const ASSETS = `cache${timestamp}`; + +// `shell` is an array of all the files generated by Rollup, +// `files` is an array of everything in the `static` directory +const to_cache = shell.concat(files); +const cached = new Set(to_cache); + +self.addEventListener('install', event => { + event.waitUntil( + caches + .open(ASSETS) + .then(cache => cache.addAll(to_cache)) + .then(() => { + self.skipWaiting(); + }) + ); +}); + +self.addEventListener('activate', event => { + event.waitUntil( + caches.keys().then(async keys => { + // delete old caches + for (const key of keys) { + if (key !== ASSETS) await caches.delete(key); + } + + self.clients.claim(); + }) + ); +}); + +self.addEventListener('fetch', event => { + if (event.request.method !== 'GET' || event.request.headers.has('range')) return; + + const url = new URL(event.request.url); + + // don't try to handle e.g. data: URIs + if (!url.protocol.startsWith('http')) return; + + // ignore dev server requests + if (url.hostname === self.location.hostname && url.port !== self.location.port) return; + + // always serve static files and Rollup-generated assets from cache + if (url.host === self.location.host && cached.has(url.pathname)) { + event.respondWith(caches.match(event.request)); + return; + } + + // for pages, you might want to serve a shell `index.html` file, + // which Sapper has generated for you. It's not right for every + // app, but if it's right for yours then uncomment this section + /* + if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) { + event.respondWith(caches.match('/index.html')); + return; + } + */ + + if (event.request.cache === 'only-if-cached') return; + + // for everything else, try the network first, falling back to + // cache if the user is offline. (If the pages never change, you + // might prefer a cache-first approach to a network-first one.) + event.respondWith( + caches + .open(`offline${timestamp}`) + .then(async cache => { + try { + const response = await fetch(event.request); + cache.put(event.request, response.clone()); + return response; + } catch(err) { + const response = await cache.match(event.request); + if (response) return response; + + throw err; + } + }) + ); +}); diff --git a/site/src/template.html b/site/src/template.html new file mode 100644 index 0000000000..c7dafc73dc --- /dev/null +++ b/site/src/template.html @@ -0,0 +1,36 @@ + + + + + + + + %sapper.base% + + + + + + + + + + %sapper.styles% + + + %sapper.head% + + + +
%sapper.html%
+ + + %sapper.scripts% + + diff --git a/site/src/user.js b/site/src/user.js new file mode 100644 index 0000000000..665db40bae --- /dev/null +++ b/site/src/user.js @@ -0,0 +1,16 @@ +import { writable } from 'svelte/store.js'; + +export const user = writable(null); + +if (process.browser) { + // TODO this is a workaround for the fact that there's currently + // no way to pass session data from server to client + fetch('/auth/me.json', { credentials: 'include' }) + .then(r => r.json()) + .then(user.set); +} + +export async function logout() { + const r = await fetch(`/auth/logout`, { method: 'POST' }); + if (r.ok) user.set(null); +} \ No newline at end of file diff --git a/site/src/utils/_process_markdown.js b/site/src/utils/_process_markdown.js new file mode 100644 index 0000000000..a79d759cbb --- /dev/null +++ b/site/src/utils/_process_markdown.js @@ -0,0 +1,15 @@ +export default function process_markdown(markdown) { + const match = /---\n([\s\S]+?)\n---/.exec(markdown); + const frontMatter = match[1]; + const content = markdown.slice(match[0].length); + + const metadata = {}; + frontMatter.split('\n').forEach(pair => { + const colonIndex = pair.indexOf(':'); + metadata[pair.slice(0, colonIndex).trim()] = pair + .slice(colonIndex + 1) + .trim(); + }); + + return { metadata, content }; +} \ No newline at end of file diff --git a/site/static/curl.js b/site/static/curl.js new file mode 100644 index 0000000000..b0ddc51a71 --- /dev/null +++ b/site/static/curl.js @@ -0,0 +1,22 @@ +/* version: 0.8.13 */ +(function(){/* + MIT License (c) copyright 2010-2013 B Cavalier & J Hann */ + (function(m){function U(){}function u(a,b){return 0==aa.call(a).indexOf("[object "+b)}function I(a){return a&&"/"==a.charAt(a.length-1)?a.substr(0,a.length-1):a}function V(a,b){var d,c,e,f;d=1;c=a;"."==c.charAt(0)&&(e=!0,c=c.replace(ba,function(a,b,c,e){c&&d++;return e||""}));if(e){e=b.split("/");f=e.length-d;if(0>f)return a;e.splice(f,d);return e.concat(c||[]).join("/")}return c}function J(a){var b=a.indexOf("!");return{h:a.substr(b+1),f:0<=b&&a.substr(0,b)}}function P(){}function v(a,b){P.prototype= + a||Q;var d=new P;P.prototype=Q;for(var c in b)d[c]=b[c];return d}function K(){function a(a,b,d){c.push([a,b,d])}function b(a,b){for(var d,e=0;d=c[e++];)(d=d[a])&&d(b)}var d,c,e;d=this;c=[];e=function(d,g){a=d?function(a){a&&a(g)}:function(a,b){b&&b(g)};e=U;b(d?0:1,g);b=U;c=k};this.then=function(b,c,e){a(b,c,e);return d};this.resolve=function(a){d.va=a;e(!0,a)};this.reject=function(a){d.ua=a;e(!1,a)};this.D=function(a){b(2,a)}}function L(a){return a instanceof K||a instanceof B}function w(a,b,d,c){L(a)? + a.then(b,d,c):b(a)}function C(a,b,d){var c;return function(){0<=--a&&b&&(c=b.apply(k,arguments));0==a&&d&&d(c);return c}}function A(){var a,b;D="";a=[].slice.call(arguments);u(a[0],"Object")&&(b=a.shift(),b=M(b));return new B(a[0],a[1],a[2],b)}function M(a,b,d){var c;D="";if(a&&(h.V(a),x=h.a(a),"preloads"in a&&(c=new B(a.preloads,k,d,E,!0),h.m(function(){E=c})),a=a.main))return new B(a,b,d)}function B(a,b,d,c,e){var f;f=h.j(x,k,[].concat(a),e);this.then=this.then=a=function(a,b){w(f,function(b){a&& + a.apply(k,b)},function(a){if(b)b(a);else throw a;});return this};this.next=function(a,b,c){return new B(a,b,c,f)};this.config=M;(b||d)&&a(b,d);h.m(function(){w(e||E,function(){w(c,function(){h.w(f)},d)})})}function W(a){var b,d;b=a.id;b==k&&(F!==k?F={L:"Multiple anonymous defines encountered"}:(b=h.ha())||(F=a));if(b!=k){d=l[b];b in l||(d=h.i(b,x),d=h.I(d.a,b),l[b]=d);if(!L(d))throw Error("duplicate define: "+b);d.ja=!1;h.J(d,a)}}function R(){var a=h.ea(arguments);W(a)}var D,x,y,G,z=m.document,S= + z&&(z.head||z.getElementsByTagName("head")[0]),ca=S&&S.getElementsByTagName("base")[0]||null,X={},Y={},N={},da="addEventListener"in m?{}:{loaded:1,complete:1},Q={},aa=Q.toString,k,l={},O={},E=!1,F,Z=/^\/|^[^:]+:\/\/|^[A-Za-z]:[\\/]/,ba=/(\.)(\.?)(?:$|\/([^\.\/]+.*)?)/g,ea=/\/\*[\s\S]*?\*\/|\/\/.*?[\n\r]/g,fa=/require\s*\(\s*(["'])(.*?[^\\])\1\s*\)|[^\\]?(["'])/g,ga=/\s*,\s*/,T,h;h={o:function(a,b,d){var c;a=V(a,b);if("."==a.charAt(0))return a;c=J(a);a=(b=c.f)||c.h;a in d.c&&(a=d.c[a].R||a);b&&(0> + b.indexOf("/")&&!(b in d.c)&&(a=I(d.T)+"/"+b),a=a+"!"+c.h);return a},j:function(a,b,d,c){function e(b,c){var d,f;d=h.o(b,g.id,a);if(!c)return d;f=J(d);if(!f.f)return d;d=l[f.f];f.h="normalize"in d?d.normalize(f.h,e,g.a)||"":e(f.h);return f.f+"!"+f.h}function f(b,d,f){var p;p=d&&function(a){d.apply(k,a)};if(u(b,"String")){if(p)throw Error("require(id, callback) not allowed");f=e(b,!0);b=l[f];if(!(f in l))throw Error("Module not resolved: "+f);return(f=L(b)&&b.b)||b}w(h.w(h.j(a,g.id,b,c)),p,f)}var g; + g=new K;g.id=b||"";g.ia=c;g.K=d;g.a=a;g.F=f;f.toUrl=function(b){return h.i(e(b,!0),a).url};g.o=e;return g},I:function(a,b,d){var c,e,f;c=h.j(a,b,k,d);e=c.resolve;f=C(1,function(a){c.v=a;try{return h.Z(c)}catch(b){c.reject(b)}});c.resolve=function(a){w(d||E,function(){e(l[c.id]=O[c.url]=f(a))})};c.M=function(a){w(d||E,function(){c.b&&(f(a),c.D(Y))})};return c},Y:function(a,b,d,c){return h.j(a,d,k,c)},ga:function(a){return a.F},N:function(a){return a.b||(a.b={})},fa:function(a){var b=a.A;b||(b=a.A= + {id:a.id,uri:h.O(a),exports:h.N(a),config:function(){return a.a}},b.b=b.exports);return b},O:function(a){return a.url||(a.url=h.H(a.F.toUrl(a.id),a.a))},V:function(a){var b,d,c,e,f;b="curl";d="define";c=e=m;if(a&&(f=a.overwriteApi||a.sa,b=a.apiName||a.la||b,c=a.apiContext||a.ka||c,d=a.defineName||a.na||d,e=a.defineContext||a.ma||e,y&&u(y,"Function")&&(m.curl=y),y=null,G&&u(G,"Function")&&(m.define=G),G=null,!f)){if(c[b]&&c[b]!=A)throw Error(b+" already exists");if(e[d]&&e[d]!=R)throw Error(d+" already exists"); + }c[b]=A;e[d]=R},a:function(a){function b(a,b){var d,c,g,n,q;for(q in a){g=a[q];u(g,"String")&&(g={path:a[q]});g.name=g.name||q;n=e;c=J(I(g.name));d=c.h;if(c=c.f)n=f[c],n||(n=f[c]=v(e),n.c=v(e.c),n.g=[]),delete a[q];c=g;var l=b,H=void 0;c.path=I(c.path||c.location||"");l&&(H=c.main||"./main","."==H.charAt(0)||(H="./"+H),c.R=V(H,c.name+"/"));c.a=c.config;c.a&&(c.a=v(e,c.a));c.W=d.split("/").length;d?(n.c[d]=c,n.g.push(d)):n.s=h.U(g.path,e)}}function d(a){var b=a.c;a.S=new RegExp("^("+a.g.sort(function(a, + c){return b[c].W-b[a].W}).join("|").replace(/\/|\./g,"\\$&")+")(?=\\/|$)");delete a.g}var c,e,f,g;"baseUrl"in a&&(a.s=a.baseUrl);"main"in a&&(a.R=a.main);"preloads"in a&&(a.ta=a.preloads);"pluginPath"in a&&(a.T=a.pluginPath);if("dontAddFileExt"in a||a.l)a.l=new RegExp(a.dontAddFileExt||a.l);c=x;e=v(c,a);e.c=v(c.c);f=a.plugins||{};e.plugins=v(c.plugins);e.C=v(c.C,a.C);e.B=v(c.B,a.B);e.g=[];b(a.packages,!0);b(a.paths,!1);for(g in f)a=h.o(g+"!","",e),e.plugins[a.substr(0,a.length-1)]=f[g];f=e.plugins; + for(g in f)if(f[g]=v(e,f[g]),a=f[g].g)f[g].g=a.concat(e.g),d(f[g]);for(g in c.c)e.c.hasOwnProperty(g)||e.g.push(g);d(e);return e},i:function(a,b){var d,c,e,f;d=b.c;e=Z.test(a)?a:a.replace(b.S,function(a){c=d[a]||{};f=c.a;return c.path||""});return{a:f||x,url:h.U(e,b)}},U:function(a,b){var d=b.s;return d&&!Z.test(a)?I(d)+"/"+a:a},H:function(a,b){return a+((b||x).l.test(a)?"":".js")},P:function(a,b,d){var c=z.createElement("script");c.onload=c.onreadystatechange=function(d){d=d||m.event;if("load"== + d.type||da[c.readyState])delete N[a.id],c.onload=c.onreadystatechange=c.onerror="",b()};c.onerror=function(){d(Error("Syntax or http error: "+a.url))};c.type=a.pa||"text/javascript";c.charset="utf-8";c.async=!a.ra;c.src=a.url;N[a.id]=c;S.insertBefore(c,ca);return c},$:function(a){var b=[],d;("string"==typeof a?a:a.toSource?a.toSource():a.toString()).replace(ea,"").replace(fa,function(a,e,f,g){g?d=d==g?k:d:d||b.push(f);return""});return b},ea:function(a){var b,d,c,e,f,g;f=a.length;c=a[f-1];e=u(c,"Function")? + c.length:-1;2==f?u(a[0],"Array")?d=a[0]:b=a[0]:3==f&&(b=a[0],d=a[1]);!d&&0 p > code { + position: relative; + top: -.1rem; + padding: .3rem .8rem .5rem; + margin: 0 .2rem; + border-radius: 0.3em; + white-space: nowrap; + background: var(--back-light); + border-top: .1rem solid #e5e5e9; + border-left: .1rem solid #e5e5e9; + /* color: inherit; */ + color: #8f9196; +} + +::selection { + background: var(--flash); + color: white; +} + +/* opinionated styles --------------------- */ +h1, h2 { + font-family: var(--font-alt); + line-height: 1.2; + pointer-events: none; +} + +li:not(.white) > h2 { + color: var(--second) +} + +blockquote { + position: relative; + margin: 1.6rem 0 2.4rem; + padding: 2rem 2.4rem 1.8rem 2.4rem; + border-radius: var(--border-r); + font-family: var(--font); + max-width: var(--linemax); +} + +blockquote p { + font-size: var(--h5); +} + +blockquote :last-child { + margin: 0; +} + + +/* buttons -------------------------------- */ +button { + font-family: inherit; + font-size: inherit; + background-color: transparent; + border: none; + color: currentColor; +} + +button:focus { outline: 0 } + +button[disabled] { + opacity: .55; + pointer-events: none; +} + +button > svg { + position: relative; + top: -.1rem; + width: 2rem !important; + height: 2rem !important; +} + +/* options */ +button[outline] { + min-height: var(--bttn-calc-h); + line-height: var(--bttn-calc-h); + border: var(--bttn-outline) solid currentColor; + background-color: white; + color: currentColor; +} + +/* links ------------------------------------- + - idea from https://up.docs.apex.sh + + How can this be solved? + Setup for all links is risky/tricky + + We need global -styles and
    -styles inside markdown. + These bleed into places, where not wanted (i.e. nav, homepage) + Same with lists - see below + + THIS WAY IS SHITTY!! + I'm too blind to see... +*/ +.linkify a:not(.open-in-repl) { + position: relative; + padding: 0 .4rem .1rem; + border-bottom: .1rem solid hsla(15, 100%, 50%, 0.5); /* muted --prime */ + user-select: none; + white-space: nowrap; + color: inherit; + transition: color .2s; +} + +.linkify a:not(.open-in-repl):hover { + color: var(--flash); +} + +.linkify a:not(.open-in-repl):before { + content: ''; + position: absolute; + width: 100%; + height: .2rem; + bottom: -.2rem; + left: 0; + white-space: nowrap; + background: var(--prime); + border-radius: var(--border-r); + visibility: hidden; + transform: scaleX(0); + transform-origin: left center; + transition: all .15s var(--out-cubic); + z-index: -1; +} + +.linkify a:not(.open-in-repl):hover:before { + visibility: visible; + transform: scaleX(1); +} + +a:hover > .icon { stroke: var(--flash) } + +/* lists ---------------------------------- */ +.listify ol, +.listify ul { + --list-padding: 2.9rem; + + list-style: none; + color: currentColor; + margin-left: var(--list-padding); +} + +.listify ol > li, +.listify ul > li { + max-width: calc(var(--linemax) - var(--list-padding)); + line-height: 1.35; + margin: 0 0 0.8em 0; +} + +.listify ul > li:before { + content: ''; + position: absolute; + margin-top: 1.4rem; + margin-left: calc(var(--list-padding) * -1); + background-color: currentColor; + width: .8em; + height: .2rem; +} + +.listify ol { list-style: decimal } + +/* tables --------------------------------- */ +table { + width: 100%; + font-size: var(--h5); +} + +td, th { + text-align: left; + border-bottom: 1px solid #eee; + padding: 0.4rem 0.8rem 0.4rem 0; +} + +table code, +table span { + white-space: pre; +} + +/* grid ----------------------------------- */ +.grid, +.grid.half { + display: grid; + grid-gap: 2.4rem; + grid-template-columns: 1fr; + align-items: center; +} + +.grid.stretch { align-items: stretch } + +.grid > .cols-2, +.grid > .cols-3 { grid-column: span 1 } + + +/* helper styles -------------------------- */ +.flex-auto { flex: 1 0 auto } + +.py0 { + padding-top: 0 !important; + padding-bottom: 0 !important; +} + +.legend, figcaption, +.post aside { + max-width: none; + margin: 0 auto; + padding: 1.6rem 0 4rem .8rem; + font: 1.2rem/1.6 var(--font-ui); +} + +.filename { + display: inline-block; + padding: 1.6rem 0 0 1rem; + font: var(--h6) var(--font-ui); +} + +.box { + padding: 2.4rem 3.2rem; + border-radius: var(--border-r); +} + +.open-in-repl { + position: absolute; + margin-top: 4rem; + margin-left: -2.5rem; + width: 3.6rem; + height: 3.6rem; + padding: .9rem; + background-color: var(--back-light); + border-radius: 50%; + border-left: .1rem solid #c7c6c6; +} + +/* theme colors --------------------------- */ +.prime { color: var(--prime) !important } +.second { color: var(--second) !important } +.flash { color: var(--flash) !important } +.black { color: black !important } +.white { color: white !important } + +.back { background-color: var(--back) !important } +.back-light { background-color: var(--back-light) !important } +.bg-prime { background-color: var(--prime) !important } +.bg-second { background-color: var(--second) !important } +.bg-flash { background-color: var(--flash) !important } + + +/* +----------------------------------------------- + media-queries +----------------------------------------------- +*/ +@media screen and (min-width: 768px) { + :root { + --side-page: 14vw; + --top-offset: 10rem; + } +} + +@media screen and (min-width: 840px) { + .grid.half, + .grid { grid-template-columns: repeat(2, 1fr) } + .grid > .cols-2, + .grid > .cols-3 { grid-column: span 2 } +} + +@media screen and (min-width: 1100px) { + .grid { grid-template-columns: repeat(3, 1fr) } + .grid > .cols-2 { grid-column: span 2 } + .grid > .cols-3 { grid-column: span 3 } +} + diff --git a/site/static/great-success.png b/site/static/great-success.png new file mode 100644 index 0000000000..c38720c34c Binary files /dev/null and b/site/static/great-success.png differ diff --git a/site/static/icons/arrow-right.svg b/site/static/icons/arrow-right.svg new file mode 100644 index 0000000000..5229286a59 --- /dev/null +++ b/site/static/icons/arrow-right.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/site/static/icons/check.svg b/site/static/icons/check.svg new file mode 100644 index 0000000000..5679642c7f --- /dev/null +++ b/site/static/icons/check.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/site/static/icons/collapse.svg b/site/static/icons/collapse.svg new file mode 100644 index 0000000000..13ff9dee82 --- /dev/null +++ b/site/static/icons/collapse.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/site/static/icons/download.svg b/site/static/icons/download.svg new file mode 100644 index 0000000000..8d255ff74b --- /dev/null +++ b/site/static/icons/download.svg @@ -0,0 +1,4 @@ + + + + diff --git a/site/static/icons/dropdown.svg b/site/static/icons/dropdown.svg new file mode 100644 index 0000000000..b3fcdbe795 --- /dev/null +++ b/site/static/icons/dropdown.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/site/static/icons/expand.svg b/site/static/icons/expand.svg new file mode 100644 index 0000000000..b429cb53c8 --- /dev/null +++ b/site/static/icons/expand.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/site/static/icons/flip.svg b/site/static/icons/flip.svg new file mode 100644 index 0000000000..09326b1e6b --- /dev/null +++ b/site/static/icons/flip.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/site/static/icons/fork.svg b/site/static/icons/fork.svg new file mode 100644 index 0000000000..57ad61bb2f --- /dev/null +++ b/site/static/icons/fork.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/site/static/icons/link.svg b/site/static/icons/link.svg new file mode 100644 index 0000000000..6772991bdd --- /dev/null +++ b/site/static/icons/link.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/site/static/icons/save.svg b/site/static/icons/save.svg new file mode 100644 index 0000000000..4bde5edde5 --- /dev/null +++ b/site/static/icons/save.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/site/static/images/svelte-android-chrome-192.png b/site/static/images/svelte-android-chrome-192.png new file mode 100644 index 0000000000..d4402ea6b1 Binary files /dev/null and b/site/static/images/svelte-android-chrome-192.png differ diff --git a/site/static/images/svelte-android-chrome-512.png b/site/static/images/svelte-android-chrome-512.png new file mode 100644 index 0000000000..2e25039e59 Binary files /dev/null and b/site/static/images/svelte-android-chrome-512.png differ diff --git a/site/static/images/svelte-apple-touch-icon.png b/site/static/images/svelte-apple-touch-icon.png new file mode 100644 index 0000000000..33a891e61e Binary files /dev/null and b/site/static/images/svelte-apple-touch-icon.png differ diff --git a/site/static/images/svelte-mstile-150.png b/site/static/images/svelte-mstile-150.png new file mode 100644 index 0000000000..875ffb275c Binary files /dev/null and b/site/static/images/svelte-mstile-150.png differ diff --git a/site/static/images/twitter-card.png b/site/static/images/twitter-card.png new file mode 100644 index 0000000000..6b50232d30 Binary files /dev/null and b/site/static/images/twitter-card.png differ diff --git a/site/static/logo.svg b/site/static/logo.svg new file mode 100644 index 0000000000..19988afd96 --- /dev/null +++ b/site/static/logo.svg @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/site/static/manifest.json b/site/static/manifest.json new file mode 100644 index 0000000000..45a9d5b907 --- /dev/null +++ b/site/static/manifest.json @@ -0,0 +1,20 @@ +{ + "background_color": "#ffffff", + "theme_color": "#ff3e00", + "name": "Svelte", + "short_name": "Svelte", + "display": "minimal-ui", + "start_url": "/", + "icons": [ + { + "src": "images/svelte-android-chrome-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "images/svelte-android-chrome-512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/site/static/prism.css b/site/static/prism.css new file mode 100644 index 0000000000..4b7f86c4a4 --- /dev/null +++ b/site/static/prism.css @@ -0,0 +1,95 @@ +/* +----------------------------------------------- + syntax-highlighting [prism] +----------------------------------------------- +*/ + +/* colors --------------------------------- */ +pre[class*='language-'] { + --background: var(--back-light); + --base: hsl(45, 7%, 45%); + --comment: hsl(210, 25%, 60%); + --keyword: hsl(204, 58%, 45%); + --function: hsl(19, 67%, 45%); + --string: hsl(41, 37%, 45%); + --number: hsl(102, 27%, 50%); + --tags: var(--function); + --important: var(--string); +} + +/* type-base ------------------------------ */ +code[class*='language-'], +pre[class*='language-'] { + background: none; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + font: 300 var(--code-fs)/1.7 var(--font-mono); + color: var(--base); + tab-size: 2; + -webkit-hyphens: none; + hyphens: none; +} + +/* code-blocks ---------------------------- */ +pre[class*='language-'] { + overflow: auto; + padding: 1.5rem 2rem; + margin: .8rem 0 2.4rem; + max-width: var(--code-w); + border-radius: var(--border-r); + box-shadow: 1px 1px 0.1rem rgba(68, 68, 68, 0.05) inset; +} + +:not(pre) > code[class*='language-'], +pre[class*='language-'] { + background: var(--background); +} + +/* tokens --------------------------------- */ +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { color: var(--comment) } + +.token.punctuation { color: var(--base) } + +.token.property, +.token.tag, +.token.constant, +.token.symbol, +.token.deleted { color: var(--tags) } + +.token.boolean, +.token.number { color: var(--number) } + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { color: var(--string) } + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string, +.token.variable { color: var(--base) } + +.token.atrule, +.token.attr-value, +.token.function, +.token.class-name { color: var(--function) } + +.token.keyword { color: var(--keyword) } + +.token.regex, +.token.important { color: var(--important) } + +.token.important, +.token.bold { font-weight: bold } +.token.italic { font-style: italic } +.token.entity { cursor: help } \ No newline at end of file diff --git a/site/static/repl-viewer.css b/site/static/repl-viewer.css new file mode 100644 index 0000000000..9d682a3961 --- /dev/null +++ b/site/static/repl-viewer.css @@ -0,0 +1,61 @@ +html, body { + position: relative; + width: 100%; + height: 100%; +} + +body { + color: #333; + margin: 0; + padding: 8px; + box-sizing: border-box; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; +} + +a { + color: rgb(0,100,200); + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +a:visited { + color: rgb(0,80,160); +} + +label { + display: block; +} + +input, button, select, textarea { + font-family: inherit; + font-size: inherit; + padding: 0.4em; + margin: 0 0 0.5em 0; + box-sizing: border-box; + border: 1px solid #ccc; + border-radius: 2px; +} + +input:disabled { + color: #ccc; +} + +input[type="range"] { + height: 0; +} + +button { + background-color: #f4f4f4; + outline: none; +} + +button:active { + background-color: #ddd; +} + +button:focus { + border-color: #666; +} \ No newline at end of file diff --git a/site/static/svelte-logo-192.png b/site/static/svelte-logo-192.png new file mode 100644 index 0000000000..3073b2d9ed Binary files /dev/null and b/site/static/svelte-logo-192.png differ diff --git a/site/static/svelte-logo-512.png b/site/static/svelte-logo-512.png new file mode 100644 index 0000000000..454e5f2aaf Binary files /dev/null and b/site/static/svelte-logo-512.png differ diff --git a/site/static/workers/bundler.js b/site/static/workers/bundler.js new file mode 100644 index 0000000000..626afc4bc7 --- /dev/null +++ b/site/static/workers/bundler.js @@ -0,0 +1,217 @@ +self.window = self; // egregious hack to get magic-string to work in a worker + +let version; + +let fulfil; +let ready = new Promise(f => fulfil = f); + +self.addEventListener('message', async event => { + switch (event.data.type) { + case 'init': + version = event.data.version; + + importScripts( + `https://unpkg.com/svelte@${version}/compiler.js`, + `https://unpkg.com/rollup/dist/rollup.browser.js` + ); + fulfil(); + + break; + + case 'bundle': + if (event.data.components.length === 0) return; + + await ready; + const result = await bundle(event.data.components); + if (result) { + postMessage(result); + } + + break; + } +}); + +const commonCompilerOptions = { + dev: true, +}; + +let cached = { + dom: null, + ssr: null +}; + +let currentToken; + +const is_svelte_module = id => id === 'svelte' || id.startsWith('svelte/'); + +const cache = new Map(); +function fetch_if_uncached(url) { + if (!cache.has(url)) { + cache.set(url, fetch(url) + .then(r => r.text()) + .catch(err => { + console.error(err); + cache.delete(url); + })); + } + + return cache.get(url); +} + +async function getBundle(mode, cache, lookup) { + let bundle; + let error; + let warningCount = 0; + + const info = {}; + + try { + bundle = await rollup.rollup({ + input: './App.html', + external: id => { + if (id[0] === '.') return false; + if (is_svelte_module(id)) return false; + if (id.startsWith('https://')) return false; + return true; + }, + plugins: [{ + resolveId(importee, importer) { + // v3 hack + if (importee === `svelte`) return `https://unpkg.com/svelte@${version}/index.mjs`; + if (importee.startsWith(`svelte`)) return `https://unpkg.com/svelte@${version}/${importee.slice(7)}.mjs`; + + if (importer && importer.startsWith(`https://`)) { + return new URL(`${importee}.mjs`, importer).href; + } + + if (importee in lookup) return importee; + }, + load(id) { + if (id.startsWith(`https://`)) return fetch_if_uncached(id); + if (id in lookup) return lookup[id].source; + }, + transform(code, id) { + if (!/\.html$/.test(id)) return null; + + const name = id.replace(/^\.\//, '').replace(/\.html$/, ''); + + const { js, css, stats } = svelte.compile(code, Object.assign({ + generate: mode, + format: 'esm', + name: name, + filename: name + '.html', + onwarn: warning => { + console.warn(warning.message); + console.log(warning.frame); + warningCount += 1; + }, + }, commonCompilerOptions)); + + return js; + } + }], + onwarn(warning) { + console.warn(warning); + warningCount += 1; + }, + cache + }); + } catch (error) { + return { error, bundle: null, info: null, warningCount: null } + } + + return { bundle, info, error: null, warningCount }; +} + +async function bundle(components) { + // console.clear(); + console.log(`running Svelte compiler version %c${svelte.VERSION}`, 'font-weight: bold'); + + const token = currentToken = {}; + + const lookup = {}; + components.forEach(component => { + const path = `./${component.name}.${component.type}`; + lookup[path] = component; + }); + + let dom; + let error; + + try { + dom = await getBundle('dom', cached.dom, lookup); + if (dom.error) { + throw dom.error; + } + + if (token !== currentToken) { + console.error(`aborted`); + return; + } + + cached.dom = dom.bundle; + + let uid = 1; + const importMap = new Map(); + + const domResult = await dom.bundle.generate({ + format: 'iife', + name: 'SvelteComponent', + globals: id => { + const name = `import_${uid++}`; + importMap.set(id, name); + return name; + }, + sourcemap: true + }); + + if (token !== currentToken) return; + + const ssr = dom.info.usesHooks + ? await getBundle('ssr', cached.ssr, lookup) + : null; + + if (ssr) { + cached.ssr = ssr.bundle; + if (ssr.error) { + throw ssr.error; + } + } + + if (token !== currentToken) return; + + const ssrResult = ssr + ? await ssr.bundle.generate({ + format: 'iife', + name: 'SvelteComponent', + globals: id => importMap.get(id), + sourcemap: true + }) + : null; + + return { + bundle: { + imports: dom.bundle.imports, + importMap + }, + dom: domResult, + ssr: ssrResult, + warningCount: dom.warningCount, + error: null + }; + } catch (err) { + const e = error || err; + delete e.toString; + + return { + bundle: null, + dom: null, + ssr: null, + warningCount: dom.warningCount, + error: Object.assign({}, e, { + message: e.message, + stack: e.stack + }) + }; + } +} \ No newline at end of file diff --git a/site/static/workers/compiler.js b/site/static/workers/compiler.js new file mode 100644 index 0000000000..c20b416588 --- /dev/null +++ b/site/static/workers/compiler.js @@ -0,0 +1,42 @@ +self.window = self; // egregious hack to get magic-string to work in a worker + +let fulfil_ready; +const ready = new Promise(f => { + fulfil_ready = f; +}); + +self.addEventListener('message', async event => { + switch (event.data.type) { + case 'init': + importScripts(`https://unpkg.com/svelte@${event.data.version}/compiler.js`); + fulfil_ready(); + break; + + case 'compile': + await ready; + postMessage(compile(event.data)); + break; + + } +}); + +const commonCompilerOptions = { + dev: false, + css: false +}; + +function compile({ source, options, entry }) { + try { + const { js, stats } = svelte.compile( + source, + Object.assign({}, commonCompilerOptions, options) + ); + + return { code: js.code, props: entry ? stats.props : null }; + } catch (err) { + let result = `/* Error compiling component\n\n${err.message}`; + if (err.frame) result += `\n${err.frame}`; + result += `\n\n*/`; + return { code: result, props: null }; + } +} diff --git a/src/compile/Component.ts b/src/compile/Component.ts index e2b7011622..3e76bf71b2 100644 --- a/src/compile/Component.ts +++ b/src/compile/Component.ts @@ -695,6 +695,7 @@ export default class Component { }); hoistable_nodes.add(node); + this.fully_hoisted.push(`[✂${node.start}-${node.end}✂]`); } } diff --git a/src/compile/render-dom/wrappers/Element/index.ts b/src/compile/render-dom/wrappers/Element/index.ts index ebb7d8666e..d7c928ac3a 100644 --- a/src/compile/render-dom/wrappers/Element/index.ts +++ b/src/compile/render-dom/wrappers/Element/index.ts @@ -532,11 +532,12 @@ export default class ElementWrapper extends Wrapper { renderer.component.declarations.push(name); renderer.component.template_references.add(name); - const { handler } = this_binding.munge(block); + const { handler, object } = this_binding.munge(block); renderer.component.partly_hoisted.push(deindent` function ${name}($$node) { ${handler.mutation} + $$make_dirty('${object}'); } `); diff --git a/src/compile/render-dom/wrappers/InlineComponent/index.ts b/src/compile/render-dom/wrappers/InlineComponent/index.ts index a73a23b8e4..00ff21a225 100644 --- a/src/compile/render-dom/wrappers/InlineComponent/index.ts +++ b/src/compile/render-dom/wrappers/InlineComponent/index.ts @@ -11,6 +11,7 @@ import deindent from '../../../../utils/deindent'; import Attribute from '../../../nodes/Attribute'; import getObject from '../../../../utils/getObject'; import Binding from '../../../nodes/Binding'; +import flattenReference from '../../../../utils/flattenReference'; export default class InlineComponentWrapper extends Wrapper { var: string; @@ -196,12 +197,15 @@ export default class InlineComponentWrapper extends Wrapper { } const munged_bindings = this.node.bindings.map(binding => { + component.has_reactive_assignments = true; + if (binding.name === 'this') { const fn = component.getUniqueName(`${this.var}_binding`); component.declarations.push(fn); component.template_references.add(fn); let lhs; + let object; if (binding.isContextual && binding.expression.node.type === 'Identifier') { // bind:x={y} — we can't just do `y = x`, we need to @@ -209,13 +213,17 @@ export default class InlineComponentWrapper extends Wrapper { const { name } = binding.expression.node; const { object, property, snippet } = block.bindings.get(name)(); lhs = snippet; + + // TODO we need to invalidate... something } else { + object = flattenReference(binding.expression.node).name; lhs = component.source.slice(binding.expression.node.start, binding.expression.node.end).trim(); } component.partly_hoisted.push(deindent` function ${fn}($$component) { ${lhs} = $$component; + ${object && `$$make_dirty('${object}');`} } `); @@ -223,8 +231,6 @@ export default class InlineComponentWrapper extends Wrapper { return `@add_binding_callback(() => ctx.${fn}(${this.var}));`; } - component.has_reactive_assignments = true; - const name = component.getUniqueName(`${this.var}_${binding.name}_binding`); component.declarations.push(name); component.template_references.add(name); diff --git a/src/compile/render-dom/wrappers/Window.ts b/src/compile/render-dom/wrappers/Window.ts index 29e8ab82d3..98e4e65f65 100644 --- a/src/compile/render-dom/wrappers/Window.ts +++ b/src/compile/render-dom/wrappers/Window.ts @@ -68,9 +68,9 @@ export default class WindowWrapper extends Wrapper { }); }); - const lock = block.getUniqueName(`window_updating`); - const clear = block.getUniqueName(`clear_window_updating`); - const timeout = block.getUniqueName(`window_updating_timeout`); + const scrolling = block.getUniqueName(`scrolling`); + const clear_scrolling = block.getUniqueName(`clear_scrolling`); + const scrolling_timeout = block.getUniqueName(`scrolling_timeout`); Object.keys(events).forEach(event => { const handler_name = block.getUniqueName(`onwindow${event}`); @@ -78,9 +78,9 @@ export default class WindowWrapper extends Wrapper { if (event === 'scroll') { // TODO other bidirectional bindings... - block.addVariable(lock, 'false'); - block.addVariable(clear, `function() { ${lock} = false; }`); - block.addVariable(timeout); + block.addVariable(scrolling, 'false'); + block.addVariable(clear_scrolling, `() => { ${scrolling} = false }`); + block.addVariable(scrolling_timeout); const condition = [ bindings.scrollX && `"${bindings.scrollX}" in this._state`, @@ -97,58 +97,61 @@ export default class WindowWrapper extends Wrapper { ${x && `${x} = window.pageXOffset;`} ${y && `${y} = window.pageYOffset;`} `); + + block.event_listeners.push(deindent` + @addListener(window, "${event}", () => { + ${scrolling} = true; + clearTimeout(${scrolling_timeout}); + ${scrolling_timeout} = setTimeout(${clear_scrolling}, 100); + ctx.${handler_name}(); + }) + `); } else { props.forEach(prop => { renderer.metaBindings.addLine( `this._state.${prop.name} = window.${prop.value};` ); }); + + block.event_listeners.push(deindent` + @addListener(window, "${event}", ctx.${handler_name}); + `); } component.declarations.push(handler_name); component.template_references.add(handler_name); component.partly_hoisted.push(deindent` function ${handler_name}() { - ${event === 'scroll' && deindent` - if (${lock}) return; - ${lock} = true; - `} ${props.map(prop => `${prop.name} = window.${prop.value}; $$make_dirty('${prop.name}');`)} - ${event === 'scroll' && `${lock} = false;`} } `); + + block.builders.init.addBlock(deindent` - window.addEventListener("${event}", ctx.${handler_name}); @add_render_callback(ctx.${handler_name}); `); - block.builders.destroy.addBlock(deindent` - window.removeEventListener("${event}", ctx.${handler_name}); - `); - component.has_reactive_assignments = true; }); // special case... might need to abstract this out if we add more special cases if (bindings.scrollX || bindings.scrollY) { - block.builders.init.addBlock(deindent` - #component.$on("state", ({ changed, current }) => { - if (${ - [bindings.scrollX, bindings.scrollY].map( - binding => binding && `changed["${binding}"]` - ).filter(Boolean).join(' || ') - } && !${lock}) { - ${lock} = true; - clearTimeout(${timeout}); - window.scrollTo(${ - bindings.scrollX ? `current["${bindings.scrollX}"]` : `window.pageXOffset` - }, ${ - bindings.scrollY ? `current["${bindings.scrollY}"]` : `window.pageYOffset` - }); - ${timeout} = setTimeout(${clear}, 100); - } - }); + block.builders.update.addBlock(deindent` + if (${ + [bindings.scrollX, bindings.scrollY].filter(Boolean).map( + b => `changed.${b}` + ).join(' || ') + } && !${scrolling}) { + ${scrolling} = true; + clearTimeout(${scrolling_timeout}); + window.scrollTo(${ + bindings.scrollX ? `current["${bindings.scrollX}"]` : `window.pageXOffset` + }, ${ + bindings.scrollY ? `current["${bindings.scrollY}"]` : `window.pageYOffset` + }); + ${scrolling_timeout} = setTimeout(${clear_scrolling}, 100); + } `); } diff --git a/test/js/samples/hoisted-const/expected.js b/test/js/samples/hoisted-const/expected.js new file mode 100644 index 0000000000..4613e724d7 --- /dev/null +++ b/test/js/samples/hoisted-const/expected.js @@ -0,0 +1,47 @@ +/* generated by Svelte vX.Y.Z */ +import { SvelteComponent as SvelteComponent_1, append, createElement, createText, detachNode, init, insert, noop, run, safe_not_equal } from "svelte/internal"; + +function create_fragment(component, ctx) { + var b, text_value = get_answer(), text, current; + + return { + c() { + b = createElement("b"); + text = createText(text_value); + }, + + m(target, anchor) { + insert(target, b, anchor); + append(b, text); + current = true; + }, + + p: noop, + + i(target, anchor) { + if (current) return; + this.m(target, anchor); + }, + + o: run, + + d(detach) { + if (detach) { + detachNode(b); + } + } + }; +} + +const ANSWER = 42; + +function get_answer() { return ANSWER; } + +class SvelteComponent extends SvelteComponent_1 { + constructor(options) { + super(); + init(this, options, noop, create_fragment, safe_not_equal); + } +} + +export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/hoisted-const/input.html b/test/js/samples/hoisted-const/input.html new file mode 100644 index 0000000000..d248a83154 --- /dev/null +++ b/test/js/samples/hoisted-const/input.html @@ -0,0 +1,6 @@ + + +{get_answer()} \ No newline at end of file diff --git a/test/js/samples/window-binding-scroll/expected.js b/test/js/samples/window-binding-scroll/expected.js index fee92a8d45..0600a211ee 100644 --- a/test/js/samples/window-binding-scroll/expected.js +++ b/test/js/samples/window-binding-scroll/expected.js @@ -1,26 +1,22 @@ /* generated by Svelte vX.Y.Z */ -import { SvelteComponent as SvelteComponent_1, add_render_callback, append, createElement, createText, detachNode, flush, init, insert, run, safe_not_equal, setData } from "svelte/internal"; +import { SvelteComponent as SvelteComponent_1, addListener, add_render_callback, append, createElement, createText, detachNode, flush, init, insert, run, safe_not_equal, setData } from "svelte/internal"; function create_fragment(component, ctx) { - var window_updating = false, clear_window_updating = function() { window_updating = false; }, window_updating_timeout, p, text0, text1, current; + var scrolling = false, clear_scrolling = () => { scrolling = false }, scrolling_timeout, p, text0, text1, current, dispose; - window.addEventListener("scroll", ctx.onwindowscroll); add_render_callback(ctx.onwindowscroll); - component.$on("state", ({ changed, current }) => { - if (changed["y"] && !window_updating) { - window_updating = true; - clearTimeout(window_updating_timeout); - window.scrollTo(window.pageXOffset, current["y"]); - window_updating_timeout = setTimeout(clear_window_updating, 100); - } - }); - return { c() { p = createElement("p"); text0 = createText("scrolled to "); text1 = createText(ctx.y); + dispose = addListener(window, "scroll", () => { + scrolling = true; + clearTimeout(scrolling_timeout); + scrolling_timeout = setTimeout(clear_scrolling, 100); + ctx.onwindowscroll(); + }); }, m(target, anchor) { @@ -31,6 +27,13 @@ function create_fragment(component, ctx) { }, p(changed, ctx) { + if (changed.y && !scrolling) { + scrolling = true; + clearTimeout(scrolling_timeout); + window.scrollTo(window.pageXOffset, current["y"]); + scrolling_timeout = setTimeout(clear_scrolling, 100); + } + if (changed.y) { setData(text1, ctx.y); } @@ -44,11 +47,11 @@ function create_fragment(component, ctx) { o: run, d(detach) { - window.removeEventListener("scroll", ctx.onwindowscroll); - if (detach) { detachNode(p); } + + dispose(); } }; } @@ -57,10 +60,7 @@ function define($$self, $$props, $$make_dirty) { let { y } = $$props; function onwindowscroll() { - if (window_updating) return; - window_updating = true; y = window.pageYOffset; $$make_dirty('y'); - window_updating = false; } $$self.$$.get = () => ({ y, onwindowscroll }); diff --git a/test/runtime/samples/binding-this-component-reactive/Foo.html b/test/runtime/samples/binding-this-component-reactive/Foo.html new file mode 100644 index 0000000000..066a48b65e --- /dev/null +++ b/test/runtime/samples/binding-this-component-reactive/Foo.html @@ -0,0 +1 @@ +
    foo
    \ No newline at end of file diff --git a/test/runtime/samples/binding-this-component-reactive/_config.js b/test/runtime/samples/binding-this-component-reactive/_config.js new file mode 100644 index 0000000000..415d2e641c --- /dev/null +++ b/test/runtime/samples/binding-this-component-reactive/_config.js @@ -0,0 +1,8 @@ +export default { + skip_if_ssr: true, + + html: ` +
    foo
    +
    has foo: true
    + ` +}; diff --git a/test/runtime/samples/binding-this-component-reactive/main.html b/test/runtime/samples/binding-this-component-reactive/main.html new file mode 100644 index 0000000000..b9a4709bd7 --- /dev/null +++ b/test/runtime/samples/binding-this-component-reactive/main.html @@ -0,0 +1,9 @@ + + + +
    + has foo: {!!foo} +
    diff --git a/test/runtime/samples/binding-this-element-reactive/_config.js b/test/runtime/samples/binding-this-element-reactive/_config.js new file mode 100644 index 0000000000..44609fa3d1 --- /dev/null +++ b/test/runtime/samples/binding-this-element-reactive/_config.js @@ -0,0 +1,5 @@ +export default { + skip_if_ssr: true, + + html: '
    has div: true
    ' +}; diff --git a/test/runtime/samples/binding-this-element-reactive/main.html b/test/runtime/samples/binding-this-element-reactive/main.html new file mode 100644 index 0000000000..cf37519b18 --- /dev/null +++ b/test/runtime/samples/binding-this-element-reactive/main.html @@ -0,0 +1,7 @@ + + +
    + has div: {!!div} +
    diff --git a/test/runtime/samples/refs-no-innerhtml/_config.js b/test/runtime/samples/binding-this-no-innerhtml/_config.js similarity index 100% rename from test/runtime/samples/refs-no-innerhtml/_config.js rename to test/runtime/samples/binding-this-no-innerhtml/_config.js diff --git a/test/runtime/samples/refs-no-innerhtml/main.html b/test/runtime/samples/binding-this-no-innerhtml/main.html similarity index 100% rename from test/runtime/samples/refs-no-innerhtml/main.html rename to test/runtime/samples/binding-this-no-innerhtml/main.html diff --git a/test/runtime/samples/refs-unset/_config.js b/test/runtime/samples/binding-this-unset/_config.js similarity index 100% rename from test/runtime/samples/refs-unset/_config.js rename to test/runtime/samples/binding-this-unset/_config.js diff --git a/test/runtime/samples/refs-unset/main.html b/test/runtime/samples/binding-this-unset/main.html similarity index 100% rename from test/runtime/samples/refs-unset/main.html rename to test/runtime/samples/binding-this-unset/main.html diff --git a/test/runtime/samples/refs/_config.js b/test/runtime/samples/binding-this/_config.js similarity index 100% rename from test/runtime/samples/refs/_config.js rename to test/runtime/samples/binding-this/_config.js diff --git a/test/runtime/samples/refs/main.html b/test/runtime/samples/binding-this/main.html similarity index 100% rename from test/runtime/samples/refs/main.html rename to test/runtime/samples/binding-this/main.html