diff --git a/.eslintrc.js b/.eslintrc.js
index 2c7f2ed1b4..2023207f74 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -22,14 +22,7 @@ module.exports = {
'arrow-spacing': 2,
'no-inner-declarations': 0,
'require-atomic-updates': 'off',
- '@typescript-eslint/indent': [
- 'error',
- 'tab',
- {
- SwitchCase: 1,
- ignoredNodes: ['TemplateLiteral']
- }
- ],
+ '@typescript-eslint/indent': 'off',
'@typescript-eslint/camelcase': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/array-type': ['error', 'array-simple'],
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000000..2488902b24
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,25 @@
+name: CI
+on: [push, pull_request]
+jobs:
+ Tests:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ node-version: [8, 10, 12]
+ os: [ubuntu-latest, windows-latest, macOS-latest]
+ steps:
+ - run: git config --global core.autocrlf false
+ - uses: actions/checkout@v1
+ - uses: actions/setup-node@v1
+ with:
+ node-version: ${{ matrix.node-version }}
+ - run: npm install
+ - run: npm test
+ env:
+ CI: true
+ Lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v1
+ - uses: actions/setup-node@v1
+ - run: 'npm i && npm run lint'
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index a4603a26e2..0000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-language: node_js
-node_js:
- - "8"
- - "10"
- - "12"
-env:
- global:
- - BUILD_TIMEOUT=20000
-
-addons:
- apt:
- packages:
- - xvfb
-
-install:
- - export DISPLAY=':99.0'
- - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
- - npm ci || npm install
-
-after_success: npm run codecov
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 12ea328f9c..566fde5005 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,62 @@
# Svelte changelog
+## 3.14.1
+
+* Deconflict block method names with other variables ([#3900](https://github.com/sveltejs/svelte/issues/3900))
+* Fix entity encoding issue in text nodes with constant expressions ([#3911](https://github.com/sveltejs/svelte/issues/3911))
+* Make code for unknown prop warnings compatible with older js engines ([#3914](https://github.com/sveltejs/svelte/issues/3914))
+
+## 3.14.0
+
+* Add `loopGuardTimeout` option that augments `for`/`while` loops to prevent infinite loops, primarily for use in the REPL ([#3887](https://github.com/sveltejs/svelte/pull/3887))
+* Keep component bindings in sync when changed in reactive statements ([#3382](https://github.com/sveltejs/svelte/issues/3382))
+* Update attributes before bindings ([#3857](https://github.com/sveltejs/svelte/issues/3857))
+* Prevent variable naming conflict ([#3899](https://github.com/sveltejs/svelte/issues/3899))
+
+
+## 3.13.0
+
+* New structured code generation, which eliminates a number of edge cases and obscure bugs ([#3539](https://github.com/sveltejs/svelte/pull/3539))
+
+Also:
+
+* Fix `{#each}` context not shadowing outer scope when using `bind:` ([#1565](https://github.com/sveltejs/svelte/issues/1565))
+* Fix edge cases in matching selectors against elements ([#1710](https://github.com/sveltejs/svelte/issues/1710))
+* Fix several bugs related to interaction of `{...spread}` attributes with other features ([#2721](https://github.com/sveltejs/svelte/issues/2721), [#2916](https://github.com/sveltejs/svelte/issues/2916), [#3421](https://github.com/sveltejs/svelte/issues/3421), [#3681](https://github.com/sveltejs/svelte/issues/3681), [#3764](https://github.com/sveltejs/svelte/issues/3764), [#3790](https://github.com/sveltejs/svelte/issues/3790))
+* Allow exiting a reactive block early with `break $` ([#2828](https://github.com/sveltejs/svelte/issues/2828))
+* Fix binding to props that have been renamed with `export { ... as ... }` ([#3508](https://github.com/sveltejs/svelte/issues/3508))
+* Fix application of style scoping class in cases of ambiguity ([#3544](https://github.com/sveltejs/svelte/issues/3544))
+* Check attributes have changed before setting them to avoid image flicker ([#3579](https://github.com/sveltejs/svelte/pull/3579))
+* Fix generating malformed code for `{@debug}` tags with no dependencies ([#3588](https://github.com/sveltejs/svelte/issues/3588))
+* Fix generated code in specific case involving compound ifs and child components ([#3595](https://github.com/sveltejs/svelte/issues/3595))
+* Fix `bind:this` binding to a store ([#3591](https://github.com/sveltejs/svelte/issues/3591))
+* Use safer `HTMLElement` check before extending class ([#3608](https://github.com/sveltejs/svelte/issues/3608))
+* Add `location` as a known global ([#3619](https://github.com/sveltejs/svelte/pull/3619))
+* Support `{#await}` with `{:catch}` but no `{:then}` ([#3623](https://github.com/sveltejs/svelte/issues/3623))
+* Clean up dead code emitted for ``s ([#3631](https://github.com/sveltejs/svelte/issues/3631))
+* Fix tracking of dependencies of compound assignments in reactive statements ([#3634](https://github.com/sveltejs/svelte/issues/3634))
+* Flush changes in newly attached block when using `{#await}` ([#3660](https://github.com/sveltejs/svelte/issues/3660))
+* Throw exception immediately when calling `createEventDispatcher()` after component instantiation ([#3667](https://github.com/sveltejs/svelte/pull/3667))
+* Fix globals shadowing contextual template scope ([#3674](https://github.com/sveltejs/svelte/issues/3674))
+* Fix `` bindings to stores ([#3832](https://github.com/sveltejs/svelte/issues/3832))
+* Deconflict generated var names with builtins ([#3724](https://github.com/sveltejs/svelte/issues/3724))
+* Allow spring/tweened values to be initially undefined ([#3761](https://github.com/sveltejs/svelte/issues/3761))
+* Warn if using `` without `customElement: true` option ([#3782](https://github.com/sveltejs/svelte/pull/3782))
+* Add `Event` to list of known globals ([#3810](https://github.com/sveltejs/svelte/pull/3810))
+* Throw helpful error on empty CSS declaration ([#3801](https://github.com/sveltejs/svelte/issues/3801))
+* Support `easing` param on `fade` transition ([#3823](https://github.com/sveltejs/svelte/pull/3823))
+* Generate valid names from filenames with unicode characters ([#3845](https://github.com/sveltejs/svelte/issues/3845))
+* Don't generate any code for markup-less components ([#2200](https://github.com/sveltejs/svelte/issues/2200))
+* Deconflict with internal name `block` ([#3854](https://github.com/sveltejs/svelte/issues/3854))
+* Set attributes before bindings, to prevent erroneous assignments to `input.files` ([#3828](https://github.com/sveltejs/svelte/issues/3828))
+* Smarter unused CSS detection ([#3825](https://github.com/sveltejs/svelte/pull/3825))
+* Allow dynamic event handlers ([#3040](https://github.com/sveltejs/svelte/issues/3040))
+* Prevent erroneous `"undefined"` class name ([#3876](https://github.com/sveltejs/svelte/pull/3876))
+* Prevent resetting of `src` attribute unless changed ([#3579](https://github.com/sveltejs/svelte/pull/3579))
+* Prevent hydration of void element 'children' ([#3882](https://github.com/sveltejs/svelte/issues/3882))
+* Hoist globals even if mentioned in `
```
+---
+
+If the initial value is `undefined` or `null`, the first value change will take effect immediately, just as with `tweened` values (see above).
+
+```js
+const size = spring();
+$: $size = big ? 100 : 10;
+```
+
### `svelte/transition`
The `svelte/transition` module exports six functions: `fade`, `fly`, `slide`, `scale`, `draw` and `crossfade`. They are for use with svelte [`transitions`](docs#Transitions).
@@ -696,7 +718,7 @@ out:draw={params}
Animates the stroke of an SVG element, like a snake in a tube. `in` transitions begin with the path invisible and draw the path to the screen over time. `out` transitions start in a visible state and gradually erase the path. `draw` only works with elements that have a `getTotalLength` method, like `` and ``.
-`scale` accepts the following parameters:
+`draw` accepts the following parameters:
* `delay` (`number`, default 0) — milliseconds before starting
* `speed` (`number`, default undefined) - the speed of the animation, see below.
@@ -945,7 +967,7 @@ app.count += 1;
---
-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 `` [element](docs#svelte_options).
+Svelte components can also be compiled to custom elements (aka web components) using the `customElement: true` compiler option. You should specify a tag name for the component using the `` [element](docs#svelte_options).
```html
@@ -1019,8 +1041,12 @@ Unlike client-side components, server-side components don't have a lifespan afte
A server-side component exposes a `render` method that can be called with optional props. It returns an object with `head`, `html`, and `css` properties, where `head` contains the contents of any `` elements encountered.
+You can import a Svelte component directly into Node using [`svelte/register`](docs#svelte_register).
+
```js
-const App = require('./App.svelte');
+require('svelte/register');
+
+const App = require('./App.svelte').default;
const { head, html, css } = App.render({
answer: 42
diff --git a/site/content/docs/04-compile-time.md b/site/content/docs/04-compile-time.md
index f47fe564af..407b1dfc13 100644
--- a/site/content/docs/04-compile-time.md
+++ b/site/content/docs/04-compile-time.md
@@ -53,6 +53,7 @@ The following options can be passed to the compiler. None are required:
| `tag` | string | null
| `accessors` | boolean | `false`
| `css` | boolean | `true`
+| `loopGuardTimeout` | number | 0
| `preserveComments` | boolean | `false`
| `preserveWhitespace` | boolean | `false`
| `outputFilename` | string | `null`
@@ -73,6 +74,7 @@ The following options can be passed to the compiler. None are required:
| `customElement` | `false` | If `true`, tells the compiler to generate a custom element constructor instead of a regular Svelte component.
| `tag` | `null` | A `string` that tells Svelte what tag name to register the custom element with. It must be a lowercase alphanumeric string with at least one hyphen, e.g. `"my-element"`.
| `css` | `true` | If `true`, styles will be included in the JavaScript class and injected at runtime. It's recommended that you set this to `false` and use the CSS that is statically generated, as it will result in smaller JavaScript bundles and better performance.
+| `loopGuardTimeout` | 0 | A `number` that tells Svelte to break the loop if it blocks the thread for more than `loopGuardTimeout` ms. This is useful to prevent infinite loops. **Only available when `dev: true`**
| `preserveComments` | `false` | If `true`, your HTML comments will be preserved during server-side rendering. By default, they are stripped out.
| `preserveWhitespace` | `false` | If `true`, whitespace inside and between elements is kept as you typed it, rather than optimised by Svelte.
| `outputFilename` | `null` | A `string` used for your JavaScript sourcemap.
@@ -315,7 +317,7 @@ walk(ast: Node, {
---
-The `walk` function provides a way to walk to abstract syntax trees generated by the parser, using the compiler's own built-in instance of [estree-walker](https://github.com/Rich-Harris/estree-walker).
+The `walk` function provides a way to walk the abstract syntax trees generated by the parser, using the compiler's own built-in instance of [estree-walker](https://github.com/Rich-Harris/estree-walker).
The walker takes an abstract syntax tree to walk and an object with two optional methods: `enter` and `leave`. For each node, `enter` is called (if present). Then, unless `this.skip()` is called during `enter`, each of the children are traversed, and then `leave` is called on the node.
diff --git a/site/content/examples/05-bindings/08-media-elements/App.svelte b/site/content/examples/05-bindings/08-media-elements/App.svelte
index fd4f843ca1..469e9e12eb 100644
--- a/site/content/examples/05-bindings/08-media-elements/App.svelte
+++ b/site/content/examples/05-bindings/08-media-elements/App.svelte
@@ -14,7 +14,7 @@
showControlsTimeout = setTimeout(() => showControls = false, 2500);
showControls = true;
- if (e.which !== 1) return; // mouse not down
+ if (!(e.buttons & 1)) return; // mouse not down
if (!duration) return; // video not loaded yet
const { left, right } = this.getBoundingClientRect();
@@ -127,4 +127,4 @@
{format(duration)}
-
\ No newline at end of file
+
diff --git a/site/content/examples/05-bindings/10-bind-this/App.svelte b/site/content/examples/05-bindings/10-bind-this/App.svelte
index 9a8fabc265..8e4b3c5bef 100644
--- a/site/content/examples/05-bindings/10-bind-this/App.svelte
+++ b/site/content/examples/05-bindings/10-bind-this/App.svelte
@@ -43,8 +43,8 @@
width: 100%;
height: 100%;
background-color: #666;
- -webkit-mask: url(logo-mask.svg) 50% 50% no-repeat;
- mask: url(logo-mask.svg) 50% 50% no-repeat;
+ -webkit-mask: url(svelte-logo-mask.svg) 50% 50% no-repeat;
+ mask: url(svelte-logo-mask.svg) 50% 50% no-repeat;
}
diff --git a/site/content/examples/12-svg/05-svg-transitions/App.svelte b/site/content/examples/12-svg/05-svg-transitions/App.svelte
index 6059b3645f..6e1e636b20 100644
--- a/site/content/examples/12-svg/05-svg-transitions/App.svelte
+++ b/site/content/examples/12-svg/05-svg-transitions/App.svelte
@@ -33,7 +33,7 @@
font-family: 'Overpass';
letter-spacing: 0.12em;
color: #676778;
- font-weight: 100;
+ font-weight: 400;
}
.centered span {
@@ -71,4 +71,4 @@
toggle me
-
\ No newline at end of file
+
diff --git a/site/content/examples/15-composition/04-modal/Modal.svelte b/site/content/examples/15-composition/04-modal/Modal.svelte
index 5ffa5989a4..4a5329b05b 100644
--- a/site/content/examples/15-composition/04-modal/Modal.svelte
+++ b/site/content/examples/15-composition/04-modal/Modal.svelte
@@ -1,9 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
diff --git a/site/content/examples/20-7guis/05-7guis-crud/App.svelte b/site/content/examples/20-7guis/05-7guis-crud/App.svelte
index f55aeb0d83..aec623d2a4 100644
--- a/site/content/examples/20-7guis/05-7guis-crud/App.svelte
+++ b/site/content/examples/20-7guis/05-7guis-crud/App.svelte
@@ -2,18 +2,9 @@
```
-> The `svelte/easing` module contains the [Penner easing equations](http://robertpenner.com/easing/), or you can supply your own `p => t` function where `p` and `t` are both values between 0 and 1.
+> The `svelte/easing` module contains the [Penner easing equations](https://web.archive.org/web/20190805215728/http://robertpenner.com/easing/), or you can supply your own `p => t` function where `p` and `t` are both values between 0 and 1.
The full set of options available to `tweened`:
@@ -37,4 +37,4 @@ The full set of options available to `tweened`:
* `easing` — a `p => t` function
* `interpolate` — a custom `(from, to) => t => value` function for interpolating between arbitrary values. By default, Svelte will interpolate between numbers, dates, and identically-shaped arrays and objects (as long as they only contain numbers and dates or other valid arrays and objects). If you want to interpolate (for example) colour strings or transformation matrices, supply a custom interpolator
-You can also pass these options to `progress.set` and `progress.update` as a second argument, in which case they will override the defaults. The `set` and `update` methods both return a promise that resolves when the tween completes.
\ No newline at end of file
+You can also pass these options to `progress.set` and `progress.update` as a second argument, in which case they will override the defaults. The `set` and `update` methods both return a promise that resolves when the tween completes.
diff --git a/site/content/tutorial/16-special-elements/07-svelte-options/app-a/Todo.svelte b/site/content/tutorial/16-special-elements/07-svelte-options/app-a/Todo.svelte
index dae595e7d0..5e0dbca300 100644
--- a/site/content/tutorial/16-special-elements/07-svelte-options/app-a/Todo.svelte
+++ b/site/content/tutorial/16-special-elements/07-svelte-options/app-a/Todo.svelte
@@ -3,7 +3,6 @@
import flash from './flash.js';
export let todo;
- export let toggle;
let div;
diff --git a/site/content/tutorial/16-special-elements/07-svelte-options/app-b/Todo.svelte b/site/content/tutorial/16-special-elements/07-svelte-options/app-b/Todo.svelte
index 447ddc601c..e7ddcedc47 100644
--- a/site/content/tutorial/16-special-elements/07-svelte-options/app-b/Todo.svelte
+++ b/site/content/tutorial/16-special-elements/07-svelte-options/app-b/Todo.svelte
@@ -5,7 +5,6 @@
import flash from './flash.js';
export let todo;
- export let toggle;
let div;
diff --git a/site/package-lock.json b/site/package-lock.json
index 189068b42d..09bd67cf1e 100644
--- a/site/package-lock.json
+++ b/site/package-lock.json
@@ -1281,9 +1281,9 @@
}
},
"@sveltejs/site-kit": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@sveltejs/site-kit/-/site-kit-1.1.3.tgz",
- "integrity": "sha512-cJfz45cqq1nfPnk1V3oYVMcSySI/GnbHyvr+vQAyHhmOpmknO3pYavXDj545YXWJvEXg2sk8Fxcah+Z/56Ka0Q==",
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@sveltejs/site-kit/-/site-kit-1.1.4.tgz",
+ "integrity": "sha512-PsFUX1C/fhV0ODdCJaEQ8OwzgmaPJVmdefiSYA+i6zttBeV19d/ow+l7SPMXxBkux+vUIl5can4BwValCukCsw==",
"dev": true,
"requires": {
"@sindresorhus/slugify": "^0.9.1",
@@ -1291,17 +1291,23 @@
}
},
"@sveltejs/svelte-repl": {
- "version": "0.1.9",
- "resolved": "https://registry.npmjs.org/@sveltejs/svelte-repl/-/svelte-repl-0.1.9.tgz",
- "integrity": "sha512-OXDfHwT5O7UXVYnf4ndTk3dKMITTmWcMty4/lOFte80ui01i47QiVy3GEe9G8FkcU1YBe+c06MMnIgm7j0Ln7Q==",
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/@sveltejs/svelte-repl/-/svelte-repl-0.1.13.tgz",
+ "integrity": "sha512-griMkrRRzAQq22awKaBN5cewGly4xeeo8qpDs8ZhQxmEk5TcX3dMhIGMLuPTSv3Yr0s2c6TDJXcN+ORJn//fpQ==",
"dev": true,
"requires": {
- "codemirror": "^5.48.4",
- "estree-walker": "^0.6.1",
+ "codemirror": "^5.49.2",
+ "estree-walker": "^0.9.0",
"sourcemap-codec": "^1.4.6",
"yootils": "0.0.16"
},
"dependencies": {
+ "estree-walker": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.9.0.tgz",
+ "integrity": "sha512-12U47o7XHUX329+x3FzNVjCx3SHEzMF0nkDv7r/HnBzX/xNTKxajBk6gyygaxrAFtLj39219oMfbtxv4KpaOiA==",
+ "dev": true
+ },
"sourcemap-codec": {
"version": "1.4.6",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz",
@@ -1587,9 +1593,9 @@
"dev": true
},
"codemirror": {
- "version": "5.48.4",
- "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.48.4.tgz",
- "integrity": "sha512-pUhZXDQ6qXSpWdwlgAwHEkd4imA0kf83hINmUEzJpmG80T/XLtDDEzZo8f6PQLuRCcUQhmzqqIo3ZPTRaWByRA==",
+ "version": "5.49.2",
+ "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.49.2.tgz",
+ "integrity": "sha512-dwJ2HRPHm8w51WB5YTF9J7m6Z5dtkqbU9ntMZ1dqXyFB9IpjoUFDj80ahRVEoVanfIp6pfASJbOlbWdEf8FOzQ==",
"dev": true
},
"color-convert": {
@@ -3368,12 +3374,6 @@
"requires": {
"@babel/helper-module-imports": "^7.0.0",
"rollup-pluginutils": "^2.8.1"
- },
- "dependencies": {
- "estree-walker": {
- "version": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
- "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="
- }
}
},
"rollup-plugin-commonjs": {
@@ -3411,10 +3411,6 @@
"rollup-pluginutils": "^2.8.1"
},
"dependencies": {
- "estree-walker": {
- "version": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
- "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="
- },
"resolve": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz",
@@ -3458,12 +3454,6 @@
"rollup-pluginutils": "^2.8.1",
"serialize-javascript": "^1.7.0",
"terser": "^4.1.0"
- },
- "dependencies": {
- "estree-walker": {
- "version": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
- "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="
- }
}
},
"rollup-pluginutils": {
@@ -3473,14 +3463,6 @@
"dev": true,
"requires": {
"estree-walker": "^0.6.1"
- },
- "dependencies": {
- "estree-walker": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
- "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==",
- "dev": true
- }
}
},
"safe-buffer": {
diff --git a/site/package.json b/site/package.json
index 3f362ac891..d08aa1f742 100644
--- a/site/package.json
+++ b/site/package.json
@@ -35,8 +35,8 @@
"@babel/preset-env": "^7.6.0",
"@babel/runtime": "^7.6.0",
"@sindresorhus/slugify": "^0.9.1",
- "@sveltejs/site-kit": "^1.1.3",
- "@sveltejs/svelte-repl": "^0.1.9",
+ "@sveltejs/site-kit": "^1.1.4",
+ "@sveltejs/svelte-repl": "^0.1.13",
"degit": "^2.1.4",
"dotenv": "^8.1.0",
"esm": "^3.2.25",
diff --git a/site/rollup.config.js b/site/rollup.config.js
index c713be33c1..9cb963a87d 100644
--- a/site/rollup.config.js
+++ b/site/rollup.config.js
@@ -13,6 +13,9 @@ const mode = process.env.NODE_ENV;
const dev = mode === 'development';
const legacy = !!process.env.SAPPER_LEGACY_BUILD;
+const onwarn = (warning, onwarn) => (warning.code === 'CIRCULAR_DEPENDENCY' && /[/\\]@sapper[/\\]/.test(warning.message)) || onwarn(warning);
+const dedupe = importee => importee === 'svelte' || importee.startsWith('svelte/');
+
export default {
client: {
input: config.client.input(),
@@ -28,7 +31,10 @@ export default {
hydratable: true,
emitCss: true
}),
- resolve(),
+ resolve({
+ browser: true,
+ dedupe
+ }),
commonjs(),
json(),
@@ -53,6 +59,7 @@ export default {
module: true
})
],
+ onwarn
},
server: {
@@ -67,7 +74,9 @@ export default {
generate: 'ssr',
dev
}),
- resolve(),
+ resolve({
+ dedupe
+ }),
commonjs(),
json()
],
@@ -78,6 +87,7 @@ export default {
require('module').builtinModules || Object.keys(process.binding('natives'))
)
],
+ onwarn
},
serviceworker: {
diff --git a/site/src/routes/_components/WhosUsingSvelte.svelte b/site/src/routes/_components/WhosUsingSvelte.svelte
index 1a0a80f4ac..2a944fe289 100644
--- a/site/src/routes/_components/WhosUsingSvelte.svelte
+++ b/site/src/routes/_components/WhosUsingSvelte.svelte
@@ -55,11 +55,14 @@
+
+
+
@@ -67,15 +70,18 @@
+
+
+ Strix Cloud
-
+ + your company?
diff --git a/site/src/routes/blog/_posts.js b/site/src/routes/blog/_posts.js
index d180ebd35e..2c86e13656 100644
--- a/site/src/routes/blog/_posts.js
+++ b/site/src/routes/blog/_posts.js
@@ -1,11 +1,10 @@
import fs from 'fs';
import path from 'path';
-import { extract_frontmatter, langs, link_renderer } from '@sveltejs/site-kit/utils/markdown.js';
+import { extract_frontmatter, link_renderer } from '@sveltejs/site-kit/utils/markdown.js';
import marked from 'marked';
import { makeSlugProcessor } from '../../utils/slug';
+import { highlight } from '../../utils/highlight';
import { SLUG_PRESERVE_UNICODE } from '../../../config';
-import PrismJS from 'prismjs';
-import 'prismjs/components/prism-bash';
const makeSlug = makeSlugProcessor(SLUG_PRESERVE_UNICODE);
@@ -32,16 +31,7 @@ export default function get_posts() {
renderer.link = link_renderer;
- renderer.code = (source, lang) => {
- const plang = langs[lang];
- const highlighted = PrismJS.highlight(
- source,
- PrismJS.languages[plang],
- lang,
- );
-
- return `
${highlighted}
`;
- };
+ renderer.code = highlight;
renderer.heading = (text, level, rawtext) => {
const fragment = makeSlug(rawtext);
diff --git a/site/src/routes/docs/_sections.js b/site/src/routes/docs/_sections.js
index 35edc16cfa..bb081a050b 100644
--- a/site/src/routes/docs/_sections.js
+++ b/site/src/routes/docs/_sections.js
@@ -1,11 +1,10 @@
import fs from 'fs';
import path from 'path';
import { SLUG_PRESERVE_UNICODE, SLUG_SEPARATOR } from '../../../config';
-import { extract_frontmatter, extract_metadata, langs, link_renderer } from '@sveltejs/site-kit/utils/markdown.js';
+import { extract_frontmatter, extract_metadata, link_renderer } from '@sveltejs/site-kit/utils/markdown.js';
import { make_session_slug_processor } from '@sveltejs/site-kit/utils/slug';
+import { highlight } from '../../utils/highlight';
import marked from 'marked';
-import PrismJS from 'prismjs';
-import 'prismjs/components/prism-bash';
const blockTypes = [
'blockquote',
@@ -73,14 +72,7 @@ export default function() {
if (meta && meta.hidden) return '';
- const plang = langs[lang];
- const highlighted = PrismJS.highlight(
- source,
- PrismJS.languages[plang],
- lang
- );
-
- const html = `
`;
+}
diff --git a/site/static/favicon.png b/site/static/favicon.png
index b8b26e3044..ddbebf3b4f 100644
Binary files a/site/static/favicon.png and b/site/static/favicon.png differ
diff --git a/site/static/images/twitter-card.png b/site/static/images/twitter-card.png
index 5e0a55e570..1e8271f655 100644
Binary files a/site/static/images/twitter-card.png and b/site/static/images/twitter-card.png differ
diff --git a/site/static/logo-mask.svg b/site/static/logo-mask.svg
deleted file mode 100644
index d7919a61ab..0000000000
--- a/site/static/logo-mask.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
\ No newline at end of file
diff --git a/site/static/organisations/bekchy.png b/site/static/organisations/bekchy.png
index 737e3f95d9..011553cb42 100644
Binary files a/site/static/organisations/bekchy.png and b/site/static/organisations/bekchy.png differ
diff --git a/site/static/organisations/dextra.png b/site/static/organisations/dextra.png
index a550304adf..725f9bd511 100644
Binary files a/site/static/organisations/dextra.png and b/site/static/organisations/dextra.png differ
diff --git a/site/static/organisations/entriwise.png b/site/static/organisations/entriwise.png
index f8e3e80e91..be305e1300 100644
Binary files a/site/static/organisations/entriwise.png and b/site/static/organisations/entriwise.png differ
diff --git a/site/static/organisations/entur.svg b/site/static/organisations/entur.svg
new file mode 100644
index 0000000000..98bb5cc937
--- /dev/null
+++ b/site/static/organisations/entur.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/site/static/organisations/fusioncharts.svg b/site/static/organisations/fusioncharts.svg
new file mode 100644
index 0000000000..6712115de8
--- /dev/null
+++ b/site/static/organisations/fusioncharts.svg
@@ -0,0 +1,18 @@
+
+
\ No newline at end of file
diff --git a/site/static/organisations/jacoux.png b/site/static/organisations/jacoux.png
new file mode 100644
index 0000000000..6d3f52017c
Binary files /dev/null and b/site/static/organisations/jacoux.png differ
diff --git a/site/static/organisations/ofof.png b/site/static/organisations/ofof.png
new file mode 100644
index 0000000000..5ecde9b0fd
Binary files /dev/null and b/site/static/organisations/ofof.png differ
diff --git a/site/static/organisations/pankod.svg b/site/static/organisations/pankod.svg
new file mode 100644
index 0000000000..34578a7739
--- /dev/null
+++ b/site/static/organisations/pankod.svg
@@ -0,0 +1,33 @@
+
+
\ No newline at end of file
diff --git a/site/static/organisations/sqltribe.svg b/site/static/organisations/sqltribe.svg
new file mode 100644
index 0000000000..2a51dfc0a7
--- /dev/null
+++ b/site/static/organisations/sqltribe.svg
@@ -0,0 +1,106 @@
+
+
\ No newline at end of file
diff --git a/site/static/organisations/tokopedia.2x.png b/site/static/organisations/tokopedia.2x.png
deleted file mode 100644
index 5ba45fdb2b..0000000000
Binary files a/site/static/organisations/tokopedia.2x.png and /dev/null differ
diff --git a/site/static/organisations/tokopedia.3x.png b/site/static/organisations/tokopedia.3x.png
deleted file mode 100644
index 52b34a0b91..0000000000
Binary files a/site/static/organisations/tokopedia.3x.png and /dev/null differ
diff --git a/site/static/organisations/tokopedia.png b/site/static/organisations/tokopedia.png
deleted file mode 100644
index 16fef7fe63..0000000000
Binary files a/site/static/organisations/tokopedia.png and /dev/null differ
diff --git a/site/static/organisations/tokopedia.svg b/site/static/organisations/tokopedia.svg
new file mode 100644
index 0000000000..d284f05d7b
--- /dev/null
+++ b/site/static/organisations/tokopedia.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/site/static/tutorial/kitten.png b/site/static/tutorial/kitten.png
index 6ef37691bc..6b0cedc945 100644
Binary files a/site/static/tutorial/kitten.png and b/site/static/tutorial/kitten.png differ
diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts
index fa8665ffde..2d696ad306 100644
--- a/src/compiler/compile/Component.ts
+++ b/src/compiler/compile/Component.ts
@@ -1,9 +1,7 @@
-import MagicString, { Bundle } from 'magic-string';
-// @ts-ignore
import { walk, childKeys } from 'estree-walker';
import { getLocator } from 'locate-character';
import Stats from '../Stats';
-import { globals, reserved } from '../utils/names';
+import { globals, reserved, is_valid } from '../utils/names';
import { namespaces, valid_namespaces } from '../utils/namespaces';
import create_module from './create_module';
import {
@@ -16,20 +14,20 @@ import Stylesheet from './css/Stylesheet';
import { test } from '../config';
import Fragment from './nodes/Fragment';
import internal_exports from './internal_exports';
-import { Node, Ast, CompileOptions, Var, Warning } from '../interfaces';
+import { Ast, CompileOptions, Var, Warning } from '../interfaces';
import error from '../utils/error';
import get_code_frame from '../utils/get_code_frame';
import flatten_reference from './utils/flatten_reference';
+import is_used_as_reference from './utils/is_used_as_reference';
import is_reference from 'is-reference';
import TemplateScope from './nodes/shared/TemplateScope';
import fuzzymatch from '../utils/fuzzymatch';
-import { remove_indentation, add_indentation } from '../utils/indentation';
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 { Node, ImportDeclaration, Identifier, Program, ExpressionStatement, AssignmentExpression, Literal } from 'estree';
import add_to_set from './utils/add_to_set';
import check_graph_for_cycles from './utils/check_graph_for_cycles';
+import { print, x, b } from 'code-red';
interface ComponentOptions {
namespace?: string;
@@ -46,40 +44,6 @@ childKeys.EachBlock = childKeys.IfBlock = ['children', 'else'];
childKeys.Attribute = ['value'];
childKeys.ExportNamedDeclaration = ['declaration', 'specifiers'];
-function remove_node(
- code: MagicString,
- start: number,
- end: number,
- body: Node,
- node: Node
-) {
- const i = body.indexOf(node);
- if (i === -1) throw new Error('node not in list');
-
- let a;
- let b;
-
- if (body.length === 1) {
- // remove everything, leave {}
- a = start;
- b = end;
- } else if (i === 0) {
- // remove everything before second node, including comments
- a = start;
- while (/\s/.test(code.original[a])) a += 1;
-
- b = body[i].end;
- while (/[\s,]/.test(code.original[b])) b += 1;
- } else {
- // remove the end of the previous node to the end of this one
- a = body[i - 1].end;
- b = node.end;
- }
-
- code.remove(a, b);
- return;
-}
-
export default class Component {
stats: Stats;
warnings: Warning[];
@@ -87,9 +51,9 @@ export default class Component {
ignore_stack: Array> = [];
ast: Ast;
+ original_ast: Ast;
source: string;
- code: MagicString;
- name: string;
+ name: Identifier;
compile_options: CompileOptions;
fragment: Fragment;
module_scope: Scope;
@@ -104,14 +68,12 @@ export default class Component {
vars: Var[] = [];
var_lookup: Map = new Map();
- imports: Node[] = [];
- module_javascript: string;
- javascript: string;
+ imports: ImportDeclaration[] = [];
hoistable_nodes: Set = new Set();
node_for_declaration: Map = new Map();
- partly_hoisted: string[] = [];
- fully_hoisted: string[] = [];
+ partly_hoisted: Array<(Node | Node[])> = [];
+ fully_hoisted: Array<(Node | Node[])> = [];
reactive_declarations: Array<{
assignees: Set;
dependencies: Set;
@@ -121,26 +83,17 @@ export default class Component {
reactive_declaration_nodes: Set = new Set();
has_reactive_assignments = false;
injected_reactive_declaration_vars: Set = new Set();
- helpers: Map = new Map();
- globals: Map = new Map();
+ helpers: Map = new Map();
+ globals: Map = new Map();
indirect_dependencies: Map> = new Map();
file: string;
locate: (c: number) => { line: number; column: number };
- // TODO this does the same as component.locate! remove one or the other
- locator: (
- search: number,
- startIndex?: number
- ) => {
- line: number;
- column: number;
- };
-
stylesheet: Stylesheet;
- aliases: Map = new Map();
+ aliases: Map = new Map();
used_names: Set = new Set();
globally_used_names: Set = new Set();
@@ -155,7 +108,7 @@ export default class Component {
stats: Stats,
warnings: Warning[]
) {
- this.name = name;
+ this.name = { type: 'Identifier', name };
this.stats = stats;
this.warnings = warnings;
@@ -163,6 +116,15 @@ export default class Component {
this.source = source;
this.compile_options = compile_options;
+ // the instance JS gets mutated, so we park
+ // a copy here for later. TODO this feels gross
+ this.original_ast = {
+ html: ast.html,
+ css: ast.css,
+ instance: ast.instance && JSON.parse(JSON.stringify(ast.instance)),
+ module: ast.module
+ };
+
this.file =
compile_options.filename &&
(typeof process !== 'undefined'
@@ -170,9 +132,7 @@ export default class Component {
.replace(process.cwd(), '')
.replace(/^[/\\]/, '')
: compile_options.filename);
- this.locate = getLocator(this.source);
-
- this.code = new MagicString(source);
+ this.locate = getLocator(this.source, { offsetLine: 1 });
// styles
this.stylesheet = new Stylesheet(
@@ -206,15 +166,16 @@ export default class Component {
}
this.tag = this.component_options.tag || compile_options.tag;
} else {
- this.tag = this.name;
+ this.tag = this.name.name;
}
- this.walk_module_js();
+ this.walk_module_js_pre_template();
this.walk_instance_js_pre_template();
this.fragment = new Fragment(this, ast.html);
this.name = this.get_unique_name(name);
+ this.walk_module_js_post_template();
this.walk_instance_js_post_template();
if (!compile_options.customElement) this.stylesheet.reify();
@@ -257,15 +218,6 @@ export default class Component {
}
}
- add_sourcemap_locations(node: Node) {
- walk(node, {
- enter: (node: Node) => {
- this.code.addSourcemapLocation(node.start);
- this.code.addSourcemapLocation(node.end);
- },
- });
- }
-
alias(name: string) {
if (!this.aliases.has(name)) {
this.aliases.set(name, this.get_unique_name(name));
@@ -274,19 +226,13 @@ export default class Component {
return this.aliases.get(name);
}
- helper(name: string) {
- const alias = this.alias(name);
- this.helpers.set(name, alias);
- return alias;
- }
-
global(name: string) {
const alias = this.alias(name);
this.globals.set(name, alias);
return alias;
}
- generate(result: string) {
+ generate(result?: Node[]) {
let js = null;
let css = null;
@@ -294,55 +240,65 @@ export default class Component {
const { compile_options, name } = this;
const { format = 'esm' } = compile_options;
- const banner = `/* ${
- this.file ? `${this.file} ` : ``
- }generated by Svelte v${'__VERSION__'} */`;
-
- result = result
- .replace(/__svelte:self__/g, this.name)
- .replace(
- compile_options.generate === 'ssr'
- ? /(@+|#+)(\w*(?:-\w*)?)/g
- : /(@+)(\w*(?:-\w*)?)/g,
- (_match: string, sigil: string, name: string) => {
- if (sigil === '@') {
- if (name[0] === '_') {
- return this.global(name.slice(1));
- }
+ const banner = `${this.file ? `${this.file} ` : ``}generated by Svelte v${'__VERSION__'}`;
- if (!internal_exports.has(name)) {
- throw new Error(
- `compiler error: this shouldn't happen! generated code is trying to use inexistent internal '${name}'`
- );
+ const program: any = { type: 'Program', body: result };
+
+ walk(program, {
+ enter: (node, parent, key) => {
+ if (node.type === 'Identifier') {
+ if (node.name[0] === '@') {
+ if (node.name[1] === '_') {
+ const alias = this.global(node.name.slice(2));
+ node.name = alias.name;
+ } else {
+ let name = node.name.slice(1);
+
+ if (compile_options.dev) {
+ if (internal_exports.has(`${name}_dev`)) {
+ name += '_dev';
+ } else if (internal_exports.has(`${name}Dev`)) {
+ name += 'Dev';
+ }
+ }
+
+ const alias = this.alias(name);
+ this.helpers.set(name, alias);
+ node.name = alias.name;
}
+ }
- if (compile_options.dev) {
- if (internal_exports.has(`${name}_dev`)) name = `${name}_dev`;
- else if (internal_exports.has(`${name}Dev`))
- name = `${name}Dev`;
+ else if (node.name[0] !== '#' && !is_valid(node.name)) {
+ // this hack allows x`foo.${bar}` where bar could be invalid
+ const literal: Literal = { type: 'Literal', value: node.name };
+
+ if (parent.type === 'Property' && key === 'key') {
+ parent.key = literal;
}
- return this.helper(name);
+ else if (parent.type === 'MemberExpression' && key === 'property') {
+ parent.property = literal;
+ parent.computed = true;
+ }
}
-
- return sigil.slice(1) + name;
}
- );
+ }
+ });
const referenced_globals = Array.from(
this.globals,
- ([name, alias]) => name !== alias && { name, alias }
+ ([name, alias]) => name !== alias.name && { name, alias }
).filter(Boolean);
if (referenced_globals.length) {
- this.helper('globals');
+ this.helpers.set('globals', this.alias('globals'));
}
const imported_helpers = Array.from(this.helpers, ([name, alias]) => ({
name,
alias,
}));
- const module = create_module(
- result,
+ create_module(
+ program,
format,
name,
banner,
@@ -355,67 +311,30 @@ export default class Component {
.map(variable => ({
name: variable.name,
as: variable.export_name,
- })),
- this.source
+ }))
);
- const parts = module.split('✂]');
- const final_chunk = parts.pop();
-
- const compiled = new Bundle({ separator: '' });
-
- function add_string(str: string) {
- compiled.addSource({
- content: new MagicString(str),
- });
- }
-
- const { filename } = compile_options;
-
- // special case — the source file doesn't actually get used anywhere. we need
- // to add an empty file to populate map.sources and map.sourcesContent
- if (!parts.length) {
- compiled.addSource({
- filename,
- content: new MagicString(this.source).remove(0, this.source.length),
- });
- }
-
- const pattern = /\[✂(\d+)-(\d+)$/;
-
- parts.forEach((str: string) => {
- const chunk = str.replace(pattern, '');
- if (chunk) add_string(chunk);
-
- const match = pattern.exec(str);
-
- const snippet = this.code.snip(+match[1], +match[2]);
-
- compiled.addSource({
- filename,
- content: snippet,
- });
- });
-
- add_string(final_chunk);
-
css = compile_options.customElement
? { code: null, map: null }
: this.stylesheet.render(compile_options.cssOutputFilename, true);
- js = {
- code: compiled.toString(),
- map: compiled.generateMap({
- includeContent: true,
- file: compile_options.outputFilename,
- }),
- };
+ js = print(program, {
+ sourceMapSource: compile_options.filename
+ });
+
+ js.map.sources = [
+ compile_options.filename ? get_relative_path(compile_options.outputFilename || '', compile_options.filename) : null
+ ];
+
+ js.map.sourcesContent = [
+ this.source
+ ];
}
return {
js,
css,
- ast: this.ast,
+ ast: this.original_ast,
warnings: this.warnings,
vars: this.vars
.filter(v => !v.global && !v.internal)
@@ -428,12 +347,13 @@ export default class Component {
reassigned: v.reassigned || false,
referenced: v.referenced || false,
writable: v.writable || false,
+ referenced_from_script: v.referenced_from_script || false,
})),
stats: this.stats.render(),
};
}
- get_unique_name(name: string) {
+ get_unique_name(name: string): Identifier {
if (test) name = `${name}$`;
let alias = name;
for (
@@ -445,7 +365,7 @@ export default class Component {
alias = `${name}_${i++}`
);
this.used_names.add(alias);
- return alias;
+ return { type: 'Identifier', name: alias };
}
get_unique_name_maker() {
@@ -459,7 +379,7 @@ export default class Component {
internal_exports.forEach(add);
this.var_lookup.forEach((_value, key) => add(key));
- return (name: string) => {
+ return (name: string): Identifier => {
if (test) name = `${name}$`;
let alias = name;
for (
@@ -469,7 +389,11 @@ export default class Component {
);
local_used_names.add(alias);
this.globally_used_names.add(alias);
- return alias;
+
+ return {
+ type: 'Identifier',
+ name: alias
+ };
};
}
@@ -507,12 +431,8 @@ export default class Component {
return;
}
- if (!this.locator) {
- this.locator = getLocator(this.source, { offsetLine: 1 });
- }
-
- const start = this.locator(pos.start);
- const end = this.locator(pos.end);
+ const start = this.locate(pos.start);
+ const end = this.locate(pos.end);
const frame = get_code_frame(this.source, start.line - 1, start.column);
@@ -525,74 +445,77 @@ export default class Component {
pos: pos.start,
filename: this.compile_options.filename,
toString: () =>
- `${warning.message} (${start.line + 1}:${start.column})\n${frame}`,
+ `${warning.message} (${start.line}:${start.column})\n${frame}`,
});
}
- extract_imports(content) {
- const { code } = this;
-
- content.body.forEach(node => {
- if (node.type === 'ImportDeclaration') {
- // imports need to be hoisted out of the IIFE
- remove_node(code, content.start, content.end, content.body, node);
- this.imports.push(node);
- }
- });
+ extract_imports(node) {
+ this.imports.push(node);
}
- extract_exports(content) {
- const { code } = this;
+ extract_exports(node) {
+ if (node.type === 'ExportDefaultDeclaration') {
+ this.error(node, {
+ code: `default-export`,
+ message: `A component cannot have a default export`,
+ });
+ }
- content.body.forEach(node => {
- if (node.type === 'ExportDefaultDeclaration') {
+ if (node.type === 'ExportNamedDeclaration') {
+ if (node.source) {
this.error(node, {
- code: `default-export`,
- message: `A component cannot have a default export`,
+ code: `not-implemented`,
+ message: `A component currently cannot have an export ... from`,
});
}
-
- if (node.type === 'ExportNamedDeclaration') {
- if (node.source) {
- this.error(node, {
- code: `not-implemented`,
- message: `A component currently cannot have an export ... from`,
+ if (node.declaration) {
+ if (node.declaration.type === 'VariableDeclaration') {
+ node.declaration.declarations.forEach(declarator => {
+ extract_names(declarator.id).forEach(name => {
+ const variable = this.var_lookup.get(name);
+ variable.export_name = name;
+ if (variable.writable && !(variable.referenced || variable.referenced_from_script)) {
+ this.warn(declarator, {
+ code: `unused-export-let`,
+ message: `${this.name.name} has unused export property '${name}'. If it is for external reference only, please consider using \`export const '${name}'\``
+ });
+ }
+ });
});
+ } else {
+ const { name } = node.declaration.id;
+
+ const variable = this.var_lookup.get(name);
+ variable.export_name = name;
}
- if (node.declaration) {
- if (node.declaration.type === 'VariableDeclaration') {
- node.declaration.declarations.forEach(declarator => {
- extract_names(declarator.id).forEach(name => {
- const variable = this.var_lookup.get(name);
- variable.export_name = name;
- });
- });
- } else {
- const { name } = node.declaration.id;
- const variable = this.var_lookup.get(name);
- variable.export_name = name;
- }
+ return node.declaration;
+ } else {
+ node.specifiers.forEach(specifier => {
+ const variable = this.var_lookup.get(specifier.local.name);
- code.remove(node.start, node.declaration.start);
- } else {
- remove_node(code, content.start, content.end, content.body, node);
- node.specifiers.forEach(specifier => {
- const variable = this.var_lookup.get(specifier.local.name);
+ if (variable) {
+ variable.export_name = specifier.exported.name;
- if (variable) {
- variable.export_name = specifier.exported.name;
- } else {
- // TODO what happens with `export { Math }` or some other global?
+ if (variable.writable && !(variable.referenced || variable.referenced_from_script)) {
+ this.warn(specifier, {
+ code: `unused-export-let`,
+ message: `${this.name.name} has unused export property '${specifier.exported.name}'. If it is for external reference only, please consider using \`export const '${specifier.exported.name}'\``
+ });
}
- });
- }
+ }
+ });
+
+ return null;
}
- });
+ }
}
extract_javascript(script) {
- const nodes_to_include = script.content.body.filter(node => {
+ if (!script) return null;
+
+ return script.content.body.filter(node => {
+ if (!node) return false;
if (this.hoistable_nodes.has(node)) return false;
if (this.reactive_declaration_nodes.has(node)) return false;
if (node.type === 'ImportDeclaration') return false;
@@ -600,39 +523,9 @@ export default class Component {
return false;
return true;
});
-
- if (nodes_to_include.length === 0) return null;
-
- let a = script.content.start;
- while (/\s/.test(this.source[a])) a += 1;
-
- let b = a;
-
- let result = '';
-
- script.content.body.forEach(node => {
- if (
- this.hoistable_nodes.has(node) ||
- this.reactive_declaration_nodes.has(node)
- ) {
- if (a !== b) result += `[✂${a}-${b}✂]`;
- a = node.end;
- }
-
- b = node.end;
- });
-
- // while (/\s/.test(this.source[a - 1])) a -= 1;
-
- b = script.content.end;
- while (/\s/.test(this.source[b - 1])) b -= 1;
-
- if (a < b) result += `[✂${a}-${b}✂]`;
-
- return result || null;
}
- walk_module_js() {
+ walk_module_js_pre_template() {
const component = this;
const script = this.ast.module;
if (!script) return;
@@ -640,7 +533,7 @@ export default class Component {
walk(script.content, {
enter(node) {
if (node.type === 'LabeledStatement' && node.label.name === '$') {
- component.warn(node, {
+ component.warn(node as any, {
code: 'module-script-reactive-declaration',
message: '$: has no effect in a module script',
});
@@ -648,30 +541,30 @@ export default class Component {
},
});
- this.add_sourcemap_locations(script.content);
-
const { scope, globals } = create_scopes(script.content);
this.module_scope = scope;
scope.declarations.forEach((node, name) => {
if (name[0] === '$') {
- this.error(node, {
+ this.error(node as any, {
code: 'illegal-declaration',
message: `The $ prefix is reserved, and cannot be used for variable and import names`,
});
}
+ const writable = node.type === 'VariableDeclaration' && (node.kind === 'var' || node.kind === 'let');
+
this.add_var({
name,
module: true,
hoistable: true,
- writable: node.kind === 'var' || node.kind === 'let',
+ writable
});
});
globals.forEach((node, name) => {
if (name[0] === '$') {
- this.error(node, {
+ this.error(node as any, {
code: 'illegal-subscription',
message: `Cannot reference store value inside ';
-function get_context(parser: Parser, attributes: Node[], start: number) {
+function get_context(parser: Parser, attributes: any[], start: number): string {
const context = attributes.find(attribute => attribute.name === 'context');
if (!context) return 'default';
@@ -28,7 +29,7 @@ function get_context(parser: Parser, attributes: Node[], start: number) {
return value;
}
-export default function read_script(parser: Parser, start: number, attributes: Node[]) {
+export default function read_script(parser: Parser, start: number, attributes: Node[]): Script {
const script_start = parser.index;
const script_end = parser.template.indexOf(script_closing_tag, script_start);
@@ -41,16 +42,19 @@ export default function read_script(parser: Parser, start: number, attributes: N
repeat(' ', script_start) + parser.template.slice(script_start, script_end);
parser.index = script_end + script_closing_tag.length;
- let ast;
+ let ast: Program;
try {
- ast = acorn.parse(source);
+ ast = acorn.parse(source) as any as Program;
} catch (err) {
parser.acorn_error(err);
}
- ast.start = script_start;
+ // TODO is this necessary?
+ (ast as any).start = script_start;
+
return {
+ type: 'Script',
start,
end: parser.index,
context: get_context(parser, attributes, start),
diff --git a/src/compiler/parse/read/style.ts b/src/compiler/parse/read/style.ts
index a14356e05b..dd9d1286f2 100644
--- a/src/compiler/parse/read/style.ts
+++ b/src/compiler/parse/read/style.ts
@@ -1,9 +1,10 @@
import parse from 'css-tree/lib/parser/index.js';
import { walk } from 'estree-walker';
import { Parser } from '../index';
-import { Node } from '../../interfaces';
+import { Node } from 'estree';
+import { Style } from '../../interfaces';
-export default function read_style(parser: Parser, start: number, attributes: Node[]) {
+export default function read_style(parser: Parser, start: number, attributes: Node[]): Style {
const content_start = parser.index;
const styles = parser.read_until(/<\/style>/);
const content_end = parser.index;
@@ -30,7 +31,7 @@ export default function read_style(parser: Parser, start: number, attributes: No
// tidy up AST
walk(ast, {
- enter: (node: Node) => {
+ enter: (node: any) => { // `any` because this isn't an ESTree node
// replace `ref:a` nodes
if (node.type === 'Selector') {
for (let i = 0; i < node.children.length; i += 1) {
@@ -46,6 +47,13 @@ export default function read_style(parser: Parser, start: number, attributes: No
}
}
+ if (node.type === 'Declaration' && node.value.type === 'Value' && node.value.children.length === 0) {
+ parser.error({
+ code: `invalid-declaration`,
+ message: `Declaration cannot be empty`
+ }, node.start);
+ }
+
if (node.loc) {
node.start = node.loc.start.offset;
node.end = node.loc.end.offset;
@@ -58,6 +66,7 @@ export default function read_style(parser: Parser, start: number, attributes: No
const end = parser.index;
return {
+ type: 'Style',
start,
end,
attributes,
@@ -65,12 +74,12 @@ export default function read_style(parser: Parser, start: number, attributes: No
content: {
start: content_start,
end: content_end,
- styles,
- },
+ styles
+ }
};
}
-function is_ref_selector(a: Node, b: Node) {
+function is_ref_selector(a: any, b: any) { // TODO add CSS node types
if (!b) return false;
return (
diff --git a/src/compiler/parse/state/mustache.ts b/src/compiler/parse/state/mustache.ts
index 8de35cee48..140e722b10 100644
--- a/src/compiler/parse/state/mustache.ts
+++ b/src/compiler/parse/state/mustache.ts
@@ -4,9 +4,9 @@ import { closing_tag_omitted } from '../utils/html';
import { whitespace } from '../../utils/patterns';
import { trim_start, trim_end } from '../../utils/trim';
import { Parser } from '../index';
-import { Node } from '../../interfaces';
+import { TemplateNode } from '../../interfaces';
-function trim_whitespace(block: Node, trim_before: boolean, trim_after: boolean) {
+function trim_whitespace(block: TemplateNode, trim_before: boolean, trim_after: boolean) {
if (!block.children || block.children.length === 0) return; // AwaitBlock
const first_child = block.children[0];
@@ -160,57 +160,47 @@ export default function mustache(parser: Parser) {
parser.stack.push(block.else);
}
- } else if (parser.eat(':then')) {
- // TODO DRY out this and the next section
- const pending_block = parser.current();
- if (pending_block.type === 'PendingBlock') {
- pending_block.end = start;
- parser.stack.pop();
- const await_block = parser.current();
+ } else if (parser.match(':then') || parser.match(':catch')) {
+ const block = parser.current();
+ const is_then = parser.eat(':then') || !parser.eat(':catch');
- if (!parser.eat('}')) {
- parser.require_whitespace();
- await_block.value = parser.read_identifier();
- parser.allow_whitespace();
- parser.eat('}', true);
+ if (is_then) {
+ if (block.type !== 'PendingBlock') {
+ parser.error({
+ code: `invalid-then-placement`,
+ message: 'Cannot have an {:then} block outside an {#await ...} block'
+ });
}
-
- const then_block: Node = {
- start,
- end: null,
- type: 'ThenBlock',
- children: [],
- skip: false
- };
-
- await_block.then = then_block;
- parser.stack.push(then_block);
- }
- } else if (parser.eat(':catch')) {
- const then_block = parser.current();
- if (then_block.type === 'ThenBlock') {
- then_block.end = start;
- parser.stack.pop();
- const await_block = parser.current();
-
- if (!parser.eat('}')) {
- parser.require_whitespace();
- await_block.error = parser.read_identifier();
- parser.allow_whitespace();
- parser.eat('}', true);
+ } else {
+ if (block.type !== 'ThenBlock' && block.type !== 'PendingBlock') {
+ parser.error({
+ code: `invalid-catch-placement`,
+ message: 'Cannot have an {:catch} block outside an {#await ...} block'
+ });
}
+ }
- const catch_block: Node = {
- start,
- end: null,
- type: 'CatchBlock',
- children: [],
- skip: false
- };
+ block.end = start;
+ parser.stack.pop();
+ const await_block = parser.current();
- await_block.catch = catch_block;
- parser.stack.push(catch_block);
+ if (!parser.eat('}')) {
+ parser.require_whitespace();
+ await_block[is_then ? 'value': 'error'] = parser.read_identifier();
+ parser.allow_whitespace();
+ parser.eat('}', true);
}
+
+ const new_block: TemplateNode = {
+ start,
+ end: null,
+ type: is_then ? 'ThenBlock': 'CatchBlock',
+ children: [],
+ skip: false
+ };
+
+ await_block[is_then ? 'then' : 'catch'] = new_block;
+ parser.stack.push(new_block);
} else if (parser.eat('#')) {
// {#if foo}, {#each foo} or {#await foo}
let type;
@@ -232,7 +222,7 @@ export default function mustache(parser: Parser) {
const expression = read_expression(parser);
- const block: Node = type === 'AwaitBlock' ?
+ const block: TemplateNode = type === 'AwaitBlock' ?
{
start,
end: null,
diff --git a/src/compiler/parse/state/tag.ts b/src/compiler/parse/state/tag.ts
index 21a7b3caea..c7e761afc2 100644
--- a/src/compiler/parse/state/tag.ts
+++ b/src/compiler/parse/state/tag.ts
@@ -4,7 +4,7 @@ import read_style from '../read/style';
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';
+import { Directive, DirectiveType, TemplateNode, Text } from '../../interfaces';
import fuzzymatch from '../../utils/fuzzymatch';
import list from '../../utils/list';
@@ -37,10 +37,8 @@ const specials = new Map([
],
]);
-// eslint-disable-next-line no-useless-escape
-const SELF = /^svelte:self(?=[\s\/>])/;
-// eslint-disable-next-line no-useless-escape
-const COMPONENT = /^svelte:component(?=[\s\/>])/;
+const SELF = /^svelte:self(?=[\s/>])/;
+const COMPONENT = /^svelte:component(?=[\s/>])/;
function parent_is_head(stack) {
let i = stack.length;
@@ -83,7 +81,7 @@ export default function tag(parser: Parser) {
parser.current().children.length
) {
parser.error({
- code: `invalid-${name.slice(7)}-content`,
+ code: `invalid-${slug}-content`,
message: `<${name}> cannot have children`
}, parser.current().children[0].start);
}
@@ -112,7 +110,7 @@ export default function tag(parser: Parser) {
: name === 'title' && parent_is_head(parser.stack) ? 'Title'
: name === 'slot' && !parser.customElement ? 'Slot' : 'Element';
- const element: Node = {
+ const element: TemplateNode = {
start,
end: null, // filled in later
type,
@@ -406,7 +404,7 @@ function read_attribute(parser: Parser, unique_names: Set) {
end: directive.end,
type: 'Identifier',
name: directive.name
- };
+ } as any;
}
return directive;
@@ -447,7 +445,7 @@ function read_attribute_value(parser: Parser) {
return value;
}
-function read_sequence(parser: Parser, done: () => boolean): Node[] {
+function read_sequence(parser: Parser, done: () => boolean): TemplateNode[] {
let current_chunk: Text = {
start: parser.index,
end: null,
@@ -464,7 +462,7 @@ function read_sequence(parser: Parser, done: () => boolean): Node[] {
}
}
- const chunks: Node[] = [];
+ const chunks: TemplateNode[] = [];
while (parser.index < parser.template.length) {
const index = parser.index;
diff --git a/src/compiler/preprocess/index.ts b/src/compiler/preprocess/index.ts
index 2faa3b97f2..4549517fbd 100644
--- a/src/compiler/preprocess/index.ts
+++ b/src/compiler/preprocess/index.ts
@@ -94,8 +94,12 @@ export default async function preprocess(
for (const fn of script) {
source = await replace_async(
source,
- /
+
+
+
+
+
+
+
+
diff --git a/test/css/samples/omit-scoping-attribute-global-descendants/expected.css b/test/css/samples/omit-scoping-attribute-global-descendants/expected.css
new file mode 100644
index 0000000000..c1fd7da897
--- /dev/null
+++ b/test/css/samples/omit-scoping-attribute-global-descendants/expected.css
@@ -0,0 +1 @@
+html body .root.svelte-xyz p.svelte-xyz{color:red}
\ No newline at end of file
diff --git a/test/css/samples/omit-scoping-attribute-global-descendants/expected.html b/test/css/samples/omit-scoping-attribute-global-descendants/expected.html
new file mode 100644
index 0000000000..3750091bc7
--- /dev/null
+++ b/test/css/samples/omit-scoping-attribute-global-descendants/expected.html
@@ -0,0 +1,5 @@
+
+
+
hello
+
+
\ No newline at end of file
diff --git a/test/css/samples/omit-scoping-attribute-global-descendants/input.svelte b/test/css/samples/omit-scoping-attribute-global-descendants/input.svelte
new file mode 100644
index 0000000000..7c9782ebad
--- /dev/null
+++ b/test/css/samples/omit-scoping-attribute-global-descendants/input.svelte
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
hello
+
+
diff --git a/test/css/samples/omit-scoping-attribute-multiple-descendants/expected.css b/test/css/samples/omit-scoping-attribute-multiple-descendants/expected.css
new file mode 100644
index 0000000000..5452f68073
--- /dev/null
+++ b/test/css/samples/omit-scoping-attribute-multiple-descendants/expected.css
@@ -0,0 +1 @@
+.root.svelte-xyz p.svelte-xyz{color:red}
\ No newline at end of file
diff --git a/test/css/samples/omit-scoping-attribute-multiple-descendants/expected.html b/test/css/samples/omit-scoping-attribute-multiple-descendants/expected.html
new file mode 100644
index 0000000000..3750091bc7
--- /dev/null
+++ b/test/css/samples/omit-scoping-attribute-multiple-descendants/expected.html
@@ -0,0 +1,5 @@
+
+
+
hello
+
+
\ No newline at end of file
diff --git a/test/css/samples/omit-scoping-attribute-multiple-descendants/input.svelte b/test/css/samples/omit-scoping-attribute-multiple-descendants/input.svelte
new file mode 100644
index 0000000000..dc77a6c794
--- /dev/null
+++ b/test/css/samples/omit-scoping-attribute-multiple-descendants/input.svelte
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
hello
+
+
diff --git a/test/css/samples/undefined-with-scope/expected.css b/test/css/samples/undefined-with-scope/expected.css
new file mode 100644
index 0000000000..5d8d69ac33
--- /dev/null
+++ b/test/css/samples/undefined-with-scope/expected.css
@@ -0,0 +1 @@
+p.svelte-xyz{color:red}
\ No newline at end of file
diff --git a/test/css/samples/undefined-with-scope/expected.html b/test/css/samples/undefined-with-scope/expected.html
new file mode 100644
index 0000000000..ddb9429bc8
--- /dev/null
+++ b/test/css/samples/undefined-with-scope/expected.html
@@ -0,0 +1 @@
+
Foo
\ No newline at end of file
diff --git a/test/css/samples/undefined-with-scope/input.svelte b/test/css/samples/undefined-with-scope/input.svelte
new file mode 100644
index 0000000000..c68fb40dea
--- /dev/null
+++ b/test/css/samples/undefined-with-scope/input.svelte
@@ -0,0 +1,3 @@
+
+
+
Foo
\ No newline at end of file
diff --git a/test/css/samples/unused-selector-string-concat/_config.js b/test/css/samples/unused-selector-string-concat/_config.js
new file mode 100644
index 0000000000..81318fd3ac
--- /dev/null
+++ b/test/css/samples/unused-selector-string-concat/_config.js
@@ -0,0 +1,143 @@
+export default {
+ warnings: [
+ {
+ code: 'css-unused-selector',
+ message: 'Unused CSS selector',
+ frame:
+ ` 9: `,
+ start: { line: 28, column: 2, character: 595 },
+ end: { line: 28, column: 9, character: 602 },
+ pos: 595,
+ },
+ ],
+};
diff --git a/test/css/samples/unused-selector-string-concat/expected.css b/test/css/samples/unused-selector-string-concat/expected.css
new file mode 100644
index 0000000000..756c2eecae
--- /dev/null
+++ b/test/css/samples/unused-selector-string-concat/expected.css
@@ -0,0 +1 @@
+.foo.svelte-xyz{color:red}.foocc.svelte-xyz{color:red}.aa.svelte-xyz{color:red}.bb.svelte-xyz{color:red}.cc.svelte-xyz{color:red}.dd.svelte-xyz{color:red}.aabar.svelte-xyz{color:red}.fooddbar.svelte-xyz{color:red}.baz.svelte-xyz{color:red}
\ No newline at end of file
diff --git a/test/css/samples/unused-selector-string-concat/input.svelte b/test/css/samples/unused-selector-string-concat/input.svelte
new file mode 100644
index 0000000000..0f69463e78
--- /dev/null
+++ b/test/css/samples/unused-selector-string-concat/input.svelte
@@ -0,0 +1,29 @@
+
+
+
+ some stuff
+
+
+
\ No newline at end of file
diff --git a/test/css/samples/unused-selector-ternary-bailed/_config.js b/test/css/samples/unused-selector-ternary-bailed/_config.js
new file mode 100644
index 0000000000..e5f82e4a85
--- /dev/null
+++ b/test/css/samples/unused-selector-ternary-bailed/_config.js
@@ -0,0 +1,3 @@
+export default {
+ warnings: [],
+};
diff --git a/test/css/samples/unused-selector-ternary-bailed/expected.css b/test/css/samples/unused-selector-ternary-bailed/expected.css
new file mode 100644
index 0000000000..042d33f3cc
--- /dev/null
+++ b/test/css/samples/unused-selector-ternary-bailed/expected.css
@@ -0,0 +1 @@
+.thing.svelte-xyz{color:blue}.active.svelte-xyz{color:blue}.thing.active.svelte-xyz{color:blue}.hover.svelte-xyz{color:blue}.hover.unused.svelte-xyz{color:blue}.unused.svelte-xyz{color:blue}
\ No newline at end of file
diff --git a/test/css/samples/unused-selector-ternary-bailed/input.svelte b/test/css/samples/unused-selector-ternary-bailed/input.svelte
new file mode 100644
index 0000000000..f9af44ec8b
--- /dev/null
+++ b/test/css/samples/unused-selector-ternary-bailed/input.svelte
@@ -0,0 +1,18 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/test/css/samples/weird-selectors/expected.css b/test/css/samples/weird-selectors/expected.css
new file mode 100644
index 0000000000..d4ead16751
--- /dev/null
+++ b/test/css/samples/weird-selectors/expected.css
@@ -0,0 +1 @@
+.-foo.svelte-xyz{color:red}[title='['].svelte-xyz{color:blue}
\ No newline at end of file
diff --git a/test/css/samples/weird-selectors/input.svelte b/test/css/samples/weird-selectors/input.svelte
new file mode 100644
index 0000000000..65db029bbd
--- /dev/null
+++ b/test/css/samples/weird-selectors/input.svelte
@@ -0,0 +1,12 @@
+
foo
+
+
bar
+
+
diff --git a/test/custom-elements/assert.js b/test/custom-elements/assert.js
index 79aba6c736..0edbd31124 100644
--- a/test/custom-elements/assert.js
+++ b/test/custom-elements/assert.js
@@ -1,3 +1,29 @@
+export function deepEqual(a, b, message) {
+ if (!is_equal(a, b)) {
+ throw new Error(message || `Expected ${JSON.stringify(a)} to equal ${JSON.stringify(b)}`);
+ }
+}
+
+function is_equal(a, b) {
+ if (a && typeof a === 'object') {
+ const is_array = Array.isArray(a);
+ if (Array.isArray(b) !== is_array) return false;
+
+ if (is_array) {
+ if (a.length !== b.length) return false;
+ return a.every((value, i) => is_equal(value, b[i]));
+ }
+
+ const a_keys = Object.keys(a).sort();
+ const b_keys = Object.keys(b).sort();
+ if (a_keys.join(',') !== b_keys.join(',')) return false;
+
+ return a_keys.every(key => is_equal(a[key], b[key]));
+ }
+
+ return a === b;
+}
+
export function equal(a, b, message) {
if (a != b) throw new Error(message || `Expected ${a} to equal ${b}`);
}
diff --git a/test/custom-elements/index.js b/test/custom-elements/index.js
index 9255d33c0e..5d2847bfab 100644
--- a/test/custom-elements/index.js
+++ b/test/custom-elements/index.js
@@ -14,7 +14,7 @@ const page = `