Merge branch 'master' into master

pull/3349/head
Rich Harris 6 years ago committed by GitHub
commit 7c953a8983
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

2
.gitignore vendored

@ -15,7 +15,7 @@ node_modules
/animate
/scratch/
/coverage/
/coverage.lcov/
/coverage.lcov
/test/sourcemaps/samples/*/output.js
/test/sourcemaps/samples/*/output.js.map
/test/sourcemaps/samples/*/output.css

@ -1,5 +1,69 @@
# Svelte changelog
## 3.11.0
* `$capture_state` and `$inject_state` HMR hooks in dev mode ([#3148](https://github.com/sveltejs/svelte/pull/3148))
* Allow unclosed tags inside if/each/etc blocks ([#2807](https://github.com/sveltejs/svelte/issues/2807))
* Invalidate unreferenced store values inside `<script>` ([#3537](https://github.com/sveltejs/svelte/issues/3537))
* Print `null` text when hydrating ([#3379](https://github.com/sveltejs/svelte/pull/3379))
## 3.10.1
* Preserve reactivity inside if block heads etc ([#3512](https://github.com/sveltejs/svelte/issues/3512))
* Fix store bindings inside each blocks ([#3455](https://github.com/sveltejs/svelte/issues/3455))
* Generate correct code for if-else blocks with static conditions ([#3505](https://github.com/sveltejs/svelte/issues/3505))
* Avoid generating unnecessary component update code ([#3526](https://github.com/sveltejs/svelte/issues/3526))
* Make `bind:currentTime` more reliable ([#3524](https://github.com/sveltejs/svelte/issues/3524))
* Prevent errors when setting spread props on SVG elements ([#3522](https://github.com/sveltejs/svelte/issues/3522))
## 3.10.0
* Add `blur` transition ([#3477](https://github.com/sveltejs/svelte/pull/3477))
* Prevent `<input type="number">` edge case with spread props ([#3426](https://github.com/sveltejs/svelte/issues/3426))
* Robustify cyclical dependency detection, improve errors ([#3459](https://github.com/sveltejs/svelte/issues/3459))
## 3.9.2
* Fix handling of additional @-rules in style blocks ([#2995](https://github.com/sveltejs/svelte/pull/2995))
* Fix if blocks with complex but static conditions ([#3447](https://github.com/sveltejs/svelte/issues/3447))
## 3.9.1
* Only update style properties if necessary ([#3433](https://github.com/sveltejs/svelte/issues/3433))
* Only update if/await blocks if necessary ([#2355](https://github.com/sveltejs/svelte/issues/2355))
* Set context correctly inside await blocks ([#2443](https://github.com/sveltejs/svelte/issues/2443))
* Handle `!important` inline styles ([#1834](https://github.com/sveltejs/svelte/issues/1834))
* Make index references reactive in event handlers inside keyed each blocks ([#2569](https://github.com/sveltejs/svelte/issues/2569))
## 3.9.0
* Support `is` attribute on elements, with a warning ([#3182](https://github.com/sveltejs/svelte/issues/3182))
* Handle missing slot prop ([#3322](https://github.com/sveltejs/svelte/issues/3322))
* Don't set undefined/null input values, unless previous value exists ([#1233](https://github.com/sveltejs/svelte/issues/1233))
* Fix style attribute optimisation bailout ([#1830](https://github.com/sveltejs/svelte/issues/1830))
## 3.8.1
* Set SVG namespace for slotted elements ([#3321](https://github.com/sveltejs/svelte/issues/3321))
## 3.8.0
* Add `self` event modifier ([#3372](https://github.com/sveltejs/svelte/issues/3372))
* Generate valid code when spreading literal ([#3185](https://github.com/sveltejs/svelte/issues/3185))
* Coerce tag values to string before checking equality ([#2290](https://github.com/sveltejs/svelte/issues/2290))
## 3.7.1
* Assume `let` variables are dynamic for slots ([#3354](https://github.com/sveltejs/svelte/issues/3354))
* Allow transition functions to return nothing ([#2246](https://github.com/sveltejs/svelte/pull/2246))
## 3.7.0
* Disable warnings via `svelte-ignore` comments ([#3351](https://github.com/sveltejs/svelte/pull/3351))
* Throw if `$` or `$$...` is referenced as global ([#3272](https://github.com/sveltejs/svelte/issues/3272))
* Remount HTML tags correctly ([#3329](https://github.com/sveltejs/svelte/pull/3329))
* Treat data attributes like other attributes ([#3337](https://github.com/sveltejs/svelte/issues/3337))
## 3.6.11
* Handle reassigned RxJS observables ([#3304](https://github.com/sveltejs/svelte/issues/3304))

@ -1,6 +1,6 @@
<p>
<a href="https://svelte.dev">
<img alt="Cybernetically enhanced web apps: Svelte" src="https://svelte-assets.surge.sh/banner.png">
<img alt="Cybernetically enhanced web apps: Svelte" src="https://sveltejs.github.io/assets/banner.png">
</a>
<a href="https://www.npmjs.com/package/svelte">

@ -1,4 +1,3 @@
--compilers ts-node/register
--require source-map-support/register
--full-trace
--recursive

306
package-lock.json generated

@ -1,6 +1,6 @@
{
"name": "svelte",
"version": "3.6.10",
"version": "3.10.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -54,6 +54,12 @@
"integrity": "sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==",
"dev": true
},
"@types/json-schema": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.3.tgz",
"integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==",
"dev": true
},
"@types/mocha": {
"version": "5.2.7",
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz",
@ -61,9 +67,9 @@
"dev": true
},
"@types/node": {
"version": "8.10.50",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.50.tgz",
"integrity": "sha512-+ZbcUwJdaBgOZpwXeT0v+gHC/jQbEfzoc9s4d0rN0JIKeQbuTrT+A2n1aQY6LpZjrLXJT7avVUqiCecCJeeZxA==",
"version": "8.10.53",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.53.tgz",
"integrity": "sha512-aOmXdv1a1/vYUn1OT1CED8ftbkmmYbKhKGSyMDeJiidLvKRKvZUQOdXwG/wcNY7T1Qb0XTlVdiYjIq00U7pLrQ==",
"dev": true
},
"@types/resolve": {
@ -76,48 +82,82 @@
}
},
"@typescript-eslint/eslint-plugin": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.11.0.tgz",
"integrity": "sha512-mXv9ccCou89C8/4avKHuPB2WkSZyY/XcTQUXd5LFZAcLw1I3mWYVjUu6eS9Ja0QkP/ClolbcW9tb3Ov/pMdcqw==",
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.13.0.tgz",
"integrity": "sha512-WQHCozMnuNADiqMtsNzp96FNox5sOVpU8Xt4meaT4em8lOG1SrOv92/mUbEHQVh90sldKSfcOc/I0FOb/14G1g==",
"dev": true,
"requires": {
"@typescript-eslint/experimental-utils": "1.11.0",
"@typescript-eslint/experimental-utils": "1.13.0",
"eslint-utils": "^1.3.1",
"functional-red-black-tree": "^1.0.1",
"regexpp": "^2.0.1",
"tsutils": "^3.7.0"
},
"dependencies": {
"@typescript-eslint/experimental-utils": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz",
"integrity": "sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.3",
"@typescript-eslint/typescript-estree": "1.13.0",
"eslint-scope": "^4.0.0"
}
},
"@typescript-eslint/typescript-estree": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz",
"integrity": "sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw==",
"dev": true,
"requires": {
"lodash.unescape": "4.0.1",
"semver": "5.5.0"
}
}
}
},
"@typescript-eslint/experimental-utils": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-1.11.0.tgz",
"integrity": "sha512-7LbfaqF6B8oa8cp/315zxKk8FFzosRzzhF8Kn/ZRsRsnpm7Qcu25cR/9RnAQo5utZ2KIWVgaALr+ZmcbG47ruw==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.1.0.tgz",
"integrity": "sha512-ZJGLYXa4nxjNzomaEk1qts38B/vludg2LOM7dRc7SppEKsMPTS1swaTKS/pom+x4d/luJGoG00BDIss7PR1NQA==",
"dev": true,
"requires": {
"@typescript-eslint/typescript-estree": "1.11.0",
"@types/json-schema": "^7.0.3",
"@typescript-eslint/typescript-estree": "2.1.0",
"eslint-scope": "^4.0.0"
}
},
"@typescript-eslint/parser": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-1.11.0.tgz",
"integrity": "sha512-5xBExyXaxVyczrZvbRKEXvaTUFFq7gIM9BynXukXZE0zF3IQP/FxF4mPmmh3gJ9egafZFqByCpPTFm3dk4SY7Q==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.1.0.tgz",
"integrity": "sha512-0+hzirRJoqE1T4lSSvCfKD+kWjIpDWfbGBiisK5CENcr+22pPkHB2sfV1giON+UxHV4A08SSrQonZk7X2zIQdw==",
"dev": true,
"requires": {
"@types/eslint-visitor-keys": "^1.0.0",
"@typescript-eslint/experimental-utils": "1.11.0",
"@typescript-eslint/typescript-estree": "1.11.0",
"@typescript-eslint/experimental-utils": "2.1.0",
"@typescript-eslint/typescript-estree": "2.1.0",
"eslint-visitor-keys": "^1.0.0"
}
},
"@typescript-eslint/typescript-estree": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.11.0.tgz",
"integrity": "sha512-fquUHF5tAx1sM2OeRCC7wVxFd1iMELWMGCzOSmJ3pLzArj9+kRixdlC4d5MncuzXpjEqc6045p3KwM0o/3FuUA==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.1.0.tgz",
"integrity": "sha512-482ErJJ7QYghBh+KA9G+Fwcuk/PLTy+9NBMz8S+6UFrUUnVvHRNAL7I70kdws2te0FBYEZW7pkDaXoT+y8UARw==",
"dev": true,
"requires": {
"glob": "^7.1.4",
"is-glob": "^4.0.1",
"lodash.unescape": "4.0.1",
"semver": "5.5.0"
"semver": "^6.2.0"
},
"dependencies": {
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
}
}
},
"abab": {
@ -127,15 +167,9 @@
"dev": true
},
"acorn": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.2.0.tgz",
"integrity": "sha512-8oe72N3WPMjA+2zVG71Ia0nXZ8DpQH+QyyHO+p06jT8eg8FGG3FbcUIi8KziHlAfheJQZeoqbvq1mQSQHXKYLw==",
"dev": true
},
"acorn-dynamic-import": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz",
"integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.0.0.tgz",
"integrity": "sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==",
"dev": true
},
"acorn-globals": {
@ -146,12 +180,20 @@
"requires": {
"acorn": "^6.0.1",
"acorn-walk": "^6.0.1"
},
"dependencies": {
"acorn": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz",
"integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==",
"dev": true
}
}
},
"acorn-jsx": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz",
"integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==",
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.2.tgz",
"integrity": "sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw==",
"dev": true
},
"acorn-walk": {
@ -236,12 +278,6 @@
"integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=",
"dev": true
},
"arg": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz",
"integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==",
"dev": true
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@ -772,9 +808,9 @@
}
},
"eslint": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-6.1.0.tgz",
"integrity": "sha512-QhrbdRD7ofuV09IuE2ySWBz0FyXCq0rriLTZXZqaWSI79CVtHVRdkFuFTViiqzZhkCgfOh9USpriuGN2gIpZDQ==",
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-6.3.0.tgz",
"integrity": "sha512-ZvZTKaqDue+N8Y9g0kp6UPZtS4FSY3qARxBs7p4f0H0iof381XHduqVerFWtK8DPtKmemqbqCFENWSQgPR/Gow==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
@ -784,9 +820,9 @@
"debug": "^4.0.1",
"doctrine": "^3.0.0",
"eslint-scope": "^5.0.0",
"eslint-utils": "^1.3.1",
"eslint-visitor-keys": "^1.0.0",
"espree": "^6.0.0",
"eslint-utils": "^1.4.2",
"eslint-visitor-keys": "^1.1.0",
"espree": "^6.1.1",
"esquery": "^1.0.1",
"esutils": "^2.0.2",
"file-entry-cache": "^5.0.1",
@ -830,9 +866,9 @@
},
"dependencies": {
"semver": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
}
@ -1065,9 +1101,9 @@
}
},
"eslint-plugin-svelte3": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-svelte3/-/eslint-plugin-svelte3-2.6.0.tgz",
"integrity": "sha512-HtoVRGo/Bjkv/S+OyPZFL/O8kwLAQpWvW4QJMP9BKDyBkrAPMlEdXCLPMFAOH4cEJmkdfgJIWuTNwOPX8W571A==",
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-svelte3/-/eslint-plugin-svelte3-2.7.3.tgz",
"integrity": "sha512-p6HhxyICX9x/x+8WSy6AVk2bmv9ayoznoTSyCvK47th/k/07ksuJixMwbGX9qxJVAmPBaYMjEIMSEZtJHPIN7w==",
"dev": true
},
"eslint-scope": {
@ -1081,26 +1117,37 @@
}
},
"eslint-utils": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz",
"integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==",
"dev": true
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz",
"integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==",
"dev": true,
"requires": {
"eslint-visitor-keys": "^1.0.0"
}
},
"eslint-visitor-keys": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
"integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
"integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==",
"dev": true
},
"espree": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-6.0.0.tgz",
"integrity": "sha512-lJvCS6YbCn3ImT3yKkPe0+tJ+mH6ljhGNjHQH9mRtiO6gjhVAOhVXW1yjnwqGwTkK3bGbye+hb00nFNmu0l/1Q==",
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-6.1.1.tgz",
"integrity": "sha512-EYbr8XZUhWbYCqQRW0duU5LxzL5bETN6AjKBGy1302qqzPaCH10QbRg3Wvco79Z8x9WbiE8HYB4e75xl6qUYvQ==",
"dev": true,
"requires": {
"acorn": "^6.0.7",
"acorn-jsx": "^5.0.0",
"eslint-visitor-keys": "^1.0.0"
"acorn": "^7.0.0",
"acorn-jsx": "^5.0.2",
"eslint-visitor-keys": "^1.1.0"
},
"dependencies": {
"acorn": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.0.0.tgz",
"integrity": "sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==",
"dev": true
}
}
},
"esprima": {
@ -1587,9 +1634,9 @@
"dev": true
},
"inquirer": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.0.tgz",
"integrity": "sha512-scfHejeG/lVZSpvCXpsB4j/wQNPM5JC8kiElOI0OUTwmc1RTpXr4H32/HOlQHcZiYl2z2VElwuCVDRG8vFmbnA==",
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz",
"integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==",
"dev": true,
"requires": {
"ansi-escapes": "^3.2.0",
@ -1850,6 +1897,12 @@
"xml-name-validator": "^3.0.0"
},
"dependencies": {
"acorn": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz",
"integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==",
"dev": true
},
"ws": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.1.1.tgz",
@ -2034,12 +2087,6 @@
}
}
},
"make-error": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz",
"integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==",
"dev": true
},
"map-age-cleaner": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
@ -2786,9 +2833,9 @@
"dev": true
},
"puppeteer": {
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.18.1.tgz",
"integrity": "sha512-luUy0HPSuWPsPZ1wAp6NinE0zgetWtudf5zwZ6dHjMWfYpTQcmKveFRox7VBNhQ98OjNA9PQ9PzQyX8k/KrxTg==",
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.19.0.tgz",
"integrity": "sha512-2S6E6ygpoqcECaagDbBopoSOPDv0pAZvTbnBgUY+6hq0/XDFDOLEMNlHF/SKJlzcaZ9ckiKjKDuueWI3FN/WXw==",
"dev": true,
"requires": {
"debug": "^4.1.0",
@ -2852,14 +2899,6 @@
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
},
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true
}
}
},
"regexpp": {
@ -2993,28 +3032,34 @@
}
},
"rollup": {
"version": "1.16.6",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-1.16.6.tgz",
"integrity": "sha512-oM3iKkzPCq9Da95wCnNfS8YlNZjgCD5c/TceKnJIthI9FOeJqnO3PUr/C5Suv9Kjzh0iphKL02PLeja3A5AMIA==",
"version": "1.20.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-1.20.3.tgz",
"integrity": "sha512-/OMCkY0c6E8tleeVm4vQVDz24CkVgvueK3r8zTYu2AQNpjrcaPwO9hE+pWj5LTFrvvkaxt4MYIp2zha4y0lRvg==",
"dev": true,
"requires": {
"@types/estree": "0.0.39",
"@types/node": "^12.0.10",
"acorn": "^6.1.1"
"@types/node": "^12.7.2",
"acorn": "^7.0.0"
},
"dependencies": {
"@types/node": {
"version": "12.0.12",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.12.tgz",
"integrity": "sha512-Uy0PN4R5vgBUXFoJrKryf5aTk3kJ8Rv3PdlHjl6UaX+Cqp1QE0yPQ68MPXGrZOfG7gZVNDIJZYyot0B9ubXUrQ==",
"version": "12.7.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.4.tgz",
"integrity": "sha512-W0+n1Y+gK/8G2P/piTkBBN38Qc5Q1ZSO6B5H3QmPCUewaiXOo2GCAWZ4ElZCcNhjJuBSUSLGFUJnmlCn5+nxOQ==",
"dev": true
},
"acorn": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.0.0.tgz",
"integrity": "sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==",
"dev": true
}
}
},
"rollup-plugin-commonjs": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.0.1.tgz",
"integrity": "sha512-x0PcCVdEc4J8igv1qe2vttz8JKAKcTs3wfIA3L8xEty3VzxgORLrzZrNWaVMc+pBC4U3aDOb9BnWLAQ8J11vkA==",
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz",
"integrity": "sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q==",
"dev": true,
"requires": {
"estree-walker": "^0.6.1",
@ -3101,9 +3146,9 @@
}
},
"rxjs": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz",
"integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==",
"version": "6.5.3",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz",
"integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==",
"dev": true,
"requires": {
"tslib": "^1.9.0"
@ -3181,9 +3226,9 @@
"dev": true
},
"source-map-support": {
"version": "0.5.12",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz",
"integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==",
"version": "0.5.13",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
"integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
"dev": true,
"requires": {
"buffer-from": "^1.0.0",
@ -3275,14 +3320,6 @@
"dev": true,
"requires": {
"safe-buffer": "~5.1.0"
},
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true
}
}
},
"strip-ansi": {
@ -3340,9 +3377,9 @@
"dev": true
},
"table": {
"version": "5.4.5",
"resolved": "https://registry.npmjs.org/table/-/table-5.4.5.tgz",
"integrity": "sha512-oGa2Hl7CQjfoaogtrOHEJroOcYILTx7BZWLGsJIlzoWmB2zmguhNfPJZsWPKYek/MgCxfco54gEi31d1uN2hFA==",
"version": "5.4.6",
"resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
"integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==",
"dev": true,
"requires": {
"ajv": "^6.10.2",
@ -3443,27 +3480,6 @@
"punycode": "^2.1.0"
}
},
"ts-node": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.3.0.tgz",
"integrity": "sha512-dyNS/RqyVTDcmNM4NIBAeDMpsAdaQ+ojdf0GOLqE6nwJOgzEkdRNzJywhDfwnuvB10oa6NLVG1rUJQCpRN7qoQ==",
"dev": true,
"requires": {
"arg": "^4.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"source-map-support": "^0.5.6",
"yn": "^3.0.0"
},
"dependencies": {
"diff": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz",
"integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==",
"dev": true
}
}
},
"tslib": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
@ -3471,9 +3487,9 @@
"dev": true
},
"tsutils": {
"version": "3.14.0",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.14.0.tgz",
"integrity": "sha512-SmzGbB0l+8I0QwsPgjooFRaRvHLBLNYM8SeQ0k6rtNDru5sCGeLJcZdwilNndN+GysuFjF5EIYgN8GfFG6UeUw==",
"version": "3.17.1",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz",
"integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==",
"dev": true,
"requires": {
"tslib": "^1.8.1"
@ -3510,9 +3526,9 @@
"dev": true
},
"typescript": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.2.tgz",
"integrity": "sha512-7KxJovlYhTX5RaRbUdkAXN1KUZ8PwWlTzQdHV6xNqvuFOs7+WBo10TQUqT19Q/Jz2hk5v9TQDIhyLhhJY4p5AA==",
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz",
"integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==",
"dev": true
},
"uglify-js": {
@ -3554,9 +3570,9 @@
"dev": true
},
"v8-compile-cache": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz",
"integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz",
"integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==",
"dev": true
},
"v8-to-istanbul": {
@ -3961,12 +3977,6 @@
"requires": {
"fd-slicer": "~1.0.1"
}
},
"yn": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.0.tgz",
"integrity": "sha512-kKfnnYkbTfrAdd0xICNFw7Atm8nKpLcLv9AZGEt+kczL/WQVai4e2V6ZN8U/O+iI6WrNuJjNNOyu4zfhl9D3Hg==",
"dev": true
}
}
}

@ -1,6 +1,6 @@
{
"name": "svelte",
"version": "3.6.11",
"version": "3.11.0",
"description": "Cybernetically enhanced web apps",
"module": "index.mjs",
"main": "index",
@ -57,18 +57,17 @@
"homepage": "https://github.com/sveltejs/svelte#README",
"devDependencies": {
"@types/mocha": "^5.2.7",
"@types/node": "=8",
"@typescript-eslint/eslint-plugin": "^1.11.0",
"@typescript-eslint/parser": "^1.11.0",
"acorn": "^6.2.0",
"acorn-dynamic-import": "^4.0.0",
"@types/node": "^8.10.53",
"@typescript-eslint/eslint-plugin": "^1.13.0",
"@typescript-eslint/parser": "^2.1.0",
"acorn": "^7.0.0",
"agadoo": "^1.0.1",
"c8": "^5.0.1",
"codecov": "^3.5.0",
"css-tree": "1.0.0-alpha22",
"eslint": "^6.1.0",
"eslint": "^6.3.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-svelte3": "^2.6.0",
"eslint-plugin-svelte3": "^2.7.3",
"estree-walker": "^0.6.1",
"is-reference": "^1.1.3",
"jsdom": "^15.1.1",
@ -76,9 +75,9 @@
"locate-character": "^2.0.5",
"magic-string": "^0.25.3",
"mocha": "^6.2.0",
"puppeteer": "^1.18.1",
"rollup": "^1.16.6",
"rollup-plugin-commonjs": "^10.0.1",
"puppeteer": "^1.19.0",
"rollup": "^1.20.3",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-json": "^4.0.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-replace": "^2.2.0",
@ -86,11 +85,10 @@
"rollup-plugin-typescript": "^1.0.1",
"rollup-plugin-virtual": "^1.0.1",
"source-map": "^0.6.1",
"source-map-support": "^0.5.12",
"source-map-support": "^0.5.13",
"tiny-glob": "^0.2.6",
"ts-node": "^8.3.0",
"tslib": "^1.10.0",
"typescript": "^3.5.2"
"typescript": "^3.5.3"
},
"nyc": {
"include": [
@ -99,6 +97,5 @@
],
"sourceMap": true,
"instrument": true
},
"dependencies": {}
}
}

@ -20,7 +20,8 @@ const ts_plugin = is_publish
const external = id => id.startsWith('svelte/');
fs.writeFileSync(`./compiler.d.ts`, `export * from './types/compiler/index';`);
const inlined_estree = fs.readFileSync('./node_modules/estree-walker/index.d.ts', 'utf-8').replace(/declare.*\{((.|[\n\r])+)\}/m, '$1');
fs.writeFileSync(`./compiler.d.ts`, `export { compile, parse, preprocess, VERSION } from './types/compiler/index';\n${inlined_estree}`);
export default [
/* runtime */

@ -6,7 +6,3 @@ DATABASE_URL=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
MAPBOX_ACCESS_TOKEN=
JWT_EXP=30d
JWT_ALG=HS512
JWT_KEY=

@ -94,6 +94,14 @@ An element or component can have multiple spread attributes, interspersed with r
<Widget {...things}/>
```
---
*`$$props`* references all props that are passed to a component including ones that are not declared with `export`. It is useful in rare cases, but not generally recommended, as it is difficult for Svelte to optimise.
```html
<Widget {...$$props}/>
```
### Text expressions
@ -111,6 +119,27 @@ Text can also contain JavaScript expressions:
```
### Comments
---
You can use HTML comments inside components.
```html
<!-- this is a comment! -->
<h1>Hello world</h1>
```
---
Comments beginning with `svelte-ignore` disable warnings for the next block of markup. Usually these are accessibility warnings; make sure that you're disabling them for a good reason.
```html
<!-- svelte-ignore a11y-autofocus -->
<input bind:value={name} autofocus>
```
### {#if ...}
```sv
@ -289,7 +318,9 @@ If you don't care about the pending state, you can also omit the initial block.
---
In a text expression, characters like `<` and `>` are escaped. With HTML expressions, they're not.
In a text expression, characters like `<` and `>` are escaped; however, with HTML expressions, they're not.
The expression should be valid standalone HTML — `{@html "<div>"}content{@html "</div>"}` will *not* work, because `</div>` is not valid HTML.
> Svelte does not sanitize expressions before injecting HTML. If the data comes from an untrusted source, you must sanitize it, or you are exposing your users to an XSS vulnerability.

@ -532,6 +532,42 @@ You can see the `fade` transition in action in the [transition tutorial](tutoria
{/if}
```
#### `blur`
```sv
transition:blur={params}
```
```sv
in:blur={params}
```
```sv
out:blur={params}
```
---
Animates a `blur` filter alongside an element's opacity.
`blur` accepts the following parameters:
* `delay` (`number`, default 0) — milliseconds before starting
* `duration` (`number`, default 400) — milliseconds the transition lasts
* `easing` (`function`, default `cubicInOut`) — an [easing function](docs#svelte_easing)
* `opacity` (`number`, default 0) - the opacity value to animate out to and in from
* `amount` (`number`, default 5) - the size of the blur in pixels
```html
<script>
import { blur } from 'svelte/transition';
</script>
{#if condition}
<div transition:blur="{{amount: 10}}">
fades in and out
</div>
{/if}
```
#### `fly`
```sv
@ -667,7 +703,7 @@ Animates the stroke of an SVG element, like a snake in a tube. `in` transitions
* `duration` (`number` | `function`, default 800) — milliseconds the transition lasts
* `easing` (`function`, default `cubicInOut`) — an [easing function](docs#svelte_easing)
The `speed` parameter is a means of setting the duration of the transition relative to the path's length. It is modifier that is applied to the length of the path: `duration = length / speed`. A path that is 1000 pixels with a speed of 1 will have a duration of `1000ms`, setting the speed to `0.5` will halve that duration and setting it to `2` will double it.
The `speed` parameter is a means of setting the duration of the transition relative to the path's length. It is modifier that is applied to the length of the path: `duration = length / speed`. A path that is 1000 pixels with a speed of 1 will have a duration of `1000ms`, setting the speed to `0.5` will double that duration and setting it to `2` will halve it.
```html
<script>
@ -907,7 +943,68 @@ app.count += 1;
### Custom element API
* TODO
---
Svelte components can also be compiled to custom elements (aka web components) using the `customElements: true` compiler option. You should specify a tag name for the component using the `<svelte:options>` [element](docs#svelte_options).
```html
<svelte:options tag="my-element">
<script>
export let name = 'world';
</script>
<h1>Hello {name}!</h1>
<slot></slot>
```
---
Alternatively, use `tag={null}` to indicate that the consumer of the custom element should name it.
```js
import MyElement from './MyElement.svelte';
customElements.define('my-element', MyElement);
```
---
Once a custom element has been defined, it can be used as a regular DOM element:
```js
document.body.innerHTML = `
<my-element>
<p>This is some slotted content</p>
</my-element>
`;
```
---
By default, custom elements are compiled with `accessors: true`, which means that any [props](docs#Attributes_and_props) are exposed as properties of the DOM element (as well as being readable/writable as attributes, where possible).
To prevent this, add `accessors={false}` to `<svelte:options>`.
```js
const el = document.querySelector('my-element');
// get the current value of the 'name' prop
console.log(el.name);
// set a new value, updating the shadow DOM
el.name = 'everybody';
```
Custom elements can be a useful way to package components for consumption in a non-Svelte app, as they will work with vanilla HTML and JavaScript as well as [most frameworks](https://custom-elements-everywhere.com/). There are, however, some important differences to be aware of:
* Styles are *encapsulated*, rather than merely *scoped*. This means that any non-component styles (such as you might have in a `global.css` file) will not apply to the custom element, including styles with the `:global(...)` modifier
* Instead of being extracted out as a separate .css file, styles are inlined into the component as a JavaScript string
* Custom elements are not generally suitable for server-side rendering, as the shadow DOM is invisible until JavaScript loads
* In Svelte, slotted content renders *lazily*. In the DOM, it renders *eagerly*. In other words, it will always be created even if the component's `<slot>` element is inside an `{#if ...}` block. Similarly, including a `<slot>` in an `{#each ...}` block will not cause the slotted content to be rendered multiple times
* The `let:` directive has no effect
* Polyfills are required to support older browsers
### Server-side component API

@ -186,15 +186,15 @@ result: {
} = svelte.preprocess(
source: string,
preprocessors: Array<{
markup?: (input: { source: string, filename: string }) => Promise<{
markup?: (input: { content: string, filename: string }) => Promise<{
code: string,
dependencies?: Array<string>
}>,
script?: (input: { source: string, attributes: Record<string, string>, filename: string }) => Promise<{
script?: (input: { content: string, attributes: Record<string, string>, filename: string }) => Promise<{
code: string,
dependencies?: Array<string>
}>,
style?: (input: { source: string, attributes: Record<string, string>, filename: string }) => Promise<{
style?: (input: { content: string, attributes: Record<string, string>, filename: string }) => Promise<{
code: string,
dependencies?: Array<string>
}>

@ -2,11 +2,11 @@
import Thing from './Thing.svelte';
let things = [
{ id: 1, value: 'a' },
{ id: 2, value: 'b' },
{ id: 3, value: 'c' },
{ id: 4, value: 'd' },
{ id: 5, value: 'e' }
{ id: 1, color: '#0d0887' },
{ id: 2, color: '#6a00a8' },
{ id: 3, color: '#b12a90' },
{ id: 4, color: '#e16462' },
{ id: 5, color: '#fca636' }
];
function handleClick() {
@ -22,14 +22,14 @@
<div>
<h2>Keyed</h2>
{#each things as thing (thing.id)}
<Thing value={thing.value}/>
<Thing current={thing.color}/>
{/each}
</div>
<div>
<h2>Unkeyed</h2>
{#each things as thing}
<Thing value={thing.value}/>
<Thing current={thing.color}/>
{/each}
</div>
</div>

@ -1,9 +1,24 @@
<script>
// `value` is updated whenever the prop value changes...
export let value;
// `current` is updated whenever the prop value changes...
export let current;
// ...but `valueAtStart` is fixed upon initialisation
const valueAtStart = value;
// ...but `initial` is fixed upon initialisation
const initial = current;
</script>
<p>{valueAtStart} / {value}</p>
<p>
<span style="background-color: {initial}">initial</span>
<span style="background-color: {current}">current</span>
</p>
<style>
span {
display: inline-block;
padding: 0.2em 0.5em;
margin: 0 0.2em 0.2em 0;
width: 4em;
text-align: center;
border-radius: 0.2em;
color: white;
}
</style>

@ -109,8 +109,8 @@
<div>
<video
poster="http://svelte-assets.surge.sh/caminandes-llamigos.jpg"
src="http://svelte-assets.surge.sh/caminandes-llamigos.mp4"
poster="http://sveltejs.github.io/assets/caminandes-llamigos.jpg"
src="http://sveltejs.github.io/assets/caminandes-llamigos.mp4"
on:mousemove={handleMousemove}
on:mousedown={handleMousedown}
bind:currentTime={time}

@ -2,10 +2,6 @@
import { onMount } from 'svelte';
let canvas;
let ctx;
let running = false;
const r = Math.random();
onMount(() => {
const ctx = canvas.getContext('2d');

@ -1,140 +1,153 @@
<script>
import { quintOut } from 'svelte/easing';
import crossfade from './crossfade.js'; // TODO put this in svelte/transition!
const { send, receive } = crossfade({
fallback(node, params) {
const style = getComputedStyle(node);
const transform = style.transform === 'none' ? '' : style.transform;
return {
duration: 600,
easing: quintOut,
css: t => `
transform: ${transform} scale(${t});
opacity: ${t}
`
};
}
import { crossfade, scale } from 'svelte/transition';
import images from './images.js';
const [send, receive] = crossfade({
duration: 200,
fallback: scale
});
let todos = [
{ id: 1, done: false, description: 'write some docs' },
{ id: 2, done: false, description: 'start writing JSConf talk' },
{ id: 3, done: true, description: 'buy some milk' },
{ id: 4, done: false, description: 'mow the lawn' },
{ id: 5, done: false, description: 'feed the turtle' },
{ id: 6, done: false, description: 'fix some bugs' },
];
let uid = todos.length + 1;
function add(input) {
const todo = {
id: uid++,
done: false,
description: input.value
};
let selected = null;
let loading = null;
todos = [todo, ...todos];
input.value = '';
}
const ASSETS = `https://sveltejs.github.io/assets/crossfade`;
function remove(todo) {
todos = todos.filter(t => t !== todo);
}
const load = image => {
const timeout = setTimeout(() => loading = image, 100);
const img = new Image();
img.onload = () => {
selected = image;
clearTimeout(timeout);
loading = null;
};
img.src = `${ASSETS}/${image.id}.jpg`;
};
</script>
<div class="container">
<div class="phone">
<h1>Photo gallery</h1>
<div class="grid">
{#each images as image}
<div class="square">
{#if selected !== image}
<button
style="background-color: {image.color};"
on:click="{() => load(image)}"
in:receive={{key:image.id}}
out:send={{key:image.id}}
>{loading === image ? '...' : image.id}</button>
{/if}
</div>
{/each}
</div>
{#if selected}
{#await selected then d}
<div class="photo" in:receive={{key:d.id}} out:send={{key:d.id}}>
<img
alt={d.alt}
src="{ASSETS}/{d.id}.jpg"
on:click="{() => selected = null}"
>
<p class='credit'>
<a target="_blank" href="https://www.flickr.com/photos/{d.path}">via Flickr</a> &ndash;
<a target="_blank" href={d.license.url}>{d.license.name}</a>
</p>
</div>
{/await}
{/if}
</div>
</div>
<style>
.new-todo {
font-size: 1.4em;
.container {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
margin: 2em 0 1em 0;
height: 100%;
top: 0;
left: 0;
}
.board {
max-width: 36em;
margin: 0 auto;
.phone {
position: relative;
display: flex;
flex-direction: column;
width: 52vmin;
height: 76vmin;
border: 2vmin solid #ccc;
border-bottom-width: 10vmin;
padding: 3vmin;
border-radius: 2vmin;
}
.left, .right {
float: left;
width: 50%;
padding: 0 1em 0 0;
box-sizing: border-box;
h1 {
font-weight: 300;
text-transform: uppercase;
font-size: 5vmin;
margin: 0.2em 0 0.5em 0;
}
h2 {
font-size: 2em;
font-weight: 200;
user-select: none;
.grid {
display: grid;
flex: 1;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(4, 1fr);
grid-gap: 2vmin;
}
label {
button {
width: 100%;
height: 100%;
color: white;
font-size: 5vmin;
border: none;
margin: 0;
will-change: transform;
}
.photo, img {
position: absolute;
top: 0;
left: 0;
display: block;
font-size: 1em;
line-height: 1;
padding: 0.5em;
margin: 0 auto 0.5em auto;
border-radius: 2px;
background-color: #eee;
user-select: none;
width: 100%;
height: 100%;
overflow: hidden;
}
input { margin: 0 }
.photo {
display: flex;
align-items: stretch;
justify-content: flex-end;
flex-direction: column;
will-change: transform;
}
.right label {
background-color: rgb(180,240,100);
img {
object-fit: cover;
cursor: pointer;
}
button {
float: right;
height: 1em;
box-sizing: border-box;
padding: 0 0.5em;
line-height: 1;
background-color: transparent;
border: none;
color: rgb(170,30,30);
opacity: 0;
transition: opacity 0.2s;
.credit {
text-align: right;
font-size: 2.5vmin;
padding: 1em;
margin: 0;
color: white;
font-weight: bold;
opacity: 0.6;
background: rgba(0,0,0,0.4);
}
label:hover button {
opacity: 1;
.credit a, .credit a:visited {
color: white;
}
</style>
<div class='board'>
<input class="new-todo" placeholder="what needs to be done?" on:keydown="{event => event.which === 13 && add(event.target)}">
<div class='left'>
<h2>todo</h2>
{#each todos.filter(t => !t.done) as todo (todo.id)}
<label
in:receive="{{key: todo.id}}"
out:send="{{key: todo.id}}"
>
<input type=checkbox bind:checked={todo.done}>
{todo.description}
<button on:click="{() => remove(todo)}">x</button>
</label>
{/each}
</div>
<div class='right'>
<h2>done</h2>
{#each todos.filter(t => t.done) as todo (todo.id)}
<label
in:receive="{{key: todo.id}}"
out:send="{{key: todo.id}}"
>
<input type=checkbox bind:checked={todo.done}>
{todo.description}
<button on:click="{() => remove(todo)}">x</button>
</label>
{/each}
</div>
</div>

@ -1,65 +0,0 @@
import { quintOut } from 'svelte/easing';
export default function crossfade({ send, receive, fallback }) {
let requested = new Map();
let provided = new Map();
function crossfade(from, node) {
const to = node.getBoundingClientRect();
const dx = from.left - to.left;
const dy = from.top - to.top;
const style = getComputedStyle(node);
const transform = style.transform === 'none' ? '' : style.transform;
return {
duration: 400,
easing: quintOut,
css: (t, u) => `
opacity: ${t};
transform: ${transform} translate(${u * dx}px,${u * dy}px);
`
};
}
return {
send(node, params) {
provided.set(params.key, {
rect: node.getBoundingClientRect()
});
return () => {
if (requested.has(params.key)) {
const { rect } = requested.get(params.key);
requested.delete(params.key);
return crossfade(rect, node);
}
// if the node is disappearing altogether
// (i.e. wasn't claimed by the other list)
// then we need to supply an outro
provided.delete(params.key);
return fallback(node, params);
};
},
receive(node, params) {
requested.set(params.key, {
rect: node.getBoundingClientRect()
});
return () => {
if (provided.has(params.key)) {
const { rect } = provided.get(params.key);
provided.delete(params.key);
return crossfade(rect, node);
}
requested.delete(params.key);
return fallback(node, params);
};
}
};
}

@ -0,0 +1,102 @@
const BY = {
name: 'CC BY 2.0',
url: 'https://creativecommons.org/licenses/by/2.0/'
};
const BY_SA = {
name: 'CC BY-SA 2.0',
url: 'https://creativecommons.org/licenses/by-sa/2.0/'
};
const BY_ND = {
name: 'CC BY-ND 2.0',
url: 'https://creativecommons.org/licenses/by-nd/2.0/'
};
// via http://labs.tineye.com/multicolr
export default [
{
color: '#001f3f',
id: '1',
alt: 'Crepuscular rays',
path: '43428526@N03/7863279376',
license: BY
},
{
color: '#0074D9',
id: '2',
alt: 'Lapland winter scene',
path: '25507134@N00/6527537485',
license: BY
},
{
color: '#7FDBFF',
id: '3',
alt: 'Jellyfish',
path: '37707866@N00/3354331318',
license: BY
},
{
color: '#39CCCC',
id: '4',
alt: 'A man scuba diving',
path: '32751486@N00/4608886209',
license: BY_SA
},
{
color: '#3D9970',
id: '5',
alt: 'Underwater scene',
path: '25483059@N08/5548569010',
license: BY
},
{
color: '#2ECC40',
id: '6',
alt: 'Ferns',
path: '8404611@N06/2447470760',
license: BY
},
{
color: '#01FF70',
id: '7',
alt: 'Posters in a bar',
path: '33917831@N00/114428206',
license: BY_SA
},
{
color: '#FFDC00',
id: '8',
alt: 'Daffodil',
path: '46417125@N04/4818617089',
license: BY_ND
},
{
color: '#FF851B',
id: '9',
alt: 'Dust storm in Sydney',
path: '56068058@N00/3945496657',
license: BY
},
{
color: '#FF4136',
id: '10',
alt: 'Postbox',
path: '31883499@N05/4216820032',
license: BY
},
{
color: '#85144b',
id: '11',
alt: 'Fireworks',
path: '8484971@N07/2625506561',
license: BY_ND
},
{
color: '#B10DC9',
id: '12',
alt: 'The Stereophonics',
path: '58028312@N00/5385464371',
license: BY_ND
}
];

@ -8,7 +8,7 @@
<!-- https://musopen.org/music/9862-the-blue-danube-op-314/ -->
<AudioPlayer
src="tutorial/music/strauss.mp3"
src="https://sveltejs.github.io/assets/music/strauss.mp3"
title="The Blue Danube Waltz"
composer="Johann Strauss"
performer="European Archive"
@ -16,7 +16,7 @@
<!-- https://musopen.org/music/43775-the-planets-op-32/ -->
<AudioPlayer
src="tutorial/music/holst.mp3"
src="https://sveltejs.github.io/assets/music/holst.mp3"
title="Mars, the Bringer of War"
composer="Gustav Holst"
performer="USAF Heritage of America Band"
@ -24,7 +24,7 @@
<!-- https://musopen.org/music/8010-3-gymnopedies/ -->
<AudioPlayer
src="tutorial/music/satie.mp3"
src="https://sveltejs.github.io/assets/music/satie.mp3"
title="Gymnopédie no. 1"
composer="Erik Satie"
performer="Prodigal Procrastinator"
@ -32,7 +32,7 @@
<!-- https://musopen.org/music/2567-symphony-no-5-in-c-minor-op-67/ -->
<AudioPlayer
src="tutorial/music/beethoven.mp3"
src="https://sveltejs.github.io/assets/music/beethoven.mp3"
title="Symphony no. 5 in Cm, Op. 67 - I. Allegro con brio"
composer="Ludwig van Beethoven"
performer="European Archive"
@ -40,7 +40,7 @@
<!-- https://musopen.org/music/43683-requiem-in-d-minor-k-626/ -->
<AudioPlayer
src="tutorial/music/mozart.mp3"
src="https://sveltejs.github.io/assets/music/mozart.mp3"
title="Requiem in D minor, K. 626 - III. Sequence - Lacrymosa"
composer="Wolfgang Amadeus Mozart"
performer="Markus Staab"

@ -2,11 +2,11 @@
import Thing from './Thing.svelte';
let things = [
{ id: 1, value: 'a' },
{ id: 2, value: 'b' },
{ id: 3, value: 'c' },
{ id: 4, value: 'd' },
{ id: 5, value: 'e' }
{ id: 1, color: '#0d0887' },
{ id: 2, color: '#6a00a8' },
{ id: 3, color: '#b12a90' },
{ id: 4, color: '#e16462' },
{ id: 5, color: '#fca636' }
];
function handleClick() {
@ -19,5 +19,5 @@
</button>
{#each things as thing}
<Thing value={thing.value}/>
<Thing current={thing.color}/>
{/each}

@ -1,9 +1,24 @@
<script>
// `value` is updated whenever the prop value changes...
export let value;
// `current` is updated whenever the prop value changes...
export let current;
// ...but `valueAtStart` is fixed upon initialisation
const valueAtStart = value;
// ...but `initial` is fixed upon initialisation
const initial = current;
</script>
<p>{valueAtStart} / {value}</p>
<p>
<span style="background-color: {initial}">initial</span>
<span style="background-color: {current}">current</span>
</p>
<style>
span {
display: inline-block;
padding: 0.2em 0.5em;
margin: 0 0.2em 0.2em 0;
width: 4em;
text-align: center;
border-radius: 0.2em;
color: white;
}
</style>

@ -2,11 +2,11 @@
import Thing from './Thing.svelte';
let things = [
{ id: 1, value: 'a' },
{ id: 2, value: 'b' },
{ id: 3, value: 'c' },
{ id: 4, value: 'd' },
{ id: 5, value: 'e' }
{ id: 1, color: '#0d0887' },
{ id: 2, color: '#6a00a8' },
{ id: 3, color: '#b12a90' },
{ id: 4, color: '#e16462' },
{ id: 5, color: '#fca636' }
];
function handleClick() {
@ -19,5 +19,5 @@
</button>
{#each things as thing (thing.id)}
<Thing value={thing.value}/>
<Thing current={thing.color}/>
{/each}

@ -1,9 +1,24 @@
<script>
// `value` is updated whenever the prop value changes...
export let value;
// `current` is updated whenever the prop value changes...
export let current;
// ...but `valueAtStart` is fixed upon initialisation
const valueAtStart = value;
// ...but `initial` is fixed upon initialisation
const initial = current;
</script>
<p>{valueAtStart} / {value}</p>
<p>
<span style="background-color: {initial}">initial</span>
<span style="background-color: {current}">current</span>
</p>
<style>
span {
display: inline-block;
padding: 0.2em 0.5em;
margin: 0 0.2em 0.2em 0;
width: 4em;
text-align: center;
border-radius: 0.2em;
color: white;
}
</style>

@ -10,7 +10,7 @@ To do that, we specify a unique identifier for the `each` block:
```html
{#each things as thing (thing.id)}
<Thing value={thing.value}/>
<Thing current={thing.value}/>
{/each}
```

@ -23,5 +23,6 @@ The full list of modifiers:
* `passive` — improves scrolling performance on touch/wheel events (Svelte will add it automatically where it's safe to do so)
* `capture` — fires the handler during the *capture* phase instead of the *bubbling* phase ([MDN docs](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#Event_bubbling_and_capture))
* `once` — remove the handler after the first time it runs
* `self` — only trigger handler if event.target is the element itself
You can chain modifiers together, e.g. `on:click|once|capture={...}`.

@ -4,7 +4,7 @@ title: Text inputs
As a general rule, data flow in Svelte is *top down* — a parent component can set props on a child component, and a component can set attributes on an element, but not the other way around.
Sometimes it's useful to break that rule. Take the case of the `<input>` element in this component — we *could* add an `on:input` event handler that set the value of `name` to `event.target.value`, but it's a bit... boilerplatey. It gets even worse with other form elements, as we'll see.
Sometimes it's useful to break that rule. Take the case of the `<input>` element in this component — we *could* add an `on:input` event handler that sets the value of `name` to `event.target.value`, but it's a bit... boilerplatey. It gets even worse with other form elements, as we'll see.
Instead, we can use the `bind:value` directive:

@ -109,8 +109,8 @@
<div>
<video
poster="https://svelte-assets.surge.sh/caminandes-llamigos.jpg"
src="https://svelte-assets.surge.sh/caminandes-llamigos.mp4"
poster="https://sveltejs.github.io/assets/caminandes-llamigos.jpg"
src="https://sveltejs.github.io/assets/caminandes-llamigos.mp4"
on:mousemove={handleMousemove}
on:mousedown={handleMousedown}
></video>

@ -109,8 +109,8 @@
<div>
<video
poster="https://svelte-assets.surge.sh/caminandes-llamigos.jpg"
src="https://svelte-assets.surge.sh/caminandes-llamigos.mp4"
poster="https://sveltejs.github.io/assets/caminandes-llamigos.jpg"
src="https://sveltejs.github.io/assets/caminandes-llamigos.mp4"
on:mousemove={handleMousemove}
on:mousedown={handleMousedown}
bind:currentTime={time}

@ -8,8 +8,8 @@ On line 116, add `currentTime={time}`, `duration` and `paused` bindings:
```html
<video
poster="https://svelte-assets.surge.sh/caminandes-llamigos.jpg"
src="https://svelte-assets.surge.sh/caminandes-llamigos.mp4"
poster="https://sveltejs.github.io/assets/caminandes-llamigos.jpg"
src="https://sveltejs.github.io/assets/caminandes-llamigos.mp4"
on:mousemove={handleMousemove}
on:mousedown={handleMousedown}
bind:currentTime={time}

@ -4,7 +4,7 @@
<!-- https://musopen.org/music/9862-the-blue-danube-op-314/ -->
<AudioPlayer
src="tutorial/music/strauss.mp3"
src="https://sveltejs.github.io/assets/music/strauss.mp3"
title="The Blue Danube Waltz"
composer="Johann Strauss"
performer="European Archive"
@ -12,7 +12,7 @@
<!-- https://musopen.org/music/43775-the-planets-op-32/ -->
<AudioPlayer
src="tutorial/music/holst.mp3"
src="https://sveltejs.github.io/assets/music/holst.mp3"
title="Mars, the Bringer of War"
composer="Gustav Holst"
performer="USAF Heritage of America Band"
@ -20,7 +20,7 @@
<!-- https://musopen.org/music/8010-3-gymnopedies/ -->
<AudioPlayer
src="tutorial/music/satie.mp3"
src="https://sveltejs.github.io/assets/music/satie.mp3"
title="Gymnopédie no. 1"
composer="Erik Satie"
performer="Prodigal Procrastinator"
@ -28,7 +28,7 @@
<!-- https://musopen.org/music/2567-symphony-no-5-in-c-minor-op-67/ -->
<AudioPlayer
src="tutorial/music/beethoven.mp3"
src="https://sveltejs.github.io/assets/music/beethoven.mp3"
title="Symphony no. 5 in Cm, Op. 67 - I. Allegro con brio"
composer="Ludwig van Beethoven"
performer="European Archive"
@ -36,7 +36,7 @@
<!-- https://musopen.org/music/43683-requiem-in-d-minor-k-626/ -->
<AudioPlayer
src="tutorial/music/mozart.mp3"
src="https://sveltejs.github.io/assets/music/mozart.mp3"
title="Requiem in D minor, K. 626 - III. Sequence - Lacrymosa"
composer="Wolfgang Amadeus Mozart"
performer="Markus Staab"

@ -4,7 +4,7 @@
<!-- https://musopen.org/music/9862-the-blue-danube-op-314/ -->
<AudioPlayer
src="tutorial/music/strauss.mp3"
src="https://sveltejs.github.io/assets/music/strauss.mp3"
title="The Blue Danube Waltz"
composer="Johann Strauss"
performer="European Archive"
@ -12,7 +12,7 @@
<!-- https://musopen.org/music/43775-the-planets-op-32/ -->
<AudioPlayer
src="tutorial/music/holst.mp3"
src="https://sveltejs.github.io/assets/music/holst.mp3"
title="Mars, the Bringer of War"
composer="Gustav Holst"
performer="USAF Heritage of America Band"
@ -20,7 +20,7 @@
<!-- https://musopen.org/music/8010-3-gymnopedies/ -->
<AudioPlayer
src="tutorial/music/satie.mp3"
src="https://sveltejs.github.io/assets/music/satie.mp3"
title="Gymnopédie no. 1"
composer="Erik Satie"
performer="Prodigal Procrastinator"
@ -28,7 +28,7 @@
<!-- https://musopen.org/music/2567-symphony-no-5-in-c-minor-op-67/ -->
<AudioPlayer
src="tutorial/music/beethoven.mp3"
src="https://sveltejs.github.io/assets/music/beethoven.mp3"
title="Symphony no. 5 in Cm, Op. 67 - I. Allegro con brio"
composer="Ludwig van Beethoven"
performer="European Archive"
@ -36,7 +36,7 @@
<!-- https://musopen.org/music/43683-requiem-in-d-minor-k-626/ -->
<AudioPlayer
src="tutorial/music/mozart.mp3"
src="https://sveltejs.github.io/assets/music/mozart.mp3"
title="Requiem in D minor, K. 626 - III. Sequence - Lacrymosa"
composer="Wolfgang Amadeus Mozart"
performer="Markus Staab"

@ -8,7 +8,7 @@
<!-- https://musopen.org/music/9862-the-blue-danube-op-314/ -->
<AudioPlayer
src="tutorial/music/strauss.mp3"
src="https://sveltejs.github.io/assets/music/strauss.mp3"
title="The Blue Danube Waltz"
composer="Johann Strauss"
performer="European Archive"
@ -16,7 +16,7 @@
<!-- https://musopen.org/music/43775-the-planets-op-32/ -->
<AudioPlayer
src="tutorial/music/holst.mp3"
src="https://sveltejs.github.io/assets/music/holst.mp3"
title="Mars, the Bringer of War"
composer="Gustav Holst"
performer="USAF Heritage of America Band"
@ -24,7 +24,7 @@
<!-- https://musopen.org/music/8010-3-gymnopedies/ -->
<AudioPlayer
src="tutorial/music/satie.mp3"
src="https://sveltejs.github.io/assets/music/satie.mp3"
title="Gymnopédie no. 1"
composer="Erik Satie"
performer="Prodigal Procrastinator"
@ -32,7 +32,7 @@
<!-- https://musopen.org/music/2567-symphony-no-5-in-c-minor-op-67/ -->
<AudioPlayer
src="tutorial/music/beethoven.mp3"
src="https://sveltejs.github.io/assets/music/beethoven.mp3"
title="Symphony no. 5 in Cm, Op. 67 - I. Allegro con brio"
composer="Ludwig van Beethoven"
performer="European Archive"
@ -40,7 +40,7 @@
<!-- https://musopen.org/music/43683-requiem-in-d-minor-k-626/ -->
<AudioPlayer
src="tutorial/music/mozart.mp3"
src="https://sveltejs.github.io/assets/music/mozart.mp3"
title="Requiem in D minor, K. 626 - III. Sequence - Lacrymosa"
composer="Wolfgang Amadeus Mozart"
performer="Markus Staab"

@ -8,7 +8,7 @@
<!-- https://musopen.org/music/9862-the-blue-danube-op-314/ -->
<AudioPlayer
src="tutorial/music/strauss.mp3"
src="https://sveltejs.github.io/assets/music/strauss.mp3"
title="The Blue Danube Waltz"
composer="Johann Strauss"
performer="European Archive"
@ -16,7 +16,7 @@
<!-- https://musopen.org/music/43775-the-planets-op-32/ -->
<AudioPlayer
src="tutorial/music/holst.mp3"
src="https://sveltejs.github.io/assets/music/holst.mp3"
title="Mars, the Bringer of War"
composer="Gustav Holst"
performer="USAF Heritage of America Band"
@ -24,7 +24,7 @@
<!-- https://musopen.org/music/8010-3-gymnopedies/ -->
<AudioPlayer
src="tutorial/music/satie.mp3"
src="https://sveltejs.github.io/assets/music/satie.mp3"
title="Gymnopédie no. 1"
composer="Erik Satie"
performer="Prodigal Procrastinator"
@ -32,7 +32,7 @@
<!-- https://musopen.org/music/2567-symphony-no-5-in-c-minor-op-67/ -->
<AudioPlayer
src="tutorial/music/beethoven.mp3"
src="https://sveltejs.github.io/assets/music/beethoven.mp3"
title="Symphony no. 5 in Cm, Op. 67 - I. Allegro con brio"
composer="Ludwig van Beethoven"
performer="European Archive"
@ -40,7 +40,7 @@
<!-- https://musopen.org/music/43683-requiem-in-d-minor-k-626/ -->
<AudioPlayer
src="tutorial/music/mozart.mp3"
src="https://sveltejs.github.io/assets/music/mozart.mp3"
title="Requiem in D minor, K. 626 - III. Sequence - Lacrymosa"
composer="Wolfgang Amadeus Mozart"
performer="Markus Staab"

@ -0,0 +1,15 @@
exports.up = DB => {
DB.sql(`
create table if not exists sessions (
uid uuid NOT NULL PRIMARY KEY DEFAULT gen_random_uuid(),
user_id integer REFERENCES users(id) not null,
expiry timestamp without time zone DEFAULT now() + interval '1 year'
);
`);
};
exports.down = DB => {
DB.sql(`
drop table if exists sessions;
`);
};

File diff suppressed because it is too large Load Diff

@ -15,37 +15,39 @@
"dependencies": {
"@polka/redirect": "^1.0.0-next.0",
"@polka/send": "^1.0.0-next.3",
"cookie": "^0.4.0",
"devalue": "^2.0.0",
"do-not-zip": "^1.0.0",
"flru": "^1.0.2",
"httpie": "^1.1.2",
"jsonwebtoken": "^8.5.1",
"marked": "^0.7.0",
"pg": "^7.12.0",
"pg": "^7.12.1",
"polka": "^1.0.0-next.4",
"prismjs": "^1.17.1",
"sirv": "^0.4.2",
"yootils": "0.0.16"
},
"devDependencies": {
"@babel/core": "^7.5.5",
"@babel/core": "^7.6.0",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-transform-runtime": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"@babel/runtime": "^7.5.5",
"@babel/plugin-transform-runtime": "^7.6.0",
"@babel/preset-env": "^7.6.0",
"@babel/runtime": "^7.6.0",
"@sindresorhus/slugify": "^0.9.1",
"@sveltejs/site-kit": "^1.1.1",
"@sveltejs/svelte-repl": "^0.1.8",
"degit": "^2.1.3",
"dotenv": "^8.0.0",
"@sveltejs/site-kit": "^1.1.3",
"@sveltejs/svelte-repl": "^0.1.9",
"degit": "^2.1.4",
"dotenv": "^8.1.0",
"esm": "^3.2.25",
"jimp": "^0.6.4",
"jimp": "^0.8.0",
"mocha": "^6.2.0",
"node-fetch": "^2.6.0",
"node-pg-migrate": "^3.21.1",
"node-pg-migrate": "^3.22.0",
"npm-run-all": "^4.1.5",
"rollup": "^1.17.0",
"rollup": "^1.21.0",
"rollup-plugin-babel": "^4.3.3",
"rollup-plugin-commonjs": "^10.0.1",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-json": "^4.0.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-replace": "^2.2.0",
@ -53,7 +55,7 @@
"rollup-plugin-terser": "^5.1.1",
"sapper": "^0.27.8",
"shelljs": "^0.8.3",
"svelte": "^3.6.9"
"svelte": "^3.10.1"
},
"engines": {
"node": ">=10.0.0"

@ -1,150 +0,0 @@
import polka from 'polka';
import devalue from 'devalue';
import send from '@polka/send';
import { get, post } from 'httpie';
import { parse, stringify } from 'querystring';
import { decode, sign, verify } from './token';
import { find, query } from '../utils/db';
const {
BASEURL,
GITHUB_CLIENT_ID,
GITHUB_CLIENT_SECRET,
} = process.env;
const OAuth = 'https://github.com/login/oauth';
function exit(res, code, msg='') {
const error = msg.charAt(0).toUpperCase() + msg.substring(1);
send(res, code, { error });
}
function onError(err, req, res) {
const error = err.message || err;
const code = err.code || err.status || 500;
res.headersSent || send(res, code, { error });
}
/**
* Middleware to determine User validity
*/
export async function isUser(req, res) {
const abort = exit.bind(null, res, 401);
const auth = req.headers.authorization;
if (!auth) return abort('Missing Authorization header');
const [scheme, token] = auth.split(' ');
if (scheme !== 'Bearer' || !token) return abort('Invalid Authorization format');
let data;
const decoded = decode(token, { complete:true });
if (!decoded || !decoded.header) return abort('Invalid token');
try {
data = await verify(token);
} catch (err) {
return abort(err.message);
}
const { uid, username } = data;
if (!uid || !username) return abort('Invalid token payload');
try {
const row = await find(`select * from users where uid = $1 and username = $2 limit 1`, [uid, username]);
return row || abort('Invalid token');
} catch (err) {
console.error('Auth.isUser', err);
return send(res, 500, 'Unknown error occurred');
}
}
export function toUser(obj={}) {
const { uid, username, name, avatar } = obj;
const token = sign({ uid, username });
return { uid, username, name, avatar, token };
}
export function API() {
const app = polka({ onError });
if (GITHUB_CLIENT_ID) {
app.get('/auth/login', (req, res) => {
try {
const Location = `${OAuth}/authorize?` + stringify({
scope: 'read:user',
client_id: GITHUB_CLIENT_ID,
redirect_uri: `${BASEURL}/auth/callback`,
});
send(res, 302, Location, { Location });
} catch (err) {
console.error('GET /auth/login', err);
send(res, 500);
}
});
app.get('/auth/callback', async (req, res) => {
try {
// Trade "code" for "access_token"
const r1 = await post(`${OAuth}/access_token?` + stringify({
code: req.query.code,
client_id: GITHUB_CLIENT_ID,
client_secret: GITHUB_CLIENT_SECRET,
}));
// Now fetch User details
const { access_token } = parse(r1.data);
const r2 = await get('https://api.github.com/user', {
headers: {
'User-Agent': 'svelte.dev',
Authorization: `token ${access_token}`
}
});
const { id, name, avatar_url, login } = r2.data;
// Upsert `users` table
const [user] = await query(`
insert into users(uid, name, username, avatar, github_token)
values ($1, $2, $3, $4, $5) on conflict (uid) do update
set (name, username, avatar, github_token, updated_at) = ($2, $3, $4, $5, now())
returning *
`, [id, name, login, avatar_url, access_token]);
send(res, 200, `
<script>
window.opener.postMessage({
user: ${devalue(toUser(user))}
}, window.location.origin);
</script>
`, {
'Content-Type': 'text/html; charset=utf-8'
});
} catch (err) {
console.error('GET /auth/callback', err);
send(res, 500, err.data, {
'Content-Type': err.headers['content-type'],
'Content-Length': err.headers['content-length']
});
}
});
} else {
// Print "Misconfigured" error
app.get('/auth/login', (req, res) => {
send(res, 500, `
<body style="font-family: sans-serif; background: rgb(255,215,215); border: 2px solid red; margin: 0; padding: 1em;">
<h1>Missing .env file</h1>
<p>In order to use GitHub authentication, you will need to <a target="_blank" href="https://github.com/settings/developers">register an OAuth application</a> and create a local .env file:</p>
<pre>GITHUB_CLIENT_ID=[YOUR_APP_ID]\nGITHUB_CLIENT_SECRET=[YOUR_APP_SECRET]\nBASEURL=http://localhost:3000</pre>
<p>The <code>BASEURL</code> variable should match the callback URL specified for your app.</p>
<p>See also <a target="_blank" href="https://github.com/sveltejs/svelte/tree/master/site#repl-github-integration">here</a></p>
</body>
`, {
'Content-Type': 'text/html; charset=utf-8'
});
});
}
return app;
}

@ -1,22 +0,0 @@
import * as jwt from 'jsonwebtoken';
const { JWT_KEY, JWT_ALG, JWT_EXP } = process.env;
const CONFIG = {
expiresIn: JWT_EXP,
issuer: 'https://svelte.dev',
audience: 'https://svelte.dev',
algorithm: JWT_ALG,
};
export const decode = jwt.decode;
export function sign(obj, opts, cb) {
opts = Object.assign({}, opts, CONFIG);
return jwt.sign(obj, JWT_KEY, opts, cb);
}
export function verify(str, opts, cb) {
opts = Object.assign({}, opts, CONFIG);
return jwt.verify(str, JWT_KEY, opts, cb);
}

@ -45,21 +45,30 @@
</style>
<div class="logos">
<a target="_blank" rel="noopener" href="https://bekchy.com"><img src="organisations/bekchy.png" alt="Bekchy logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://beyonk.com"><img src="organisations/beyonk.svg" alt="Beyonk logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://buydotstar.com"><img src="organisations/buydotstar.svg" alt="buy.* logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://chess.com" style="background-color: rgb(49,46,43);"><img src="organisations/chess.svg" alt="Chess.com logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://comigosaude.com.br"><img src="organisations/comigo.svg" alt="Comigo logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://datawrapper.de"><img src="organisations/datawrapper.svg" alt="Datawrapper logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://db.nomics.world" style="background-color: rgb(15,39,47);"><picture><source type="image/webp" srcset="organisations/dbnomics.webp"><img src="organisations/dbnomics.jpg" alt="DBNomics logo" loading="lazy"></picture></a>
<a target="_blank" rel="noopener" href="https://deck.nl"><img src="organisations/deck.svg" alt="Deck logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://dextra.com.br/pt/"><img src="organisations/dextra.png" alt="Dextra logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://www.entriwise.com/"><img src="organisations/entriwise.png" alt="Entriwise logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://from-now-on.com"><img src="organisations/from-now-on.png" alt="From-Now-On logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://godaddy.com"><img src="organisations/godaddy.svg" alt="GoDaddy logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://www.grainger.com"><img src="organisations/grainger.svg" alt="Grainger logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="http://healthtree.org/"><img src="organisations/healthtree.png" alt="HealthTree logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://itslearning.com"><img src="organisations/itslearning.svg" alt="itslearning logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://www.metrovias.com.ar/"><img src="organisations/metrovias.svg" alt="Metrovias logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="http://mustlab.ru"><img src="organisations/mustlab.png" alt="Mustlab logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://www.nesta.org.uk"><img src="organisations/nesta.svg" alt="Nesta logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://www.nonkositelecoms.com"><img src="organisations/nonkosi.svg" alt="Nonkosi Telecoms logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://www.nzz.ch"><img src="organisations/nzz.svg" alt="Neue Zürcher Zeitung logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://nytimes.com"><img src="organisations/nyt.svg" alt="The New York Times logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://oberonspace.xyz"><img src="organisations/oberonspace.svg" alt="OberonSPACE logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://openstate.eu"><img src="organisations/open-state-foundation.svg" alt="Open State Foundation logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://panascais.net"><img src="organisations/panascais.svg" alt="Panascais logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://razorpay.com"><img src="organisations/razorpay.svg" alt="Razorpay logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://sp.nl"><img src="organisations/socialist-party.svg" alt="Socialist Party logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://www.stone.co"><img src="organisations/stone.svg" alt="Stone Payments logo" loading="lazy"></a>
@ -68,6 +77,6 @@
<a target="_blank" rel="noopener" href="https://thunderdome.dev"><img src="organisations/thunderdome.svg" alt="Thunderdome logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://m.tokopedia.com"><img src="organisations/tokopedia.png" alt="Tokopedia logo" srcset="organisations/tokopedia.2x.png 2x, organisations/tokopedia.3x.png 3x" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://webdesq.net"><img src="organisations/webdesq.svg" alt="Webdesq logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="http://healthtree.org/"><img src="organisations/healthtree.png" alt="HealthTree logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://zevvle.com/"><img src="organisations/zevvle.svg" alt="Zevvle logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://github.com/sveltejs/svelte/blob/master/site/src/routes/_components/WhosUsingSvelte.svelte" class="add-yourself"><span>+ your company?</span></a>
</div>

@ -1,11 +1,32 @@
<script>
import { setContext } from 'svelte';
import { stores } from '@sapper/app';
import { Icon, Icons, Nav, NavItem } from '@sveltejs/site-kit';
import PreloadingIndicator from '../components/PreloadingIndicator.svelte';
const { page, preloading } = stores();
export let segment;
const { page, preloading, session } = stores();
setContext('app', {
login: () => {
const login_window = window.open(`${window.location.origin}/auth/login`, 'login', 'width=600,height=400');
window.addEventListener('message', function handler(event) {
login_window.close();
window.removeEventListener('message', handler);
$session.user = event.data.user;
});
},
logout: async () => {
const r = await fetch(`/auth/logout`, {
credentials: 'include'
});
if (r.ok) $session.user = null;
}
});
</script>
<Icons/>

@ -0,0 +1,26 @@
import send from '@polka/send';
import { query } from '../../utils/db';
export async function get(req, res) {
if (req.user) {
const page_size = 100;
const offset = req.query.offset ? parseInt(req.query.offset) : 0;
const rows = await query(`
select g.uid, g.name, coalesce(g.updated_at, g.created_at) as updated_at
from gists g
where g.user_id = $1
order by id desc
limit ${page_size + 1}
offset $2
`, [req.user.id, offset]);
rows.forEach(row => {
row.uid = row.uid.replace(/-/g, '');
});
const more = rows.length > page_size;
send(res, 200, { apps: rows.slice(0, page_size), offset: more ? offset + page_size : null });
} else {
send(res, 401);
}
}

@ -0,0 +1,140 @@
<script context="module">
export async function preload(page, { user }) {
let apps = [];
let offset = null;
if (user) {
var url = 'apps.json';
if (page.query.offset) {
url += `?offset=${encodeURIComponent(page.query.offset)}`;
}
const r = await this.fetch(url, {
credentials: 'include'
});
if (!r.ok) return this.error(r.status, await r.text());
({ apps, offset } = await r.json());
}
return { user, apps, offset };
}
</script>
<script>
import { getContext } from 'svelte';
export let user;
export let apps;
export let offset;
const { login, logout } = getContext('app');
const formatter = new Intl.DateTimeFormat(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: '2-digit'
});
const format = str => formatter.format(new Date(str));
</script>
<svelte:head>
<title>Your apps • Svelte</title>
</svelte:head>
<div class="apps">
{#if user}
<header>
<h1>Your apps</h1>
<div class="user">
<img class="avatar" alt="{user.name} avatar" src="{user.avatar}">
<span>
{user.name}
(<a on:click|preventDefault={logout} href="auth/logout">log out</a>)
</span>
</div>
</header>
<ul>
{#each apps as app}
<li>
<a href="repl/{app.uid}">
<h2>{app.name}</h2>
<span>updated {format(app.updated_at)}</span>
</a>
</li>
{/each}
</ul>
{#if offset !== null}
<div><a href="apps?offset={offset}">Next page...</a></div>
{/if}
{:else}
<p>Please <a on:click|preventDefault={login} href="auth/login">log in</a> to see your saved apps.</p>
{/if}
</div>
<style>
.apps {
padding: var(--top-offset) var(--side-nav) 6rem var(--side-nav);
max-width: var(--main-width);
margin: 0 auto;
}
header {
margin: 0 0 1em 0;
}
h1 {
font-size: 4rem;
font-weight: 400;
}
.user {
display: flex;
padding: 0 0 0 3.2rem;
position: relative;
margin: 1rem 0 5rem 0;
color: var(--text);
}
.avatar {
position: absolute;
left: 0;
top: 0.1rem;
width: 2.4rem;
height: 2.4rem;
border: 1px solid rgba(0,0,0,0.3);
border-radius: 0.2rem;
}
ul {
list-style: none;
}
li {
margin: 0 0 1em 0;
}
h2 {
color: var(--text);
font-size: var(--h3);
font-weight: 400;
}
li a {
border: none;
}
li a:hover h2 {
color: var(--flash);
}
li span {
font-size: 14px;
color: #999;
}
</style>

@ -0,0 +1,6 @@
export const oauth = 'https://github.com/login/oauth';
export const baseurl = process.env.BASEURL;
export const secure = baseurl && baseurl.startsWith('https:');
export const client_id = process.env.GITHUB_CLIENT_ID;
export const client_secret = process.env.GITHUB_CLIENT_SECRET;

@ -0,0 +1,54 @@
import send from '@polka/send';
import devalue from 'devalue';
import * as cookie from 'cookie';
import * as httpie from 'httpie';
import { parse, stringify } from 'querystring';
import { sanitize_user, create_user, create_session } from '../../utils/auth';
import { oauth, secure, client_id, client_secret } from './_config.js';
export async function get(req, res) {
try {
// Trade "code" for "access_token"
const r1 = await httpie.post(`${oauth}/access_token?` + stringify({
code: req.query.code,
client_id,
client_secret,
}));
// Now fetch User details
const { access_token } = parse(r1.data);
const r2 = await httpie.get('https://api.github.com/user', {
headers: {
'User-Agent': 'svelte.dev',
Authorization: `token ${access_token}`
}
});
const user = await create_user(r2.data, access_token);
const session = await create_session(user);
res.writeHead(200, {
'Set-Cookie': cookie.serialize('sid', session.uid, {
maxAge: 31536000,
path: '/',
httpOnly: true,
secure
}),
'Content-Type': 'text/html; charset=utf-8'
});
res.end(`
<script>
window.opener.postMessage({
user: ${devalue(sanitize_user(user))}
}, window.location.origin);
</script>
`);
} catch (err) {
console.error('GET /auth/callback', err);
send(res, 500, err.data, {
'Content-Type': err.headers['content-type'],
'Content-Length': err.headers['content-length']
});
}
}

@ -0,0 +1,27 @@
import send from '@polka/send';
import { stringify } from 'querystring';
import { oauth, baseurl, client_id } from './_config.js';
export const get = client_id
? (req, res) => {
const Location = `${oauth}/authorize?` + stringify({
scope: 'read:user',
client_id,
redirect_uri: `${baseurl}/auth/callback`,
});
send(res, 302, Location, { Location });
}
: (req, res) => {
send(res, 500, `
<body style="font-family: sans-serif; background: rgb(255,215,215); border: 2px solid red; margin: 0; padding: 1em;">
<h1>Missing .env file</h1>
<p>In order to use GitHub authentication, you will need to <a target="_blank" href="https://github.com/settings/developers">register an OAuth application</a> and create a local .env file:</p>
<pre>GITHUB_CLIENT_ID=[YOUR_APP_ID]\nGITHUB_CLIENT_SECRET=[YOUR_APP_SECRET]\nBASEURL=http://localhost:3000</pre>
<p>The <code>BASEURL</code> variable should match the callback URL specified for your app.</p>
<p>See also <a target="_blank" href="https://github.com/sveltejs/svelte/tree/master/site#repl-github-integration">here</a></p>
</body>
`, {
'Content-Type': 'text/html; charset=utf-8'
});
};

@ -0,0 +1,17 @@
import send from '@polka/send';
import * as cookie from 'cookie';
import { secure } from './_config.js';
import { delete_session } from '../../utils/auth.js';
export async function get(req, res) {
await delete_session(req.cookies.sid);
send(res, 200, '', {
'Set-Cookie': cookie.serialize('sid', '', {
maxAge: -1,
path: '/',
httpOnly: true,
secure
})
});
}

@ -1,8 +0,0 @@
import send from '@polka/send';
import { isUser, toUser } from '../../backend/auth';
export async function get(req, res) {
const user = await isUser(req, res);
res.setHeader('Cache-Control', 'private, no-cache, no-store');
return send(res, 200, user ? toUser(user) : null);
}

@ -1,18 +1,23 @@
<script>
import { user, logout } from '../../../../../user.js';
import { getContext } from 'svelte';
import { stores } from '@sapper/app';
const { session } = stores();
const { logout } = getContext('app');
let showMenu = false;
let name;
$: name = $user.name || $user.username;
$: name = $session.user.name || $session.user.username;
</script>
<div class="user" on:mouseenter="{() => showMenu = true}" on:mouseleave="{() => showMenu = false}">
<span>{name}</span>
<img alt="{name} avatar" src="{$user.avatar}">
<img alt="{name} avatar" src="{$session.user.avatar}">
{#if showMenu}
<div class="menu">
<a href="apps">Your saved apps</a>
<button on:click={logout}>Log out</button>
</div>
{/if}
@ -64,7 +69,7 @@
.menu {
position: absolute;
width: calc(100% + 1.6rem);
min-width: 6em;
min-width: 10em;
top: 3rem;
right: -1.6rem;
background-color: var(--second);
@ -72,17 +77,25 @@
z-index: 99;
text-align: left;
border-radius: 0.4rem;
display: flex;
flex-direction: column;
}
.menu button {
.menu button, .menu a {
background-color: transparent;
font-family: var(--font);
font-size: 1.6rem;
/* opacity: 0.7; */
opacity: 0.7;
padding: 0.4rem 0;
text-decoration: none;
text-align: left;
border: none;
color: inherit;
}
.menu button:hover {
.menu button:hover, .menu a:hover {
opacity: 1;
color: inherit;
}
@media (min-width: 600px) {

@ -1,14 +1,16 @@
<script>
import { createEventDispatcher } from 'svelte';
import { createEventDispatcher, getContext } from 'svelte';
import { stores } from '@sapper/app';
import UserMenu from './UserMenu.svelte';
import { Icon } from '@sveltejs/site-kit';
import * as doNotZip from 'do-not-zip';
import downloadBlob from '../../../_utils/downloadBlob.js';
import { user } from '../../../../../user.js';
import { enter } from '../../../../../utils/events.js';
import { isMac } from '../../../../../utils/compat.js';
const dispatch = createEventDispatcher();
const { session } = stores();
const { login } = getContext('app');
export let repl;
export let gist;
@ -25,8 +27,7 @@
return new Promise(f => setTimeout(f, ms));
}
$: Authorization = $user && `Bearer ${$user.token}`;
$: canSave = $user && gist && gist.owner === $user.uid;
$: canSave = $session.user && gist && gist.owner === $session.user.uid;
function handleKeydown(event) {
if (event.which === 83 && (isMac ? event.metaKey : event.ctrlKey)) {
@ -35,19 +36,6 @@
}
}
function login(event) {
event.preventDefault();
const loginWindow = window.open(`${window.location.origin}/auth/login`, 'login', 'width=600,height=400');
const handleLogin = event => {
loginWindow.close();
user.set(event.data.user);
window.removeEventListener('message', handleLogin);
};
window.addEventListener('message', handleLogin);
}
async function fork(intentWasSave) {
saving = true;
@ -56,7 +44,7 @@
try {
const r = await fetch(`repl/create.json`, {
method: 'POST',
headers: { Authorization },
credentials: 'include',
body: JSON.stringify({
name,
files: components.map(component => ({
@ -111,7 +99,7 @@
const r = await fetch(`repl/${gist.uid}.json`, {
method: 'PATCH',
headers: { Authorization },
credentials: 'include',
body: JSON.stringify({
name,
files: components.map(component => ({
@ -199,7 +187,7 @@ export default app;` });
<Icon name="download" />
</button>
<button class="icon" disabled="{saving || !$user}" on:click={() => fork(false)} title="fork">
<button class="icon" disabled="{saving || !$session.user}" on:click={() => fork(false)} title="fork">
{#if justForked}
<Icon name="check" />
{:else}
@ -207,7 +195,7 @@ export default app;` });
{/if}
</button>
<button class="icon" disabled="{saving || !$user}" on:click={save} title="save">
<button class="icon" disabled="{saving || !$session.user}" on:click={save} title="save">
{#if justSaved}
<Icon name="check" />
{:else}
@ -215,10 +203,10 @@ export default app;` });
{/if}
</button>
{#if $user}
{#if $session.user}
<UserMenu/>
{:else}
<button class="icon" on:click={login}>
<button class="icon" on:click|preventDefault={login}>
<Icon name="log-in" />
<span>&nbsp;Log in to save</span>
</button>

@ -2,7 +2,6 @@ import send from '@polka/send';
import body from '../_utils/body.js';
import * as httpie from 'httpie';
import { query, find } from '../../../utils/db';
import { isUser } from '../../../backend/auth';
import { get_example } from '../../examples/_examples.js';
const { GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET } = process.env;
@ -24,7 +23,7 @@ async function import_gist(req, res) {
if (!user) {
const { id, name, login, avatar_url } = data.owner;
[user] = await query(`
user = await find(`
insert into users(uid, name, username, avatar)
values ($1, $2, $3, $4)
returning *
@ -44,9 +43,10 @@ async function import_gist(req, res) {
});
// add gist to database...
const [gist] = await query(`
await query(`
insert into gists(uid, user_id, name, files)
values ($1, $2, $3, $4) returning *`, [req.params.id, user.id, data.description, JSON.stringify(files)]);
values ($1, $2, $3, $4)
`, [req.params.id, user.id, data.description, JSON.stringify(files)]);
send(res, 200, {
uid: req.params.id,
@ -92,8 +92,8 @@ export async function get(req, res) {
}
export async function patch(req, res) {
const user = await isUser(req, res);
if (!user) return; // response already sent
const { user } = req;
if (!user) return;
let id, uid = req.params.id;

@ -10,14 +10,15 @@
<script>
import Repl from '@sveltejs/svelte-repl';
import { onMount } from 'svelte';
import { goto } from '@sapper/app';
import { user } from '../../../user.js';
import { goto, stores } from '@sapper/app';
import InputOutputToggle from '../../../components/Repl/InputOutputToggle.svelte';
import AppControls from './_components/AppControls/index.svelte';
export let version;
export let id;
const { session } = stores();
let repl;
let gist;
let name = 'Loading...';
@ -107,7 +108,7 @@
$: mobile = width < 540;
$: relaxed = is_relaxed_gist || ($user && gist && $user.uid === gist.owner);
$: relaxed = is_relaxed_gist || ($session.user && gist && $session.user.uid === gist.owner);
</script>
<style>

@ -1,10 +1,9 @@
import send from '@polka/send';
import body from './_utils/body.js';
import { query } from '../../utils/db';
import { isUser } from '../../backend/auth';
export async function post(req, res) {
const user = await isUser(req, res);
const { user } = req;
if (!user) return; // response already sent
try {

@ -1,11 +1,22 @@
import polka from 'polka';
import send from '@polka/send';
import sirv from 'sirv';
import * as sapper from '@sapper/server';
import { API } from './backend/auth';
import { sanitize_user, authenticate } from './utils/auth';
const { PORT = 3000 } = process.env;
API()
.use(
const app = polka({
onError: (err, req, res) => {
const error = err.message || err;
const code = err.code || err.status || 500;
res.headersSent || send(res, code, { error });
}
});
app.use(
authenticate(),
sirv('static', {
dev: process.env.NODE_ENV === 'development',
setHeaders(res) {
@ -15,7 +26,10 @@ API()
}),
sapper.middleware({
//
session: req => ({
user: sanitize_user(req.user)
})
)
.listen(PORT);
})
);
app.listen(PORT);

@ -1,33 +0,0 @@
import { writable } from 'svelte/store';
export const user = writable(null);
if (process.browser) {
const storageKey = 'svelte-dev:token';
// On load, get the last-known user token (if any)
// Note: We can skip this all by writing User data?
const token = localStorage.getItem(storageKey);
// Write changes to localStorage
user.subscribe(obj => {
if (obj) {
localStorage.setItem(storageKey, obj.token);
} else {
localStorage.removeItem(storageKey);
}
});
if (token) {
// If token, refresh the User data from API
const headers = { Authorization: `Bearer ${token}` };
fetch('/auth/me.json', { headers })
.then(r => r.ok ? r.json() : null)
.then(user.set);
}
}
export function logout() {
user.set(null);
}

@ -0,0 +1,65 @@
import * as cookie from 'cookie';
import flru from 'flru';
import { find, query } from './db';
export const sanitize_user = obj => obj && ({
uid: obj.uid,
username: obj.username,
name: obj.name,
avatar: obj.avatar
});
const session_cache = flru(1000);
export const create_user = async (gh_user, access_token) => {
return await find(`
insert into users(uid, name, username, avatar, github_token)
values ($1, $2, $3, $4, $5) on conflict (uid) do update
set (name, username, avatar, github_token, updated_at) = ($2, $3, $4, $5, now())
returning id, uid, username, name, avatar
`, [gh_user.id, gh_user.name, gh_user.login, gh_user.avatar_url, access_token]);
};
export const create_session = async user => {
const session = await find(`
insert into sessions(user_id)
values ($1)
returning uid
`, [user.id]);
session_cache.set(session.uid, user);
return session;
};
export const delete_session = async sid => {
await query(`delete from sessions where uid = $1`, [sid]);
session_cache.set(sid, null);
};
const get_user = async sid => {
if (!sid) return null;
if (!session_cache.has(sid)) {
session_cache.set(sid, await find(`
select users.id, users.uid, users.username, users.name, users.avatar
from sessions
left join users on sessions.user_id = users.id
where sessions.uid = $1 and expiry > now()
`, [sid]));
}
return session_cache.get(sid);
};
export const authenticate = () => {
// this is a convenient time to clear out expired sessions
query(`delete from sessions where expiry < now()`);
return async (req, res, next) => {
req.cookies = cookie.parse(req.headers.cookie || '');
req.user = await get_user(req.cookies.sid);
next();
};
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg viewBox="0 0 257 60" xmlns="http://www.w3.org/2000/svg">
<style>
.logo {
font-size: 60px;
text-align: center;
font-family: "Times New Roman", Times, serif;
}
</style>
<text x="65" y="46" class="logo">buy.*</text>
</svg>

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

@ -0,0 +1 @@
<svg viewBox="0 0 122 29" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><g fill-rule="nonzero"><path d="M91.431 1.26l-.55 3.392h1.185l-1.481 9.084H89.38l-.582 3.58h12.676l.912-5.608h-4.08l-.315 2.028H94.83l.47-2.865h3.483l.624-3.863h-3.49l.375-2.356h2.98l-.26 1.634h3.965l.813-5.027M68.414 1.26l-.543 3.392h1.182l-.744 4.585-4.152-7.978h-5.365l-.54 3.393h1.19l-1.48 9.084h-1.184l-.572 3.58h6.71l.573-3.58h-1.19l.865-5.413 4.539 8.994h3.583l2.051-12.665h1.191l.548-3.393M37.499 1.26l-.545 3.392h1.191l-4.632 9.084h-1.175l-.587 3.58h6.703l.589-3.58H37.84l.597-1.326h4.15l.168 1.326h-1.187l-.577 3.58 6.891-.002.582-3.578H47.27L45.155 1.259H37.5zm4.36 3.99l.333 3.78H40.23M27.38 7.89c-.772.71-1.974.433-3.023.48l.571-3.515c1.19.027 2.826-.335 3.267 1.024.104.775-.168 1.507-.815 2.011zm1.064 2.971c.794-.187 1.481-.669 2.128-1.234 1.462-1.34 2.274-3.287 1.688-5.296-.561-1.884-2.542-2.943-4.38-3.069h-7.957l-.54 3.391h1.184l-1.471 9.084h-1.198l-.577 3.58h6.491l.587-3.58h-.904l.342-2.142 3.19 5.723h3.993l.586-3.58h-1.492M55.496 17.31l.574-3.578H54.88l1.48-9.084h1.185l.54-3.392h-6.885l-.55 3.392h1.184l-1.465 9.084h-1.194l-.576 3.585M112.25 7.886c-.772.711-1.973.435-3.024.482l.572-3.518c1.19.03 2.827-.333 3.267 1.027.103.774-.17 1.506-.815 2.01zm1.063 2.973c.795-.189 1.482-.67 2.128-1.235 1.463-1.34 2.275-3.288 1.689-5.296-.562-1.885-2.543-2.943-4.38-3.07h-7.959l-.538 3.393h1.184l-1.472 9.084h-1.198l-.576 3.58h6.491l.586-3.58h-.904l.343-2.143 3.19 5.723h3.993l.586-3.58h-1.494"/><path d="M25.076 20.897l-1.157 7.157h90.227l1.163-7.157H25.076zM8.546 20.897H6.942l-1.157 7.157H7.39l1.157-7.157zM13.74 20.897h-2.258l-1.156 7.157h2.258l1.156-7.157zM3.35 20.897h-.949l-1.156 7.157h.95l1.156-7.157zM24.13 20.897h-3.565l-1.156 7.157h3.566l1.155-7.157zM18.935 20.897h-2.912l-1.156 7.157h2.912l1.156-7.157z" fill="#ed1b2d"/><path d="M120.652 15.57a1.605 1.605 0 0 1-1.621 1.615 1.61 1.61 0 0 1-1.614-1.62c0-.89.707-1.62 1.625-1.62.922 0 1.614.735 1.61 1.624zm.16.005a1.764 1.764 0 0 0-1.781-1.79c-.956 0-1.774.754-1.774 1.78 0 1.056.848 1.78 1.77 1.78 1.006 0 1.779-.785 1.784-1.77zm-2.494-1.13v2.224h.325v-1.034h.159c.32.006.498.045.562.376.011.054.046.304.057.354.02.105.04.205.089.304h.374c-.101-.199-.114-.319-.165-.644-.045-.269-.12-.4-.359-.49.274-.044.5-.225.5-.535a.535.535 0 0 0-.34-.505c-.134-.05-.26-.05-.518-.05h-.684zm.33.25h.304c.268 0 .378 0 .473.084a.321.321 0 0 1 .106.246c0 .356-.37.35-.509.35h-.375M15.144 1.24h3.308l-.878 5.45h-3.76c-.21-.879-1.108-1.707-1.986-1.896-1.61-.356-3.259.209-4.283 1.506-1.127 1.574-1.629 4.22-.564 5.938.732 1.153 2.141 1.58 3.458 1.37 1.024-.187 2.057-1.098 2.496-2.062l-4.191-.003.524-3.262h8.83l-1.465 9.04H13.23l.21-1.023c-.402.535-1.48 1.445-4.138 1.445-3.356 0-7.024-2.848-7.024-7.873 0-4.136 3.119-9.155 8.654-9.136 1.41.006 2.992.361 4.013 1.34M86.611 1.24h3.308l-.878 5.45H85.28c-.208-.879-1.107-1.707-1.985-1.896-1.61-.356-3.258.209-4.282 1.506-1.128 1.574-1.63 4.22-.565 5.938.732 1.153 2.141 1.58 3.458 1.37 1.025-.187 2.057-1.098 2.496-2.062l-4.191-.003.524-3.262h8.83l-1.465 9.04h-3.402l.208-1.023c-.4.535-1.478 1.445-4.137 1.445-3.356 0-7.024-2.848-7.024-7.873 0-4.136 3.12-9.155 8.654-9.136 1.41.006 2.993.361 4.013 1.34"/></g></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 187.09 22.97"><defs><style>.cls-1{fill:#606463;}.cls-1,.cls-2,.cls-3{fill-rule:evenodd;}.cls-2{fill:#fff;}.cls-3{fill:#fcc14f;}</style></defs><title>Asset 1</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="187.09 0 0 0 0 22.97 187.09 22.97 187.09 0 187.09 0"/><path class="cls-2" d="M147.91,10.73a6.74,6.74,0,0,0-1.44-4.25,12.93,12.93,0,0,0-4.1-3.38,14.42,14.42,0,0,0-5.69,3.24,5.79,5.79,0,0,0-2.09,4.39,6.13,6.13,0,0,0,2.09,4.39,16,16,0,0,0,5.62,3.24,13.57,13.57,0,0,0,4.18-3.46,6.69,6.69,0,0,0,1.44-4.18Z"/><path class="cls-2" d="M153.53,4.75a18.74,18.74,0,0,1,6.12,4.61,9.65,9.65,0,0,1,2.16,5.91,9.3,9.3,0,0,1-.5,3.1,15.6,15.6,0,0,0,5.62-3.24,6.11,6.11,0,0,0,2.16-4.39c0-2.52-1.73-4.68-5-6.48A25.83,25.83,0,0,0,151.8,1.58l-5.69.5H146a21.19,21.19,0,0,1,7.49,2.66Z"/><path class="cls-2" d="M144.82,2.38l-1.51.36a16.1,16.1,0,0,1,6,4.46,9.08,9.08,0,0,1,2.23,6,9.5,9.5,0,0,1-2.74,6.48l3,.14,3-.14a8.43,8.43,0,0,0,2.09-5.47,9.55,9.55,0,0,0-3.31-6.91,21.9,21.9,0,0,0-8.79-4.9Z"/><polygon class="cls-3" points="35.93 5.62 36.01 5.62 33.05 18.22 36.29 18.22 39.61 2.95 34.49 2.95 28.45 13.97 28.45 13.97 27.36 2.95 22.32 2.95 19.08 18.22 21.96 18.22 24.77 5.62 24.84 5.62 26.07 18.22 28.95 18.22 35.93 5.62 35.93 5.62"/><path class="cls-3" d="M45.58,8.86a1.67,1.67,0,0,1,1.87,1.87l-.07.79h-5A6,6,0,0,1,43.5,9.58a3.08,3.08,0,0,1,2.09-.72ZM50.26,11A4.18,4.18,0,0,0,49,7.78,5.16,5.16,0,0,0,45.66,6.7,6,6,0,0,0,41,8.79a7.26,7.26,0,0,0-1.87,4.82,4.63,4.63,0,0,0,1.51,3.74,6.05,6.05,0,0,0,4,1.22,10.6,10.6,0,0,0,3.6-.72l.36-2.3a7.77,7.77,0,0,1-3.46.79c-2.09,0-3.1-.94-3.1-2.81H50A10.83,10.83,0,0,0,50.26,11Z"/><path class="cls-3" d="M56.39,9.15H59L59.48,7H56.82l.72-3.38L54.37,4.68,53.86,7H51.78l-.5,2.16h2.16L52.57,13a9.61,9.61,0,0,0-.36,2.59c0,2,1.15,3,3.38,3l1.58-.36.5-2.23-1.44.29c-.72,0-1.08-.36-1.08-1.15L55.31,14l1.08-4.9Z"/><path class="cls-3" d="M66.61,9.51l1.08.14.65-2.88L67.19,6.7a3.11,3.11,0,0,0-1.94.72,4.23,4.23,0,0,0-1.37,1.66H63.8l.36-2.16H61.43l-2.3,11.31h3l1-5a6,6,0,0,1,1.08-2.45,2.85,2.85,0,0,1,2.45-1.22Z"/><path class="cls-3" d="M74.6,9c1.51,0,2.3.86,2.3,2.66a5.32,5.32,0,0,1-.86,3,3,3,0,0,1-2.59,1.58c-1.51,0-2.3-.94-2.3-2.66a5.73,5.73,0,0,1,.86-3.1A3,3,0,0,1,74.6,9Zm3.89-1a5.26,5.26,0,0,0-3.74-1.3,6.52,6.52,0,0,0-6.63,6.7,5.26,5.26,0,0,0,1.3,3.74,5.22,5.22,0,0,0,3.82,1.44,6.68,6.68,0,0,0,4.82-1.94,6.79,6.79,0,0,0,1.87-5A4.73,4.73,0,0,0,78.49,8Z"/><polygon class="cls-3" points="92.46 6.99 89.3 6.99 84.9 15.27 84.83 15.27 83.68 6.99 80.65 6.99 82.6 18.22 85.84 18.22 92.46 6.99 92.46 6.99"/><polygon class="cls-3" points="91.89 18.22 94.84 18.22 97.29 6.91 94.33 6.91 91.89 18.22 91.89 18.22"/><path class="cls-3" d="M105.07,13a7.53,7.53,0,0,1-1,2.38,2.84,2.84,0,0,1-2.3.94,2.09,2.09,0,0,1-1-.22,1,1,0,0,1-.5-.94c0-1.44,1.22-2.16,3.82-2.16Zm.36-1.73h-1.08a13.64,13.64,0,0,0-4.46.58,3.37,3.37,0,0,0-2.45,3.38,2.88,2.88,0,0,0,1,2.45,3.93,3.93,0,0,0,2.52.86,4.68,4.68,0,0,0,3.6-2.09h.07l-.29,1.73h2.45l.79-4.25a30.31,30.31,0,0,0,.58-3.82,3.25,3.25,0,0,0-1.3-2.74,5.64,5.64,0,0,0-3.1-.72,10.51,10.51,0,0,0-3.82.72l-.43,2.38a7.94,7.94,0,0,1,3.67-.94c1.51,0,2.3.5,2.3,1.44l-.07,1Z"/><path class="cls-3" d="M117.74,9.36l.65-2.16a11.13,11.13,0,0,0-3.24-.5,6.83,6.83,0,0,0-3.31.79,3.14,3.14,0,0,0-1.51,2.81A3.26,3.26,0,0,0,112.19,13l1.3.94a1.48,1.48,0,0,1,.65,1.15c0,.86-.72,1.22-2,1.22a5.41,5.41,0,0,1-2.81-.86l-.65,2.38a7.22,7.22,0,0,0,3.46.72,6.79,6.79,0,0,0,3.46-.86,3.31,3.31,0,0,0,1.66-3q0-1.51-1.94-2.81l-1.3-.94a1.25,1.25,0,0,1-.65-.94c0-.79.65-1.15,1.94-1.15a8.72,8.72,0,0,1,2.45.5Z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.5 KiB

@ -0,0 +1,9 @@
<svg width="197" height="36" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient x1="0%" y1="49.167%" x2="100%" y2="50.833%" id="a">
<stop stop-color="#594666" offset="0%"/>
<stop stop-color="#341D44" offset="100%"/>
</linearGradient>
</defs>
<path d="M17.3 2.021c1.298 1.356 1.948 3.557 1.922 6.678 0 3.096-.624 5.374-1.897 6.832-1.274 1.458-3.272 2.175-5.97 2.175h-4.27V24H.491V0h10.315c3.047 0 5.195.665 6.494 2.021zm-4.97 9.135v-3.94c0-.64-.125-1.05-.375-1.28-.25-.205-.7-.333-1.349-.333H7.11v7.164h3.497c.649 0 1.099-.102 1.348-.332.25-.256.375-.665.375-1.28zM25.452 0h10.232l6.229 24h-6.604L30.73 6.455h-.326L25.827 24h-6.604l6.23-24zm159.162 11.145c1.251 1.124 1.877 2.883 1.877 5.254 0 2.664-.746 4.595-2.19 5.792-1.47 1.198-3.83 1.809-7.104 1.809-3.203 0-6.165-.415-8.91-1.222l.915-5.328a24.544 24.544 0 0 0 7.272 1.1c1.252 0 2.312-.073 3.203-.22v-3.153l-3.3-.293c-1.95-.171-3.49-.513-4.598-1.002-1.108-.489-1.903-1.222-2.408-2.15-.506-.93-.747-2.2-.747-3.789 0-1.98.265-3.544.819-4.692.53-1.15 1.42-1.98 2.649-2.493C173.32.244 174.982 0 177.1 0c2.818 0 5.538.318 8.212.978l-.795 5.18a51.023 51.023 0 0 0-3.203-.537 21.435 21.435 0 0 0-2.841-.17c-1.228 0-2.24.072-3.034.195v3.152l2.961.245c2.89.269 4.96.953 6.213 2.102zM58.772 12.64h.025V0h6.597v24h-6.497l-6.072-9.979c-.35-.614-.575-1.1-.725-1.51-.125-.409-.2-.792-.2-1.15h-.225V24H45.08V0h6.52l6.021 9.979c.282.473.525.97.725 1.484.125.41.2.793.2 1.177h.225zm50.362-1.471c1.252 1.124 1.878 2.884 1.902 5.23 0 2.664-.722 4.595-2.191 5.792C107.376 23.39 105.016 24 101.74 24c-3.202 0-6.164-.415-8.909-1.222l.915-5.328a24.251 24.251 0 0 0 7.248 1.1c1.276 0 2.336-.073 3.227-.22v-3.153l-3.3-.293c-1.95-.171-3.49-.513-4.599-1.002-1.107-.489-1.926-1.222-2.407-2.15-.506-.93-.747-2.2-.747-3.789 0-1.98.29-3.544.819-4.692.53-1.15 1.42-1.98 2.649-2.493C97.864.244 99.527 0 101.645 0c2.793 0 5.514.318 8.187.978l-.794 5.205a51.06 51.06 0 0 0-3.203-.537 21.343 21.343 0 0 0-2.842-.171c-1.227 0-2.24.073-3.034.195v3.153l2.962.244c2.89.269 4.96.954 6.213 2.102zm18.05 7.112c.898-.136 1.785-.34 2.653-.61l.458 5.376c-.944.35-1.922.596-2.919.733-1.061.147-2.268.22-3.668.22-2.484 0-4.39-.367-5.766-1.1-1.375-.733-2.317-1.93-2.895-3.64-.555-1.688-.845-4.107-.845-7.236 0-3.177.314-5.596.917-7.307.603-1.71 1.64-2.933 3.088-3.641C119.655.366 121.707 0 124.36 0c.99 0 2.075.098 3.233.269 1.159.17 2.027.39 2.582.635l-.796 5.132c-.724-.22-1.544-.39-2.485-.512-.941-.123-1.714-.196-2.341-.196-1.35 0-2.292.195-2.799.562-.506.367-.748 1.05-.748 2.053v10.216c1.206.244 2.268.367 3.21.367.988 0 1.978-.074 2.967-.245zM157.997 0h6.596v24h-6.596V0zM85.146 0l6.367 24h-6.75l-4.68-17.545h-.358L75.046 24h-6.75l6.367-24h10.483zm63.72 0l6.23 24h-6.604l-4.579-17.545h-.325L139.011 24h-6.604l6.227-24h10.232z" transform="translate(4.509 6)" fill="url(#a)" fill-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2219.27 506.75"><path d="M431.45,2755.61L543.53,2584c9.12-13.74,13.75-30,13.75-47.54a95.29,95.29,0,0,0-24.77-64.64c-16.92-18.56-39.14-30.17-64.25-30.17h-232L120,2627.61H290.57l-109.84,168.6a83.29,83.29,0,0,0-13.82,46.68c0,23.67,8.53,45.24,25.35,63.64,23.17,25.35,51.13,32.36,67.2,32.36l250.4,0.5,108.49-183.77H431.45Zm53.15,129-3.41,5.41-217-1.38-2.25.08a44.72,44.72,0,0,1-44.67-44.67,48.4,48.4,0,0,1,6.43-23.43l0.57-.9,150.94-228.06,5.46-8.77,1.83-3.3H208.41l54.79-84H468.26c11.5,0,20.45,3,28.17,11.49,8.1,8.88,12,18.2,12,29.46,0,7.92-1.8,14.24-5.5,19.81L346.8,2792.24l-5.46,7.92-1.83,2.45h194l-10.24,17.61C506.76,2848.2,492.66,2871.85,484.6,2884.64Z" transform="translate(-119.96 -2432.63)" fill="#da291c"/><path d="M784.72,2832.35a20.51,20.51,0,0,1-4.81-13.36,16.91,16.91,0,0,1,2.14-8.55l146.42-235.13H792.73a7.31,7.31,0,0,1-7.48-7.48v-24.58a7.31,7.31,0,0,1,7.48-7.48H965.87a16,16,0,0,1,12.56,5.88,19.26,19.26,0,0,1,5.08,12.83,16.87,16.87,0,0,1-2.67,9.08L834.41,2798.68H978.7a7.31,7.31,0,0,1,7.48,7.48v24.58a7.31,7.31,0,0,1-7.48,7.48H797A15.16,15.16,0,0,1,784.72,2832.35Z" transform="translate(-119.96 -2432.63)" fill="#da291c"/><path d="M1097.33,2822.73a113.59,113.59,0,0,1-42.22-42.48,115.42,115.42,0,0,1-15.5-58.51v-76.42a115.46,115.46,0,0,1,15.5-58.51,115.81,115.81,0,0,1,200.93,0,115.54,115.54,0,0,1,15.5,58.51v36.34a19.16,19.16,0,0,1-19.24,19.24h-171v23.51a73.34,73.34,0,0,0,9.89,37.41,72.54,72.54,0,0,0,27,27,73.34,73.34,0,0,0,37.41,9.89h77.49a7.31,7.31,0,0,1,7.48,7.48v24.58a7.31,7.31,0,0,1-7.48,7.48h-77.49A114,114,0,0,1,1097.33,2822.73Zm132.53-160.31v-19.77a74.58,74.58,0,0,0-111.69-64.39,73.77,73.77,0,0,0-36.87,64.39v19.77h148.56Z" transform="translate(-119.96 -2432.63)" fill="#da291c"/><path d="M1426.23,2836.35a9.37,9.37,0,0,1-4-5.08l-100.46-283.76a9.09,9.09,0,0,1-.53-3.74,7.69,7.69,0,0,1,2.4-5.61,8.32,8.32,0,0,1,6.15-2.41h24.58a11.31,11.31,0,0,1,6.15,1.87,9.47,9.47,0,0,1,4,5.08l84.43,243.68,82.83-243.68a9.39,9.39,0,0,1,4-5.08,11.29,11.29,0,0,1,6.15-1.87h23.51a8.2,8.2,0,0,1,7.21,3.47q2.41,3.48.8,8.28L1473,2831.28a9.44,9.44,0,0,1-4,5.08,11.24,11.24,0,0,1-6.15,1.87h-30.46A11.22,11.22,0,0,1,1426.23,2836.35Z" transform="translate(-119.96 -2432.63)" fill="#da291c"/><path d="M1724.42,2836.35a9.37,9.37,0,0,1-4-5.08l-100.46-283.76a9.09,9.09,0,0,1-.53-3.74,7.69,7.69,0,0,1,2.4-5.61,8.32,8.32,0,0,1,6.15-2.41h24.58a11.31,11.31,0,0,1,6.15,1.87,9.47,9.47,0,0,1,4,5.08l84.43,243.68L1830,2542.71a9.39,9.39,0,0,1,4-5.08,11.29,11.29,0,0,1,6.15-1.87h23.51a8.2,8.2,0,0,1,7.21,3.47q2.41,3.48.8,8.28l-100.46,283.76a9.44,9.44,0,0,1-4,5.08,11.24,11.24,0,0,1-6.15,1.87h-30.46A11.22,11.22,0,0,1,1724.42,2836.35Z" transform="translate(-119.96 -2432.63)" fill="#da291c"/><path d="M1956.35,2832.61a18.55,18.55,0,0,1-5.61-13.63V2472.17H1901a7.31,7.31,0,0,1-7.48-7.48v-24.58a7.31,7.31,0,0,1,7.48-7.48h72.14a19.16,19.16,0,0,1,19.24,19.24v346.81h56.64a7.31,7.31,0,0,1,7.48,7.48v24.58a7.31,7.31,0,0,1-7.48,7.48H1970A18.55,18.55,0,0,1,1956.35,2832.61Z" transform="translate(-119.96 -2432.63)" fill="#da291c"/><path d="M2165,2822.73a113.59,113.59,0,0,1-42.22-42.48,115.42,115.42,0,0,1-15.5-58.51v-76.42a115.46,115.46,0,0,1,15.5-58.51,115.81,115.81,0,0,1,200.93,0,115.55,115.55,0,0,1,15.5,58.51v36.34a19.16,19.16,0,0,1-19.24,19.24H2149v23.51a73.34,73.34,0,0,0,9.89,37.41,72.54,72.54,0,0,0,27,27,73.34,73.34,0,0,0,37.41,9.89h77.49a7.31,7.31,0,0,1,7.48,7.48v24.58a7.31,7.31,0,0,1-7.48,7.48h-77.49A114,114,0,0,1,2165,2822.73Zm132.53-160.31v-19.77a74.58,74.58,0,0,0-111.69-64.39,73.77,73.77,0,0,0-36.87,64.39v19.77h148.56Z" transform="translate(-119.96 -2432.63)" fill="#da291c"/></svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

@ -23,6 +23,8 @@ import get_object from './utils/get_object';
import unwrap_parens from './utils/unwrap_parens';
import Slot from './nodes/Slot';
import { Node as ESTreeNode } from 'estree';
import add_to_set from './utils/add_to_set';
import check_graph_for_cycles from './utils/check_graph_for_cycles';
interface ComponentOptions {
namespace?: string;
@ -70,6 +72,8 @@ function remove_node(code: MagicString, start: number, end: number, body: Node,
export default class Component {
stats: Stats;
warnings: Warning[];
ignores: Set<string>;
ignore_stack: Array<Set<string>> = [];
ast: Ast;
source: string;
@ -442,6 +446,10 @@ export default class Component {
message: string;
}
) {
if (this.ignores && this.ignores.has(warning.code)) {
return;
}
if (!this.locator) {
this.locator = getLocator(this.source, { offsetLine: 1 });
}
@ -662,7 +670,7 @@ export default class Component {
this.node_for_declaration.set(name, node);
});
globals.forEach((_node, name) => {
globals.forEach((node, name) => {
if (this.var_lookup.has(name)) return;
if (this.injected_reactive_declaration_vars.has(name)) {
@ -679,6 +687,13 @@ export default class Component {
injected: true
});
} else if (name[0] === '$') {
if (name === '$' || name[1] === '$') {
this.error(node, {
code: 'illegal-global',
message: `${name} is an illegal variable name`
});
}
this.add_var({
name,
injected: true,
@ -798,7 +813,7 @@ export default class Component {
const variable = this.var_lookup.get(name);
if (variable && (variable.subscribable && variable.reassigned)) {
return `$$subscribe_${name}(), $$invalidate('${name}', ${value || name})`;
return `$$subscribe_${name}($$invalidate('${name}', ${value || name}))`;
}
if (name[0] === '$' && name[1] !== '$') {
@ -1055,9 +1070,12 @@ export default class Component {
} else if (name[0] === '$' && !owner) {
hoistable = false;
} else if (owner === instance_scope) {
const variable = var_lookup.get(name);
if (variable.reassigned || variable.mutated) hoistable = false;
if (name === fn_declaration.id.name) return;
const variable = var_lookup.get(name);
if (variable.hoistable) return;
if (top_level_function_declarations.has(name)) {
@ -1188,14 +1206,27 @@ export default class Component {
});
});
const add_declaration = declaration => {
if (seen.has(declaration)) {
const cycle = check_graph_for_cycles(unsorted_reactive_declarations.reduce((acc, declaration) => {
declaration.assignees.forEach(v => {
declaration.dependencies.forEach(w => {
if (!declaration.assignees.has(w)) {
acc.push([v, w]);
}
});
});
return acc;
}, []));
if (cycle && cycle.length) {
const declarationList = lookup.get(cycle[0]);
const declaration = declarationList[0];
this.error(declaration.node, {
code: 'cyclical-reactive-declaration',
message: 'Cyclical dependency detected'
message: `Cyclical dependency detected: ${cycle.join(' → ')}`
});
}
const add_declaration = declaration => {
if (this.reactive_declarations.indexOf(declaration) !== -1) {
return;
}
@ -1235,10 +1266,18 @@ export default class Component {
warn_if_undefined(name: string, node, template_scope: TemplateScope) {
if (name[0] === '$') {
name = name.slice(1);
if (name === '$' || name[1] === '$' && name !== '$$props') {
this.error(node, {
code: 'illegal-global',
message: `${name} is an illegal variable name`
});
}
this.has_reactive_assignments = true; // TODO does this belong here?
if (name[0] === '$') return; // $$props
if (name === '$$props') return;
name = name.slice(1);
}
if (this.var_lookup.has(name) && !this.var_lookup.get(name).global) return;
@ -1253,6 +1292,17 @@ export default class Component {
message
});
}
push_ignores(ignores) {
this.ignores = new Set(this.ignores || []);
add_to_set(this.ignores, ignores);
this.ignore_stack.push(this.ignores);
}
pop_ignores() {
this.ignore_stack.pop();
this.ignores = this.ignore_stack[this.ignore_stack.length - 1];
}
}
function process_component_options(component: Component, nodes) {

@ -9,7 +9,32 @@ function remove_css_prefix(name: string): string {
return name.replace(/^-((webkit)|(moz)|(o)|(ms))-/, '');
}
const is_keyframes_node = (node: Node) => remove_css_prefix(node.name) === 'keyframes';
const is_keyframes_node = (node: Node) =>
remove_css_prefix(node.name) === 'keyframes';
const at_rule_has_declaration = ({ block }: Node): true =>
block &&
block.children &&
block.children.find((node: Node) => node.type === 'Declaration');
function minify_declarations(
code: MagicString,
start: number,
declarations: Declaration[]
): number {
let c = start;
declarations.forEach((declaration, i) => {
const separator = i > 0 ? ';' : '';
if ((declaration.node.start - c) > separator.length) {
code.overwrite(c, declaration.node.start, separator);
}
declaration.minify(code);
c = declaration.node.end;
});
return c;
}
// https://github.com/darkskyapp/string-hash/blob/master/index.js
function hash(str: string): string {
@ -64,16 +89,7 @@ class Rule {
code.remove(c, this.node.block.start);
c = this.node.block.start + 1;
this.declarations.forEach((declaration, i) => {
const separator = i > 0 ? ';' : '';
if ((declaration.node.start - c) > separator.length) {
code.overwrite(c, declaration.node.start, separator);
}
declaration.minify(code);
c = declaration.node.end;
});
c = minify_declarations(code, c, this.declarations);
code.remove(c, this.node.block.end - 1);
}
@ -141,10 +157,12 @@ class Declaration {
class Atrule {
node: Node;
children: Array<Atrule|Rule>;
declarations: Declaration[];
constructor(node: Node) {
this.node = node;
this.children = [];
this.declarations = [];
}
apply(node: Element, stack: Element[]) {
@ -179,11 +197,6 @@ class Atrule {
});
code.remove(c, this.node.block.start);
} else if (is_keyframes_node(this.node)) {
let c = this.node.start + this.node.name.length + 1;
if (this.node.expression.start - c > 1) code.overwrite(c, this.node.expression.start, ' ');
c = this.node.expression.end;
if (this.node.block.start - c > 0) code.remove(c, this.node.block.start);
} else if (this.node.name === 'supports') {
let c = this.node.start + 9;
if (this.node.expression.start - c > 1) code.overwrite(c, this.node.expression.start, ' ');
@ -192,12 +205,26 @@ class Atrule {
c = query.end;
});
code.remove(c, this.node.block.start);
} else {
let c = this.node.start + this.node.name.length + 1;
if (this.node.expression) {
if (this.node.expression.start - c > 1) code.overwrite(c, this.node.expression.start, ' ');
c = this.node.expression.end;
}
if (this.node.block && this.node.block.start - c > 0) {
code.remove(c, this.node.block.start);
}
}
// TODO other atrules
if (this.node.block) {
let c = this.node.block.start + 1;
if (this.declarations.length) {
c = minify_declarations(code, c, this.declarations);
// if the atrule has children, leave the last declaration semicolon alone
if (this.children.length) c++;
}
this.children.forEach(child => {
if (child.is_used(dev)) {
@ -296,6 +323,11 @@ export default class Stylesheet {
this.keyframes.set(expression.name, `${this.id}-${expression.name}`);
}
});
} else if (at_rule_has_declaration(node)) {
const at_rule_declarations = node.block.children
.filter(node => node.type === 'Declaration')
.map(node => new Declaration(node));
atrule.declarations.push(...at_rule_declarations);
}
current_atrule = atrule;

@ -5,27 +5,28 @@ import Node from './shared/Node';
import Element from './Element';
import Text from './Text';
import Expression from './shared/Expression';
import TemplateScope from './shared/TemplateScope';
export default class Attribute extends Node {
type: 'Attribute';
start: number;
end: number;
scope: TemplateScope;
component: Component;
parent: Element;
name: string;
is_spread: boolean;
is_true: boolean;
is_dynamic: boolean;
is_static: boolean;
is_synthetic: boolean;
should_cache: boolean;
expression?: Expression;
chunks: Array<Text | Expression>;
dependencies: Set<string>;
constructor(component, parent, scope, info) {
super(component, parent, scope, info);
this.scope = scope;
if (info.type === 'Spread') {
this.name = null;
@ -37,9 +38,7 @@ export default class Attribute extends Node {
this.dependencies = this.expression.dependencies;
this.chunks = null;
this.is_dynamic = true; // TODO not necessarily
this.is_static = false;
this.should_cache = false; // TODO does this mean anything here?
}
else {
@ -62,22 +61,13 @@ export default class Attribute extends Node {
add_to_set(this.dependencies, expression.dependencies);
return expression;
});
this.is_dynamic = this.dependencies.size > 0;
this.should_cache = this.is_dynamic
? this.chunks.length === 1
// @ts-ignore todo: probably error
? this.chunks[0].node.type !== 'Identifier' || scope.names.has(this.chunks[0].node.name)
: true
: false;
}
}
get_dependencies() {
if (this.is_spread) return this.expression.dynamic_dependencies();
const dependencies = new Set();
const dependencies: Set<string> = new Set();
this.chunks.forEach(chunk => {
if (chunk.type === 'Expression') {
add_to_set(dependencies, chunk.dynamic_dependencies());
@ -113,7 +103,7 @@ export default class Attribute extends Node {
}
get_static_value() {
if (this.is_spread || this.is_dynamic) return null;
if (this.is_spread || this.dependencies.size > 0) return null;
return this.is_true
? true
@ -122,4 +112,13 @@ export default class Attribute extends Node {
? (this.chunks[0] as Text).data
: '';
}
should_cache() {
return this.is_static
? false
: this.chunks.length === 1
// @ts-ignore todo: probably error
? this.chunks[0].node.type !== 'Identifier' || this.scope.names.has(this.chunks[0].node.name)
: true;
}
}

@ -1,11 +1,17 @@
import Node from './shared/Node';
const pattern = /^\s*svelte-ignore\s+([\s\S]+)\s*$/m;
export default class Comment extends Node {
type: 'Comment';
data: string;
ignores: string[];
constructor(component, parent, scope, info) {
super(component, parent, scope, info);
this.data = info.data;
const match = pattern.exec(this.data);
this.ignores = match ? match[1].split(/[^\S]/).map(x => x.trim()).filter(Boolean) : [];
}
}

@ -63,7 +63,8 @@ const valid_modifiers = new Set([
'stopPropagation',
'capture',
'once',
'passive'
'passive',
'self'
]);
const passive_events = new Set([
@ -83,7 +84,7 @@ function get_namespace(parent: Element, element: Element, explicit_namespace: st
: null);
}
if (element.name.toLowerCase() === 'svg') return namespaces.svg;
if (svg.test(element.name.toLowerCase())) return namespaces.svg;
if (parent_element.name.toLowerCase() === 'foreignobject') return null;
return parent_element.namespace;
@ -414,6 +415,13 @@ export default class Element extends Node {
}
}
if (name === 'is') {
component.warn(attribute, {
code: 'avoid-is',
message: `The 'is' attribute is not supported cross-browser and should be avoided`
});
}
attribute_map.set(attribute.name, attribute);
});

@ -7,13 +7,12 @@ import { Node } from '../../../interfaces';
import { globals , sanitize } from '../../../utils/names';
import deindent from '../../utils/deindent';
import Wrapper from '../../render_dom/wrappers/shared/Wrapper';
import TemplateScope from './TemplateScope';
import get_object from '../../utils/get_object';
import { nodes_match } from '../../../utils/nodes_match';
import Block from '../../render_dom/Block';
import { INode } from '../interfaces';
import is_dynamic from '../../render_dom/wrappers/shared/is_dynamic';
import { invalidate } from '../../utils/invalidate';
const binary_operators: Record<string, number> = {
'**': 15,
@ -147,7 +146,10 @@ export default class Expression {
contextual_dependencies.add(name);
if (!lazy) {
const owner = template_scope.get_owner(name);
const is_index = owner.type === 'EachBlock' && owner.key && name === owner.index;
if (!lazy || is_index) {
template_scope.dependencies_for_name.get(name).forEach(name => dependencies.add(name));
}
} else {
@ -238,7 +240,6 @@ export default class Expression {
const { code } = component;
let function_expression;
let pending_assignments: Set<string> = new Set();
let dependencies: Set<string>;
let contextual_dependencies: Set<string>;
@ -306,16 +307,6 @@ export default class Expression {
if (map.has(node)) scope = scope.parent;
if (node === function_expression) {
if (pending_assignments.size > 0) {
if (node.type !== 'ArrowFunctionExpression') {
// this should never happen!
throw new Error(`Well that's odd`);
}
// TOOD optimisation — if this is an event handler,
// the return value doesn't matter
}
const name = component.get_unique_name(
sanitize(get_function_name(node, owner))
);
@ -331,40 +322,11 @@ export default class Expression {
args.push(original_params);
}
let body = code.slice(node.body.start, node.body.end).trim();
if (node.body.type !== 'BlockStatement') {
if (pending_assignments.size > 0) {
const dependencies = new Set();
pending_assignments.forEach(name => {
if (template_scope.names.has(name)) {
template_scope.dependencies_for_name.get(name).forEach(dependency => {
dependencies.add(dependency);
});
} else {
dependencies.add(name);
}
});
const insert = Array.from(dependencies).map(name => component.invalidate(name)).join('; ');
pending_assignments = new Set();
component.has_reactive_assignments = true;
body = deindent`
{
const $$result = ${body};
${insert};
return $$result;
}
`;
} else {
body = `{\n\treturn ${body};\n}`;
}
}
const body = code.slice(node.body.start, node.body.end).trim();
const fn = deindent`
${node.async && 'async '}function${node.generator && '*'} ${name}(${args.join(', ')}) ${body}
`;
const fn = node.type === 'FunctionExpression'
? `${node.async ? 'async ' : ''}function${node.generator ? '*' : ''} ${name}(${args.join(', ')}) ${body}`
: `const ${name} = ${node.async ? 'async ' : ''}(${args.join(', ')}) => ${body};`;
if (dependencies.size === 0 && contextual_dependencies.size === 0) {
// we can hoist this out of the component completely
@ -418,66 +380,26 @@ export default class Expression {
contextual_dependencies = null;
}
if (node.type === 'AssignmentExpression') {
const names = node.left.type === 'MemberExpression'
? [get_object(node.left).name]
: extract_names(node.left);
if (node.operator === '=' && nodes_match(node.left, node.right)) {
const dirty = names.filter(name => {
return !scope.declarations.has(name);
});
if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') {
const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument;
if (dirty.length) component.has_reactive_assignments = true;
// normally (`a = 1`, `b.c = 2`), there'll be a single name
// (a or b). In destructuring cases (`[d, e] = [e, d]`) there
// may be more, in which case we need to tack the extra ones
// onto the initial function call
const names = new Set(extract_names(assignee));
code.overwrite(node.start, node.end, dirty.map(n => component.invalidate(n)).join('; '));
} else {
const traced: Set<string> = new Set();
names.forEach(name => {
if (scope.declarations.has(name)) return;
const variable = component.var_lookup.get(name);
if (variable && variable.hoistable) return;
pending_assignments.add(name);
});
}
} else if (node.type === 'UpdateExpression') {
const { name } = get_object(node.argument);
if (scope.declarations.has(name)) return;
const variable = component.var_lookup.get(name);
if (variable && variable.hoistable) return;
pending_assignments.add(name);
}
if (/Statement/.test(node.type)) {
if (pending_assignments.size > 0) {
const has_semi = code.original[node.end - 1] === ';';
const insert = (
(has_semi ? ' ' : '; ') +
Array.from(pending_assignments).map(name => component.invalidate(name)).join('; ')
);
if (/^(Break|Continue|Return)Statement/.test(node.type)) {
if (node.argument) {
code.overwrite(node.start, node.argument.start, `var $$result = `);
code.appendLeft(node.end, `${insert}; return $$result`);
} else {
code.prependRight(node.start, `${insert}; `);
}
} else if (parent && /(If|For(In|Of)?|While)Statement/.test(parent.type) && node.type !== 'BlockStatement') {
code.prependRight(node.start, '{ ');
code.appendLeft(node.end, `${insert}; }`);
const dependencies = template_scope.dependencies_for_name.get(name);
if (dependencies) {
dependencies.forEach(name => traced.add(name));
} else {
code.appendLeft(node.end, `${insert};`);
traced.add(name);
}
});
component.has_reactive_assignments = true;
pending_assignments = new Set();
}
invalidate(component, scope, code, node, traced);
}
}
});

@ -42,9 +42,20 @@ function get_constructor(type) {
export default function map_children(component, parent, scope, children: Node[]) {
let last = null;
let ignores = [];
return children.map(child => {
const constructor = get_constructor(child.type);
const use_ignores = child.type !== 'Text' && child.type !== 'Comment' && ignores.length;
if (use_ignores) component.push_ignores(ignores);
const node = new constructor(component, parent, scope, child);
if (use_ignores) component.pop_ignores(), ignores = [];
if (node.type === 'Comment' && node.ignores.length) {
ignores.push(...node.ignores);
}
if (last) last.next = node;
node.prev = last;

@ -10,7 +10,7 @@ export interface BlockOptions {
renderer?: Renderer;
comment?: string;
key?: string;
bindings?: Map<string, { object: string; property: string; snippet: string }>;
bindings?: Map<string, { object: string; property: string; snippet: string; store: string; tail: string }>;
dependencies?: Set<string>;
}
@ -27,7 +27,7 @@ export default class Block {
dependencies: Set<string>;
bindings: Map<string, { object: string; property: string; snippet: string }>;
bindings: Map<string, { object: string; property: string; snippet: string; store: string; tail: string }>;
builders: {
init: CodeBuilder;

@ -7,9 +7,8 @@ import { CompileOptions } from '../../interfaces';
import { walk } from 'estree-walker';
import { stringify_props } from '../utils/stringify_props';
import add_to_set from '../utils/add_to_set';
import get_object from '../utils/get_object';
import { extract_names } from '../utils/scope';
import { nodes_match } from '../../utils/nodes_match';
import { invalidate } from '../utils/invalidate';
export default function dom(
component: Component,
@ -94,7 +93,7 @@ export default function dom(
const body = [];
const not_equal = component.component_options.immutable ? `@not_equal` : `@safe_not_equal`;
let dev_props_check;
let dev_props_check; let inject_state; let capture_state;
props.forEach(x => {
const variable = component.var_lookup.get(x.name);
@ -151,6 +150,29 @@ export default function dom(
}`)}
`;
}
capture_state = (uses_props || writable_props.length > 0) ? deindent`
() => {
return { ${component.vars.filter(prop => prop.writable).map(prop => prop.name).join(", ")} };
}
` : deindent`
() => {
return {}
}
`;
inject_state = (uses_props || writable_props.length > 0) ? deindent`
${$$props} => {
${uses_props && component.invalidate('$$props', `$$props = @assign(@assign({}, $$props), $$new_props)`)}
${component.vars.filter(prop => prop.writable).map(prop => deindent`
if ('${prop.name}' in $$props) ${component.invalidate(prop.name, `${prop.name} = ${$$props}.${prop.name}`)};
`)}
}
` : deindent`
${$$props} => {
return
}
`;
}
// instrument assignments
@ -158,8 +180,6 @@ export default function dom(
let scope = component.instance_scope;
const map = component.instance_scope_map;
let pending_assignments = new Set();
walk(component.ast.instance.content, {
enter: (node) => {
if (map.has(node)) {
@ -167,97 +187,25 @@ export default function dom(
}
},
leave(node, parent) {
leave(node) {
if (map.has(node)) {
scope = scope.parent;
}
if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') {
const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument;
let names = [];
if (assignee.type === 'MemberExpression') {
const left_object_name = get_object(assignee).name;
left_object_name && (names = [left_object_name]);
} else {
names = extract_names(assignee);
}
if (node.operator === '=' && nodes_match(node.left, node.right)) {
const dirty = names.filter(name => {
return name[0] === '$' || scope.find_owner(name) === component.instance_scope;
});
if (dirty.length) component.has_reactive_assignments = true;
code.overwrite(node.start, node.end, dirty.map(n => component.invalidate(n)).join('; '));
} else {
const single = (
node.type === 'AssignmentExpression' &&
assignee.type === 'Identifier' &&
parent.type === 'ExpressionStatement' &&
assignee.name[0] !== '$'
);
names.forEach(name => {
const owner = scope.find_owner(name);
if (owner && owner !== component.instance_scope) return;
const variable = component.var_lookup.get(name);
if (variable && (variable.hoistable || variable.global || variable.module)) return;
if (single && !(variable.subscribable && variable.reassigned)) {
if (variable.referenced || variable.is_reactive_dependency || variable.export_name) {
code.prependRight(node.start, `$$invalidate('${name}', `);
code.appendLeft(node.end, `)`);
}
} else {
pending_assignments.add(name);
}
// normally (`a = 1`, `b.c = 2`), there'll be a single name
// (a or b). In destructuring cases (`[d, e] = [e, d]`) there
// may be more, in which case we need to tack the extra ones
// onto the initial function call
const names = new Set(extract_names(assignee));
component.has_reactive_assignments = true;
});
}
}
if (pending_assignments.size > 0) {
if (node.type === 'ArrowFunctionExpression') {
const insert = Array.from(pending_assignments).map(name => component.invalidate(name)).join('; ');
pending_assignments = new Set();
code.prependRight(node.body.start, `{ const $$result = `);
code.appendLeft(node.body.end, `; ${insert}; return $$result; }`);
pending_assignments = new Set();
}
else if (/Statement/.test(node.type)) {
const insert = Array.from(pending_assignments).map(name => component.invalidate(name)).join('; ');
if (/^(Break|Continue|Return)Statement/.test(node.type)) {
if (node.argument) {
code.overwrite(node.start, node.argument.start, `var $$result = `);
code.appendLeft(node.argument.end, `; ${insert}; return $$result`);
} else {
code.prependRight(node.start, `${insert}; `);
}
} else if (parent && /(If|For(In|Of)?|While)Statement/.test(parent.type) && node.type !== 'BlockStatement') {
code.prependRight(node.start, '{ ');
code.appendLeft(node.end, `${code.original[node.end - 1] === ';' ? '' : ';'} ${insert}; }`);
} else {
code.appendLeft(node.end, `${code.original[node.end - 1] === ';' ? '' : ';'} ${insert};`);
}
pending_assignments = new Set();
}
invalidate(component, scope, code, node, names);
}
}
});
if (pending_assignments.size > 0) {
throw new Error(`TODO this should not happen!`);
}
component.rewrite_props(({ name, reassigned }) => {
const value = `$${name}`;
@ -390,7 +338,7 @@ export default function dom(
const store = component.var_lookup.get(name);
if (store && store.reassigned) {
return `${$name}, $$unsubscribe_${name} = @noop, $$subscribe_${name} = () => { $$unsubscribe_${name}(); $$unsubscribe_${name} = @subscribe(${name}, $$value => { ${$name} = $$value; $$invalidate('${$name}', ${$name}); }) }`;
return `${$name}, $$unsubscribe_${name} = @noop, $$subscribe_${name} = () => ($$unsubscribe_${name}(), $$unsubscribe_${name} = @subscribe(${name}, $$value => { ${$name} = $$value; $$invalidate('${$name}', ${$name}); }), ${name})`;
}
return $name;
@ -426,6 +374,10 @@ export default function dom(
${set && `$$self.$set = ${set};`}
${capture_state && `$$self.$capture_state = ${capture_state};`}
${inject_state && `$$self.$inject_state = ${inject_state};`}
${injected.length && `let ${injected.join(', ')};`}
${reactive_declarations.length > 0 && deindent`

@ -188,7 +188,6 @@ export default class AwaitBlockWrapper extends Wrapper {
conditions.push(
`(${dependencies.map(dep => `'${dep}' in changed`).join(' || ')})`
);
}
conditions.push(
`${promise} !== (${promise} = ${snippet})`,
@ -212,6 +211,13 @@ export default class AwaitBlockWrapper extends Wrapper {
${conditions.join(' && ')}
`);
}
} else {
if (this.pending.block.has_update_method) {
block.builders.update.add_block(deindent`
${info}.block.p(changed, @assign(@assign({}, ctx), ${info}.resolved));
`);
}
}
if (this.pending.block.has_outro_method) {
block.builders.outro.add_block(deindent`

@ -121,11 +121,19 @@ export default class EachBlockWrapper extends Wrapper {
view_length: fixed_length === null ? `${iterations}.[✂${c}-${c+4}✂]` : fixed_length
};
const store =
node.expression.node.type === 'Identifier' &&
node.expression.node.name[0] === '$'
? node.expression.node.name.slice(1)
: null;
node.contexts.forEach(prop => {
this.block.bindings.set(prop.key.name, {
object: this.vars.each_block_value,
property: this.index_name,
snippet: attach_head(`${this.vars.each_block_value}[${this.index_name}]`, prop.tail)
snippet: attach_head(`${this.vars.each_block_value}[${this.index_name}]`, prop.tail),
store,
tail: attach_head(`[${this.index_name}]`, prop.tail)
});
});
@ -178,7 +186,7 @@ export default class EachBlockWrapper extends Wrapper {
const snippet = this.node.expression.render(block);
block.builders.init.add_line(`var ${this.vars.each_block_value} = ${snippet};`);
block.builders.init.add_line(`let ${this.vars.each_block_value} = ${snippet};`);
renderer.blocks.push(deindent`
function ${this.vars.get_each_context}(ctx, list, i) {
@ -214,7 +222,9 @@ export default class EachBlockWrapper extends Wrapper {
if (this.block.has_intro_method || this.block.has_outro_method) {
block.builders.intro.add_block(deindent`
for (var #i = 0; #i < ${this.vars.data_length}; #i += 1) @transition_in(${this.vars.iterations}[#i]);
for (let #i = 0; #i < ${this.vars.data_length}; #i += 1) {
@transition_in(${this.vars.iterations}[#i]);
}
`);
}
@ -234,7 +244,7 @@ export default class EachBlockWrapper extends Wrapper {
if (this.else) {
const each_block_else = component.get_unique_name(`${this.var}_else`);
block.builders.init.add_line(`var ${each_block_else} = null;`);
block.builders.init.add_line(`let ${each_block_else} = null;`);
// TODO neaten this up... will end up with an empty line in the block
block.builders.init.add_block(deindent`
@ -339,7 +349,7 @@ export default class EachBlockWrapper extends Wrapper {
// @ts-ignore todo: probably error
this.node.key.render()};
for (var #i = 0; #i < ${this.vars.each_block_value}.${length}; #i += 1) {
for (let #i = 0; #i < ${this.vars.each_block_value}.${length}; #i += 1) {
let child_ctx = ${this.vars.get_each_context}(ctx, ${this.vars.each_block_value}, #i);
let key = ${get_key}(child_ctx);
${lookup}.set(key, ${iterations}[#i] = ${create_each_block}(key, child_ctx));
@ -347,17 +357,23 @@ export default class EachBlockWrapper extends Wrapper {
`);
block.builders.create.add_block(deindent`
for (#i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].c();
for (let #i = 0; #i < ${view_length}; #i += 1) {
${iterations}[#i].c();
}
`);
if (parent_nodes && this.renderer.options.hydratable) {
block.builders.claim.add_block(deindent`
for (#i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].l(${parent_nodes});
for (let #i = 0; #i < ${view_length}; #i += 1) {
${iterations}[#i].l(${parent_nodes});
}
`);
}
block.builders.mount.add_block(deindent`
for (#i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].m(${initial_mount_node}, ${initial_anchor_node});
for (let #i = 0; #i < ${view_length}; #i += 1) {
${iterations}[#i].m(${initial_mount_node}, ${initial_anchor_node});
}
`);
const dynamic = this.block.has_update_method;
@ -382,12 +398,16 @@ export default class EachBlockWrapper extends Wrapper {
if (this.block.has_outros) {
block.builders.outro.add_block(deindent`
for (#i = 0; #i < ${view_length}; #i += 1) @transition_out(${iterations}[#i]);
for (let #i = 0; #i < ${view_length}; #i += 1) {
@transition_out(${iterations}[#i]);
}
`);
}
block.builders.destroy.add_block(deindent`
for (#i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].d(${parent_node ? '' : 'detaching'});
for (let #i = 0; #i < ${view_length}; #i += 1) {
${iterations}[#i].d(${parent_node ? '' : 'detaching'});
}
`);
}
@ -418,29 +438,29 @@ export default class EachBlockWrapper extends Wrapper {
} = this.vars;
block.builders.init.add_block(deindent`
var ${iterations} = [];
let ${iterations} = [];
for (var #i = 0; #i < ${data_length}; #i += 1) {
for (let #i = 0; #i < ${data_length}; #i += 1) {
${iterations}[#i] = ${create_each_block}(${this.vars.get_each_context}(ctx, ${this.vars.each_block_value}, #i));
}
`);
block.builders.create.add_block(deindent`
for (var #i = 0; #i < ${view_length}; #i += 1) {
for (let #i = 0; #i < ${view_length}; #i += 1) {
${iterations}[#i].c();
}
`);
if (parent_nodes && this.renderer.options.hydratable) {
block.builders.claim.add_block(deindent`
for (var #i = 0; #i < ${view_length}; #i += 1) {
for (let #i = 0; #i < ${view_length}; #i += 1) {
${iterations}[#i].l(${parent_nodes});
}
`);
}
block.builders.mount.add_block(deindent`
for (var #i = 0; #i < ${view_length}; #i += 1) {
for (let #i = 0; #i < ${view_length}; #i += 1) {
${iterations}[#i].m(${initial_mount_node}, ${initial_anchor_node});
}
`);
@ -503,7 +523,9 @@ export default class EachBlockWrapper extends Wrapper {
`);
remove_old_blocks = deindent`
@group_outros();
for (#i = ${this.vars.each_block_value}.${length}; #i < ${view_length}; #i += 1) ${out}(#i);
for (#i = ${this.vars.each_block_value}.${length}; #i < ${view_length}; #i += 1) {
${out}(#i);
}
@check_outros();
`;
} else {
@ -515,11 +537,14 @@ export default class EachBlockWrapper extends Wrapper {
`;
}
// We declare `i` as block scoped here, as the `remove_old_blocks` code
// may rely on continuing where this iteration stopped.
const update = deindent`
${!this.block.has_update_method && `const #old_length = ${this.vars.each_block_value}.length;`}
${this.vars.each_block_value} = ${snippet};
for (var #i = ${start}; #i < ${this.vars.each_block_value}.${length}; #i += 1) {
let #i;
for (#i = ${start}; #i < ${this.vars.each_block_value}.${length}; #i += 1) {
const child_ctx = ${this.vars.get_each_context}(ctx, ${this.vars.each_block_value}, #i);
${for_loop_body}
@ -538,8 +563,10 @@ export default class EachBlockWrapper extends Wrapper {
if (this.block.has_outros) {
block.builders.outro.add_block(deindent`
${iterations} = ${iterations}.filter(@_Boolean);
for (let #i = 0; #i < ${view_length}; #i += 1) @transition_out(${iterations}[#i]);`
);
for (let #i = 0; #i < ${view_length}; #i += 1) {
@transition_out(${iterations}[#i]);
}
`);
}
block.builders.destroy.add_block(`@destroy_each(${iterations}, detaching);`);

@ -70,7 +70,8 @@ export default class AttributeWrapper {
const is_legacy_input_type = element.renderer.component.compile_options.legacy && name === 'type' && this.parent.node.name === 'input';
if (this.node.is_dynamic) {
const dependencies = this.node.get_dependencies();
if (dependencies.length > 0) {
let value;
// TODO some of this code is repeated in Tag.ts — would be good to
@ -92,7 +93,7 @@ export default class AttributeWrapper {
const is_select_value_attribute =
name === 'value' && element.node.name === 'select';
const should_cache = (this.node.should_cache || is_select_value_attribute);
const should_cache = (this.node.should_cache() || is_select_value_attribute);
const last = should_cache && block.get_unique_name(
`${element.var}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value`
@ -147,9 +148,6 @@ export default class AttributeWrapper {
updater = `${method}(${element.var}, "${name}", ${should_cache ? last : value});`;
}
// only add an update if mutations are involved (or it's a select?)
const dependencies = this.node.get_dependencies();
if (dependencies.length > 0 || is_select_value_attribute) {
const changed_check = (
(block.has_outros ? `!#current || ` : '') +
dependencies.map(dependency => `changed.${dependency}`).join(' || ')
@ -165,7 +163,6 @@ export default class AttributeWrapper {
condition,
updater
);
}
} else {
const value = this.node.get_value(block);
@ -189,7 +186,7 @@ export default class AttributeWrapper {
const update_value = `${element.var}.value = ${element.var}.__value;`;
block.builders.hydrate.add_line(update_value);
if (this.node.is_dynamic) block.builders.update.add_line(update_value);
if (this.node.get_dependencies().length > 0) block.builders.update.add_line(update_value);
}
}

@ -67,7 +67,7 @@ export default class BindingWrapper {
this.is_readonly = this.node.is_readonly;
this.needs_lock = this.node.name === 'currentTime'; // TODO others?
this.needs_lock = this.node.name === 'currentTime' || (parent.node.name === 'input' && parent.node.get_static_attribute_value('type') === 'number'); // TODO others?
}
get_dependencies() {
@ -208,6 +208,10 @@ function get_dom_updater(
return `${element.var}.checked = ${condition};`;
}
if (binding.node.name === 'value') {
return `@set_input_value(${element.var}, ${binding.snippet});`;
}
return `${element.var}.${binding.node.name} = ${binding.snippet};`;
}
@ -245,7 +249,7 @@ function get_event_handler(
snippet?: string;
} {
const value = get_value_from_dom(renderer, binding.parent, binding);
const store = binding.object[0] === '$' ? binding.object.slice(1) : null;
let store = binding.object[0] === '$' ? binding.object.slice(1) : null;
let tail = '';
if (binding.node.expression.node.type === 'MemberExpression') {
@ -254,7 +258,13 @@ function get_event_handler(
}
if (binding.node.is_contextual) {
const { object, property, snippet } = block.bindings.get(name);
const binding = block.bindings.get(name);
const { object, property, snippet } = binding;
if (binding.store) {
store = binding.store;
tail = `${binding.tail}${tail}`;
}
return {
uses_context: true,

@ -10,6 +10,7 @@ import Text from '../../../nodes/Text';
export interface StyleProp {
key: string;
value: Array<Text|Expression>;
important: boolean;
}
export default class StyleAttributeWrapper extends AttributeWrapper {
@ -35,8 +36,7 @@ export default class StyleAttributeWrapper extends AttributeWrapper {
} else {
const snippet = chunk.render();
add_to_set(prop_dependencies, chunk.dependencies);
add_to_set(prop_dependencies, chunk.dynamic_dependencies());
return chunk.get_precedence() <= 13 ? `(${snippet})` : snippet;
}
})
@ -51,7 +51,7 @@ export default class StyleAttributeWrapper extends AttributeWrapper {
block.builders.update.add_conditional(
condition,
`@set_style(${this.parent.var}, "${prop.key}", ${value});`
`@set_style(${this.parent.var}, "${prop.key}", ${value}${prop.important ? ', 1' : ''});`
);
}
} else {
@ -59,7 +59,7 @@ export default class StyleAttributeWrapper extends AttributeWrapper {
}
block.builders.hydrate.add_line(
`@set_style(${this.parent.var}, "${prop.key}", ${value});`
`@set_style(${this.parent.var}, "${prop.key}", ${value}${prop.important ? ', 1' : ''});`
);
});
}
@ -97,7 +97,7 @@ function optimize_style(value: Array<Text|Expression>) {
const result = get_style_value(chunks);
props.push({ key, value: result.value });
props.push({ key, value: result.value, important: result.important });
chunks = result.chunks;
}
@ -110,8 +110,9 @@ function get_style_value(chunks: Array<Text | Expression>) {
let in_url = false;
let quote_mark = null;
let escaped = false;
let closed = false;
while (chunks.length) {
while (chunks.length && !closed) {
const chunk = chunks.shift();
if (chunk.type === 'Text') {
@ -132,6 +133,7 @@ function get_style_value(chunks: Array<Text | Expression>) {
} else if (char === 'u' && chunk.data.slice(c, c + 4) === 'url(') {
in_url = true;
} else if (char === ';' && !in_url && !quote_mark) {
closed = true;
break;
}
@ -167,9 +169,19 @@ function get_style_value(chunks: Array<Text | Expression>) {
}
}
let important = false;
const last_chunk = value[value.length - 1];
if (last_chunk && last_chunk.type === 'Text' && /\s*!important\s*$/.test(last_chunk.data)) {
important = true;
last_chunk.data = last_chunk.data.replace(/\s*!important\s*$/, '');
if (!last_chunk.data) value.pop();
}
return {
chunks,
value
value,
important
};
}

@ -383,6 +383,11 @@ export default class ElementWrapper extends Wrapper {
return `@_document.createElementNS("${namespace}", "${name}")`;
}
const is = this.attributes.find(attr => attr.node.name === 'is');
if (is) {
return `@element_is("${name}", ${is.render_chunks().join(' + ')});`;
}
return `@element("${name}")`;
}
@ -463,15 +468,25 @@ export default class ElementWrapper extends Wrapper {
// TODO dry this out — similar code for event handlers and component bindings
if (has_local_function) {
// need to create a block-local function that calls an instance-level function
if (animation_frame) {
block.builders.init.add_block(deindent`
function ${handler}() {
${animation_frame && deindent`
@_cancelAnimationFrame(${animation_frame});
if (!${this.var}.paused) ${animation_frame} = @raf(${handler});`}
if (!${this.var}.paused) {
${animation_frame} = @raf(${handler});
${needs_lock && `${lock} = true;`}
}
ctx.${handler}.call(${this.var}${contextual_dependencies.size > 0 ? ', ctx' : ''});
}
`);
} else {
block.builders.init.add_block(deindent`
function ${handler}() {
${needs_lock && `${lock} = true;`}
ctx.${handler}.call(${this.var}${contextual_dependencies.size > 0 ? ', ctx' : ''});
}
`);
}
callee = handler;
} else {
@ -550,8 +565,9 @@ export default class ElementWrapper extends Wrapper {
add_attributes(block: Block) {
// Get all the class dependencies first
this.attributes.forEach((attribute) => {
if (attribute.node.name === 'class' && attribute.node.is_dynamic) {
this.class_dependencies.push(...attribute.node.dependencies);
if (attribute.node.name === 'class') {
const dependencies = attribute.node.get_dependencies();
this.class_dependencies.push(...dependencies);
}
});
@ -605,12 +621,14 @@ export default class ElementWrapper extends Wrapper {
}
`);
const fn = this.node.namespace === namespaces.svg ? `set_svg_attributes` : `set_attributes`;
block.builders.hydrate.add_line(
`@set_attributes(${this.var}, ${data});`
`@${fn}(${this.var}, ${data});`
);
block.builders.update.add_block(deindent`
@set_attributes(${this.var}, @get_spread_update(${levels}, [
@${fn}(${this.var}, @get_spread_update(${levels}, [
${updates.join(',\n')}
]));
`);

@ -7,6 +7,7 @@ import create_debugging_comment from './shared/create_debugging_comment';
import ElseBlock from '../../nodes/ElseBlock';
import FragmentWrapper from './Fragment';
import deindent from '../../utils/deindent';
import { walk } from 'estree-walker';
function is_else_if(node: ElseBlock) {
return (
@ -17,7 +18,9 @@ function is_else_if(node: ElseBlock) {
class IfBlockBranch extends Wrapper {
block: Block;
fragment: FragmentWrapper;
condition: string;
dependencies?: string[];
condition?: string;
snippet?: string;
is_dynamic: boolean;
var = null;
@ -32,13 +35,34 @@ class IfBlockBranch extends Wrapper {
) {
super(renderer, block, parent, node);
this.condition = (node as IfBlock).expression && (node as IfBlock).expression.render(block);
const { expression } = (node as IfBlock);
const is_else = !expression;
if (expression) {
this.dependencies = expression.dynamic_dependencies();
// TODO is this the right rule? or should any non-reference count?
// const should_cache = !is_reference(expression.node, null) && dependencies.length > 0;
let should_cache = false;
walk(expression.node, {
enter(node) {
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
should_cache = true;
}
}
});
if (should_cache) {
this.condition = block.get_unique_name(`show_if`);
this.snippet = expression.render(block);
} else {
this.condition = expression.render(block);
}
}
this.block = block.child({
comment: create_debugging_comment(node, parent.renderer.component),
name: parent.renderer.component.get_unique_name(
(node as IfBlock).expression ? `create_if_block` : `create_else_block`
)
name: parent.renderer.component.get_unique_name(is_else ? `create_else_block` : `create_if_block`)
});
this.fragment = new FragmentWrapper(renderer, this.block, node.children, parent, strip_whitespace, next_sibling);
@ -50,6 +74,7 @@ class IfBlockBranch extends Wrapper {
export default class IfBlockWrapper extends Wrapper {
node: IfBlock;
branches: IfBlockBranch[];
needs_update = false;
var = 'if_block';
@ -88,10 +113,16 @@ export default class IfBlockWrapper extends Wrapper {
block.add_dependencies(node.expression.dependencies);
if (branch.block.dependencies.size > 0) {
// the condition, or its contents, is dynamic
is_dynamic = true;
block.add_dependencies(branch.block.dependencies);
}
if (branch.dependencies && branch.dependencies.length > 0) {
// the condition itself is dynamic
this.needs_update = true;
}
if (branch.block.has_intros) has_intros = true;
if (branch.block.has_outros) has_outros = true;
@ -157,6 +188,10 @@ export default class IfBlockWrapper extends Wrapper {
const detaching = (parent_node && parent_node !== '@_document.head') ? '' : 'detaching';
if (this.node.else) {
this.branches.forEach(branch => {
if (branch.snippet) block.add_variable(branch.condition);
});
if (has_outros) {
this.render_compound_with_outros(block, parent_node, parent_nodes, dynamic, vars, detaching);
@ -211,17 +246,33 @@ export default class IfBlockWrapper extends Wrapper {
const current_block_type_and = has_else ? '' : `${current_block_type} && `;
/* eslint-disable @typescript-eslint/indent,indent */
if (this.needs_update) {
block.builders.init.add_block(deindent`
function ${select_block_type}(changed, ctx) {
${this.branches.map(({ dependencies, condition, snippet, block }) => condition
? deindent`
${snippet && (
dependencies.length > 0
? `if ((${condition} == null) || ${dependencies.map(n => `changed.${n}`).join(' || ')}) ${condition} = !!(${snippet})`
: `if (${condition} == null) ${condition} = !!(${snippet})`
)}
if (${condition}) return ${block.name};`
: `return ${block.name};`)}
}
`);
} else {
block.builders.init.add_block(deindent`
function ${select_block_type}(ctx) {
${this.branches
.map(({ condition, block }) => `${condition ? `if (${condition}) ` : ''}return ${block.name};`)
.join('\n')}
function ${select_block_type}(changed, ctx) {
${this.branches.map(({ condition, snippet, block }) => condition
? `if (${snippet || condition}) return ${block.name};`
: `return ${block.name};`)}
}
`);
}
/* eslint-enable @typescript-eslint/indent,indent */
block.builders.init.add_block(deindent`
var ${current_block_type} = ${select_block_type}(ctx);
var ${current_block_type} = ${select_block_type}(null, ctx);
var ${name} = ${current_block_type_and}${current_block_type}(ctx);
`);
@ -231,6 +282,7 @@ export default class IfBlockWrapper extends Wrapper {
`${if_name}${name}.m(${initial_mount_node}, ${anchor_node});`
);
if (this.needs_update) {
const update_mount_node = this.get_update_mount_node(anchor);
const change_block = deindent`
@ -245,7 +297,7 @@ export default class IfBlockWrapper extends Wrapper {
if (dynamic) {
block.builders.update.add_block(deindent`
if (${current_block_type} === (${current_block_type} = ${select_block_type}(ctx)) && ${name}) {
if (${current_block_type} === (${current_block_type} = ${select_block_type}(changed, ctx)) && ${name}) {
${name}.p(changed, ctx);
} else {
${change_block}
@ -253,11 +305,14 @@ export default class IfBlockWrapper extends Wrapper {
`);
} else {
block.builders.update.add_block(deindent`
if (${current_block_type} !== (${current_block_type} = ${select_block_type}(ctx))) {
if (${current_block_type} !== (${current_block_type} = ${select_block_type}(changed, ctx))) {
${change_block}
}
`);
}
} else if (dynamic) {
block.builders.update.add_line(`${name}.p(changed, ctx);`);
}
block.builders.destroy.add_line(`${if_name}${name}.d(${detaching});`);
}
@ -293,23 +348,36 @@ export default class IfBlockWrapper extends Wrapper {
var ${if_blocks} = [];
function ${select_block_type}(ctx) {
${this.branches
.map(({ condition }, i) => `${condition ? `if (${condition}) ` : ''}return ${i};`)
.join('\n')}
${this.needs_update
? deindent`
function ${select_block_type}(changed, ctx) {
${this.branches.map(({ dependencies, condition, snippet }, i) => condition
? deindent`
${snippet && `if ((${condition} == null) || ${dependencies.map(n => `changed.${n}`).join(' || ')}) ${condition} = !!(${snippet})`}
if (${condition}) return ${String(i)};`
: `return ${i};`)}
${!has_else && `return -1;`}
}
`
: deindent`
function ${select_block_type}(changed, ctx) {
${this.branches.map(({ condition, snippet }, i) => condition
? `if (${snippet || condition}) return ${String(i)};`
: `return ${i};`)}
${!has_else && `return -1;`}
}
`}
`);
/* eslint-enable @typescript-eslint/indent,indent */
if (has_else) {
block.builders.init.add_block(deindent`
${current_block_type_index} = ${select_block_type}(ctx);
${current_block_type_index} = ${select_block_type}(null, ctx);
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](ctx);
`);
} else {
block.builders.init.add_block(deindent`
if (~(${current_block_type_index} = ${select_block_type}(ctx))) {
if (~(${current_block_type_index} = ${select_block_type}(null, ctx))) {
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](ctx);
}
`);
@ -322,6 +390,7 @@ export default class IfBlockWrapper extends Wrapper {
`${if_current_block_type_index}${if_blocks}[${current_block_type_index}].m(${initial_mount_node}, ${anchor_node});`
);
if (this.needs_update) {
const update_mount_node = this.get_update_mount_node(anchor);
const destroy_old_block = deindent`
@ -363,7 +432,7 @@ export default class IfBlockWrapper extends Wrapper {
if (dynamic) {
block.builders.update.add_block(deindent`
var ${previous_block_index} = ${current_block_type_index};
${current_block_type_index} = ${select_block_type}(ctx);
${current_block_type_index} = ${select_block_type}(changed, ctx);
if (${current_block_type_index} === ${previous_block_index}) {
${if_current_block_type_index}${if_blocks}[${current_block_type_index}].p(changed, ctx);
} else {
@ -373,12 +442,15 @@ export default class IfBlockWrapper extends Wrapper {
} else {
block.builders.update.add_block(deindent`
var ${previous_block_index} = ${current_block_type_index};
${current_block_type_index} = ${select_block_type}(ctx);
${current_block_type_index} = ${select_block_type}(changed, ctx);
if (${current_block_type_index} !== ${previous_block_index}) {
${change_block}
}
`);
}
} else if (dynamic) {
block.builders.update.add_line(`${name}.p(changed, ctx);`);
}
block.builders.destroy.add_line(deindent`
${if_current_block_type_index}${if_blocks}[${current_block_type_index}].d(${detaching});
@ -395,6 +467,8 @@ export default class IfBlockWrapper extends Wrapper {
) {
const branch = this.branches[0];
if (branch.snippet) block.add_variable(branch.condition, branch.snippet);
block.builders.init.add_block(deindent`
var ${name} = (${branch.condition}) && ${branch.block.name}(ctx);
`);
@ -406,6 +480,7 @@ export default class IfBlockWrapper extends Wrapper {
`if (${name}) ${name}.m(${initial_mount_node}, ${anchor_node});`
);
if (branch.dependencies.length > 0) {
const update_mount_node = this.get_update_mount_node(anchor);
const enter = dynamic
@ -426,14 +501,12 @@ export default class IfBlockWrapper extends Wrapper {
${name}.c();
${has_transitions && `@transition_in(${name}, 1);`}
${name}.m(${update_mount_node}, ${anchor});
${has_transitions && `} else {
@transition_in(${name}, 1);`}
}
} ${has_transitions && `else @transition_in(${name}, 1);`}
`;
block.builders.bubble.add_line(
`if (${name}) bubbles.push(...${name}.bbl());`
);
if (branch.snippet) {
block.builders.update.add_block(`if (${branch.dependencies.map(n => `changed.${n}`).join(' || ')}) ${branch.condition} = ${branch.snippet}`);
}
// no `p()` here — we don't want to update outroing nodes,
// as that will typically result in glitching
@ -459,6 +532,15 @@ export default class IfBlockWrapper extends Wrapper {
}
`);
}
} else if (dynamic) {
block.builders.update.add_block(
`if (${branch.condition}) ${name}.p(changed, ctx);`
);
}
block.builders.bubble.add_line(
`if (${name}) bubbles.push(...${name}.bbl());`
);
block.builders.destroy.add_line(`${if_name}${name}.d(${detaching});`);
}

@ -168,7 +168,9 @@ export default class InlineComponentWrapper extends Wrapper {
const non_let_dependencies = Array.from(fragment_dependencies).filter(name => !this.node.scope.is_let(name));
if (!uses_spread && (this.node.attributes.filter(a => a.is_dynamic).length || this.node.bindings.length || non_let_dependencies.length > 0)) {
const dynamic_attributes = this.node.attributes.filter(a => a.get_dependencies().length > 0);
if (!uses_spread && (dynamic_attributes.length > 0 || this.node.bindings.length > 0 || non_let_dependencies.length > 0)) {
updates.push(`var ${name_changes} = {};`);
}
@ -220,22 +222,19 @@ export default class InlineComponentWrapper extends Wrapper {
const conditions = Array.from(all_dependencies).map(dep => `changed.${dep}`).join(' || ');
updates.push(deindent`
var ${name_changes} = ${all_dependencies.size === 1 ? `${conditions}` : `(${conditions})`} ? @get_spread_update(${levels}, [
var ${name_changes} = ${conditions ? `(${conditions}) ? @get_spread_update(${levels}, [
${changes.join(',\n')}
]) : {};
]) : {}` : '{}'};
`);
} else {
this.node.attributes
.filter((attribute: Attribute) => attribute.is_dynamic)
.forEach((attribute: Attribute) => {
if (attribute.dependencies.size > 0) {
/* eslint-disable @typescript-eslint/indent,indent */
dynamic_attributes.forEach((attribute: Attribute) => {
const dependencies = attribute.get_dependencies();
if (dependencies.length > 0) {
const condition = dependencies.map(dependency => `changed.${dependency}`).join(' || ');
updates.push(deindent`
if (${[...attribute.dependencies]
.map(dependency => `changed.${dependency}`)
.join(' || ')}) ${name_changes}${quote_prop_if_necessary(attribute.name)} = ${attribute.get_value(block)};
if (${condition}) ${name_changes}${quote_prop_if_necessary(attribute.name)} = ${attribute.get_value(block)};
`);
/* eslint-enable @typescript-eslint/indent,indent */
}
});
}

@ -2,7 +2,6 @@ import Renderer from '../Renderer';
import Block from '../Block';
import Tag from './shared/Tag';
import Wrapper from './shared/Wrapper';
import deindent from '../../utils/deindent';
import MustacheTag from '../../nodes/MustacheTag';
import RawMustacheTag from '../../nodes/RawMustacheTag';
@ -19,95 +18,47 @@ export default class RawMustacheTagWrapper extends Tag {
this.cannot_use_innerhtml();
}
render(block: Block, parent_node: string, parent_nodes: string) {
const name = this.var;
render(block: Block, parent_node: string, _parent_nodes: string) {
const in_head = parent_node === '@_document.head';
const needs_anchors = !parent_node || in_head;
// if in head always needs anchors
if (in_head) {
this.prev = null;
this.next = null;
}
const can_use_innerhtml = !in_head && parent_node && !this.prev && !this.next;
// TODO use is_dom_node instead of type === 'Element'?
const needs_anchor_before = this.prev ? this.prev.node.type !== 'Element' : needs_anchors;
const needs_anchor_after = this.next ? this.next.node.type !== 'Element' : needs_anchors;
if (can_use_innerhtml) {
const insert = content => `${parent_node}.innerHTML = ${content};`;
const anchor_before = needs_anchor_before
? block.get_unique_name(`${name}_before`)
: (this.prev && this.prev.var) || 'null';
const { init } = this.rename_this_method(
block,
content => insert(content)
);
const anchor_after = needs_anchor_after
? block.get_unique_name(`${name}_after`)
: (this.next && this.next.var) || 'null';
block.builders.mount.add_line(insert(init));
}
let detach: string;
let insert: (content: string) => string;
let use_innerhtml = false;
else {
const needs_anchor = in_head || (this.next && !this.next.is_dom_node());
if (anchor_before === 'null' && anchor_after === 'null') {
use_innerhtml = true;
detach = `${parent_node}.innerHTML = '';`;
insert = content => `${parent_node}.innerHTML = ${content};`;
} else if (anchor_before === 'null') {
detach = `@detach_before(${anchor_after});`;
insert = content => `${anchor_after}.insertAdjacentHTML("beforebegin", ${content});`;
} else if (anchor_after === 'null') {
detach = `@detach_after(${anchor_before});`;
insert = content => `${anchor_before}.insertAdjacentHTML("afterend", ${content});`;
} else {
detach = `@detach_between(${anchor_before}, ${anchor_after});`;
insert = content => `${anchor_before}.insertAdjacentHTML("afterend", ${content});`;
}
const html_tag = block.get_unique_name('html_tag');
const html_anchor = needs_anchor && block.get_unique_name('html_anchor');
block.add_variable(html_tag);
const { init } = this.rename_this_method(
block,
content => deindent`
${!use_innerhtml && detach}
${insert(content)}
`
content => `${html_tag}.p(${content});`
);
// we would have used comments here, but the `insertAdjacentHTML` api only
// exists for `Element`s.
if (needs_anchor_before) {
block.add_element(
anchor_before,
`@element('noscript')`,
parent_nodes && `@element('noscript')`,
parent_node,
true
);
}
const update_anchor = in_head ? 'null' : needs_anchor ? html_anchor : this.next ? this.next.var : 'null';
function add_anchor_after() {
block.add_element(
anchor_after,
`@element('noscript')`,
parent_nodes && `@element('noscript')`,
parent_node
);
}
block.builders.hydrate.add_line(`${html_tag} = new @HtmlTag(${init}, ${update_anchor});`);
block.builders.mount.add_line(`${html_tag}.m(${parent_node || '#target'}${parent_node ? '' : ', anchor'});`);
if (needs_anchor_after && anchor_before === 'null') {
// anchor_after needs to be in the DOM before we
// insert the HTML...
add_anchor_after();
if (needs_anchor) {
block.add_element(html_anchor, '@empty()', '@empty()', parent_node);
}
block.builders.mount.add_line(insert(init));
if (needs_anchors) {
block.builders.destroy.add_conditional('detaching', needs_anchor_before
? `${detach}\n@detach(${anchor_before});`
: detach);
if (!parent_node || in_head) {
block.builders.destroy.add_conditional('detaching', `${html_tag}.d();`);
}
if (needs_anchor_after && anchor_before !== 'null') {
// ...otherwise it should go afterwards
add_anchor_after();
}
}
}

@ -84,6 +84,7 @@ export default class SlotWrapper extends Wrapper {
});
const dynamic_dependencies = Array.from(attribute.dependencies).filter(name => {
if (this.node.scope.is_let(name)) return true;
const variable = renderer.component.var_lookup.get(name);
return is_dynamic(variable);
});
@ -105,7 +106,7 @@ export default class SlotWrapper extends Wrapper {
}
const slot = block.get_unique_name(`${sanitize(slot_name)}_slot`);
const slot_definition = block.get_unique_name(`${sanitize(slot_name)}_slot`);
const slot_definition = block.get_unique_name(`${sanitize(slot_name)}_slot_template`);
block.builders.init.add_block(deindent`
const ${slot_definition} = ctx.$$slots${quote_prop_if_necessary(slot_name)};
@ -162,6 +163,7 @@ export default class SlotWrapper extends Wrapper {
const dynamic_dependencies = Array.from(this.dependencies).filter(name => {
if (name === '$$scope') return true;
if (this.node.scope.is_let(name)) return true;
const variable = renderer.component.var_lookup.get(name);
return is_dynamic(variable);
});
@ -171,7 +173,10 @@ export default class SlotWrapper extends Wrapper {
block.builders.update.add_block(deindent`
if (${slot} && ${slot}.p && ${update_conditions}) {
${slot}.p(@get_slot_changes(${slot_definition}, ctx, changed, ${get_slot_changes}), @get_slot_context(${slot_definition}, ctx, ${get_slot_context}));
${slot}.p(
@get_slot_changes(${slot_definition}, ctx, changed, ${get_slot_changes}),
@get_slot_context(${slot_definition}, ctx, ${get_slot_context})
);
}
`);

@ -66,11 +66,12 @@ export default class TextWrapper extends Wrapper {
render(block: Block, parent_node: string, parent_nodes: string) {
if (this.skip) return;
const use_space = this.use_space();
block.add_element(
this.var,
this.use_space() ? `@space()` : `@text(${stringify(this.data)})`,
parent_nodes && `@claim_text(${parent_nodes}, ${stringify(this.data)})`,
use_space ? `@space()` : `@text(${stringify(this.data)})`,
parent_nodes && (use_space ? `@claim_space(${parent_nodes})` : `@claim_text(${parent_nodes}, ${stringify(this.data)})`),
parent_node
);
}

@ -24,7 +24,7 @@ export default class Tag extends Wrapper {
const value = this.node.should_cache && block.get_unique_name(`${this.var}_value`);
const content = this.node.should_cache ? value : snippet;
if (this.node.should_cache) block.add_variable(value, snippet);
if (this.node.should_cache) block.add_variable(value, `${snippet} + ""`);
if (dependencies.length > 0) {
const changed_check = (
@ -32,7 +32,7 @@ export default class Tag extends Wrapper {
dependencies.map((dependency: string) => `changed.${dependency}`).join(' || ')
);
const update_cached_value = `${value} !== (${value} = ${snippet})`;
const update_cached_value = `${value} !== (${value} = ${snippet} + "")`;
const condition = this.node.should_cache
? `(${changed_check}) && ${update_cached_value}`

@ -10,6 +10,7 @@ export default function add_event_handlers(
let snippet = handler.render(block);
if (handler.modifiers.has('preventDefault')) snippet = `@prevent_default(${snippet})`;
if (handler.modifiers.has('stopPropagation')) snippet = `@stop_propagation(${snippet})`;
if (handler.modifiers.has('self')) snippet = `@self(${snippet})`;
const opts = ['passive', 'once', 'capture'].filter(mod => handler.modifiers.has(mod));

@ -8,7 +8,7 @@ export default function(node: Slot, renderer: Renderer, options: RenderOptions)
const slot_data = get_slot_data(node.values, true);
const arg = slot_data.length > 0 ? `{ ${slot_data.join(', ')} }` : '';
const arg = slot_data.length > 0 ? `{ ${slot_data.join(', ')} }` : '{}';
renderer.append(`\${$$slots${prop} ? $$slots${prop}(${arg}) : \``);

@ -0,0 +1,36 @@
export default function check_graph_for_cycles(edges: Array<[any, any]>): any[] {
const graph: Map<any, any[]> = edges.reduce((g, edge) => {
const [u, v] = edge;
if (!g.has(u)) g.set(u, []);
if (!g.has(v)) g.set(v, []);
g.get(u).push(v);
return g;
}, new Map());
const visited = new Set();
const on_stack = new Set();
const cycles = [];
function visit (v) {
visited.add(v);
on_stack.add(v);
graph.get(v).forEach(w => {
if (!visited.has(w)) {
visit(w);
} else if (on_stack.has(w)) {
cycles.push([...on_stack, w]);
}
});
on_stack.delete(v);
}
graph.forEach((_, v) => {
if (!visited.has(v)) {
visit(v);
}
});
return cycles[0];
}

@ -0,0 +1,66 @@
import Component from '../Component';
import MagicString from 'magic-string';
import { Node } from '../../interfaces';
import { nodes_match } from '../../utils/nodes_match';
import { Scope } from './scope';
export function invalidate(component: Component, scope: Scope, code: MagicString, node: Node, names: Set<string>) {
const [head, ...tail] = Array.from(names).filter(name => {
const owner = scope.find_owner(name);
if (owner && owner !== component.instance_scope) return false;
const variable = component.var_lookup.get(name);
return variable && (
!variable.hoistable &&
!variable.global &&
!variable.module &&
(
variable.referenced ||
variable.is_reactive_dependency ||
variable.export_name ||
variable.name[0] === '$'
)
);
});
if (head) {
component.has_reactive_assignments = true;
if (node.operator === '=' && nodes_match(node.left, node.right) && tail.length === 0) {
code.overwrite(node.start, node.end, component.invalidate(head));
} else {
let suffix = ')';
if (head[0] === '$') {
code.prependRight(node.start, `${component.helper('set_store_value')}(${head.slice(1)}, `);
} else {
let prefix = `$$invalidate`;
const variable = component.var_lookup.get(head);
if (variable.subscribable && variable.reassigned) {
prefix = `$$subscribe_${head}($$invalidate`;
suffix += `)`;
}
code.prependRight(node.start, `${prefix}('${head}', `);
}
const extra_args = tail.map(name => component.invalidate(name));
const pass_value = (
extra_args.length > 0 ||
(node.type === 'AssignmentExpression' && node.left.type !== 'Identifier') ||
(node.type === 'UpdateExpression' && !node.prefix)
);
if (pass_value) {
extra_args.unshift(head);
}
suffix = `${extra_args.map(arg => `, ${arg}`).join('')}${suffix}`;
code.appendLeft(node.end, suffix);
}
}
}

@ -1,15 +1,16 @@
import * as acorn from 'acorn';
import dynamicImport from 'acorn-dynamic-import';
const Parser = acorn.Parser.extend(dynamicImport);
const Parser = acorn.Parser;
export const parse = (source: string) => Parser.parse(source, {
sourceType: 'module',
ecmaVersion: 9,
// @ts-ignore TODO pending release of fixed types
ecmaVersion: 11,
preserveParens: true
});
export const parse_expression_at = (source: string, index: number) => Parser.parseExpressionAt(source, index, {
ecmaVersion: 9,
// @ts-ignore TODO pending release of fixed types
ecmaVersion: 11,
preserveParens: true
});

@ -1,5 +1,6 @@
import read_context from '../read/context';
import read_expression from '../read/expression';
import { closing_tag_omitted } from '../utils/html';
import { whitespace } from '../../utils/patterns';
import { trim_start, trim_end } from '../../utils/trim';
import { Parser } from '../index';
@ -41,6 +42,12 @@ export default function mustache(parser: Parser) {
let block = parser.current();
let expected;
if (closing_tag_omitted(block.name)) {
block.end = start;
parser.stack.pop();
block = parser.current();
}
if (block.type === 'ElseBlock' || block.type === 'PendingBlock' || block.type === 'ThenBlock' || block.type === 'CatchBlock') {
block.end = start;
parser.stack.pop();

@ -1,7 +1,7 @@
import read_expression from '../read/expression';
import read_script from '../read/script';
import read_style from '../read/style';
import { decode_character_references } from '../utils/html';
import { decode_character_references, closing_tag_omitted } from '../utils/html';
import { is_void } from '../../utils/names';
import { Parser } from '../index';
import { Directive, DirectiveType, Node, Text } from '../../interfaces';
@ -42,31 +42,6 @@ const SELF = /^svelte:self(?=[\s\/>])/;
// eslint-disable-next-line no-useless-escape
const COMPONENT = /^svelte:component(?=[\s\/>])/;
// based on http://developers.whatwg.org/syntax.html#syntax-tag-omission
const disallowed_contents = new Map([
['li', new Set(['li'])],
['dt', new Set(['dt', 'dd'])],
['dd', new Set(['dt', 'dd'])],
[
'p',
new Set(
'address article aside blockquote div dl fieldset footer form h1 h2 h3 h4 h5 h6 header hgroup hr main menu nav ol p pre section table ul'.split(
' '
)
),
],
['rt', new Set(['rt', 'rp'])],
['rp', new Set(['rt', 'rp'])],
['optgroup', new Set(['optgroup'])],
['option', new Set(['option', 'optgroup'])],
['thead', new Set(['tbody', 'tfoot'])],
['tbody', new Set(['tbody', 'tfoot'])],
['tfoot', new Set(['tbody'])],
['tr', new Set(['tr', 'tbody'])],
['td', new Set(['td', 'th', 'tr'])],
['th', new Set(['td', 'th', 'tr'])],
]);
function parent_is_head(stack) {
let i = stack.length;
while (i--) {
@ -176,14 +151,10 @@ export default function tag(parser: Parser) {
parser.stack.pop();
return;
} else if (disallowed_contents.has(parent.name)) {
// can this be a child of the parent element, or does it implicitly
// close it, like `<li>one<li>two`?
if (disallowed_contents.get(parent.name).has(name)) {
} else if (closing_tag_omitted(parent.name, name)) {
parent.end = start;
parser.stack.pop();
}
}
const unique_names: Set<string> = new Set();

@ -112,3 +112,40 @@ function validate_code(code: number) {
return NUL;
}
// based on http://developers.whatwg.org/syntax.html#syntax-tag-omission
const disallowed_contents = new Map([
['li', new Set(['li'])],
['dt', new Set(['dt', 'dd'])],
['dd', new Set(['dt', 'dd'])],
[
'p',
new Set(
'address article aside blockquote div dl fieldset footer form h1 h2 h3 h4 h5 h6 header hgroup hr main menu nav ol p pre section table ul'.split(
' '
)
),
],
['rt', new Set(['rt', 'rp'])],
['rp', new Set(['rt', 'rp'])],
['optgroup', new Set(['optgroup'])],
['option', new Set(['option', 'optgroup'])],
['thead', new Set(['tbody', 'tfoot'])],
['tbody', new Set(['tbody', 'tfoot'])],
['tfoot', new Set(['tbody'])],
['tr', new Set(['tr', 'tbody'])],
['td', new Set(['td', 'th', 'tr'])],
['th', new Set(['td', 'th', 'tr'])],
]);
// can this be a child of the parent element, or does it implicitly
// close it, like `<li>one<li>two`?
export function closing_tag_omitted(current: string, next?: string) {
if (disallowed_contents.has(current)) {
if (!next || disallowed_contents.get(current).has(next)) {
return true;
}
}
return false;
}

@ -1,10 +1,14 @@
import { SourceMap } from 'magic-string';
export interface Processed {
code: string;
map?: object | string;
dependencies?: string[];
}
export interface PreprocessorGroup {
markup?: (options: {
content: string;
filename: string;
}) => { code: string; map?: SourceMap | string; dependencies?: string[] };
}) => Processed | Promise<Processed>;
style?: Preprocessor;
script?: Preprocessor;
}
@ -13,13 +17,7 @@ export type Preprocessor = (options: {
content: string;
attributes: Record<string, string | boolean>;
filename?: string;
}) => { code: string; map?: SourceMap | string; dependencies?: string[] };
interface Processed {
code: string;
map?: SourceMap | string;
dependencies?: string[];
}
}) => Processed | Promise<Processed>;
function parse_attributes(str: string) {
const attrs = {};
@ -85,7 +83,7 @@ export default async function preprocess(
const style = preprocessors.map(p => p.style).filter(Boolean);
for (const fn of markup) {
const processed: Processed = await fn({
const processed = await fn({
content: source,
filename
});
@ -98,7 +96,7 @@ export default async function preprocess(
source,
/<script(\s[^]*?)?>([^]*?)<\/script>/gi,
async (match, attributes = '', content) => {
const processed: Processed = await fn({
const processed = await fn({
content,
attributes: parse_attributes(attributes),
filename

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

Loading…
Cancel
Save