Merge branch 'sveltejs:master' into disable-video-warning

pull/7880/head
Carlos Ivanchuk 4 years ago committed by GitHub
commit 6e0b8b0ec4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,7 +1,47 @@
name: CI name: CI
on: [push, pull_request] on:
push:
branches: [ master ]
pull_request:
permissions:
contents: read # to fetch code (actions/checkout)
jobs: jobs:
Setup:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
cache: npm
- run: npm install
env:
SKIP_PREPARE: true
- run: npm run build
env:
PUBLISH: true
- name: Upload build assets
id: upload-artifact
uses: actions/upload-artifact@v3
with:
name: build-assets
path: |
index.*
compiler.*
ssr.*
action/
animate/
easing/
internal/
motion/
store/
transition/
types/
Tests: Tests:
needs: Setup
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
timeout-minutes: 15 timeout-minutes: 15
strategy: strategy:
@ -9,21 +49,28 @@ jobs:
node-version: [8, 10, 12, 14, 16] node-version: [8, 10, 12, 14, 16]
os: [ubuntu-latest, windows-latest, macOS-latest] os: [ubuntu-latest, windows-latest, macOS-latest]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v3
- uses: actions/setup-node@v2 - uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: npm cache: npm
- name: Download build assets
uses: actions/download-artifact@v3
id: download-artifact
with:
name: build-assets
- run: npm install - run: npm install
- run: npm test env:
SKIP_PREPARE: true
- run: npm run test:integration
env: env:
CI: true CI: true
Lint: Lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 5 timeout-minutes: 5
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v3
- uses: actions/setup-node@v2 - uses: actions/setup-node@v3
with: with:
cache: npm cache: npm
- run: 'npm i && npm run lint' - run: 'npm i && npm run lint'
@ -34,8 +81,11 @@ jobs:
matrix: matrix:
os: [ubuntu-latest, windows-latest, macOS-latest] os: [ubuntu-latest, windows-latest, macOS-latest]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v3
- uses: actions/setup-node@v2 - uses: actions/setup-node@v3
with: with:
cache: npm cache: npm
- run: 'npm i && npm run test:unit' - run: npm install
env:
SKIP_PREPARE: true
- run: npm run test:unit

@ -7,6 +7,8 @@ on:
paths: paths:
- site/content/** - site/content/**
permissions: {}
jobs: jobs:
release: release:
name: Deploy docs name: Deploy docs

@ -1,7 +1,7 @@
const is_unit_test = process.env.UNIT_TEST;
module.exports = { module.exports = {
file: [ file: is_unit_test ? [] : ['test/test.ts'],
'test/test.ts'
],
require: [ require: [
'sucrase/register' 'sucrase/register'
] ]

@ -0,0 +1,15 @@
module.exports = {
spec: [
'src/**/__test__.ts',
],
require: [
'sucrase/register'
],
recursive: true,
};
// add coverage options when running 'npx c8 mocha'
if (process.env.NODE_V8_COVERAGE) {
module.exports.fullTrace = true;
module.exports.require.push('source-map-support/register');
}

@ -1,19 +1,35 @@
# Svelte changelog # Svelte changelog
## Unreleased ## Unreleased
* Upgraded `css-tree` to `2.1.0` ([#7572](https://github.com/sveltejs/svelte/pull/7572))
## 3.52.0
* Throw compile-time error when attempting to update `const` variable ([#4895](https://github.com/sveltejs/svelte/issues/4895))
* Warn when using `<a target="_blank">` without `rel="noreferrer"` ([#6188](https://github.com/sveltejs/svelte/issues/6188))
* Support `style:foo|important` modifier ([#7365](https://github.com/sveltejs/svelte/issues/7365))
* Fix hydration regression with `{@html}` and components in `<svelte:head>` ([#7941](https://github.com/sveltejs/svelte/pull/7941))
## 3.51.0
* Fix hydration issue with using `{@html}` and components in `svelte:head` ([#4533](https://github.com/sveltejs/svelte/issues/4533), [#6463](https://github.com/sveltejs/svelte/issues/6463), [#7444](https://github.com/sveltejs/svelte/issues/7444))
* Warn instead of throwing error if `<svelte:element>` is void tag ([#7566](https://github.com/sveltejs/svelte/issues/7566))
* Treat `inert` as boolean attribute ([#7785](https://github.com/sveltejs/svelte/pull/7785))
* Supporting scoped style for `<svelte:element>` ([#7443](https://github.com/sveltejs/svelte/issues/7443))
* Supports SVG elements with `<svelte:element>`([#7613](https://github.com/sveltejs/svelte/issues/7613))
* Warn user when binding on a `{...rest}` object in `{#each}` block ([#6860](https://github.com/sveltejs/svelte/issues/6860))
* Supports `--style-props` for `<svelte:component>` ([#7461](https://github.com/sveltejs/svelte/issues/7461))
* Add a11y warnings: * Add a11y warnings:
* `a11y-no-noninteractive-tabindex`: check for tabindex on non-interactive elements ([#6693](https://github.com/sveltejs/svelte/pull/6693))
* `a11y-click-events-have-key-events`: check if click event is accompanied by key events ([#5073](https://github.com/sveltejs/svelte/pull/5073)) * `a11y-click-events-have-key-events`: check if click event is accompanied by key events ([#5073](https://github.com/sveltejs/svelte/pull/5073))
* `a11y-role-has-required-aria-props` do not warn when elements matched their semantic role ([#7838](https://github.com/sveltejs/svelte/pull/7838)) * `a11y-no-noninteractive-tabindex`: check for tabindex on non-interactive elements ([#6693](https://github.com/sveltejs/svelte/pull/6693))
* Supports custom element in `<svelte:element>` ([#7766](https://github.com/sveltejs/svelte/pull/7766)) * Warn when two-way binding to `{...rest}` object in `{#each}` block ([#6860](https://github.com/sveltejs/svelte/issues/6860))
* Support `--style-props` on `<svelte:component>` ([#7461](https://github.com/sveltejs/svelte/issues/7461))
* Supports nullish values for component event handlers ([#7568](https://github.com/sveltejs/svelte/issues/7568))
* Supports SVG elements with `<svelte:element>`([#7613](https://github.com/sveltejs/svelte/issues/7613))
* Treat `inert` as boolean attribute ([#7785](https://github.com/sveltejs/svelte/pull/7785))
* Support `--style-props` for SVG components ([#7808](https://github.com/sveltejs/svelte/issues/7808))
* Fix false positive dev warnings about unset props when they are bound ([#4457](https://github.com/sveltejs/svelte/issues/4457))
* Fix hydration with `{@html}` and components in `<svelte:head>` ([#4533](https://github.com/sveltejs/svelte/issues/4533), [#6463](https://github.com/sveltejs/svelte/issues/6463), [#7444](https://github.com/sveltejs/svelte/issues/7444))
* Support scoped style for `<svelte:element>` ([#7443](https://github.com/sveltejs/svelte/issues/7443))
* Improve error message for invalid value for `<svelte:component this={...}>` ([#7550](https://github.com/sveltejs/svelte/issues/7550))
* Improve error message when using logic blocks or tags at invalid location ([#7552](https://github.com/sveltejs/svelte/issues/7552))
* Warn instead of throwing error if `<svelte:element>` is a void tag ([#7566](https://github.com/sveltejs/svelte/issues/7566))
* Supports custom elements in `<svelte:element>` ([#7733](https://github.com/sveltejs/svelte/issues/7733))
* Fix calling component unmount if a component is mounted and then immediately unmounted ([#7817](https://github.com/sveltejs/svelte/issues/7817))
* Do not generate `a11y-role-has-required-aria-props` warning when elements match their semantic role ([#7837](https://github.com/sveltejs/svelte/issues/7837))
* Improve performance of custom element data setting in `<svelte:element>` ([#7869](https://github.com/sveltejs/svelte/pull/7869)) * Improve performance of custom element data setting in `<svelte:element>` ([#7869](https://github.com/sveltejs/svelte/pull/7869))
## 3.50.1 ## 3.50.1
@ -1188,7 +1204,7 @@ Also:
## 2.12.0 ## 2.12.0
* Initialise actions on mount rather than hydrate ([#1653](https://github.com/sveltejs/svelte/pull/1653)) * Initialise actions on mount rather than hydrate ([#1653](https://github.com/sveltejs/svelte/pull/1653))
* Allow non-existent components to be destroyed ([#1677](https://github.com/sveltejs/svelte/pull/1677)) * Allow nonexistent components to be destroyed ([#1677](https://github.com/sveltejs/svelte/pull/1677))
* Pass AMD ID from CLI correctly ([#1672](https://github.com/sveltejs/svelte/pull/1672)) * Pass AMD ID from CLI correctly ([#1672](https://github.com/sveltejs/svelte/pull/1672))
* Minor AST tweaks ([#1673](https://github.com/sveltejs/svelte/pull/1673), [#1674](https://github.com/sveltejs/svelte/pull/1674)) * Minor AST tweaks ([#1673](https://github.com/sveltejs/svelte/pull/1673), [#1674](https://github.com/sveltejs/svelte/pull/1674))
* Reduce code duplication in component initialisation ([#1670](https://github.com/sveltejs/svelte/pull/1670)) * Reduce code duplication in component initialisation ([#1670](https://github.com/sveltejs/svelte/pull/1670))

@ -11,7 +11,7 @@ The [Open Source Guides](https://opensource.guide/) website has a collection of
There are many ways to contribute to Svelte, and many of them do not involve writing any code. Here's a few ideas to get started: There are many ways to contribute to Svelte, and many of them do not involve writing any code. Here's a few ideas to get started:
- Simply start using Svelte. Go through the [Getting Started](https://svelte.dev/blog/the-easiest-way-to-get-started) guide. Does everything work as expected? If not, we're always looking for improvements. Let us know by [opening an issue](#reporting-new-issues). - Simply start using Svelte. Go through the [Getting Started](https://svelte.dev/docs#getting-started) guide. Does everything work as expected? If not, we're always looking for improvements. Let us know by [opening an issue](#reporting-new-issues).
- Look through the [open issues](https://github.com/sveltejs/svelte/issues). A good starting point would be issues tagged [good first issue](https://github.com/sveltejs/svelte/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). Provide workarounds, ask for clarification, or suggest labels. Help [triage issues](#triaging-issues-and-pull-requests). - Look through the [open issues](https://github.com/sveltejs/svelte/issues). A good starting point would be issues tagged [good first issue](https://github.com/sveltejs/svelte/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). Provide workarounds, ask for clarification, or suggest labels. Help [triage issues](#triaging-issues-and-pull-requests).
- If you find an issue you would like to fix, [open a pull request](#your-first-pull-request). - If you find an issue you would like to fix, [open a pull request](#your-first-pull-request).
- Read through our [tutorials](https://svelte.dev/tutorial/basics). If you find anything that is confusing or can be improved, you can make edits by clicking "Edit this chapter" at the bottom left of the tutorial page. - Read through our [tutorials](https://svelte.dev/tutorial/basics). If you find anything that is confusing or can be improved, you can make edits by clicking "Edit this chapter" at the bottom left of the tutorial page.

185
package-lock.json generated

@ -1,18 +1,18 @@
{ {
"name": "svelte", "name": "svelte",
"version": "3.50.1", "version": "3.52.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "svelte", "name": "svelte",
"version": "3.49.0", "version": "3.52.0",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@ampproject/remapping": "^0.3.0", "@ampproject/remapping": "^0.3.0",
"@rollup/plugin-commonjs": "^11.0.0", "@rollup/plugin-commonjs": "^11.0.0",
"@rollup/plugin-json": "^4.0.1", "@rollup/plugin-json": "^4.0.1",
"@rollup/plugin-node-resolve": "^6.0.0", "@rollup/plugin-node-resolve": "^11.2.1",
"@rollup/plugin-replace": "^2.3.0", "@rollup/plugin-replace": "^2.3.0",
"@rollup/plugin-sucrase": "^3.1.0", "@rollup/plugin-sucrase": "^3.1.0",
"@rollup/plugin-typescript": "^2.0.1", "@rollup/plugin-typescript": "^2.0.1",
@ -28,7 +28,7 @@
"aria-query": "^5.0.0", "aria-query": "^5.0.0",
"axobject-query": "^3.0.1", "axobject-query": "^3.0.1",
"code-red": "^0.2.5", "code-red": "^0.2.5",
"css-tree": "^1.1.2", "css-tree": "^2.1.0",
"eslint": "^8.0.0", "eslint": "^8.0.0",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-svelte3": "^4.0.0", "eslint-plugin-svelte3": "^4.0.0",
@ -209,21 +209,54 @@
} }
}, },
"node_modules/@rollup/plugin-node-resolve": { "node_modules/@rollup/plugin-node-resolve": {
"version": "6.0.0", "version": "11.2.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-6.0.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz",
"integrity": "sha512-GqWz1CfXOsqpeVMcoM315+O7zMxpRsmhWyhJoxLFHVSp9S64/u02i7len/FnbTNbmgYs+sZyilasijH8UiuboQ==", "integrity": "sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@rollup/pluginutils": "^3.0.0", "@rollup/pluginutils": "^3.1.0",
"@types/resolve": "0.0.8", "@types/resolve": "1.17.1",
"builtin-modules": "^3.1.0", "builtin-modules": "^3.1.0",
"deepmerge": "^4.2.2",
"is-module": "^1.0.0", "is-module": "^1.0.0",
"resolve": "^1.11.1" "resolve": "^1.19.0"
},
"engines": {
"node": ">= 10.0.0"
},
"peerDependencies": {
"rollup": "^1.20.0||^2.0.0"
}
},
"node_modules/@rollup/plugin-node-resolve/node_modules/@rollup/pluginutils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
"integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
"dev": true,
"dependencies": {
"@types/estree": "0.0.39",
"estree-walker": "^1.0.1",
"picomatch": "^2.2.2"
}, },
"engines": { "engines": {
"node": ">= 8.0.0" "node": ">= 8.0.0"
},
"peerDependencies": {
"rollup": "^1.20.0||^2.0.0"
} }
}, },
"node_modules/@rollup/plugin-node-resolve/node_modules/@types/estree": {
"version": "0.0.39",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
"integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
"dev": true
},
"node_modules/@rollup/plugin-node-resolve/node_modules/estree-walker": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
"integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
"dev": true
},
"node_modules/@rollup/plugin-replace": { "node_modules/@rollup/plugin-replace": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.3.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.3.0.tgz",
@ -331,9 +364,9 @@
"dev": true "dev": true
}, },
"node_modules/@types/resolve": { "node_modules/@types/resolve": {
"version": "0.0.8", "version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
"integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "*" "@types/node": "*"
@ -1077,25 +1110,17 @@
"dev": true "dev": true
}, },
"node_modules/css-tree": { "node_modules/css-tree": {
"version": "1.1.2", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.2.tgz", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz",
"integrity": "sha512-wCoWush5Aeo48GLhfHPbmvZs59Z+M7k5+B1xDnXbdWNcEF423DoFdqSWE0PM5aNk5nI5cp1q7ms36zGApY/sKQ==", "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"mdn-data": "2.0.14", "mdn-data": "2.0.28",
"source-map": "^0.6.1" "source-map-js": "^1.0.1"
},
"engines": {
"node": ">=8.0.0"
}
}, },
"node_modules/css-tree/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0",
"npm": ">=7.0.0"
} }
}, },
"node_modules/cssom": { "node_modules/cssom": {
@ -1177,6 +1202,15 @@
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
"dev": true "dev": true
}, },
"node_modules/deepmerge": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/define-properties": { "node_modules/define-properties": {
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
@ -3108,9 +3142,9 @@
} }
}, },
"node_modules/mdn-data": { "node_modules/mdn-data": {
"version": "2.0.14", "version": "2.0.28",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz",
"integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==",
"dev": true "dev": true
}, },
"node_modules/merge2": { "node_modules/merge2": {
@ -4055,6 +4089,15 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-support": { "node_modules/source-map-support": {
"version": "0.5.13", "version": "0.5.13",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
@ -4884,16 +4927,42 @@
} }
}, },
"@rollup/plugin-node-resolve": { "@rollup/plugin-node-resolve": {
"version": "6.0.0", "version": "11.2.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-6.0.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz",
"integrity": "sha512-GqWz1CfXOsqpeVMcoM315+O7zMxpRsmhWyhJoxLFHVSp9S64/u02i7len/FnbTNbmgYs+sZyilasijH8UiuboQ==", "integrity": "sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@rollup/pluginutils": "^3.0.0", "@rollup/pluginutils": "^3.1.0",
"@types/resolve": "0.0.8", "@types/resolve": "1.17.1",
"builtin-modules": "^3.1.0", "builtin-modules": "^3.1.0",
"deepmerge": "^4.2.2",
"is-module": "^1.0.0", "is-module": "^1.0.0",
"resolve": "^1.11.1" "resolve": "^1.19.0"
},
"dependencies": {
"@rollup/pluginutils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
"integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
"dev": true,
"requires": {
"@types/estree": "0.0.39",
"estree-walker": "^1.0.1",
"picomatch": "^2.2.2"
}
},
"@types/estree": {
"version": "0.0.39",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
"integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
"dev": true
},
"estree-walker": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
"integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
"dev": true
}
} }
}, },
"@rollup/plugin-replace": { "@rollup/plugin-replace": {
@ -4997,9 +5066,9 @@
"dev": true "dev": true
}, },
"@types/resolve": { "@types/resolve": {
"version": "0.0.8", "version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
"integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/node": "*" "@types/node": "*"
@ -5545,21 +5614,13 @@
"dev": true "dev": true
}, },
"css-tree": { "css-tree": {
"version": "1.1.2", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.2.tgz", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz",
"integrity": "sha512-wCoWush5Aeo48GLhfHPbmvZs59Z+M7k5+B1xDnXbdWNcEF423DoFdqSWE0PM5aNk5nI5cp1q7ms36zGApY/sKQ==", "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==",
"dev": true, "dev": true,
"requires": { "requires": {
"mdn-data": "2.0.14", "mdn-data": "2.0.28",
"source-map": "^0.6.1" "source-map-js": "^1.0.1"
},
"dependencies": {
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
}
} }
}, },
"cssom": { "cssom": {
@ -5626,6 +5687,12 @@
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
"dev": true "dev": true
}, },
"deepmerge": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
"dev": true
},
"define-properties": { "define-properties": {
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
@ -7103,9 +7170,9 @@
} }
}, },
"mdn-data": { "mdn-data": {
"version": "2.0.14", "version": "2.0.28",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz",
"integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==",
"dev": true "dev": true
}, },
"merge2": { "merge2": {
@ -7837,6 +7904,12 @@
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true "dev": true
}, },
"source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true
},
"source-map-support": { "source-map-support": {
"version": "0.5.13", "version": "0.5.13",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",

@ -1,6 +1,6 @@
{ {
"name": "svelte", "name": "svelte",
"version": "3.50.1", "version": "3.52.0",
"description": "Cybernetically enhanced web apps", "description": "Cybernetically enhanced web apps",
"module": "index.mjs", "module": "index.mjs",
"main": "index", "main": "index",
@ -86,15 +86,15 @@
}, },
"types": "types/runtime/index.d.ts", "types": "types/runtime/index.d.ts",
"scripts": { "scripts": {
"test": "mocha --exit", "test": "npm run test:unit && npm run test:integration",
"test:unit": "mocha --require sucrase/register --recursive src/**/__test__.ts --exit", "test:integration": "mocha --exit",
"quicktest": "mocha", "test:unit": "mocha --config .mocharc.unit.js --exit",
"quicktest": "mocha --exit",
"build": "rollup -c && npm run tsd", "build": "rollup -c && npm run tsd",
"prepare": "npm run build", "prepare": "node scripts/skip_in_ci.js npm run build",
"dev": "rollup -cw", "dev": "rollup -cw",
"pretest": "npm run build",
"posttest": "agadoo internal/index.mjs", "posttest": "agadoo internal/index.mjs",
"prepublishOnly": "node check_publish_env.js && npm run lint && npm test", "prepublishOnly": "node check_publish_env.js && npm run lint && npm run build && npm test",
"tsd": "node ./generate-type-definitions.js", "tsd": "node ./generate-type-definitions.js",
"lint": "eslint \"{src,test}/**/*.{ts,js}\"" "lint": "eslint \"{src,test}/**/*.{ts,js}\""
}, },
@ -118,7 +118,7 @@
"@ampproject/remapping": "^0.3.0", "@ampproject/remapping": "^0.3.0",
"@rollup/plugin-commonjs": "^11.0.0", "@rollup/plugin-commonjs": "^11.0.0",
"@rollup/plugin-json": "^4.0.1", "@rollup/plugin-json": "^4.0.1",
"@rollup/plugin-node-resolve": "^6.0.0", "@rollup/plugin-node-resolve": "^11.2.1",
"@rollup/plugin-replace": "^2.3.0", "@rollup/plugin-replace": "^2.3.0",
"@rollup/plugin-sucrase": "^3.1.0", "@rollup/plugin-sucrase": "^3.1.0",
"@rollup/plugin-typescript": "^2.0.1", "@rollup/plugin-typescript": "^2.0.1",
@ -134,7 +134,7 @@
"aria-query": "^5.0.0", "aria-query": "^5.0.0",
"axobject-query": "^3.0.1", "axobject-query": "^3.0.1",
"code-red": "^0.2.5", "code-red": "^0.2.5",
"css-tree": "^1.1.2", "css-tree": "^2.1.0",
"eslint": "^8.0.0", "eslint": "^8.0.0",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-svelte3": "^4.0.0", "eslint-plugin-svelte3": "^4.0.0",

@ -0,0 +1,7 @@
if (process.env.SKIP_PREPARE) {
console.log('Skipped "prepare" script');
} else {
const { execSync } = require("child_process");
const command = process.argv.slice(2).join(" ");
execSync(command, { stdio: "inherit" });
}

@ -63,4 +63,4 @@ npx degit your-name/template my-new-project
And that's it! Do `npm run build` to create a production-ready version of your app, and check the project template's [README](https://github.com/sveltejs/template/blob/master/README.md) for instructions on how to easily deploy your app to the web with [Vercel](https://vercel.com) or [Surge](http://surge.sh/). And that's it! Do `npm run build` to create a production-ready version of your app, and check the project template's [README](https://github.com/sveltejs/template/blob/master/README.md) for instructions on how to easily deploy your app to the web with [Vercel](https://vercel.com) or [Surge](http://surge.sh/).
You're not restricted to using Rollup — there are also integrations for [webpack](https://github.com/sveltejs/svelte-loader), [Browserify](https://github.com/tehshrike/sveltify) and others, or you can use the [Svelte CLI](https://github.com/sveltejs/svelte-cli) (Update from 2019: with Svelte 3 the CLI was deprecated and we now use [sirv-cli](https://www.npmjs.com/package/sirv-cli) in our template. Feel free to use whatever tool you like!) or the [API](https://github.com/sveltejs/svelte/tree/v2#api) directly. If you make a project template using one of these tools, please share it with the [Svelte Discord chatroom](chat), or via [@sveltejs](https://twitter.com/sveltejs) on Twitter! You're not restricted to using Rollup — there are also integrations for [webpack](https://github.com/sveltejs/svelte-loader), [Browserify](https://github.com/tehshrike/sveltify) and others, or you can use the [Svelte CLI](https://github.com/sveltejs/svelte-cli) (Update from 2019: with Svelte 3 the CLI was deprecated and we now use [sirv-cli](https://www.npmjs.com/package/sirv-cli) in our template. Feel free to use whatever tool you like!) or the [API](https://github.com/sveltejs/svelte/tree/v2#api) directly. If you make a project template using one of these tools, please share it with the [Svelte Discord chatroom](https://svelte.dev/chat), or via [@sveltejs](https://twitter.com/sveltejs) on Twitter!

@ -81,4 +81,4 @@ I believe the next frontier of web performance is 'whole-app optimisation'. Curr
Speaking of Glimmer, the idea of compiling components to bytecode is one that we'll probably steal in 2018. A framework like Sapper could conceivably determine which compilation mode to use based on the characteristics of your app. It could even serve JavaScript for the initial route for the fastest possible startup time, then lazily serve a bytecode interpreter for subsequent routes, resulting in the optimal combination of startup size and total app size. Speaking of Glimmer, the idea of compiling components to bytecode is one that we'll probably steal in 2018. A framework like Sapper could conceivably determine which compilation mode to use based on the characteristics of your app. It could even serve JavaScript for the initial route for the fastest possible startup time, then lazily serve a bytecode interpreter for subsequent routes, resulting in the optimal combination of startup size and total app size.
Mostly, though, we want the direction of Sapper to be determined by its users. If you're the kind of developer who enjoys life on the bleeding edge and would like to help shape the future of how we build web apps, please join us on [GitHub](https://github.com/sveltejs/svelte) and [Discord](chat). Mostly, though, we want the direction of Sapper to be determined by its users. If you're the kind of developer who enjoys life on the bleeding edge and would like to help shape the future of how we build web apps, please join us on [GitHub](https://github.com/sveltejs/svelte) and [Discord](https://svelte.dev/chat).

@ -12,7 +12,7 @@ Almost a year after we first started talking about version 2 on the Svelte issue
## tl;dr ## tl;dr
Each of these items is described in more depth below. If you get stuck, ask for help in our friendly [Discord chatroom](chat). Each of these items is described in more depth below. If you get stuck, ask for help in our friendly [Discord chatroom](https://svelte.dev/chat).
- Install Svelte v2 from npm - Install Svelte v2 from npm
- Upgrade your templates with [svelte-upgrade](https://github.com/sveltejs/svelte-upgrade) - Upgrade your templates with [svelte-upgrade](https://github.com/sveltejs/svelte-upgrade)
@ -201,4 +201,4 @@ Before, there was a `svelte.validate` method which checked your component was va
## My app is broken! Help! ## My app is broken! Help!
Hopefully this covers everything, and the update should be easier for you than it was for us. But if you find bugs, or discover things that aren't mentioned here, swing by [Discord chatroom](chat) or raise an issue on the [tracker](https://github.com/sveltejs/svelte/issues). Hopefully this covers everything, and the update should be easier for you than it was for us. But if you find bugs, or discover things that aren't mentioned here, swing by [Discord chatroom](https://svelte.dev/chat) or raise an issue on the [tracker](https://github.com/sveltejs/svelte/issues).

@ -14,7 +14,7 @@ Earlier this month, I had the privilege of appearing on [The Changelog](https://
...and, most importantly, Svelte 3. ...and, most importantly, Svelte 3.
Unless you hang out in our [Discord server](chat) or follow [@sveltejs](https://twitter.com/sveltejs) on Twitter, you might not know that Svelte 3 is just around the corner, and it's going to be a huge release. We've rethought the developer experience from the ground up, and while it *will* be a nuisance if you need to upgrade a Svelte 2 app (more on that soon) we think you're going to love it. Unless you hang out in our [Discord server](https://svelte.dev/chat) or follow [@sveltejs](https://twitter.com/sveltejs) on Twitter, you might not know that Svelte 3 is just around the corner, and it's going to be a huge release. We've rethought the developer experience from the ground up, and while it *will* be a nuisance if you need to upgrade a Svelte 2 app (more on that soon) we think you're going to love it.
On the podcast [Adam](https://twitter.com/adamstac), [Jerod](https://twitter.com/jerodsanto) and I talk about some of the changes and why we're making them. You can listen here or on the [podcast page](https://changelog.com/podcast/332). On the podcast [Adam](https://twitter.com/adamstac), [Jerod](https://twitter.com/jerodsanto) and I talk about some of the changes and why we're making them. You can listen here or on the [podcast page](https://changelog.com/podcast/332).

@ -12,7 +12,7 @@ Welcome to the first edition of our "What's new in Svelte" series! We'll try to
2. `_` is now supported as a "numerical separator", similar to a `.` or `,` ([Example](https://svelte.dev/repl/844c39e91d1248649fe54af839fab570?version=3.26.0), **3.26.0**) 2. `_` is now supported as a "numerical separator", similar to a `.` or `,` ([Example](https://svelte.dev/repl/844c39e91d1248649fe54af839fab570?version=3.26.0), **3.26.0**)
3. `import.meta` now works in template expressions ([Example](https://svelte.dev/repl/9630de41957a4c80a4fce264360a6bc7?version=3.26.0), **3.26.0**) 3. `import.meta` now works in template expressions ([Example](https://svelte.dev/repl/9630de41957a4c80a4fce264360a6bc7?version=3.26.0), **3.26.0**)
4. CSS Selectors with `~` and `+` combinators are now supported ([Example](https://svelte.dev/repl/91ad9257d2d1430185a504a18cc60172?version=3.29.0), **3.27.0**, with a compiler fix in **3.29.0**) 4. CSS Selectors with `~` and `+` combinators are now supported ([Example](https://svelte.dev/repl/91ad9257d2d1430185a504a18cc60172?version=3.29.0), **3.27.0**, with a compiler fix in **3.29.0**)
5. The `{#key}` block is now available to key arbitrary content on an expression. Whever the expression changes, the contents inside the `{#key}` block will be destroyed and recreated. For an in-depth explanation and to find out how it's implemented, check out the [new blog post](https://lihautan.com/contributing-to-svelte-implement-key-block/) of Svelte Team member Tan Li Hau. ([More info](https://github.com/sveltejs/svelte/issues/1469), **3.29.0**) 5. The `{#key}` block is now available to key arbitrary content on an expression. Whenever the expression changes, the contents inside the `{#key}` block will be destroyed and recreated. For an in-depth explanation and to find out how it's implemented, check out the [new blog post](https://lihautan.com/contributing-to-svelte-implement-key-block/) of Svelte Team member Tan Li Hau. ([More info](https://github.com/sveltejs/svelte/issues/1469), **3.29.0**)
6. Slots can now be forwarded through child components! This used to only be possible by adding extra wrapper `<div>`s ([More info](https://github.com/sveltejs/svelte/issues/2079), **3.29.0**) 6. Slots can now be forwarded through child components! This used to only be possible by adding extra wrapper `<div>`s ([More info](https://github.com/sveltejs/svelte/issues/2079), **3.29.0**)
7. When using TypeScript, you can now type the `createEventDispatcher` method: 7. When using TypeScript, you can now type the `createEventDispatcher` method:
```html ```html
@ -53,7 +53,7 @@ For all the features and bugfixes see the CHANGELOG for [Svelte](https://github.
## Svelte Showcase ## Svelte Showcase
- [This CustomMenu example](https://svelte.dev/repl/3a33725c3adb4f57b46b597f9dade0c1?version=3.25.0) demos how to replace the OS right-click menu - [This CustomMenu example](https://svelte.dev/repl/3a33725c3adb4f57b46b597f9dade0c1?version=3.25.0) demos how to replace the OS right-click menu
- [Github Tetris](https://svelte.dev/repl/cc1eaa7c66964fedb5e70e3ecbbaa0e1?version=3.25.1) lets you play a Tetris-like game in a git commit history - [GitHub Tetris](https://svelte.dev/repl/cc1eaa7c66964fedb5e70e3ecbbaa0e1?version=3.25.1) lets you play a Tetris-like game in a git commit history
- [Who are my representatives?](https://whoaremyrepresentatives.us/) is a website built with Svelte to help US residents get more info on their congressional representatives - [Who are my representatives?](https://whoaremyrepresentatives.us/) is a website built with Svelte to help US residents get more info on their congressional representatives
- [Pick Palette](https://github.com/bluwy/pick-palette) is a color palette manager made with Svelte! - [Pick Palette](https://github.com/bluwy/pick-palette) is a color palette manager made with Svelte!

@ -66,7 +66,7 @@ New changes to the Svelte Society website include [a new cheat sheet](https://sv
- [eleventy-plugin-embed-svelte](https://github.com/shalomscott/eleventy-plugin-embed-svelte) makes it easy to embed Svelte components into an 11ty site. - [eleventy-plugin-embed-svelte](https://github.com/shalomscott/eleventy-plugin-embed-svelte) makes it easy to embed Svelte components into an 11ty site.
- [svelte-tailwind-extension-boilerplate](https://github.com/kyrelldixon/svelte-tailwind-extension-boilerplate) is a good foundation for a Chrome extension using either JavaScript or TypeScript, Svelte for the frontend, Tailwind CSS for styling, Jest for testing, and Rollup as the build system. - [svelte-tailwind-extension-boilerplate](https://github.com/kyrelldixon/svelte-tailwind-extension-boilerplate) is a good foundation for a Chrome extension using either JavaScript or TypeScript, Svelte for the frontend, Tailwind CSS for styling, Jest for testing, and Rollup as the build system.
- [snowpack-ui](https://github.com/rajasegar/snowpack-ui) lets you run & manage Snowpack projects from the browser instead of the terminal - [snowpack-ui](https://github.com/rajasegar/snowpack-ui) lets you run & manage Snowpack projects from the browser instead of the terminal
- [Svelte for Appwrite](https://dev.to/torstendittmann/svelte-for-appwrite-4fkg) explains how to integrate with Appwrite, a self-hosted Firebase alternative [Github Repo](https://github.com/appwrite/sdk-for-svelte) - [Svelte for Appwrite](https://dev.to/torstendittmann/svelte-for-appwrite-4fkg) explains how to integrate with Appwrite, a self-hosted Firebase alternative [GitHub Repo](https://github.com/appwrite/sdk-for-svelte)
- [here-maps-svelte](https://github.com/peopledrivemecrazy/here-maps-svelte) makes it easy to include HERE maps in a Svelte app - [here-maps-svelte](https://github.com/peopledrivemecrazy/here-maps-svelte) makes it easy to include HERE maps in a Svelte app
- [p5-svelte](https://github.com/tonyketcham/p5-svelte) is an absolutely dead simple way of tossing the creative coding/sketching tool, p5, into a project - [p5-svelte](https://github.com/tonyketcham/p5-svelte) is an absolutely dead simple way of tossing the creative coding/sketching tool, p5, into a project
- [svelte-windicss-preprocess](https://github.com/voorjaar/svelte-windicss-preprocess) is a Svelte preprocessor to compile tailwindcss at build time based on windicss compiler - [svelte-windicss-preprocess](https://github.com/voorjaar/svelte-windicss-preprocess) is a Svelte preprocessor to compile tailwindcss at build time based on windicss compiler

@ -49,7 +49,7 @@ As the northern hemisphere heats up, Svelte has stayed cool with lots of perform
**Libraries, Tools & Components** **Libraries, Tools & Components**
- [svelte-pipeline](https://github.com/novacbn/svelte-pipeline) provides custom Javascript contexts and the Svelte Compiler as Svelte Stores, for REPLs, Editors, etc. - [svelte-pipeline](https://github.com/novacbn/svelte-pipeline) provides custom JavaScript contexts and the Svelte Compiler as Svelte Stores, for REPLs, Editors, etc.
- [Sveltotron](https://github.com/Salemmous/sveltotron) is an Electron-based app made to inspect your Svelte app - [Sveltotron](https://github.com/Salemmous/sveltotron) is an Electron-based app made to inspect your Svelte app
- [svelte-qr-reader-writer](https://github.com/pleasemarkdarkly/svelte-qr-reader-writer) is a Svelte component that helps read and write data from QR codes - [svelte-qr-reader-writer](https://github.com/pleasemarkdarkly/svelte-qr-reader-writer) is a Svelte component that helps read and write data from QR codes
- [svelte-stack-router](https://www.npmjs.com/package/svelte-stack-router) Aims to make Svelte apps feel more native by routing with Stacks - [svelte-stack-router](https://www.npmjs.com/package/svelte-stack-router) Aims to make Svelte apps feel more native by routing with Stacks

@ -52,7 +52,7 @@ To see all updates to SvelteKit, check out the [SvelteKit changelog](https://git
- [How to Create a Blog with SvelteKit and Strapi](https://strapi.io/blog/how-to-create-a-blog-with-svelte-kit-strapi) is a step-by-step tutorial by Aarnav Pai from Strapi - [How to Create a Blog with SvelteKit and Strapi](https://strapi.io/blog/how-to-create-a-blog-with-svelte-kit-strapi) is a step-by-step tutorial by Aarnav Pai from Strapi
- [Sveltekit Markdown Blog](https://www.youtube.com/watch?v=sKKgT0SEioI&list=PLm_Qt4aKpfKgonq1zwaCS6kOD-nbOKx7V) is a YouTube tutorial series by WebJeda. - [Sveltekit Markdown Blog](https://www.youtube.com/watch?v=sKKgT0SEioI&list=PLm_Qt4aKpfKgonq1zwaCS6kOD-nbOKx7V) is a YouTube tutorial series by WebJeda.
- [Using Custom Elements in Svelte](https://css-tricks.com/using-custom-elements-in-svelte/) is a deep dive into custom elements by Geoff Rich. - [Using Custom Elements in Svelte](https://css-tricks.com/using-custom-elements-in-svelte/) is a deep dive into custom elements by Geoff Rich.
- [learn / graphql / svelte](https://hasura.io/learn/graphql/svelte-apollo/introduction/) is a free 2-hour GraphQL course course from Hasura. - [learn / graphql / svelte](https://hasura.io/learn/graphql/svelte-apollo/introduction/) is a free 2-hour GraphQL course from Hasura.
- [How to add Magic Link to a SvelteKit application](https://magic.link/posts/magic-svelte) is a guide to the popular password-less login pattern. - [How to add Magic Link to a SvelteKit application](https://magic.link/posts/magic-svelte) is a guide to the popular password-less login pattern.
**Libraries, Tools & Components** **Libraries, Tools & Components**

@ -57,7 +57,7 @@ To see all updates to SvelteKit, check out the [SvelteKit changelog](https://git
**Looking for a Svelte project to work on? Interested in helping make Svelte's presence on the web better?** Check out [the list of open issues](https://github.com/svelte-society/sveltesociety-2021/issues) if you'd like to contribute to the Svelte Society rewrite in SvelteKit. **Looking for a Svelte project to work on? Interested in helping make Svelte's presence on the web better?** Check out [the list of open issues](https://github.com/svelte-society/sveltesociety-2021/issues) if you'd like to contribute to the Svelte Society rewrite in SvelteKit.
**Podcasts Featuring Svelte** **Podcasts Featuring Svelte**
- [Syntax Podast: From React to SvelteKit](https://podcasts.apple.com/us/podcast/from-react-to-sveltekit/id1253186678?i=1000536276106) Scott talks with Wes about moving Level Up Tutorials from React to SvelteKit — why he did it, how, benefits, things to watch out for, and more! - [Syntax Podcast: From React to SvelteKit](https://podcasts.apple.com/us/podcast/from-react-to-sveltekit/id1253186678?i=1000536276106) Scott talks with Wes about moving Level Up Tutorials from React to SvelteKit — why he did it, how, benefits, things to watch out for, and more!
- [Web Rush Podcast: Svelte Tools and Svelte Society](https://www.webrush.io/episodes/episode-150-svelte-tools-and-svelte-society) Kevin Åberg Kultalahti talks about what Svelte Society is, what he's excited about with Svelte, how important documentation is to any product, and much _much_ more - [Web Rush Podcast: Svelte Tools and Svelte Society](https://www.webrush.io/episodes/episode-150-svelte-tools-and-svelte-society) Kevin Åberg Kultalahti talks about what Svelte Society is, what he's excited about with Svelte, how important documentation is to any product, and much _much_ more
- [Svelte: The Compiled Future of Front End](https://www.arahansen.com/the-compiled-future-of-front-end/) details the history of component-based frontends and how a compiler changes everything - [Svelte: The Compiled Future of Front End](https://www.arahansen.com/the-compiled-future-of-front-end/) details the history of component-based frontends and how a compiler changes everything
- [Svelte Radio: Contributing to Svelte with Martin 'Grygrflzr' Krisnanto Putra](https://share.transistor.fm/s/10aa305c) Grygrflzr shares his journey to becoming a maintainer and his views on React, Vite and a host of other things - [Svelte Radio: Contributing to Svelte with Martin 'Grygrflzr' Krisnanto Putra](https://share.transistor.fm/s/10aa305c) Grygrflzr shares his journey to becoming a maintainer and his views on React, Vite and a host of other things

@ -41,7 +41,7 @@ To see all updates to Svelte and SvelteKit, check out the [Svelte](https://githu
- [DottoBit](https://dottobit.com/) is a multi-color 16-bit drawing program with URL sharing built-in - [DottoBit](https://dottobit.com/) is a multi-color 16-bit drawing program with URL sharing built-in
- [Former Fast Document for Print](https://github.com/zummon/former) is an invoice generator with beautiful designs, abilities for international languages and auto calculation - [Former Fast Document for Print](https://github.com/zummon/former) is an invoice generator with beautiful designs, abilities for international languages and auto calculation
- [Helvetikon](https://github.com/noahsalvi/helvetikon) is a community maintained dictionary for the Swiss German language - [Helvetikon](https://github.com/noahsalvi/helvetikon) is a community maintained dictionary for the Swiss German language
- [Palitra App](https://palitra.app/) is a search-based color pallete generator - [Palitra App](https://palitra.app/) is a search-based color palette generator
**Podcasts Featuring Svelte** **Podcasts Featuring Svelte**
- [Svelte Radio](https://www.svelteradio.com/episodes/svelte-summit-is-coming-up-and-svelte-is-growing) dives into the tech behind the recently released Svelte Summit website and a bunch of other fun stuff! - [Svelte Radio](https://www.svelteradio.com/episodes/svelte-summit-is-coming-up-and-svelte-is-growing) dives into the tech behind the recently released Svelte Summit website and a bunch of other fun stuff!

@ -69,7 +69,7 @@ _To Read_
- [Building an iOS app with Svelte, Capacitor and Firebase](https://harryherskowitz.com/2022/01/05/tapedrop-app.html) by Harry Herskowitz - [Building an iOS app with Svelte, Capacitor and Firebase](https://harryherskowitz.com/2022/01/05/tapedrop-app.html) by Harry Herskowitz
- [Mutating Query Params in SvelteKit Without Page Reloads or Navigations](https://dev.to/mohamadharith/mutating-query-params-in-sveltekit-without-page-reloads-or-navigations-2i2b) and [Workaround for Bubbling Custom Events in Svelte](https://dev.to/mohamadharith/workaround-for-bubbling-custom-events-in-svelte-3khk) by Mohamad Harith - [Mutating Query Params in SvelteKit Without Page Reloads or Navigations](https://dev.to/mohamadharith/mutating-query-params-in-sveltekit-without-page-reloads-or-navigations-2i2b) and [Workaround for Bubbling Custom Events in Svelte](https://dev.to/mohamadharith/workaround-for-bubbling-custom-events-in-svelte-3khk) by Mohamad Harith
- [How to build a full stack serverless application with Svelte and GraphQL](https://dev.to/shadid12/how-to-build-a-full-stack-serverless-application-with-svelte-graphql-and-fauna-5427) by Shadid Haque - [How to build a full stack serverless application with Svelte and GraphQL](https://dev.to/shadid12/how-to-build-a-full-stack-serverless-application-with-svelte-graphql-and-fauna-5427) by Shadid Haque
- [How to Deploy SvelteKit Apps to Github Pages](https://sveltesaas.com/articles/sveltekit-github-pages-guide/) - [How to Deploy SvelteKit Apps to GitHub Pages](https://sveltesaas.com/articles/sveltekit-github-pages-guide/)
- [Creating a dApp with SvelteKit](https://anthonyriley.org/2021/12/31/creating-a-dapp-with-sveltekit/) by Anthony Riley - [Creating a dApp with SvelteKit](https://anthonyriley.org/2021/12/31/creating-a-dapp-with-sveltekit/) by Anthony Riley
- [Comparing Svelte Reactivity Options](https://opendirective.net/2022/01/06/comparing-svelte-reactivity-options/) by Steve Lee - [Comparing Svelte Reactivity Options](https://opendirective.net/2022/01/06/comparing-svelte-reactivity-options/) by Steve Lee

@ -16,7 +16,7 @@ Let's dive in!
## What's new in SvelteKit ## What's new in SvelteKit
- Vite 2.9.9 was released as one of the last Vite 2 releases. The Svelte team has been hard at work contributing to the the Vite 3 release to make the integration between SvelteKit and Vite smoother than ever ([Vite 3.0 Milestone](https://github.com/vitejs/vite/milestone/5)) - Vite 2.9.9 was released as one of the last Vite 2 releases. The Svelte team has been hard at work contributing to the Vite 3 release to make the integration between SvelteKit and Vite smoother than ever ([Vite 3.0 Milestone](https://github.com/vitejs/vite/milestone/5))
- `config.kit.alias` lets you more easily declare a custom alias to replace values in `import` statements ([Docs](https://kit.svelte.dev/docs/configuration#alias), [PR](https://github.com/sveltejs/kit/pull/4964)) - `config.kit.alias` lets you more easily declare a custom alias to replace values in `import` statements ([Docs](https://kit.svelte.dev/docs/configuration#alias), [PR](https://github.com/sveltejs/kit/pull/4964))
- Pages marked for prerendering will now fail during SSR at runtime ([PR](https://github.com/sveltejs/kit/pull/4812)) - Pages marked for prerendering will now fail during SSR at runtime ([PR](https://github.com/sveltejs/kit/pull/4812))

@ -26,7 +26,7 @@ We will also be utilizing OpenCollective funds to allow Svelte core maintainers
- Introduced `@sveltejs/kit/experimental/vite` which allows SvelteKit to interoperate with other tools in the Vite ecosystem like Vitest and Storybook ([#5094](https://github.com/sveltejs/kit/pull/5094)). Please [leave feedback](https://github.com/sveltejs/kit/issues/5184) as to whether the feature works and is helpful as we consider taking it out of experimental and making `vite.config.js` required for all users - Introduced `@sveltejs/kit/experimental/vite` which allows SvelteKit to interoperate with other tools in the Vite ecosystem like Vitest and Storybook ([#5094](https://github.com/sveltejs/kit/pull/5094)). Please [leave feedback](https://github.com/sveltejs/kit/issues/5184) as to whether the feature works and is helpful as we consider taking it out of experimental and making `vite.config.js` required for all users
- Streaming in endpoints is now supported ([#3419](https://github.com/sveltejs/kit/issues/3419)). This was enabled by switching to the Undici `fetch` implementation ([#5117](https://github.com/sveltejs/kit/pull/5117)) - Streaming in endpoints is now supported ([#3419](https://github.com/sveltejs/kit/issues/3419)). This was enabled by switching to the Undici `fetch` implementation ([#5117](https://github.com/sveltejs/kit/pull/5117))
- Static assets can now be symlinked in development environments ([#5089](https://github.com/sveltejs/kit/pull/5089)) - Static assets can now be symlinked in development environments ([#5089](https://github.com/sveltejs/kit/pull/5089))
- `server` and `prod` environment variables are now available as a correlary to `browser` and `dev` ([#5251](https://github.com/sveltejs/kit/pull/5251)) - `server` and `prod` environment variables are now available as a corollary to `browser` and `dev` ([#5251](https://github.com/sveltejs/kit/pull/5251))
--- ---
@ -63,7 +63,7 @@ _To Watch_
- [SvelteKit Page Endpoints](https://www.youtube.com/watch?v=yQRf2wmTu5w), [Named Layouts](https://www.youtube.com/watch?v=UHX9TJ0BxZY) and [Passing data from page component to layout component with $page.stuff](https://www.youtube.com/watch?v=CXaCstU5pcw) by lihautan - [SvelteKit Page Endpoints](https://www.youtube.com/watch?v=yQRf2wmTu5w), [Named Layouts](https://www.youtube.com/watch?v=UHX9TJ0BxZY) and [Passing data from page component to layout component with $page.stuff](https://www.youtube.com/watch?v=CXaCstU5pcw) by lihautan
- [🍞 & 🧈: Magically load data with SvelteKit Endpoints](https://www.youtube.com/watch?v=f6prqYlbTE4) by Johnny Magrippis - [🍞 & 🧈: Magically load data with SvelteKit Endpoints](https://www.youtube.com/watch?v=f6prqYlbTE4) by Johnny Magrippis
- [Svelte for React developers](https://www.youtube.com/watch?v=7tsrwrx5HtQ) by frontendtier - [Svelte for React developers](https://www.youtube.com/watch?v=7tsrwrx5HtQ) by frontendtier
- [Learn Svelte JS || Javascript Compiler for Building Front end Applications](https://www.youtube.com/watch?v=1rKRarJJFrY&list=PLIGDNOJWiL1-7zCgdR7MKuho-tPC6Ra6C&index=1) by Code with tsksharma - [Learn Svelte JS || JavaScript Compiler for Building Front end Applications](https://www.youtube.com/watch?v=1rKRarJJFrY&list=PLIGDNOJWiL1-7zCgdR7MKuho-tPC6Ra6C&index=1) by Code with tsksharma
- [SvelteKit Authentication](https://www.youtube.com/watch?v=T935Ya4W5X0&list=PLA9WiRZ-IS_zKrDzhOhV5RGKKTHNIyTDO&index=1) by Joy of Code - [SvelteKit Authentication](https://www.youtube.com/watch?v=T935Ya4W5X0&list=PLA9WiRZ-IS_zKrDzhOhV5RGKKTHNIyTDO&index=1) by Joy of Code
- [Svelte + websockets: Build a real-time Auction app](https://www.youtube.com/watch?v=CqgsWFrwQIU) by Evgeny Maksimov - [Svelte + websockets: Build a real-time Auction app](https://www.youtube.com/watch?v=CqgsWFrwQIU) by Evgeny Maksimov
@ -85,7 +85,7 @@ _To Read_
- [Svelte Brick Gallery](https://github.com/anotherempty/svelte-brick-gallery) is a masonry-like image gallery component for Svelte - [Svelte Brick Gallery](https://github.com/anotherempty/svelte-brick-gallery) is a masonry-like image gallery component for Svelte
- [use-vest](https://github.com/enyo/use-vest) is a Svelte action for Vest - a library that makes it easy to validate forms and show errors when necessary - [use-vest](https://github.com/enyo/use-vest) is a Svelte action for Vest - a library that makes it easy to validate forms and show errors when necessary
- [Svelidate](https://github.com/svelidate/svelidate) is a simple and lightweight form validation library for Svelte with no dependencies - [Svelidate](https://github.com/svelidate/svelidate) is a simple and lightweight form validation library for Svelte with no dependencies
- [Svve11](https://github.com/oslabs-beta/Svve11) is an "accessbility-first" component library for Svelte - [Svve11](https://github.com/oslabs-beta/Svve11) is an "accessibility-first" component library for Svelte
- [Slidy](https://github.com/Valexr/Slidy) is a simple, configurable & reusable carousel sliding action script with templates & some useful plugins - [Slidy](https://github.com/Valexr/Slidy) is a simple, configurable & reusable carousel sliding action script with templates & some useful plugins
- [Svelte Component Snippets](https://marketplace.visualstudio.com/items?itemName=brysonbw.svelte-component-snippets) is a VS Code extension with access to common Svelte snippets - [Svelte Component Snippets](https://marketplace.visualstudio.com/items?itemName=brysonbw.svelte-component-snippets) is a VS Code extension with access to common Svelte snippets
- [Svelte Confetti](https://github.com/Mitcheljager/svelte-confetti) adds a little bit of flair to your app with some confetti 🎊 - [Svelte Confetti](https://github.com/Mitcheljager/svelte-confetti) adds a little bit of flair to your app with some confetti 🎊

@ -0,0 +1,139 @@
---
title: "What's new in Svelte: October 2022"
description: "Svelte Summit, `use:enhance`, and a SvelteKit Release Candidate!"
author: Daniel Sandoval
authorURL: https://desandoval.net
---
There's a bunch of updates this month... from new features in Svelte and SvelteKit to a whole 2-day *summit*! Plus, the Svelte extension gets some helpful new tools, new accessibility (a11y) warnings, and Tan Li Hau teaches us how to build our own Svelte and a Svelte spreadsheet 😎
## What happened at Svelte Summit?
A lot! Below you can find all the talks, by timestamp, from each livestream. Bite-size videos of the event will be coming soon to the Svelte Society channel, so be sure to [Subscribe](https://www.youtube.com/c/SvelteSociety), if you haven't already!
_Day One_
- [12:31](https://www.youtube.com/watch?v=pJcbZr5VlV4&t=751s) - How to get Svelte adopted at work
- [33:21](https://www.youtube.com/watch?v=pJcbZr5VlV4&t=2001s) - Animating Data Visualization in Svelte
- [2:20:36](https://www.youtube.com/watch?v=pJcbZr5VlV4&t=8436s) - Red flags & code smells
- [2:53:42](https://www.youtube.com/watch?v=pJcbZr5VlV4&t=10422s) - Enhance your DX
- [4:42:41](https://www.youtube.com/watch?v=pJcbZr5VlV4&t=16961s) - Svelte in UBS knowledge graph
- [5:06:42](https://www.youtube.com/watch?v=pJcbZr5VlV4&t=18402s) - How to migrate react libraries to svelte
- [5:45:27](https://www.youtube.com/watch?v=pJcbZr5VlV4&t=20727s) - DX magic in the world of Svelte
- [7:25:39](https://www.youtube.com/watch?v=pJcbZr5VlV4&t=26739s) - Data visualizations powered by Svelte
- [7:59:38](https://www.youtube.com/watch?v=pJcbZr5VlV4&t=28778s) - Partial Hydration in Svelte for Increased Performance
- [8:20:49](https://www.youtube.com/watch?v=pJcbZr5VlV4&t=30049s) - Building the future, faster
_Day Two_
- [24:09](https://www.youtube.com/watch?v=A8jkJTWacow&t=1449s) - Scrollytell me why: Ain't nothing but a piece of cake
- [2:02:40](https://www.youtube.com/watch?v=A8jkJTWacow&t=7360s) - I told you my dog wouldnt run
- [2:29:43](https://www.youtube.com/watch?v=A8jkJTWacow&t=8983s) - 10Xing Svelte
- [3:04:56](https://www.youtube.com/watch?v=A8jkJTWacow&t=11096s) - Svemix? Re-svmix? Re-svelte?: Bringing Svelte to Remix Router
- [5:09:39](https://www.youtube.com/watch?v=A8jkJTWacow&t=18579s) - Having fun with stores: an interactive demo of Sveltes built in state management library
- [5:37:06](https://www.youtube.com/watch?v=A8jkJTWacow&t=20226s) - When Keeping it Svelte Goes Wrong. An Analysis of Some of the Worst Svelte I Have Ever Coded
- [7:22:05](https://www.youtube.com/watch?v=A8jkJTWacow&t=26525s) - Getting started with Hooks
- [7:38:14](https://www.youtube.com/watch?v=A8jkJTWacow&t=27494s) - Special Announcement*
*In the final talk of the summit, Rich Harris announces the first Release Candidate of SvelteKit! With no planned breaking changes left, the team is hard at work squashing bugs and adding the remaining features for 1.0...
## More SvelteKit Updates
- `use:enhance` is the easiest way to progressively enhance a form ([Docs](https://kit.svelte.dev/docs/form-actions#progressive-enhancement-use-enhance), [#6633](https://github.com/sveltejs/kit/pull/6633), [#6828](https://github.com/sveltejs/kit/pull/6828), [#7012](https://github.com/sveltejs/kit/pull/7012))
- The demo app has been updated to add the Sverdle game, which Rich demoed at Svelte Summit and demonstrates `use:enhance` ([#6979](https://github.com/sveltejs/kit/pull/6979))
- Cloudflare Pages `_routes.json` specification is now supported by `adapter-cloudflare` ([#6530](https://github.com/sveltejs/kit/pull/6530))
- Improved build performance by running asset and page compression in parallel ([#6710](https://github.com/sveltejs/kit/pull/6710))
**Breaking changes:**
- Node 16.14 is now the minimum version to run SvelteKit ([#6388](https://github.com/sveltejs/kit/pull/6388))
- `App.PrivateEnv` and `App.PublicEnv` have been removed in favour of generated types ([#6413](https://github.com/sveltejs/kit/pull/6413))
- `%sveltekit.message%` has been replaced with `%sveltekit.error.message%` ([6659](https://github.com/sveltejs/kit/pull/6659))
- `App.PageError` is now `App.Error` - check for it in your hooks ([Docs](https://kit.svelte.dev/docs/hooks#shared-hooks-handleerror), [#6963](https://github.com/sveltejs/kit/pull/6963))
- `externalFetch` is now `handleFetch` and will run for all fetch calls in `load` that run on the server ([#6565](https://github.com/sveltejs/kit/pull/6565))
For a full list of changes, check out SvelteKit's [CHANGELOG](https://github.com/sveltejs/kit/blob/master/packages/kit/CHANGELOG.md).
## Svelte Updates
- New a11y warnings for `incorrect-aria-attribute-type`, `no-abstract-role`, `interactive-element-to-noninteractive-role` and `role-has-required-aria-props`.`no-noninteractive-tabindex` and `click-events-have-key-events` coming soon! (**3.50.0**)
- New types for `ComponentEvents` and `SveltePreprocessor` (**3.50.0**)
- Improved parsing speed when encountering large blocks of whitespace (**3.50.0**)
- All global JavaScript objects and functions are now recognized as known globals (**3.50.1**)
For all the changes to the Svelte compiler, including upcoming changes, check out the [CHANGELOG](https://github.com/sveltejs/svelte/blob/master/CHANGELOG.md).
## New in Language Tools
- Better code formatting for editor suggestion (**106.0.0**, [#1598](https://github.com/sveltejs/language-tools/pull/1598))
- Easily create SvelteKit route files from the context menu or command palette (**106.1.0**, [#1620](https://github.com/sveltejs/language-tools/pull/1620))
---
## Ask Questions in the Svelte Discord
In case you missed the announcement, the Svelte Discord has an exciting new update... a forum! The new [questions channel](https://discord.com/channels/457912077277855764/1023340103071965194) utilizes Discord's new forums feature to help the community better ask, find and answer questions!
It can be used for anything you may be trying to accomplish using Svelte including using SvelteKit, community libraries, tools, etc. So ask away!
---
## Community Showcase
**Apps & Sites built with Svelte**
- [Timeflow](https://www.timeflow.site/) is a smart calendar & task manager that dynamically schedules your tasks between your events
- [GeoQuest](https://github.com/woutdp/geoquest) is an open source geography game
- [Houses Of](https://housesof.world/) is a project showcasing charismatic houses around the world
- [Greenfield Brewery](https://greenfield-brewery.vercel.app/) is a tool for quickly installing a lot of homebrew casks
- [Gram Jam](https://gramjam.app/) is a word puzzle game inspired by match three games and Scrabble
- [Beatbump](https://github.com/snuffyDev/Beatbump) is a privacy-respecting alternative frontend for YouTube Music
- [RoomOS Device Widgets](https://github.com/wxsd-sales/roomos-device-widgets) is an app for demoing RoomOS device capabilities in Kiosk/PWA mode
- [World Seed](https://store.steampowered.com/app/1870320/World_Seed/) is a full blown online multiplayer game
- [Lirify](https://lirify-tan.vercel.app/) is a song lyrics writing web app tool made in Latvia
- [Splet Tech Konferencija](https://www.splet.rs/) is a tech conference in Serbia with a *very* fancy website
- [Unbounded](https://unbounded.polkadot.network/) is an open-source variable font - funded by blockchain (and an awesome-looking website)
- [Porter's Paints](https://shop.porterspaints.com/) is an eCommerce site for (you guessed it) paints built with Svelte
- [CRAN/E](https://www.cran-e.com/) is a search engine for modern R-packages
**Learning Resources**
_Starring the Svelte team_
- [Upgrading SvelteKit](https://www.youtube.com/watch?v=vzeZskhjoeQ) by Svelte Sirens (with Brittney, Kev, and GHOST!)
- [Build your own Svelte](https://www.youtube.com/watch?v=mwvyKGw2CzU) by lihautan
- [Native Page Transitions in SvelteKit: Part 1](https://geoffrich.net/posts/page-transitions-1/) by Geoff Rich
- [Build a cross platform app with Tauri](https://ghostdev.xyz/posts/build-a-cross-platform-app-with-tauri/) by GHOST
_To Watch_
- [How To Use Future CSS In Svelte](https://www.youtube.com/watch?v=eqwtoaP-0pk) and [Master Animation With Svelte](https://www.youtube.com/watch?v=3RlBfUQCiAQ) by Joy of Code
- [Svelte Kit Form Actions 101 - New Svelte Kit API](https://www.youtube.com/watch?v=i5zdnv83mxY) and [Svelte Kit Form Actions - Real World Examples - Q&A](https://www.youtube.com/watch?v=PK2Mpt1q6K8) by LevelUpTuts
_To Read_
- [What's new in `svelte-kit, 1.0.0-next.445`: (group) layout](https://dev.to/parables/whats-new-in-svelte-kit-100-next445-group-layout-1ld5) by Parables
- [Handling breaking changes in SvelteKit pre-1.0](https://maier.tech/posts/handling-breaking-changes-in-sveltekit-pre-1-0) by Thilo Maier
- [Svelte Custom Stores Demystified](https://raqueebuddinaziz.com/blog/svelte-custom-stores-demystified/) by Raqueebuddin Aziz
- Sveltekit Changes: [Advanced Layouts](https://dev.to/theether0/sveltekit-changes-advanced-layouts-3id4), [Form Actions and Progressive Enhancement](https://dev.to/theether0/sveltekit-changes-form-actions-and-progressive-enhancement-31h9) and [Cookies and Authentication](https://dev.to/theether0/sveltekit-changes-session-and-cookies-enb) by Shivam Meena
- [How to add an Emoji Picker to Sveltekit](https://xvrc.net/) by Xavier Coiffard
- [Get Started with SvelteKit Headless WordPress](https://plus.rodneylab.com/tutorials/get-started-sveltekit-headless-wordpress) by Rodney Lab
- [Speed up SvelteKit Pages With a Redis Cache](https://www.captaincodeman.com/speed-up-sveltekit-pages-with-a-redis-cache) and [How to await Firebase Auth with SvelteKit](https://www.captaincodeman.com/how-to-await-firebase-auth-with-sveltekit) by Captain Codeman
- [Deploying SvelteKit with NodeJS to a Server Using GitLab and PM2](https://abyteofcoding.com/blog/deploying-sveltekit-with-nodejs-pm2-to-server/) by A Byte of Coding
- [Quality of Life Tips when using SvelteKit in VS Code](https://www.reddit.com/r/sveltejs/comments/xltgyp/quality_of_life_tips_when_using_sveltekit_in_vs/) by doa-doa
**Libraries, Tools & Components**
- [Svelte Fit](https://github.com/leveluptuts/svelte-fit) is an extremely simple, no dependency fit text library
- [svelte-switch-case](https://github.com/l-portet/svelte-switch-case) is a switch case syntax for your Svelte components
- [svelte-canvas-confetti](https://github.com/andreasmcdermott/svelte-canvas-confetti) uses a single canvas to render full-screen confetti
- [@slidy/svelte](https://github.com/Valexr/Slidy/tree/master/packages/svelte) is a simple, configurable & reusable carousel component built with Svelte - based on `@slidy/core`
- [svelte-currency-input](https://github.com/canutin/svelte-currency-input) is a form input that converts numbers to localized currency formats as you type
- [Adria](https://github.com/pilcrowOnPaper/adria) is a super simple form validation library, with autocomplete and value/type checking
- [Canopy](https://github.com/oslabs-beta/canopy) is a Svelte debugging app for the Chrome devtools panel
- [MenuFramework](https://github.com/MyHwu9508/altv-os-menu-framework) is a menu framework written for [alt:V](https://altv.mp/#/)
- [whyframe](https://whyframe.dev/) gives iframes superpowers, making it easy to render anything in isolation
- [@svelte-put/modal](https://github.com/vnphanquang/svelte-put/tree/main/packages/misc/modal) is a solution to async, declarative, type-safe modals in Svelte
- [Kitty](https://github.com/grottopress/kitty) is a collection of libraries and handlers for developing secure frontend apps
- [svelte-turnstile](https://github.com/ghostdevv/svelte-turnstile) is a component for Cloudflare Turnstile, the privacy focused CAPTCHA replacement
_UI Kits and Starters_
- [QWER](https://github.com/kwchang0831/svelte-QWER) is a blog starter built with SvelteKit
- [SvelteKit Zero API](https://github.com/Refzlund/sveltekit-zero-api) provides type-safety between the frontend and backend - creating a structure for easy APIs
- [sveltekit-monorepo](https://github.com/sw-yx/sveltekit-monorepo) is monorepo starter with 2022 tech
- [svelte-component-test-recipes](https://github.com/davipon/svelte-component-test-recipes) uses `vitest`, `@testing-library/svelte`, and `svelte-htm` to test Svelte components that seemed to be hard to test
Whew! That's a lot of updates. Let us know if we missed anything on [Reddit](https://www.reddit.com/r/sveltejs/) or [Discord](https://discord.com/invite/yy75DKs)!
See ya next month 👋

@ -0,0 +1,116 @@
---
title: "What's new in Svelte: November 2022"
description: "Better forms, routes and inline styles across SvelteKit and Svelte"
author: Daniel Sandoval
authorURL: https://desandoval.net
---
It's been a busy October for the Svelte community. `use:enhance` and Advanced Routes got some great improvements in SvelteKit while the Svelte compiler released a minor version to improve the day-to-day dev experience.
There's also a _huge_ showcase to cover... so let's jump in!
## What's new in SvelteKit
- The new `update` method for `use:enhance` lets you easily get back the default form behavior while augmenting it with your own logic ([docs](https://kit.svelte.dev/docs/form-actions#progressive-enhancement-use-enhance), [#7083](https://github.com/sveltejs/kit/pull/7083) and [#7326](https://github.com/sveltejs/kit/pull/7326))
- `[[optional]]` parameters are now available for routing ([docs](https://kit.svelte.dev/docs/advanced-routing#optional-parameters), [#7051](https://github.com/sveltejs/kit/pull/7051))
- `goto` now has `invalidateAll` to (re-)run all `load` functions belonging to the new active page ([docs](https://kit.svelte.dev/docs/modules#app-navigation-goto), [#7407](https://github.com/sveltejs/kit/pull/7407))
- `config.kit.paths.base` is now used in adapters looking for static assets - fixing 404 issues across `adapter-netlify`, `adapter-vercel`, `adapter-cloudflare`, and `adapter-cloudflare-workers` ([#4448](https://github.com/sveltejs/kit/pull/4448))
**Breaking changes:**
- Errors will now be thrown when routes conflict ([#7051](https://github.com/sveltejs/kit/pull/7051))
- The global `fetch` override has been removed when prerendering ([#7318](https://github.com/sveltejs/kit/pull/7318))
- Route IDs have been prefixed with `/` ([#7338](https://github.com/sveltejs/kit/pull/7338))
## Svelte changes
- New accessibility warnings, `a11y-click-events-have-key-events` and `a11y-no-noninteractive-tabindex`, will now warn when your components lack required key events or tabindex. While `a11y-role-has-required-aria-props` will no longer warn when elements match their semantic role (**3.51.0**)
- `--style-props` are now supported on `<svelte:component>` and `<svg>` (**3.51.0**, Component Example: [Before](https://svelte.dev/repl/48984f20503f4959b70f24f4130d164b?version=3.47.0) and [After](https://svelte.dev/repl/48984f20503f4959b70f24f4130d164b?version=3.51.0), SVG Example: [Before](https://svelte.dev/repl/b7a3f94255914044b35462234ccaea43?version=3.50.0) and [After](https://svelte.dev/repl/b7a3f94255914044b35462234ccaea43?version=3.51.0))
- "nullish" values for component event handlers are now supported (**3.51.0**, [Example](https://svelte.dev/repl/9228022922af4c76af68ce42349ccbf9?version=3.51.0))
- Scoped styles can now be applied to `<svelte:element>` (**3.51.0**, [Example](https://svelte.dev/repl/23d982fc6f4f4f06a6aa227860fa2d84?version=3.51.0))
- You can now use `important` in inline style tags: `style:foo|important` (**3.52.0**, [#7365](https://github.com/sveltejs/svelte/issues/7365))
- A warning will now be thrown when using `<a target="_blank">` without `rel="noreferrer"` (**3.52.0**, [#6188](https://github.com/sveltejs/svelte/issues/6188))
Tom Smykowski also released a great summary of [all the changes in 3.52.0](https://tomaszs2.medium.com/svelte-3-52-0-improves-dev-experience-45f8c460bb96)! For all the changes to the Svelte compiler, including upcoming changes, check out the [CHANGELOG](https://github.com/sveltejs/svelte/blob/master/CHANGELOG.md).
---
## Community Showcase
**Apps & Sites built with Svelte**
- [AttendZen](https://www.attendzen.io/) is an event management and marketing platform for in-person, virtual or hybrid events
- [Gram Jam](https://gramjam.app/) is a challenging daily word game using SvelteKit
- [Collabwriting](https://collabwriting.com/) is an actionable knowledge base for your team
- [Dazzle](https://dazzlega.me/) is a Puzzle Game made with Svelte & DallE
- [Figma Autoname plugin](https://github.com/Hugo-Dz/figma_autoname_client_app) automatically names your Figma layers in one click
- [DECK](https://github.com/sfx101/deck) is powerful and high performant local web development studio for MacOS, Ubuntu and Windows
- [Asm editor](https://github.com/Specy/asm-editor) is a webapp to write and run m68k assembly code
- [Nucleus](https://github.com/mellobacon/Nucleus) is a text editor featuring a clean and easy to use user interface inspired by Visual Studio Code, Atom, Fleet, and others
- [Observer](https://github.com/diamonc/observer) is a frontend for Arth Panel, an open-source & self-hosted minecraft server panel
- [.PLAN](https://plan.lodzero.com/) is a cloud-based notetaking app with markdown and section support
- [Stablecog](https://github.com/yekta/stablecog) is a simple, free & open source AI image generator
- [FreeSpeech AAC](https://github.com/merkie/freespeech) is a free and open-source assistive communication app written in TypeScript
- [sqrdoff](https://sqrdoff.cubieverse.co/) is a dots and boxes to enjoy playing with friends
- [itty](https://launch.itty-sh.pages.dev/) is a fresh take on the traditional link-shortener
- [splits](https://splits.best/) lets you track your splits, calculate your race pace, become a better athlete
- [Weaver](https://jrende.xyz/weaver/) is an application for creating [weave drafts](https://www.gistyarn.com/blogs/how-to-weave/how-to-read-a-weaving-draft)
**Learning Resources**
_To Watch_
- [Starting With Svelte - Brittney Postma](https://www.youtube.com/watch?v=pdKJzrPA0DY) by fitcevents
- [Learn Svelte from scratch with Geoff Rich: A Svelte tutorial](https://www.youtube.com/watch?v=QoR0AZ-Rov8) by Kelvin Omereshone
- [How To Connect to MongoDB in Svelte Kit](https://www.youtube.com/watch?v=gwktlvFHLMA) by LevelUpTuts
- [SvelteKit Authentication Using Cookies](https://www.youtube.com/watch?v=E3VG-dLCRUk), [Make A Typing Game With Svelte](https://www.youtube.com/watch?v=kMz_Ba_OF2w) and [SvelteKit Tailwind CSS Setup With Automatic Class Sorting](https://www.youtube.com/watch?v=J_G_xP0chog) by Joy of Code
- [Authentication with SvelteKit & PocketBase](https://www.youtube.com/watch?v=doDKaKDvB30) and [Form Actions in SvelteKit (+page)](https://www.youtube.com/watch?v=52nXUwQWeKI) by Huntabyte
- [Sybil - Episode 1 - Rust knowledge management with SurrealDB](https://www.youtube.com/watch?v=eC7IePI5rIk) by Raphael Darley
_To Read_
- [4 things I miss from Svelte after working in React](https://geoffrich.net/posts/4-things-i-miss-svelte/) and [Create dynamic social card images with Svelte components](https://geoffrich.net/posts/svelte-social-image/) by Geoff Rich
- [First-class Vite support in Storybook 7.0](https://storybook.js.org/blog/first-class-vite-support-in-storybook/) (Svelte and SvelteKit included) by Ian VanSchooten
- [Better Svelte support is coming to WebStorm](https://blog.jetbrains.com/webstorm/2022/09/webstorm-2022-3-eap1/#information_regarding_svelte_support) from JetBrains
- [Dark Mode](https://pyronaur.com/dark-mode/) Toggle by pyronaur
- [HeadlessUI Components with Svelte](https://www.captaincodeman.com/headlessui-components-with-svelte) by Captain Codeman
- [Using Sequelize with SvelteKit](https://cherrific.io/0xedB00816FB204b4CD9bCb45FF2EF693E99723484/story/23) by MetaZebre
- [Implementing Maintenance mode on a SvelteKit site](https://blog.encodeart.dev/implementing-maintenance-mode-on-a-sveltekit-site) by Andreas Söderlund
- [ActionStore: Real-time Svelte stores for Rails](https://dev.to/buhrmi/actionstore-real-time-svelte-stores-for-rails-4jhg) by Stefan Buhrmester
- [Svelte CSS Image Slider: with Bouncy Overscroll](https://rodneylab.com/svelte-css-image-slider/) and [SvelteKit Local Edge Functions: Edge on Localhost](https://rodneylab.com/sveltekit-local-edge-functions/) by Rodney Lab
- [Creating a Svelte Tabs component with Slot props](https://blog.openreplay.com/creating-a-svelte-tabs-component-with-slot-props/) by Shinichi Okada
- [Sky Cart: An Open Source, cloud-agnostic shopping cart using Stripe Checkout](https://dev.to/stripe/sky-cart-an-open-source-cloud-agnostic-shopping-cart-using-stripe-checkout-o5k) by Mike Bifulco for Stripe
**Libraries, Tools & Components**
- [Threlte](https://threlte.xyz/) is a component library for Svelte to build and render three.js scenes declaratively and state-driven in Svelte apps. It's being featured again to highlight the new "Playground" button in its examples
- [Svelte Turnstile](https://github.com/ghostdevv/svelte-turnstile) is a library to integrate Cloudflare's Turnstile (a new CAPTCHA alternative) into a Svelte app
- [ActionStore](https://github.com/buhrmi/actionstore) allows you to push data directly into Svelte stores via ActionCable
- [SvelteKit + `<is-land>`](https://sveltekit-is-land.vercel.app/) is an experiment with partial hydration in SvelteKit using `@11ty/is-land`
- [Svelte Color Select](https://github.com/CaptainCodeman/svelte-color-select) is an Okhsv Color Selection component for Svelte using OKLab perceptual colorspace
- [svelte-awesome-color-picker](https://github.com/Ennoriel/svelte-awesome-color-picker) is a highly customizable color picker component library
- [rx-svelte-forms](https://www.npmjs.com/package/rx-svelte-forms) creates reactive Svelte forms inspired by Angular reactive forms
- [svelte-wc-bind](https://www.npmjs.com/package/svelte-wc-bind) enables two way data binding in Svelte web components
- [svelte-preprocess-style-child-component](https://github.com/valterkraemer/svelte-preprocess-style-child-component) allows you to style elements inside a child component using similar syntax as CSS Shadow Parts
- [unplugin-svelte-components](https://www.npmjs.com/package/unplugin-svelte-components) allows for on-demand components auto importing for Svelte
- [sveltekit-search-params](https://github.com/paoloricciuti/sveltekit-search-params) aims to be the fastest way to read AND WRITE from query search params in SvelteKit
- [svelte-crop-window](https://github.com/sabine/svelte-crop-window) is a crop window component for images and videos that supports touch gestures (pinch zoom, rotate, pan), as well as mousewheel zoom, mouse-dragging the image, and rotating on right mouse button
- [Open Graph Image Generation](https://github.com/etherCorps/sveltekit-og) lets you generate open graph images dynamically from HTML/CSS without a browser in SvelteKit
- [Svelte Tap Hold](https://github.com/binsarjr/svelte-taphold) is a minimalistic tap and hold component for Svelte/SvelteKit
- [svelte-copy](https://github.com/ghostdevv/svelte-copy)'s new version lets you customize the events that cause text to be copied to the clipboard
_UI Kits, Integrations and Starters_
- [SvelteKit Statiko](https://github.com/ivodolenc/sveltekit-statiko) is a multi-featured assistant for SvelteKit static projects
- [Svelte-TailwindCSS UI (STWUI)](https://github.com/N00nDay/stwui) is a Tailwind-based UI that is currently in pre-release beta
- [KitBase](https://github.com/kevmodrome/kitbase) is a starter template for SvelteKit and PocketBase
- [UnoCSS Vite Plugin (svelte-scoped)](https://github.com/unocss/unocss/tree/main/examples/sveltekit-scoped) is a scoped-CSS utility for Vite/SvelteKit
- [SvelteKit Auth App](https://github.com/fabiorodriguesroque/sveltekit_auth_app) is an example of how we can create an authentication system with SvelteKit using JsonWebToken and Prisma
- [SvelteKit Supabase Dashboard](https://github.com/xulioc/sveltekit-supabase-dashboard) is a simple dashboard inspired by Supabase UI made with SvelteKit as frontend and Supabase as backend
- [Supakit](https://github.com/j4w8n/supakit) is a Supabase auth helper for SvelteKit
- [@bun-community/sveltekit-adapter-bun](https://www.npmjs.com/package/@bun-community/sveltekit-adapter-bun) is an adapter for SvelteKit apps that generates a standalone Bun server
- [hooks-as-store](https://github.com/micha-lmxt/hooks-as-store) lets you use React custom hooks in Svelte Apps
_Fun ones_
- [svelte-typewriter-store](https://github.com/paoloricciuti/svelte-typewriter-store) is the simplest way to get a rotating typewriter effect in Svelte
- [Aksel](https://www.npmjs.com/package/aksel) is the seagull you needed on your site
- [Svelte-Dodge](https://github.com/WbaN314/svelte-dodge) makes components dodge the pointer
That's it for this month! Let us know if we missed anything on [Reddit](https://www.reddit.com/r/sveltejs/) or [Discord](https://discord.gg/svelte)
See ya next month 👋

@ -0,0 +1,22 @@
---
title: Getting started
---
---
To try Svelte in an interactive online environment you can try [the REPL](https://svelte.dev/repl) or [StackBlitz](https://node.new/svelte).
To create a project locally, run:
```
npm create vite@latest myapp -- --template svelte
cd myapp
npm install
npm run dev
```
Or use [SvelteKit](https://kit.svelte.dev/), the official application framework from the Svelte team (currently in release candidate status).
The Svelte team maintains a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode) and the Svelte community has created a list of integrations with various other [tooling and editors](https://sveltesociety.dev/tools).
If you're having trouble, get help on [Discord](https://svelte.dev/chat) or [StackOverflow](https://stackoverflow.com/questions/tagged/svelte).

@ -192,6 +192,25 @@ Total: {total}
</button> </button>
``` ```
---
It is important to note that the reactive blocks are ordered via simple static analysis at compile time, and all the compiler looks at are the variables that are assigned to and used within the block itself, not in any functions called by them. This means that `yDependent` will not be updated when `x` is updated in the following example:
```sv
<script>
let x = 0;
let y = 0;
const setY = (value) => {
y = value;
}
$: yDependent = y;
$: setY(x);
</script>
```
Moving the line `$: yDependent = y` below `$: setY(x)` will cause `yDependent` to be updated when `x` is updated.
--- ---
If a statement consists entirely of an assignment to an undeclared variable, Svelte will inject a `let` declaration on your behalf. If a statement consists entirely of an assignment to an undeclared variable, Svelte will inject a `let` declaration on your behalf.

@ -158,7 +158,7 @@ You can use HTML comments inside components.
--- ---
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. 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.
```sv ```sv
<!-- svelte-ignore a11y-autofocus --> <!-- svelte-ignore a11y-autofocus -->
@ -593,7 +593,7 @@ The simplest bindings reflect the value of a property, such as `input.value`.
--- ---
If the name matches the value, you can use a shorthand. If the name matches the value, you can use shorthand.
```sv ```sv
<!-- These are equivalent --> <!-- These are equivalent -->
@ -750,7 +750,7 @@ Videos additionally have readonly `videoWidth` and `videoHeight` bindings.
--- ---
Block-level elements have 4 readonly bindings, measured using a technique similar to [this one](http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/): Block-level elements have 4 read-only bindings, measured using a technique similar to [this one](http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/):
* `clientWidth` * `clientWidth`
* `clientHeight` * `clientHeight`
@ -874,6 +874,9 @@ The `style:` directive provides a shorthand for setting multiple styles on an el
<!-- Multiple styles can be included --> <!-- Multiple styles can be included -->
<div style:color style:width="12rem" style:background-color={darkMode ? "black" : "white"}>...</div> <div style:color style:width="12rem" style:background-color={darkMode ? "black" : "white"}>...</div>
<!-- Styles can be marked as important -->
<div style:color|important="red">...</div>
``` ```
--- ---
@ -981,7 +984,7 @@ transition = (node: HTMLElement, params: any) => {
A transition is triggered by an element entering or leaving the DOM as a result of a state change. A transition is triggered by an element entering or leaving the DOM as a result of a state change.
When a block is transitioning out, all elements inside the block, including those that do not have their own transitions, are kept in the DOM until every transition in the block has completed. When a block is transitioning out, all elements inside the block, including those that do not have their own transitions, are kept in the DOM until every transition in the block has been completed.
The `transition:` directive indicates a *bidirectional* transition, which means it can be smoothly reversed while the transition is in progress. The `transition:` directive indicates a *bidirectional* transition, which means it can be smoothly reversed while the transition is in progress.
@ -1241,7 +1244,7 @@ As with actions and transitions, animations can have parameters.
--- ---
Animations can use custom functions that provide the `node`, an `animation` object and any `parameters` as arguments. The `animation` parameter is an object containing `from` and `to` properties each containing a [DOMRect](https://developer.mozilla.org/en-US/docs/Web/API/DOMRect#Properties) describing the geometry of the element in its `start` and `end` positions. The `from` property is the DOMRect of the element in its starting position, the `to` property is the DOMRect of the element in its final position after the list has been reordered and the DOM updated. Animations can use custom functions that provide the `node`, an `animation` object and any `parameters` as arguments. The `animation` parameter is an object containing `from` and `to` properties each containing a [DOMRect](https://developer.mozilla.org/en-US/docs/Web/API/DOMRect#Properties) describing the geometry of the element in its `start` and `end` positions. The `from` property is the DOMRect of the element in its starting position, and the `to` property is the DOMRect of the element in its final position after the list has been reordered and the DOM updated.
If the returned object has a `css` method, Svelte will create a CSS animation that plays on the element. If the returned object has a `css` method, Svelte will create a CSS animation that plays on the element.
@ -1374,7 +1377,23 @@ Desugars to this:
--- ---
Svelte's CSS Variables support allows for easily themable components: For SVG namespace, the example above desugars into using `<g>` instead:
```sv
<g style="--rail-color: black; --track-color: rgb(0, 0, 255)">
<Slider
bind:value
min={0}
max={100}
/>
</g>
```
**Note**: Since this is an extra `<g>`, beware that your CSS structure might accidentally target this. Be mindful of this added wrapper element when using this feature.
---
Svelte's CSS Variables support allows for easily themeable components:
```sv ```sv
<!-- Slider.svelte --> <!-- Slider.svelte -->
@ -1638,7 +1657,7 @@ If `this` is falsy, no component is rendered.
The `<svelte:element>` element lets you render an element of a dynamically specified type. This is useful for example when displaying rich text content from a CMS. Any properties and event listeners present will be applied to the element. The `<svelte:element>` element lets you render an element of a dynamically specified type. This is useful for example when displaying rich text content from a CMS. Any properties and event listeners present will be applied to the element.
The only supported binding is `bind:this`, since the element type specific bindings that Svelte does at build time (e.g. `bind:value` for input elements) does not work with a dynamic tag type. The only supported binding is `bind:this`, since the element type specific bindings that Svelte does at build time (e.g. `bind:value` for input elements) do not work with a dynamic tag type.
If `this` has a nullish value, the element and its children will not be rendered. If `this` has a nullish value, the element and its children will not be rendered.

@ -268,7 +268,7 @@ Some HTML elements have default ARIA roles. Giving these elements an ARIA role t
Tab key navigation should be limited to elements on the page that can be interacted with. Tab key navigation should be limited to elements on the page that can be interacted with.
```sv ```sv
<!-- A11y: not interactive element cannot have positive tabIndex value --> <!-- A11y: noninteractive element cannot have positive tabIndex value -->
<div tabindex='0' /> <div tabindex='0' />
``` ```

@ -11,7 +11,7 @@
<ul> <ul>
{#each cats as { id, name }, i} {#each cats as { id, name }, i}
<li> <li>
<a target="_blank" href="https://www.youtube.com/watch?v={id}"> <a target="_blank" rel="noreferrer" href="https://www.youtube.com/watch?v={id}">
{i + 1}: {name} {i + 1}: {name}
</a> </a>
</li> </li>

@ -10,7 +10,7 @@
{#if yes} {#if yes}
<p>Thank you. We will bombard your inbox and sell your personal details.</p> <p>Thank you. We will bombard your inbox and sell your personal details.</p>
{:else} {:else}
<p>You must opt in to continue. If you're not paying, you're the product.</p> <p>You must opt-in to continue. If you're not paying, you're the product.</p>
{/if} {/if}
<button disabled={!yes}> <button disabled={!yes}>

@ -43,4 +43,8 @@
background-color: #ff3e00; background-color: #ff3e00;
color: white; color: white;
} }
p {
pointer-events: none;
}
</style> </style>

@ -4,7 +4,7 @@ title: Basics
Welcome to the Svelte tutorial. This will teach you everything you need to know to build fast, small web applications easily. Welcome to the Svelte tutorial. This will teach you everything you need to know to build fast, small web applications easily.
You can also consult the [API docs](/docs) and the [examples](/examples), or — if you're impatient to start hacking on your machine locally — the [60-second quickstart](/blog/the-easiest-way-to-get-started). You can also consult the [API docs](/docs) and the [examples](/examples), or — if you're impatient to start hacking on your machine locally — the [60-second quickstart](/docs#getting-started).
## What is Svelte? ## What is Svelte?
@ -24,7 +24,7 @@ You'll need to have basic familiarity with HTML, CSS and JavaScript to understan
As you progress through the tutorial, you'll be presented with mini exercises designed to illustrate new features. Later chapters build on the knowledge gained in earlier ones, so it's recommended that you go from start to finish. If necessary, you can navigate via the dropdown above (click 'Introduction / Basics'). As you progress through the tutorial, you'll be presented with mini exercises designed to illustrate new features. Later chapters build on the knowledge gained in earlier ones, so it's recommended that you go from start to finish. If necessary, you can navigate via the dropdown above (click 'Introduction / Basics').
Each tutorial chapter will have a 'Show me' button that you can click if you get stuck following the instructions. Try not to rely on it too much; you will learn faster by figuring out where to put each suggested code block and manually typing it in to the editor. Each tutorial chapter will have a 'Show me' button that you can click if you get stuck following the instructions. Try not to rely on it too much; you will learn faster by figuring out where to put each suggested code block and manually typing it into the editor.
## Understanding components ## Understanding components

@ -10,7 +10,7 @@
{#if yes} {#if yes}
<p>Thank you. We will bombard your inbox and sell your personal details.</p> <p>Thank you. We will bombard your inbox and sell your personal details.</p>
{:else} {:else}
<p>You must opt in to continue. If you're not paying, you're the product.</p> <p>You must opt-in to continue. If you're not paying, you're the product.</p>
{/if} {/if}
<button disabled={!yes}> <button disabled={!yes}>

@ -10,7 +10,7 @@
{#if yes} {#if yes}
<p>Thank you. We will bombard your inbox and sell your personal details.</p> <p>Thank you. We will bombard your inbox and sell your personal details.</p>
{:else} {:else}
<p>You must opt in to continue. If you're not paying, you're the product.</p> <p>You must opt-in to continue. If you're not paying, you're the product.</p>
{/if} {/if}
<button disabled={!yes}> <button disabled={!yes}>

@ -4,7 +4,7 @@ title: Congratulations!
You've now finished the Svelte tutorial and are ready to start building apps. You can refer back to individual chapters at any time (click the title above to reveal a dropdown) or continue your learning via the [API reference](/docs), [Examples](/examples) and [Blog](/blog). If you're a Twitter user, you can get updates via [@sveltejs](https://twitter.com/sveltejs). You've now finished the Svelte tutorial and are ready to start building apps. You can refer back to individual chapters at any time (click the title above to reveal a dropdown) or continue your learning via the [API reference](/docs), [Examples](/examples) and [Blog](/blog). If you're a Twitter user, you can get updates via [@sveltejs](https://twitter.com/sveltejs).
To get set up in your local development environment, check out [the quickstart guide](/blog/the-easiest-way-to-get-started). To get set up in your local development environment, check out [the quickstart guide](/docs#getting-started).
If you're looking for a more expansive framework that includes routing, server-side rendering and everything else, take a look at [SvelteKit](https://kit.svelte.dev). If you're looking for a more expansive framework that includes routing, server-side rendering and everything else, take a look at [SvelteKit](https://kit.svelte.dev).

@ -47,6 +47,10 @@ interface ComponentOptions {
preserveWhitespace?: boolean; preserveWhitespace?: boolean;
} }
const regex_leading_directory_separator = /^[/\\]/;
const regex_starts_with_term_export = /^Export/;
const regex_contains_term_function = /Function/;
export default class Component { export default class Component {
stats: Stats; stats: Stats;
warnings: Warning[]; warnings: Warning[];
@ -136,7 +140,7 @@ export default class Component {
(typeof process !== 'undefined' (typeof process !== 'undefined'
? compile_options.filename ? compile_options.filename
.replace(process.cwd(), '') .replace(process.cwd(), '')
.replace(/^[/\\]/, '') .replace(regex_leading_directory_separator, '')
: compile_options.filename); : compile_options.filename);
this.locate = getLocator(this.source, { offsetLine: 1 }); this.locate = getLocator(this.source, { offsetLine: 1 });
@ -638,7 +642,7 @@ export default class Component {
body.splice(i, 1); body.splice(i, 1);
} }
if (/^Export/.test(node.type)) { if (regex_starts_with_term_export.test(node.type)) {
const replacement = this.extract_exports(node, true); const replacement = this.extract_exports(node, true);
if (replacement) { if (replacement) {
body[i] = replacement; body[i] = replacement;
@ -788,6 +792,42 @@ export default class Component {
scope = map.get(node); scope = map.get(node);
} }
let deep = false;
let names: string[] | undefined;
if (node.type === 'AssignmentExpression') {
deep = node.left.type === 'MemberExpression';
names = deep
? [get_object(node.left).name]
: extract_names(node.left);
} else if (node.type === 'UpdateExpression') {
deep = node.argument.type === 'MemberExpression';
const { name } = get_object(node.argument);
names = [name];
}
if (names) {
names.forEach(name => {
let current_scope = scope;
let declaration;
while (current_scope) {
if (current_scope.declarations.has(name)) {
declaration = current_scope.declarations.get(name);
break;
}
current_scope = current_scope.parent;
}
if (declaration && declaration.kind === 'const' && !deep) {
component.error(node as any, {
code: 'assignment-to-const',
message: 'You are assigning to a const'
});
}
});
}
if (node.type === 'ImportDeclaration') { if (node.type === 'ImportDeclaration') {
component.extract_imports(node); component.extract_imports(node);
// TODO: to use actual remove // TODO: to use actual remove
@ -795,7 +835,7 @@ export default class Component {
return this.skip(); return this.skip();
} }
if (/^Export/.test(node.type)) { if (regex_starts_with_term_export.test(node.type)) {
const replacement = component.extract_exports(node); const replacement = component.extract_exports(node);
if (replacement) { if (replacement) {
this.replace(replacement); this.replace(replacement);
@ -918,7 +958,7 @@ export default class Component {
} }
if (name[1] !== '$' && scope.has(name.slice(1)) && scope.find_owner(name.slice(1)) !== this.instance_scope) { if (name[1] !== '$' && scope.has(name.slice(1)) && scope.find_owner(name.slice(1)) !== this.instance_scope) {
if (!((/Function/.test(parent.type) && prop === 'params') || (parent.type === 'VariableDeclarator' && prop === 'id'))) { if (!((regex_contains_term_function.test(parent.type) && prop === 'params') || (parent.type === 'VariableDeclarator' && prop === 'id'))) {
return this.error(node as any, compiler_errors.contextual_store); return this.error(node as any, compiler_errors.contextual_store);
} }
} }
@ -965,7 +1005,7 @@ export default class Component {
walk(this.ast.instance.content, { walk(this.ast.instance.content, {
enter(node: Node) { enter(node: Node) {
if (/Function/.test(node.type)) { if (regex_contains_term_function.test(node.type)) {
return this.skip(); return this.skip();
} }
@ -1089,7 +1129,7 @@ export default class Component {
this.replace(b` this.replace(b`
${node.declarations.length ? node : null} ${node.declarations.length ? node : null}
${ props.length > 0 && b`let { ${ props } } = $$props;`} ${ props.length > 0 && b`let { ${props} } = $$props;`}
${inserts} ${inserts}
` as any); ` as any);
return this.skip(); return this.skip();
@ -1460,6 +1500,8 @@ export default class Component {
} }
} }
const regex_valid_tag_name = /^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/;
function process_component_options(component: Component, nodes) { function process_component_options(component: Component, nodes) {
const component_options: ComponentOptions = { const component_options: ComponentOptions = {
immutable: component.compile_options.immutable || false, immutable: component.compile_options.immutable || false,
@ -1473,7 +1515,7 @@ function process_component_options(component: Component, nodes) {
const node = nodes.find(node => node.name === 'svelte:options'); const node = nodes.find(node => node.name === 'svelte:options');
function get_value(attribute, {code, message}) { function get_value(attribute, { code, message }) {
const { value } = attribute; const { value } = attribute;
const chunk = value[0]; const chunk = value[0];
@ -1505,7 +1547,7 @@ function process_component_options(component: Component, nodes) {
return component.error(attribute, compiler_errors.invalid_tag_attribute); return component.error(attribute, compiler_errors.invalid_tag_attribute);
} }
if (tag && !/^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/.test(tag)) { if (tag && !regex_valid_tag_name.test(tag)) {
return component.error(attribute, compiler_errors.invalid_tag_property); return component.error(attribute, compiler_errors.invalid_tag_property);
} }

@ -281,5 +281,9 @@ export default {
invalid_component_style_directive: { invalid_component_style_directive: {
code: 'invalid-component-style-directive', code: 'invalid-component-style-directive',
message: 'Style directives cannot be used on components' message: 'Style directives cannot be used on components'
} },
invalid_style_directive_modifier: (valid: string) => ({
code: 'invalid-style-directive-modifier',
message: `Valid modifiers for style directives are: ${valid}`
})
}; };

@ -185,7 +185,7 @@ export default {
}), }),
a11y_no_noninteractive_tabindex: { a11y_no_noninteractive_tabindex: {
code: 'a11y-no-noninteractive-tabindex', code: 'a11y-no-noninteractive-tabindex',
message: 'A11y: not interactive element cannot have positive tabIndex value' message: 'A11y: noninteractive element cannot have positive tabIndex value'
}, },
redundant_event_modifier_for_touch: { redundant_event_modifier_for_touch: {
code: 'redundant-event-modifier', code: 'redundant-event-modifier',
@ -197,6 +197,6 @@ export default {
}, },
invalid_rest_eachblock_binding: (rest_element_name: string) => ({ invalid_rest_eachblock_binding: (rest_element_name: string) => ({
code: 'invalid-rest-eachblock-binding', code: 'invalid-rest-eachblock-binding',
message: `...${rest_element_name} operator will create a new object and binding propogation with original object will not work` message: `...${rest_element_name} operator will create a new object and binding propagation with original object will not work`
}) })
}; };

@ -9,6 +9,7 @@ import EachBlock from '../nodes/EachBlock';
import IfBlock from '../nodes/IfBlock'; import IfBlock from '../nodes/IfBlock';
import AwaitBlock from '../nodes/AwaitBlock'; import AwaitBlock from '../nodes/AwaitBlock';
import compiler_errors from '../compiler_errors'; import compiler_errors from '../compiler_errors';
import { regex_starts_with_whitespace, regex_ends_with_whitespace } from '../../utils/patterns';
enum BlockAppliesToNode { enum BlockAppliesToNode {
NotPossible, NotPossible,
@ -25,6 +26,8 @@ const whitelist_attribute_selector = new Map([
['dialog', new Set(['open'])] ['dialog', new Set(['open'])]
]); ]);
const regex_is_single_css_selector = /[^\\],(?!([^([]+[^\\]|[^([\\])[)\]])/;
export default class Selector { export default class Selector {
node: CssNode; node: CssNode;
stylesheet: Stylesheet; stylesheet: Stylesheet;
@ -157,7 +160,7 @@ export default class Selector {
for (const block of this.blocks) { for (const block of this.blocks) {
for (const selector of block.selectors) { for (const selector of block.selectors) {
if (selector.type === 'PseudoClassSelector' && selector.name === 'global') { if (selector.type === 'PseudoClassSelector' && selector.name === 'global') {
if (/[^\\],(?!([^([]+[^\\]|[^([\\])[)\]])/.test(selector.children[0].value)) { if (regex_is_single_css_selector.test(selector.children[0].value)) {
component.error(selector, compiler_errors.css_invalid_global_selector); component.error(selector, compiler_errors.css_invalid_global_selector);
} }
} }
@ -209,7 +212,7 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: Array<{
} }
if (block.combinator) { if (block.combinator) {
if (block.combinator.type === 'WhiteSpace') { if (block.combinator.type === 'Combinator' && block.combinator.name === ' ') {
for (const ancestor_block of blocks) { for (const ancestor_block of blocks) {
if (ancestor_block.global) { if (ancestor_block.global) {
continue; continue;
@ -281,12 +284,14 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: Array<{
return true; return true;
} }
const regex_backslash_and_following_character = /\\(.)/g;
function block_might_apply_to_node(block: Block, node: Element): BlockAppliesToNode { function block_might_apply_to_node(block: Block, node: Element): BlockAppliesToNode {
let i = block.selectors.length; let i = block.selectors.length;
while (i--) { while (i--) {
const selector = block.selectors[i]; const selector = block.selectors[i];
const name = typeof selector.name === 'string' && selector.name.replace(/\\(.)/g, '$1'); const name = typeof selector.name === 'string' && selector.name.replace(regex_backslash_and_following_character, '$1');
if (selector.type === 'PseudoClassSelector' && (name === 'host' || name === 'root')) { if (selector.type === 'PseudoClassSelector' && (name === 'host' || name === 'root')) {
return BlockAppliesToNode.NotPossible; return BlockAppliesToNode.NotPossible;
@ -371,7 +376,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
const start_with_space = []; const start_with_space = [];
const remaining = []; const remaining = [];
current_possible_values.forEach((current_possible_value: string) => { current_possible_values.forEach((current_possible_value: string) => {
if (/^\s/.test(current_possible_value)) { if (regex_starts_with_whitespace.test(current_possible_value)) {
start_with_space.push(current_possible_value); start_with_space.push(current_possible_value);
} else { } else {
remaining.push(current_possible_value); remaining.push(current_possible_value);
@ -392,7 +397,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
prev_values = combined; prev_values = combined;
start_with_space.forEach((value: string) => { start_with_space.forEach((value: string) => {
if (/\s$/.test(value)) { if (regex_ends_with_whitespace.test(value)) {
possible_values.add(value); possible_values.add(value);
} else { } else {
prev_values.push(value); prev_values.push(value);
@ -406,7 +411,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
} }
current_possible_values.forEach((current_possible_value: string) => { current_possible_values.forEach((current_possible_value: string) => {
if (/\s$/.test(current_possible_value)) { if (regex_ends_with_whitespace.test(current_possible_value)) {
possible_values.add(current_possible_value); possible_values.add(current_possible_value);
} else { } else {
prev_values.push(current_possible_value); prev_values.push(current_possible_value);

@ -9,9 +9,12 @@ import hash from '../utils/hash';
import compiler_warnings from '../compiler_warnings'; import compiler_warnings from '../compiler_warnings';
import { extract_ignores_above_position } from '../../utils/extract_svelte_ignore'; import { extract_ignores_above_position } from '../../utils/extract_svelte_ignore';
import { push_array } from '../../utils/push_array'; import { push_array } from '../../utils/push_array';
import { regex_only_whitespaces, regex_whitespace } from '../../utils/patterns';
const regex_css_browser_prefix = /^-((webkit)|(moz)|(o)|(ms))-/;
function remove_css_prefix(name: string): string { function remove_css_prefix(name: string): string {
return name.replace(/^-((webkit)|(moz)|(o)|(ms))-/, ''); return name.replace(regex_css_browser_prefix, '');
} }
const is_keyframes_node = (node: CssNode) => const is_keyframes_node = (node: CssNode) =>
@ -147,10 +150,10 @@ class Declaration {
// Don't minify whitespace in custom properties, since some browsers (Chromium < 99) // Don't minify whitespace in custom properties, since some browsers (Chromium < 99)
// treat --foo: ; and --foo:; differently // treat --foo: ; and --foo:; differently
if (first.type === 'Raw' && /^\s+$/.test(first.value)) return; if (first.type === 'Raw' && regex_only_whitespaces.test(first.value)) return;
let start = first.start; let start = first.start;
while (/\s/.test(code.original[start])) start += 1; while (regex_whitespace.test(code.original[start])) start += 1;
if (start - c > 1) { if (start - c > 1) {
code.overwrite(c, start, ':'); code.overwrite(c, start, ':');

@ -35,6 +35,9 @@ const valid_options = [
'cssHash' 'cssHash'
]; ];
const regex_valid_identifier = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/;
const regex_starts_with_lowercase_character = /^[a-z]/;
function validate_options(options: CompileOptions, warnings: Warning[]) { function validate_options(options: CompileOptions, warnings: Warning[]) {
const { name, filename, loopGuardTimeout, dev, namespace } = options; const { name, filename, loopGuardTimeout, dev, namespace } = options;
@ -48,11 +51,11 @@ function validate_options(options: CompileOptions, warnings: Warning[]) {
} }
}); });
if (name && !/^[a-zA-Z_$][a-zA-Z_$0-9]*$/.test(name)) { if (name && !regex_valid_identifier.test(name)) {
throw new Error(`options.name must be a valid identifier (got '${name}')`); throw new Error(`options.name must be a valid identifier (got '${name}')`);
} }
if (name && /^[a-z]/.test(name)) { if (name && regex_starts_with_lowercase_character.test(name)) {
const message = 'options.name should be capitalised'; const message = 'options.name should be capitalised';
warnings.push({ warnings.push({
code: 'options-lowercase-name', code: 'options-lowercase-name',

@ -3,7 +3,7 @@ import get_object from '../utils/get_object';
import Expression from './shared/Expression'; import Expression from './shared/Expression';
import Component from '../Component'; import Component from '../Component';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import {dimensions} from '../../utils/patterns'; import { regex_dimensions } from '../../utils/patterns';
import { Node as ESTreeNode } from 'estree'; import { Node as ESTreeNode } from 'estree';
import { TemplateNode } from '../../interfaces'; import { TemplateNode } from '../../interfaces';
import Element from './Element'; import Element from './Element';
@ -88,7 +88,7 @@ export default class Binding extends Node {
const type = parent.get_static_attribute_value('type'); const type = parent.get_static_attribute_value('type');
this.is_readonly = this.is_readonly =
dimensions.test(this.name) || regex_dimensions.test(this.name) ||
(isElement(parent) && (isElement(parent) &&
((parent.is_media_node() && read_only_media_attributes.has(this.name)) || ((parent.is_media_node() && read_only_media_attributes.has(this.name)) ||
(parent.name === 'input' && type === 'file')) /* TODO others? */); (parent.name === 'input' && type === 'file')) /* TODO others? */);

@ -11,7 +11,7 @@ import StyleDirective from './StyleDirective';
import Text from './Text'; import Text from './Text';
import { namespaces } from '../../utils/namespaces'; import { namespaces } from '../../utils/namespaces';
import map_children from './shared/map_children'; import map_children from './shared/map_children';
import { dimensions, start_newline } from '../../utils/patterns'; import { regex_dimensions, regex_starts_with_newline, regex_non_whitespace_character } from '../../utils/patterns';
import fuzzymatch from '../../utils/fuzzymatch'; import fuzzymatch from '../../utils/fuzzymatch';
import list from '../../utils/list'; import list from '../../utils/list';
import Let from './Let'; import Let from './Let';
@ -203,6 +203,10 @@ function is_valid_aria_attribute_value(schema: ARIAPropertyDefinition, value: st
} }
} }
const regex_any_repeated_whitespaces = /[\s]+/g;
const regex_heading_tags = /^h[1-6]$/;
const regex_illegal_attribute_character = /(^[0-9-.])|[\^$@%&#?!|()[\]{}^*+~;]/;
export default class Element extends Node { export default class Element extends Node {
type: 'Element'; type: 'Element';
name: string; name: string;
@ -246,14 +250,14 @@ export default class Element extends Node {
if (this.name === 'pre' || this.name === 'textarea') { if (this.name === 'pre' || this.name === 'textarea') {
const first = info.children[0]; const first = info.children[0];
if (first && first.type === 'Text') { if (first && first.type === 'Text') {
// The leading newline character needs to be stripped because of a qirk, // The leading newline character needs to be stripped because of a quirk,
// it is ignored by browsers if the tag and its contents are set through // it is ignored by browsers if the tag and its contents are set through
// innerHTML (NOT if set through the innerHTML of the tag or dynamically). // innerHTML (NOT if set through the innerHTML of the tag or dynamically).
// Therefore strip it here but add it back in the appropriate // Therefore strip it here but add it back in the appropriate
// places if there's another newline afterwards. // places if there's another newline afterwards.
// see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions // see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions
// see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element // see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
first.data = first.data.replace(start_newline, ''); first.data = first.data.replace(regex_starts_with_newline, '');
} }
} }
@ -398,7 +402,7 @@ export default class Element extends Node {
// Errors // Errors
if (/(^[0-9-.])|[\^$@%&#?!|()[\]{}^*+~;]/.test(name)) { if (regex_illegal_attribute_character.test(name)) {
return component.error(attribute, compiler_errors.illegal_attribute(name)); return component.error(attribute, compiler_errors.illegal_attribute(name));
} }
@ -464,7 +468,7 @@ export default class Element extends Node {
component.warn(attribute, compiler_warnings.a11y_unknown_aria_attribute(type, match)); component.warn(attribute, compiler_warnings.a11y_unknown_aria_attribute(type, match));
} }
if (name === 'aria-hidden' && /^h[1-6]$/.test(this.name)) { if (name === 'aria-hidden' && regex_heading_tags.test(this.name)) {
component.warn(attribute, compiler_warnings.a11y_hidden(this.name)); component.warn(attribute, compiler_warnings.a11y_hidden(this.name));
} }
@ -609,6 +613,26 @@ export default class Element extends Node {
const href_attribute = attribute_map.get('href') || attribute_map.get('xlink:href'); const href_attribute = attribute_map.get('href') || attribute_map.get('xlink:href');
const id_attribute = attribute_map.get('id'); const id_attribute = attribute_map.get('id');
const name_attribute = attribute_map.get('name'); const name_attribute = attribute_map.get('name');
const target_attribute = attribute_map.get('target');
if (target_attribute && target_attribute.get_static_value() === '_blank' && href_attribute) {
const href_static_value = href_attribute.get_static_value() ? href_attribute.get_static_value().toLowerCase() : null;
if (href_static_value === null || href_static_value.match(/^(https?:)?\/\//i)) {
const rel = attribute_map.get('rel');
const rel_values = rel ? rel.get_static_value().split(' ') : [];
const expected_values = ['noreferrer'];
expected_values.forEach(expected_value => {
if (!rel || rel && rel_values.indexOf(expected_value) < 0) {
component.warn(this, {
code: `security-anchor-rel-${expected_value}`,
message: `Security: Anchor with "target=_blank" should have rel attribute containing the value "${expected_value}"`
});
}
});
}
}
if (href_attribute) { if (href_attribute) {
const href_value = href_attribute.get_static_value(); const href_value = href_attribute.get_static_value();
@ -732,7 +756,7 @@ export default class Element extends Node {
if (this.name === 'figure') { if (this.name === 'figure') {
const children = this.children.filter(node => { const children = this.children.filter(node => {
if (node.type === 'Comment') return false; if (node.type === 'Comment') return false;
if (node.type === 'Text') return /\S/.test(node.data); if (node.type === 'Text') return regex_non_whitespace_character.test(node.data);
return true; return true;
}); });
@ -864,7 +888,7 @@ export default class Element extends Node {
if (this.name !== 'video') { if (this.name !== 'video') {
return component.error(binding, compiler_errors.invalid_binding_element_with('<video>', name)); return component.error(binding, compiler_errors.invalid_binding_element_with('<video>', name));
} }
} else if (dimensions.test(name)) { } else if (regex_dimensions.test(name)) {
if (this.name === 'svg' && (name === 'offsetWidth' || name === 'offsetHeight')) { if (this.name === 'svg' && (name === 'offsetWidth' || name === 'offsetHeight')) {
return component.error(binding, compiler_errors.invalid_binding_on(binding.name, `<svg>. Use '${name.replace('offset', 'client')}' instead`)); return component.error(binding, compiler_errors.invalid_binding_on(binding.name, `<svg>. Use '${name.replace('offset', 'client')}' instead`));
} else if (is_svg(this.name)) { } else if (is_svg(this.name)) {
@ -991,7 +1015,7 @@ export default class Element extends Node {
if (attribute && !attribute.is_true) { if (attribute && !attribute.is_true) {
attribute.chunks.forEach((chunk, index) => { attribute.chunks.forEach((chunk, index) => {
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
let data = chunk.data.replace(/[\s\n\t]+/g, ' '); let data = chunk.data.replace(regex_any_repeated_whitespaces, ' ');
if (index === 0) { if (index === 0) {
data = data.trimLeft(); data = data.trimLeft();
} else if (index === attribute.chunks.length - 1) { } else if (index === attribute.chunks.length - 1) {
@ -1005,12 +1029,14 @@ export default class Element extends Node {
} }
} }
const regex_starts_with_vovel = /^[aeiou]/;
function should_have_attribute( function should_have_attribute(
node, node,
attributes: string[], attributes: string[],
name = node.name name = node.name
) { ) {
const article = /^[aeiou]/.test(attributes[0]) ? 'an' : 'a'; const article = regex_starts_with_vovel.test(attributes[0]) ? 'an' : 'a';
const sequence = attributes.length > 1 ? const sequence = attributes.length > 1 ?
attributes.slice(0, -1).join(', ') + ` or ${attributes[attributes.length - 1]}` : attributes.slice(0, -1).join(', ') + ` or ${attributes[attributes.length - 1]}` :
attributes[0]; attributes[0];
@ -1018,10 +1044,12 @@ function should_have_attribute(
node.component.warn(node, compiler_warnings.a11y_missing_attribute(name, article, sequence)); node.component.warn(node, compiler_warnings.a11y_missing_attribute(name, article, sequence));
} }
const regex_minus_sign = /-/;
function within_custom_element(parent: INode) { function within_custom_element(parent: INode) {
while (parent) { while (parent) {
if (parent.type === 'InlineComponent') return false; if (parent.type === 'InlineComponent') return false;
if (parent.type === 'Element' && /-/.test(parent.name)) return true; if (parent.type === 'Element' && regex_minus_sign.test(parent.name)) return true;
parent = parent.parent; parent = parent.parent;
} }
return false; return false;

@ -6,6 +6,8 @@ import { Identifier } from 'estree';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import { TemplateNode } from '../../interfaces'; import { TemplateNode } from '../../interfaces';
const regex_contains_term_function_expression = /FunctionExpression/;
export default class EventHandler extends Node { export default class EventHandler extends Node {
type: 'EventHandler'; type: 'EventHandler';
name: string; name: string;
@ -25,7 +27,7 @@ export default class EventHandler extends Node {
this.expression = new Expression(component, this, template_scope, info.expression); this.expression = new Expression(component, this, template_scope, info.expression);
this.uses_context = this.expression.uses_context; this.uses_context = this.expression.uses_context;
if (/FunctionExpression/.test(info.expression.type) && info.expression.params.length === 0) { if (regex_contains_term_function_expression.test(info.expression.type) && info.expression.params.length === 0) {
// TODO make this detection more accurate — if `event.preventDefault` isn't called, and // TODO make this detection more accurate — if `event.preventDefault` isn't called, and
// `event` is passed to another function, we can make it passive // `event` is passed to another function, we can make it passive
this.can_make_passive = true; this.can_make_passive = true;
@ -55,7 +57,7 @@ export default class EventHandler extends Node {
} }
const node = this.expression.node; const node = this.expression.node;
if (/FunctionExpression/.test(node.type)) { if (regex_contains_term_function_expression.test(node.type)) {
return false; return false;
} }

@ -5,6 +5,7 @@ import Component from '../Component';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import { TemplateNode } from '../../interfaces'; import { TemplateNode } from '../../interfaces';
import compiler_errors from '../compiler_errors'; import compiler_errors from '../compiler_errors';
import { regex_non_whitespace_character } from '../../utils/patterns';
export default class Head extends Node { export default class Head extends Node {
type: 'Head'; type: 'Head';
@ -20,7 +21,7 @@ export default class Head extends Node {
} }
this.children = map_children(component, parent, scope, info.children.filter(child => { this.children = map_children(component, parent, scope, info.children.filter(child => {
return (child.type !== 'Text' || /\S/.test(child.data)); return (child.type !== 'Text' || regex_non_whitespace_character.test(child.data));
})); }));
if (this.children.length > 0) { if (this.children.length > 0) {

@ -10,6 +10,7 @@ import TemplateScope from './shared/TemplateScope';
import { INode } from './interfaces'; import { INode } from './interfaces';
import { TemplateNode } from '../../interfaces'; import { TemplateNode } from '../../interfaces';
import compiler_errors from '../compiler_errors'; import compiler_errors from '../compiler_errors';
import { regex_only_whitespaces } from '../../utils/patterns';
export default class InlineComponent extends Node { export default class InlineComponent extends Node {
type: 'InlineComponent'; type: 'InlineComponent';
@ -22,6 +23,7 @@ export default class InlineComponent extends Node {
css_custom_properties: Attribute[] = []; css_custom_properties: Attribute[] = [];
children: INode[]; children: INode[];
scope: TemplateScope; scope: TemplateScope;
namespace: string;
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
@ -33,6 +35,7 @@ export default class InlineComponent extends Node {
} }
this.name = info.name; this.name = info.name;
this.namespace = get_namespace(parent, component.namespace);
this.expression = this.name === 'svelte:component' this.expression = this.name === 'svelte:component'
? new Expression(component, this, scope, info.expression) ? new Expression(component, this, scope, info.expression)
@ -163,5 +166,15 @@ export default class InlineComponent extends Node {
} }
function not_whitespace_text(node) { function not_whitespace_text(node) {
return !(node.type === 'Text' && /^\s+$/.test(node.data)); return !(node.type === 'Text' && regex_only_whitespaces.test(node.data));
}
function get_namespace(parent: Node, explicit_namespace: string) {
const parent_element = parent.find_nearest(/^Element/);
if (!parent_element) {
return explicit_namespace;
}
return parent_element.namespace;
} }

@ -1,20 +1,42 @@
import { TemplateNode } from '../../interfaces'; import { TemplateNode } from '../../interfaces';
import list from '../../utils/list';
import compiler_errors from '../compiler_errors';
import Component from '../Component'; import Component from '../Component';
import { nodes_to_template_literal } from '../utils/nodes_to_template_literal'; import { nodes_to_template_literal } from '../utils/nodes_to_template_literal';
import Expression from './shared/Expression'; import Expression from './shared/Expression';
import Node from './shared/Node'; import Node from './shared/Node';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
const valid_modifiers = new Set(['important']);
export default class StyleDirective extends Node { export default class StyleDirective extends Node {
type: 'StyleDirective'; type: 'StyleDirective';
name: string; name: string;
modifiers: Set<string>;
expression: Expression; expression: Expression;
should_cache: boolean; should_cache: boolean;
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { constructor(
component: Component,
parent: Node,
scope: TemplateScope,
info: TemplateNode
) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.name = info.name; this.name = info.name;
this.modifiers = new Set(info.modifiers);
for (const modifier of this.modifiers) {
if (!valid_modifiers.has(modifier)) {
component.error(
this,
compiler_errors.invalid_style_directive_modifier(
list([...valid_modifiers])
)
);
}
}
// Convert the value array to an expression so it's easier to handle // Convert the value array to an expression so it's easier to handle
// the StyleDirective going forward. // the StyleDirective going forward.
@ -34,6 +56,9 @@ export default class StyleDirective extends Node {
this.expression = new Expression(component, this, scope, raw_expression); this.expression = new Expression(component, this, scope, raw_expression);
this.should_cache = raw_expression.expressions.length > 0; this.should_cache = raw_expression.expressions.length > 0;
} }
}
get important() {
return this.modifiers.has('important');
} }
} }

@ -3,6 +3,7 @@ import Component from '../Component';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import { INode } from './interfaces'; import { INode } from './interfaces';
import { TemplateNode } from '../../interfaces'; import { TemplateNode } from '../../interfaces';
import { regex_non_whitespace_character } from '../../utils/patterns';
// Whitespace inside one of these elements will not result in // Whitespace inside one of these elements will not result in
// a whitespace node being created in any circumstances. (This // a whitespace node being created in any circumstances. (This
@ -16,6 +17,8 @@ const elements_without_text = new Set([
'video' 'video'
]); ]);
const regex_ends_with_svg = /svg$/;
export default class Text extends Node { export default class Text extends Node {
type: 'Text'; type: 'Text';
data: string; data: string;
@ -28,7 +31,7 @@ export default class Text extends Node {
} }
should_skip() { should_skip() {
if (/\S/.test(this.data)) return false; if (regex_non_whitespace_character.test(this.data)) return false;
const parent_element = this.find_nearest(/(?:Element|InlineComponent|SlotTemplate|Head)/); const parent_element = this.find_nearest(/(?:Element|InlineComponent|SlotTemplate|Head)/);
if (!parent_element) return false; if (!parent_element) return false;
@ -37,7 +40,7 @@ export default class Text extends Node {
if (parent_element.type === 'InlineComponent') return parent_element.children.length === 1 && this === parent_element.children[0]; if (parent_element.type === 'InlineComponent') return parent_element.children.length === 1 && this === parent_element.children[0];
// svg namespace exclusions // svg namespace exclusions
if (/svg$/.test(parent_element.namespace)) { if (regex_ends_with_svg.test(parent_element.namespace)) {
if (this.prev && this.prev.type === 'Element' && this.prev.name === 'tspan') return false; if (this.prev && this.prev.type === 'Element' && this.prev.name === 'tspan') return false;
} }

@ -4,6 +4,8 @@ import Node from './Node';
import { INode } from '../interfaces'; import { INode } from '../interfaces';
import compiler_warnings from '../../compiler_warnings'; import compiler_warnings from '../../compiler_warnings';
const regex_non_whitespace_characters = /[^ \r\n\f\v\t]/;
export default class AbstractBlock extends Node { export default class AbstractBlock extends Node {
block: Block; block: Block;
children: INode[]; children: INode[];
@ -17,7 +19,7 @@ export default class AbstractBlock extends Node {
const child = this.children[0]; const child = this.children[0];
if (!child || (child.type === 'Text' && !/[^ \r\n\f\v\t]/.test(child.data))) { if (!child || (child.type === 'Text' && !regex_non_whitespace_characters.test(child.data))) {
this.component.warn(this, compiler_warnings.empty_block); this.component.warn(this, compiler_warnings.empty_block);
} }
} }

@ -21,6 +21,8 @@ import compiler_errors from '../../compiler_errors';
type Owner = INode; type Owner = INode;
const regex_contains_term_function_expression = /FunctionExpression/;
export default class Expression { export default class Expression {
type: 'Expression' = 'Expression'; type: 'Expression' = 'Expression';
component: Component; component: Component;
@ -72,7 +74,7 @@ export default class Expression {
scope = map.get(node); scope = map.get(node);
} }
if (!function_expression && /FunctionExpression/.test(node.type)) { if (!function_expression && regex_contains_term_function_expression.test(node.type)) {
function_expression = node; function_expression = node;
} }
@ -126,6 +128,7 @@ export default class Expression {
deep = node.left.type === 'MemberExpression'; deep = node.left.type === 'MemberExpression';
names = extract_names(deep ? get_object(node.left) : node.left); names = extract_names(deep ? get_object(node.left) : node.left);
} else if (node.type === 'UpdateExpression') { } else if (node.type === 'UpdateExpression') {
deep = node.argument.type === 'MemberExpression';
names = extract_names(get_object(node.argument)); names = extract_names(get_object(node.argument));
} }
} }
@ -147,7 +150,26 @@ export default class Expression {
component.add_reference(node, name); component.add_reference(node, name);
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
if (variable) variable[deep ? 'mutated' : 'reassigned'] = true;
if (variable) {
variable[deep ? 'mutated' : 'reassigned'] = true;
}
const declaration: any = scope.find_owner(name)?.declarations.get(name);
if (declaration) {
if (declaration.kind === 'const' && !deep) {
component.error(node, {
code: 'assignment-to-const',
message: 'You are assigning to a const'
});
}
} else if (variable && variable.writable === false && !deep) {
component.error(node, {
code: 'assignment-to-const',
message: 'You are assigning to a const'
});
}
} }
}); });
} }

@ -3,6 +3,7 @@ import Wrapper from './wrappers/shared/Wrapper';
import { b, x } from 'code-red'; import { b, x } from 'code-red';
import { Node, Identifier, ArrayPattern } from 'estree'; import { Node, Identifier, ArrayPattern } from 'estree';
import { is_head } from './wrappers/shared/is_head'; import { is_head } from './wrappers/shared/is_head';
import { regex_double_quotes } from '../../utils/patterns';
export interface Bindings { export interface Bindings {
object: Identifier; object: Identifier;
@ -415,7 +416,7 @@ export default class Block {
block: ${block}, block: ${block},
id: ${this.name || 'create_fragment'}.name, id: ${this.name || 'create_fragment'}.name,
type: "${this.type}", type: "${this.type}",
source: "${this.comment ? this.comment.replace(/"/g, '\\"') : ''}", source: "${this.comment ? this.comment.replace(regex_double_quotes, '\\"') : ''}",
ctx: #ctx ctx: #ctx
}); });
return ${block};` return ${block};`

@ -168,7 +168,7 @@ export default class Renderer {
return member; return member;
} }
invalidate(name: string, value?, main_execution_context: boolean = false) { invalidate(name: string, value?: unknown, main_execution_context: boolean = false) {
return renderer_invalidate(this, name, value, main_execution_context); return renderer_invalidate(this, name, value, main_execution_context);
} }

@ -12,6 +12,7 @@ import { RawSourceMap, DecodedSourceMap } from '@ampproject/remapping/dist/types
import { flatten } from '../../utils/flatten'; import { flatten } from '../../utils/flatten';
import check_enable_sourcemap from '../utils/check_enable_sourcemap'; import check_enable_sourcemap from '../utils/check_enable_sourcemap';
import { push_array } from '../../utils/push_array'; import { push_array } from '../../utils/push_array';
import { regex_backslashes } from '../../utils/patterns';
export default function dom( export default function dom(
component: Component, component: Component,
@ -82,7 +83,7 @@ export default function dom(
} }
const uses_slots = component.var_lookup.has('$$slots'); const uses_slots = component.var_lookup.has('$$slots');
let compute_slots; let compute_slots: Node[] | undefined;
if (uses_slots) { if (uses_slots) {
compute_slots = b` compute_slots = b`
const $$slots = @compute_slots(#slots); const $$slots = @compute_slots(#slots);
@ -121,7 +122,7 @@ export default function dom(
const accessors = []; const accessors = [];
const not_equal = component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; const not_equal = component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`;
let dev_props_check: Node[] | Node; let missing_props_check: Node[] | Node;
let inject_state: Expression; let inject_state: Expression;
let capture_state: Expression; let capture_state: Expression;
let props_inject: Node[] | Node; let props_inject: Node[] | Node;
@ -227,13 +228,13 @@ export default function dom(
const expected = props.filter(prop => prop.writable && !prop.initialised); const expected = props.filter(prop => prop.writable && !prop.initialised);
if (expected.length) { if (expected.length) {
dev_props_check = b` missing_props_check = b`
const { ctx: #ctx } = this.$$; $$self.$$.on_mount.push(function () {
const props = ${options.customElement ? x`this.attributes` : x`options.props || {}`};
${expected.map(prop => b` ${expected.map(prop => b`
if (${renderer.reference(prop.name)} === undefined && !('${prop.export_name}' in props)) { if (${prop.name} === undefined && !(('${prop.export_name}' in $$props) || $$self.$$.bound[$$self.$$.props['${prop.export_name}']])) {
@_console.warn("<${component.tag}> was created without expected prop '${prop.export_name}'"); @_console.warn("<${component.tag}> was created without expected prop '${prop.export_name}'");
}`)} }`)}
});
`; `;
} }
@ -395,7 +396,7 @@ export default function dom(
if (has_definition) { if (has_definition) {
const reactive_declarations: (Node | Node[]) = []; const reactive_declarations: (Node | Node[]) = [];
const fixed_reactive_declarations = []; // not really 'reactive' but whatever const fixed_reactive_declarations: Node[] = []; // not really 'reactive' but whatever
component.reactive_declarations.forEach(d => { component.reactive_declarations.forEach(d => {
const dependencies = Array.from(d.dependencies); const dependencies = Array.from(d.dependencies);
@ -440,7 +441,7 @@ export default function dom(
return b`let ${$name};`; return b`let ${$name};`;
}); });
let unknown_props_check; let unknown_props_check: Node[] | undefined;
if (component.compile_options.dev && !(uses_props || uses_rest)) { if (component.compile_options.dev && !(uses_props || uses_rest)) {
unknown_props_check = b` unknown_props_check = b`
const writable_props = [${writable_props.map(prop => x`'${prop.export_name}'`)}]; const writable_props = [${writable_props.map(prop => x`'${prop.export_name}'`)}];
@ -476,6 +477,7 @@ export default function dom(
${instance_javascript} ${instance_javascript}
${missing_props_check}
${unknown_props_check} ${unknown_props_check}
${renderer.binding_groups.size > 0 && b`const $$binding_groups = [${[...renderer.binding_groups.keys()].map(_ => x`[]`)}];`} ${renderer.binding_groups.size > 0 && b`const $$binding_groups = [${[...renderer.binding_groups.keys()].map(_ => x`[]`)}];`}
@ -529,12 +531,10 @@ export default function dom(
constructor(options) { constructor(options) {
super(); super();
${css.code && b`this.shadowRoot.innerHTML = \`<style>${css.code.replace(/\\/g, '\\\\')}${css_sourcemap_enabled && options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ''}</style>\`;`} ${css.code && b`this.shadowRoot.innerHTML = \`<style>${css.code.replace(regex_backslashes, '\\\\')}${css_sourcemap_enabled && options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ''}</style>\`;`}
@init(this, { target: this.shadowRoot, props: ${init_props}, customElement: true }, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, null, ${dirty}); @init(this, { target: this.shadowRoot, props: ${init_props}, customElement: true }, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, null, ${dirty});
${dev_props_check}
if (options) { if (options) {
if (options.target) { if (options.target) {
@insert(options.target, this, options.anchor); @insert(options.target, this, options.anchor);
@ -594,8 +594,6 @@ export default function dom(
super(${options.dev && 'options'}); super(${options.dev && 'options'});
@init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${optional_parameters}); @init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${optional_parameters});
${options.dev && b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`} ${options.dev && b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`}
${dev_props_check}
} }
} }
`[0] as ClassDeclaration; `[0] as ClassDeclaration;

@ -80,7 +80,7 @@ export function invalidate(renderer: Renderer, scope: Scope, node: Node, names:
return invalidate; return invalidate;
} }
export function renderer_invalidate(renderer: Renderer, name: string, value?, main_execution_context: boolean = false) { export function renderer_invalidate(renderer: Renderer, name: string, value?: unknown, main_execution_context: boolean = false) {
const variable = renderer.component.var_lookup.get(name); const variable = renderer.component.var_lookup.get(name);
if (variable && (variable.subscribable && (variable.reassigned || variable.export_name))) { if (variable && (variable.subscribable && (variable.reassigned || variable.export_name))) {

@ -582,7 +582,7 @@ export default class EachBlockWrapper extends Wrapper {
const start = this.block.has_update_method ? 0 : '#old_length'; const start = this.block.has_update_method ? 0 : '#old_length';
let remove_old_blocks; let remove_old_blocks: Node[];
if (this.block.has_outros) { if (this.block.has_outros) {
const out = block.get_unique_name('out'); const out = block.get_unique_name('out');

@ -10,6 +10,7 @@ import handle_select_value_binding from './handle_select_value_binding';
import { Identifier, Node } from 'estree'; import { Identifier, Node } from 'estree';
import { namespaces } from '../../../../utils/namespaces'; import { namespaces } from '../../../../utils/namespaces';
import { boolean_attributes } from '../../../../../shared/boolean_attributes'; import { boolean_attributes } from '../../../../../shared/boolean_attributes';
import { regex_double_quotes } from '../../../../utils/patterns';
const non_textlike_input_types = new Set([ const non_textlike_input_types = new Set([
'button', 'button',
@ -42,9 +43,12 @@ export class BaseAttributeWrapper {
} }
} }
render(_block: Block) {} render(_block: Block) { }
} }
const regex_minus_sign = /-/;
const regex_invalid_variable_identifier_characters = /[^a-zA-Z_$]/g;
export default class AttributeWrapper extends BaseAttributeWrapper { export default class AttributeWrapper extends BaseAttributeWrapper {
node: Attribute; node: Attribute;
parent: ElementWrapper; parent: ElementWrapper;
@ -115,7 +119,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
// xlink is a special case... we could maybe extend this to generic // xlink is a special case... we could maybe extend this to generic
// namespaced attributes but I'm not sure that's applicable in // namespaced attributes but I'm not sure that's applicable in
// HTML5? // HTML5?
const method = /-/.test(element.node.name) const method = regex_minus_sign.test(element.node.name)
? '@set_custom_element_data' ? '@set_custom_element_data'
: name.slice(0, 6) === 'xlink:' : name.slice(0, 6) === 'xlink:'
? '@xlink_attr' ? '@xlink_attr'
@ -126,7 +130,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
const dependencies = this.get_dependencies(); const dependencies = this.get_dependencies();
const value = this.get_value(block); const value = this.get_value(block);
let updater; let updater: Node[];
const init = this.get_init(block, value); const init = this.get_init(block, value);
if (is_legacy_input_type) { if (is_legacy_input_type) {
@ -196,7 +200,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
get_init(block: Block, value) { get_init(block: Block, value) {
this.last = this.should_cache && block.get_unique_name( this.last = this.should_cache && block.get_unique_name(
`${this.parent.var.name}_${this.name.replace(/[^a-zA-Z_$]/g, '_')}_value` `${this.parent.var.name}_${this.name.replace(regex_invalid_variable_identifier_characters, '_')}_value`
); );
if (this.should_cache) block.add_variable(this.last); if (this.should_cache) block.add_variable(this.last);
@ -254,7 +258,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
return metadata; return metadata;
} }
get_value(block) { get_value(block: Block) {
if (this.node.is_true) { if (this.node.is_true) {
if (this.metadata && boolean_attributes.has(this.metadata.property_name.toLowerCase())) { if (this.metadata && boolean_attributes.has(this.metadata.property_name.toLowerCase())) {
return x`true`; return x`true`;
@ -283,7 +287,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
return value; return value;
} }
get_class_name_text(block) { get_class_name_text(block: Block) {
const scoped_css = this.node.chunks.some((chunk: Text) => chunk.synthetic); const scoped_css = this.node.chunks.some((chunk: Text) => chunk.synthetic);
const rendered = this.render_chunks(block); const rendered = this.render_chunks(block);
@ -313,7 +317,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
return `="${value.map(chunk => { return `="${value.map(chunk => {
return chunk.type === 'Text' return chunk.type === 'Text'
? chunk.data.replace(/"/g, '\\"') ? chunk.data.replace(regex_double_quotes, '\\"')
: `\${${chunk.manipulate()}}`; : `\${${chunk.manipulate()}}`;
}).join('')}"`; }).join('')}"`;
} }
@ -381,13 +385,14 @@ function should_cache(attribute: AttributeWrapper) {
return attribute.is_src || attribute.node.should_cache(); return attribute.is_src || attribute.node.should_cache();
} }
const regex_contains_checked_or_group = /checked|group/;
function is_indirectly_bound_value(attribute: AttributeWrapper) { function is_indirectly_bound_value(attribute: AttributeWrapper) {
const element = attribute.parent; const element = attribute.parent;
return attribute.name === 'value' && return attribute.name === 'value' &&
(element.node.name === 'option' || // TODO check it's actually bound (element.node.name === 'option' || // TODO check it's actually bound
(element.node.name === 'input' && (element.node.name === 'input' &&
element.node.bindings.some( element.node.bindings.some(
(binding) => (binding) => regex_contains_checked_or_group.test(binding.name)
/checked|group/.test(binding.name)
))); )));
} }

@ -299,8 +299,8 @@ function get_binding_group(renderer: Renderer, value: Binding, block: Block) {
for (const dep of contextual_dependencies) { for (const dep of contextual_dependencies) {
const context = block.bindings.get(dep); const context = block.bindings.get(dep);
let key; let key: string;
let name; let name: string;
if (context) { if (context) {
key = context.object.name; key = context.object.name;
name = context.property.name; name = context.property.name;
@ -364,7 +364,7 @@ function get_event_handler(
const contextual_dependencies = new Set<string>(binding.node.expression.contextual_dependencies); const contextual_dependencies = new Set<string>(binding.node.expression.contextual_dependencies);
const context = block.bindings.get(name); const context = block.bindings.get(name);
let set_store; let set_store: Node[] | undefined;
if (context) { if (context) {
const { object, property, store, snippet } = context; const { object, property, store, snippet } = context;

@ -26,7 +26,7 @@ export default class EventHandlerWrapper {
} }
} }
get_snippet(block) { get_snippet(block: Block) {
const snippet = this.node.expression ? this.node.expression.manipulate(block) : block.renderer.reference(this.node.handler_name); const snippet = this.node.expression ? this.node.expression.manipulate(block) : block.renderer.reference(this.node.handler_name);
if (this.node.reassigned) { if (this.node.reassigned) {

@ -69,6 +69,8 @@ export default class StyleAttributeWrapper extends AttributeWrapper {
} }
} }
const regex_style_prop_key = /^\s*([\w-]+):\s*/;
function optimize_style(value: Array<Text | Expression>) { function optimize_style(value: Array<Text | Expression>) {
const props: StyleProp[] = []; const props: StyleProp[] = [];
let chunks = value.slice(); let chunks = value.slice();
@ -78,7 +80,7 @@ function optimize_style(value: Array<Text | Expression>) {
if (chunk.type !== 'Text') return null; if (chunk.type !== 'Text') return null;
const key_match = /^\s*([\w-]+):\s*/.exec(chunk.data); const key_match = regex_style_prop_key.exec(chunk.data);
if (!key_match) return null; if (!key_match) return null;
const key = key_match[1]; const key = key_match[1];
@ -106,6 +108,9 @@ function optimize_style(value: Array<Text | Expression>) {
return props; return props;
} }
const regex_important_flag = /\s*!important\s*$/;
const regex_semicolon_or_whitespace = /[;\s]/;
function get_style_value(chunks: Array<Text | Expression>) { function get_style_value(chunks: Array<Text | Expression>) {
const value: Array<Text | Expression> = []; const value: Array<Text | Expression> = [];
@ -151,7 +156,7 @@ function get_style_value(chunks: Array<Text | Expression>) {
} as Text); } as Text);
} }
while (/[;\s]/.test(chunk.data[c])) c += 1; while (regex_semicolon_or_whitespace.test(chunk.data[c])) c += 1;
const remaining_data = chunk.data.slice(c); const remaining_data = chunk.data.slice(c);
if (remaining_data) { if (remaining_data) {
@ -172,9 +177,9 @@ function get_style_value(chunks: Array<Text | Expression>) {
let important = false; let important = false;
const last_chunk = value[value.length - 1]; const last_chunk = value[value.length - 1];
if (last_chunk && last_chunk.type === 'Text' && /\s*!important\s*$/.test(last_chunk.data)) { if (last_chunk && last_chunk.type === 'Text' && regex_important_flag.test(last_chunk.data)) {
important = true; important = true;
last_chunk.data = last_chunk.data.replace(/\s*!important\s*$/, ''); last_chunk.data = last_chunk.data.replace(regex_important_flag, '');
if (!last_chunk.data) value.pop(); if (!last_chunk.data) value.pop();
} }

@ -6,7 +6,7 @@ svg_attributes.forEach(name => {
svg_attribute_lookup.set(name.toLowerCase(), name); svg_attribute_lookup.set(name.toLowerCase(), name);
}); });
export default function fix_attribute_casing(name) { export default function fix_attribute_casing(name: string) {
name = name.toLowerCase(); name = name.toLowerCase();
return svg_attribute_lookup.get(name) || name; return svg_attribute_lookup.get(name) || name;
} }

@ -12,14 +12,14 @@ import { namespaces } from '../../../../utils/namespaces';
import AttributeWrapper from './Attribute'; import AttributeWrapper from './Attribute';
import StyleAttributeWrapper from './StyleAttribute'; import StyleAttributeWrapper from './StyleAttribute';
import SpreadAttributeWrapper from './SpreadAttribute'; import SpreadAttributeWrapper from './SpreadAttribute';
import { dimensions, start_newline } from '../../../../utils/patterns'; import { regex_dimensions, regex_starts_with_newline, regex_backslashes } from '../../../../utils/patterns';
import Binding from './Binding'; import Binding from './Binding';
import add_to_set from '../../../utils/add_to_set'; import add_to_set from '../../../utils/add_to_set';
import { add_event_handler } from '../shared/add_event_handlers'; import { add_event_handler } from '../shared/add_event_handlers';
import { add_action } from '../shared/add_actions'; import { add_action } from '../shared/add_actions';
import bind_this from '../shared/bind_this'; import bind_this from '../shared/bind_this';
import { is_head } from '../shared/is_head'; import { is_head } from '../shared/is_head';
import { Identifier, ExpressionStatement, CallExpression } from 'estree'; import { Identifier, ExpressionStatement, CallExpression, Node } from 'estree';
import EventHandler from './EventHandler'; import EventHandler from './EventHandler';
import { extract_names } from 'periscopic'; import { extract_names } from 'periscopic';
import Action from '../../../nodes/Action'; import Action from '../../../nodes/Action';
@ -34,12 +34,16 @@ interface BindingGroup {
bindings: Binding[]; bindings: Binding[];
} }
const regex_contains_radio_or_checkbox_or_file = /radio|checkbox|file/;
const regex_contains_radio_or_checkbox_or_range_or_file = /radio|checkbox|range|file/;
const events = [ const events = [
{ {
event_names: ['input'], event_names: ['input'],
filter: (node: Element, _name: string) => filter: (node: Element, _name: string) =>
node.name === 'textarea' || node.name === 'textarea' ||
node.name === 'input' && !/radio|checkbox|range|file/.test(node.get_static_attribute_value('type') as string) node.name === 'input' &&
!regex_contains_radio_or_checkbox_or_range_or_file.test(node.get_static_attribute_value('type') as string)
}, },
{ {
event_names: ['input'], event_names: ['input'],
@ -51,7 +55,8 @@ const events = [
event_names: ['change'], event_names: ['change'],
filter: (node: Element, _name: string) => filter: (node: Element, _name: string) =>
node.name === 'select' || node.name === 'select' ||
node.name === 'input' && /radio|checkbox|file/.test(node.get_static_attribute_value('type') as string) node.name === 'input' &&
regex_contains_radio_or_checkbox_or_file.test(node.get_static_attribute_value('type') as string)
}, },
{ {
event_names: ['change', 'input'], event_names: ['change', 'input'],
@ -62,7 +67,7 @@ const events = [
{ {
event_names: ['elementresize'], event_names: ['elementresize'],
filter: (_node: Element, name: string) => filter: (_node: Element, name: string) =>
dimensions.test(name) regex_dimensions.test(name)
}, },
// media events // media events
@ -136,6 +141,8 @@ const events = [
]; ];
const CHILD_DYNAMIC_ELEMENT_BLOCK = 'child_dynamic_element'; const CHILD_DYNAMIC_ELEMENT_BLOCK = 'child_dynamic_element';
const regex_invalid_variable_identifier_characters = /[^a-zA-Z0-9_$]/g;
const regex_minus_signs = /-/g;
export default class ElementWrapper extends Wrapper { export default class ElementWrapper extends Wrapper {
node: Element; node: Element;
@ -182,7 +189,7 @@ export default class ElementWrapper extends Wrapper {
this.var = { this.var = {
type: 'Identifier', type: 'Identifier',
name: node.name.replace(/[^a-zA-Z0-9_$]/g, '_') name: node.name.replace(regex_invalid_variable_identifier_characters, '_')
}; };
this.void = is_void(node.name); this.void = is_void(node.name);
@ -527,7 +534,7 @@ export default class ElementWrapper extends Wrapper {
.filter((attr) => !(attr instanceof SpreadAttributeWrapper) && !attr.property_name) .filter((attr) => !(attr instanceof SpreadAttributeWrapper) && !attr.property_name)
.map((attr) => p`${(attr as StyleAttributeWrapper | AttributeWrapper).name}: true`); .map((attr) => p`${(attr as StyleAttributeWrapper | AttributeWrapper).name}: true`);
let reference; let reference: string | ReturnType<typeof x>;
if (this.node.tag_expr.node.type === 'Literal') { if (this.node.tag_expr.node.type === 'Literal') {
if (this.node.namespace) { if (this.node.namespace) {
reference = `"${this.node.tag_expr.node.value}"`; reference = `"${this.node.tag_expr.node.value}"`;
@ -547,7 +554,7 @@ export default class ElementWrapper extends Wrapper {
} }
} }
add_directives_in_order (block: Block) { add_directives_in_order(block: Block) {
type OrderedAttribute = EventHandler | BindingGroup | Binding | Action; type OrderedAttribute = EventHandler | BindingGroup | Binding | Action;
const binding_groups = events const binding_groups = events
@ -561,7 +568,7 @@ export default class ElementWrapper extends Wrapper {
const this_binding = this.bindings.find(b => b.node.name === 'this'); const this_binding = this.bindings.find(b => b.node.name === 'this');
function getOrder (item: OrderedAttribute) { function getOrder(item: OrderedAttribute) {
if (item instanceof EventHandler) { if (item instanceof EventHandler) {
return item.node.start; return item.node.start;
} else if (item instanceof Binding) { } else if (item instanceof Binding) {
@ -627,7 +634,7 @@ export default class ElementWrapper extends Wrapper {
// media bindings — awkward special case. The native timeupdate events // media bindings — awkward special case. The native timeupdate events
// fire too infrequently, so we need to take matters into our // fire too infrequently, so we need to take matters into our
// own hands // own hands
let animation_frame; let animation_frame: Identifier | undefined;
if (binding_group.events[0] === 'timeupdate') { if (binding_group.events[0] === 'timeupdate') {
animation_frame = block.get_unique_name(`${this.var.name}_animationframe`); animation_frame = block.get_unique_name(`${this.var.name}_animationframe`);
block.add_variable(animation_frame); block.add_variable(animation_frame);
@ -867,9 +874,7 @@ export default class ElementWrapper extends Wrapper {
} }
} }
add_transitions( add_transitions(block: Block) {
block: Block
) {
const { intro, outro } = this.node; const { intro, outro } = this.node;
if (!intro && !outro) return; if (!intro && !outro) return;
@ -926,7 +931,7 @@ export default class ElementWrapper extends Wrapper {
const fn = this.renderer.reference(intro.name); const fn = this.renderer.reference(intro.name);
let intro_block; let intro_block: Node[];
if (outro) { if (outro) {
intro_block = b` intro_block = b`
@ -1025,7 +1030,7 @@ export default class ElementWrapper extends Wrapper {
${outro && b`@add_transform(${this.var}, ${rect});`} ${outro && b`@add_transform(${this.var}, ${rect});`}
`); `);
let params; let params: Node | ReturnType<typeof x>;
if (this.node.animation.expression) { if (this.node.animation.expression) {
params = this.node.animation.expression.manipulate(block); params = this.node.animation.expression.manipulate(block);
@ -1053,8 +1058,8 @@ export default class ElementWrapper extends Wrapper {
const has_spread = this.node.attributes.some(attr => attr.is_spread); const has_spread = this.node.attributes.some(attr => attr.is_spread);
this.node.classes.forEach(class_directive => { this.node.classes.forEach(class_directive => {
const { expression, name } = class_directive; const { expression, name } = class_directive;
let snippet; let snippet: Node | string;
let dependencies; let dependencies: Set<string>;
if (expression) { if (expression) {
snippet = expression.manipulate(block); snippet = expression.manipulate(block);
dependencies = expression.dependencies; dependencies = expression.dependencies;
@ -1095,16 +1100,16 @@ export default class ElementWrapper extends Wrapper {
add_styles(block: Block) { add_styles(block: Block) {
const has_spread = this.node.attributes.some(attr => attr.is_spread); const has_spread = this.node.attributes.some(attr => attr.is_spread);
this.node.styles.forEach((style_directive) => { this.node.styles.forEach((style_directive) => {
const { name, expression, should_cache } = style_directive; const { name, expression, should_cache, important } = style_directive;
const snippet = expression.manipulate(block); const snippet = expression.manipulate(block);
let cached_snippet; let cached_snippet: Identifier | undefined;
if (should_cache) { if (should_cache) {
cached_snippet = block.get_unique_name(`style_${name.replace(/-/g, '_')}`); cached_snippet = block.get_unique_name(`style_${name.replace(regex_minus_signs, '_')}`);
block.add_variable(cached_snippet, snippet); block.add_variable(cached_snippet, snippet);
} }
const updater = b`@set_style(${this.var}, "${name}", ${should_cache ? cached_snippet : snippet}, false)`; const updater = b`@set_style(${this.var}, "${name}", ${should_cache ? cached_snippet : snippet}, ${important ? 1 : null})`;
block.chunks.hydrate.push(updater); block.chunks.hydrate.push(updater);
@ -1129,7 +1134,7 @@ export default class ElementWrapper extends Wrapper {
}); });
} }
add_manual_style_scoping(block) { add_manual_style_scoping(block: Block) {
if (this.node.needs_manual_style_scoping) { if (this.node.needs_manual_style_scoping) {
const updater = b`@toggle_class(${this.var}, "${this.node.component.stylesheet.id}", true);`; const updater = b`@toggle_class(${this.var}, "${this.node.component.stylesheet.id}", true);`;
block.chunks.hydrate.push(updater); block.chunks.hydrate.push(updater);
@ -1138,10 +1143,13 @@ export default class ElementWrapper extends Wrapper {
} }
} }
const regex_backticks = /`/g;
const regex_dollar_signs = /\$/g;
function to_html(wrappers: Array<ElementWrapper | TextWrapper | MustacheTagWrapper | RawMustacheTagWrapper>, block: Block, literal: any, state: any, can_use_raw_text?: boolean) { function to_html(wrappers: Array<ElementWrapper | TextWrapper | MustacheTagWrapper | RawMustacheTagWrapper>, block: Block, literal: any, state: any, can_use_raw_text?: boolean) {
wrappers.forEach(wrapper => { wrappers.forEach(wrapper => {
if (wrapper instanceof TextWrapper) { if (wrapper instanceof TextWrapper) {
// Don't add the <pre>/<textare> newline logic here because pre/textarea.innerHTML // Don't add the <pre>/<textarea> newline logic here because pre/textarea.innerHTML
// would keep the leading newline, too, only someParent.innerHTML = '..<pre/textarea>..' won't // would keep the leading newline, too, only someParent.innerHTML = '..<pre/textarea>..' won't
if ((wrapper as TextWrapper).use_space()) state.quasi.value.raw += ' '; if ((wrapper as TextWrapper).use_space()) state.quasi.value.raw += ' ';
@ -1155,9 +1163,9 @@ function to_html(wrappers: Array<ElementWrapper | TextWrapper | MustacheTagWrapp
); );
state.quasi.value.raw += (raw ? wrapper.data : escape_html(wrapper.data)) state.quasi.value.raw += (raw ? wrapper.data : escape_html(wrapper.data))
.replace(/\\/g, '\\\\') .replace(regex_backslashes, '\\\\')
.replace(/`/g, '\\`') .replace(regex_backticks, '\\`')
.replace(/\$/g, '\\$'); .replace(regex_dollar_signs, '\\$');
} else if (wrapper instanceof MustacheTagWrapper || wrapper instanceof RawMustacheTagWrapper) { } else if (wrapper instanceof MustacheTagWrapper || wrapper instanceof RawMustacheTagWrapper) {
literal.quasis.push(state.quasi); literal.quasis.push(state.quasi);
literal.expressions.push(wrapper.node.expression.manipulate(block)); literal.expressions.push(wrapper.node.expression.manipulate(block));
@ -1192,7 +1200,7 @@ function to_html(wrappers: Array<ElementWrapper | TextWrapper | MustacheTagWrapp
// Two or more leading newlines are required to restore the leading newline immediately after `<pre>`. // Two or more leading newlines are required to restore the leading newline immediately after `<pre>`.
// see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element // see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
const first = wrapper.fragment.nodes[0]; const first = wrapper.fragment.nodes[0];
if (first && first.node.type === 'Text' && start_newline.test(first.node.data)) { if (first && first.node.type === 'Text' && regex_starts_with_newline.test(first.node.data)) {
state.quasi.value.raw += '\n'; state.quasi.value.raw += '\n';
} }
} }
@ -1204,7 +1212,7 @@ function to_html(wrappers: Array<ElementWrapper | TextWrapper | MustacheTagWrapp
// Two or more leading newlines are required to restore the leading newline immediately after `<textarea>`. // Two or more leading newlines are required to restore the leading newline immediately after `<textarea>`.
// see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions // see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions
const first = value_attribute.node.chunks[0]; const first = value_attribute.node.chunks[0];
if (first && first.type === 'Text' && start_newline.test(first.data)) { if (first && first.type === 'Text' && regex_starts_with_newline.test(first.data)) {
state.quasi.value.raw += '\n'; state.quasi.value.raw += '\n';
} }
to_html_for_attr_value(value_attribute, block, literal, state); to_html_for_attr_value(value_attribute, block, literal, state);

@ -21,6 +21,7 @@ import Block from '../Block';
import { trim_start, trim_end } from '../../../utils/trim'; import { trim_start, trim_end } from '../../../utils/trim';
import { link } from '../../../utils/link'; import { link } from '../../../utils/link';
import { Identifier } from 'estree'; import { Identifier } from 'estree';
import { regex_starts_with_whitespace } from '../../../utils/patterns';
const wrappers = { const wrappers = {
AwaitBlock, AwaitBlock,
@ -64,7 +65,7 @@ export default class FragmentWrapper {
this.nodes = []; this.nodes = [];
let last_child: Wrapper; let last_child: Wrapper;
let window_wrapper; let window_wrapper: Window | undefined;
let i = nodes.length; let i = nodes.length;
while (i--) { while (i--) {
@ -92,7 +93,7 @@ export default class FragmentWrapper {
// *unless* there is no whitespace between this node and its next sibling // *unless* there is no whitespace between this node and its next sibling
if (this.nodes.length === 0) { if (this.nodes.length === 0) {
const should_trim = ( const should_trim = (
next_sibling ? (next_sibling.node.type === 'Text' && /^\s/.test(next_sibling.node.data) && trimmable_at(child, next_sibling)) : !child.has_ancestor('EachBlock') next_sibling ? (next_sibling.node.type === 'Text' && regex_starts_with_whitespace.test(next_sibling.node.data) && trimmable_at(child, next_sibling)) : !child.has_ancestor('EachBlock')
); );
if (should_trim && !child.keep_space()) { if (should_trim && !child.keep_space()) {

@ -33,7 +33,7 @@ export default class HeadWrapper extends Wrapper {
} }
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) { render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
let nodes; let nodes: Identifier;
if (this.renderer.options.hydratable && this.fragment.nodes.length) { if (this.renderer.options.hydratable && this.fragment.nodes.length) {
nodes = block.get_unique_name('head_nodes'); nodes = block.get_unique_name('head_nodes');
block.chunks.claim.push(b`const ${nodes} = @head_selector('${this.node.id}', @_document.head);`); block.chunks.claim.push(b`const ${nodes} = @head_selector('${this.node.id}', @_document.head);`);

@ -13,6 +13,8 @@ import { Identifier, Node } from 'estree';
import { push_array } from '../../../utils/push_array'; import { push_array } from '../../../utils/push_array';
import { add_const_tags, add_const_tags_context } from './shared/add_const_tags'; import { add_const_tags, add_const_tags_context } from './shared/add_const_tags';
type DetachingOrNull = 'detaching' | null;
function is_else_if(node: ElseBlock) { function is_else_if(node: ElseBlock) {
return ( return (
node && node.children.length === 1 && node.children[0].type === 'IfBlock' node && node.children.length === 1 && node.children[0].type === 'IfBlock'
@ -213,7 +215,7 @@ export default class IfBlockWrapper extends Wrapper {
const vars = { name, anchor, if_exists_condition, has_else, has_transitions }; const vars = { name, anchor, if_exists_condition, has_else, has_transitions };
const detaching = parent_node && !is_head(parent_node) ? null : 'detaching'; const detaching: DetachingOrNull = parent_node && !is_head(parent_node) ? null : 'detaching';
if (this.node.else) { if (this.node.else) {
this.branches.forEach(branch => { this.branches.forEach(branch => {
@ -275,9 +277,9 @@ export default class IfBlockWrapper extends Wrapper {
block: Block, block: Block,
parent_node: Identifier, parent_node: Identifier,
_parent_nodes: Identifier, _parent_nodes: Identifier,
dynamic, dynamic: boolean,
{ name, anchor, has_else, if_exists_condition, has_transitions }, { name, anchor, has_else, if_exists_condition, has_transitions },
detaching detaching: DetachingOrNull
) { ) {
const select_block_type = this.renderer.component.get_unique_name('select_block_type'); const select_block_type = this.renderer.component.get_unique_name('select_block_type');
const current_block_type = block.get_unique_name('current_block_type'); const current_block_type = block.get_unique_name('current_block_type');
@ -414,9 +416,9 @@ export default class IfBlockWrapper extends Wrapper {
block: Block, block: Block,
parent_node: Identifier, parent_node: Identifier,
_parent_nodes: Identifier, _parent_nodes: Identifier,
dynamic, dynamic: boolean,
{ name, anchor, has_else, has_transitions, if_exists_condition }, { name, anchor, has_else, has_transitions, if_exists_condition },
detaching detaching: DetachingOrNull
) { ) {
const select_block_type = this.renderer.component.get_unique_name('select_block_type'); const select_block_type = this.renderer.component.get_unique_name('select_block_type');
const current_block_type_index = block.get_unique_name('current_block_type_index'); const current_block_type_index = block.get_unique_name('current_block_type_index');
@ -428,8 +430,8 @@ export default class IfBlockWrapper extends Wrapper {
const if_ctx = select_block_ctx ? x`${select_block_ctx}(#ctx, ${current_block_type_index})` : x`#ctx`; const if_ctx = select_block_ctx ? x`${select_block_ctx}(#ctx, ${current_block_type_index})` : x`#ctx`;
const if_current_block_type_index = has_else const if_current_block_type_index = has_else
? nodes => nodes ? (nodes: Node[]) => nodes
: nodes => b`if (~${current_block_type_index}) { ${nodes} }`; : (nodes: Node[]) => b`if (~${current_block_type_index}) { ${nodes} }`;
block.add_variable(current_block_type_index); block.add_variable(current_block_type_index);
block.add_variable(name); block.add_variable(name);
@ -593,9 +595,9 @@ export default class IfBlockWrapper extends Wrapper {
block: Block, block: Block,
parent_node: Identifier, parent_node: Identifier,
_parent_nodes: Identifier, _parent_nodes: Identifier,
dynamic, dynamic: boolean,
{ name, anchor, if_exists_condition, has_transitions }, { name, anchor, if_exists_condition, has_transitions },
detaching detaching: DetachingOrNull
) { ) {
const branch = this.branches[0]; const branch = this.branches[0];
const if_ctx = branch.get_ctx_name ? x`${branch.get_ctx_name}(#ctx)` : x`#ctx`; const if_ctx = branch.get_ctx_name ? x`${branch.get_ctx_name}(#ctx)` : x`#ctx`;

@ -20,9 +20,12 @@ import { string_to_member_expression } from '../../../utils/string_to_member_exp
import SlotTemplate from '../../../nodes/SlotTemplate'; import SlotTemplate from '../../../nodes/SlotTemplate';
import { is_head } from '../shared/is_head'; import { is_head } from '../shared/is_head';
import compiler_warnings from '../../../compiler_warnings'; import compiler_warnings from '../../../compiler_warnings';
import { namespaces } from '../../../../utils/namespaces';
type SlotDefinition = { block: Block; scope: TemplateScope; get_context?: Node; get_changes?: Node }; type SlotDefinition = { block: Block; scope: TemplateScope; get_context?: Node; get_changes?: Node };
const regex_invalid_variable_identifier_characters = /[^a-zA-Z_$]/g;
export default class InlineComponentWrapper extends Wrapper { export default class InlineComponentWrapper extends Wrapper {
var: Identifier; var: Identifier;
slots: Map<string, SlotDefinition> = new Map(); slots: Map<string, SlotDefinition> = new Map();
@ -136,7 +139,7 @@ export default class InlineComponentWrapper extends Wrapper {
child.render(block, null, x`#nodes` as Identifier); child.render(block, null, x`#nodes` as Identifier);
}); });
let props; let props: Identifier | undefined;
const name_changes = block.get_unique_name(`${name.name}_changes`); const name_changes = block.get_unique_name(`${name.name}_changes`);
const uses_spread = !!this.node.attributes.find(a => a.is_spread); const uses_spread = !!this.node.attributes.find(a => a.is_spread);
@ -150,7 +153,9 @@ export default class InlineComponentWrapper extends Wrapper {
} }
const has_css_custom_properties = this.node.css_custom_properties.length > 0; const has_css_custom_properties = this.node.css_custom_properties.length > 0;
const css_custom_properties_wrapper = has_css_custom_properties ? block.get_unique_name('div') : null; const is_svg_namespace = this.node.namespace === namespaces.svg;
const css_custom_properties_wrapper_element = is_svg_namespace ? 'g' : 'div';
const css_custom_properties_wrapper = has_css_custom_properties ? block.get_unique_name(css_custom_properties_wrapper_element) : null;
if (has_css_custom_properties) { if (has_css_custom_properties) {
block.add_variable(css_custom_properties_wrapper); block.add_variable(css_custom_properties_wrapper);
} }
@ -229,7 +234,7 @@ export default class InlineComponentWrapper extends Wrapper {
: null; : null;
const unchanged = dependencies.size === 0; const unchanged = dependencies.size === 0;
let change_object; let change_object: Node | ReturnType<typeof x>;
if (attr.is_spread) { if (attr.is_spread) {
const value = attr.expression.manipulate(block); const value = attr.expression.manipulate(block);
initial_props.push(value); initial_props.push(value);
@ -411,7 +416,7 @@ export default class InlineComponentWrapper extends Wrapper {
const snippet = this.node.expression.manipulate(block); const snippet = this.node.expression.manipulate(block);
if (has_css_custom_properties) { if (has_css_custom_properties) {
this.set_css_custom_properties(block, css_custom_properties_wrapper); this.set_css_custom_properties(block, css_custom_properties_wrapper, css_custom_properties_wrapper_element, is_svg_namespace);
} }
block.chunks.init.push(b` block.chunks.init.push(b`
@ -425,7 +430,7 @@ export default class InlineComponentWrapper extends Wrapper {
} }
if (${switch_value}) { if (${switch_value}) {
${name} = new ${switch_value}(${switch_props}(#ctx)); ${name} = @construct_svelte_component(${switch_value}, ${switch_props}(#ctx));
${munged_bindings} ${munged_bindings}
${munged_handlers} ${munged_handlers}
@ -440,7 +445,7 @@ export default class InlineComponentWrapper extends Wrapper {
block.chunks.mount.push(b`if (${name}) @mount_component(${name}, ${mount_target}, ${mount_anchor});`); block.chunks.mount.push(b`if (${name}) @mount_component(${name}, ${mount_target}, ${mount_anchor});`);
if (to_claim) { if (to_claim) {
if (css_custom_properties_wrapper) claim_nodes = this.create_css_custom_properties_wrapper_claim_chunk(block, claim_nodes, css_custom_properties_wrapper); if (css_custom_properties_wrapper) claim_nodes = this.create_css_custom_properties_wrapper_claim_chunk(block, claim_nodes, css_custom_properties_wrapper, css_custom_properties_wrapper_element, is_svg_namespace);
block.chunks.claim.push(b`if (${name}) @claim_component(${name}.$$.fragment, ${claim_nodes});`); block.chunks.claim.push(b`if (${name}) @claim_component(${name}.$$.fragment, ${claim_nodes});`);
} }
@ -473,7 +478,7 @@ export default class InlineComponentWrapper extends Wrapper {
if (${switch_value}) { if (${switch_value}) {
${update_insert} ${update_insert}
${name} = new ${switch_value}(${switch_props}(#ctx)); ${name} = @construct_svelte_component(${switch_value}, ${switch_props}(#ctx));
${munged_bindings} ${munged_bindings}
${munged_handlers} ${munged_handlers}
@ -514,7 +519,7 @@ export default class InlineComponentWrapper extends Wrapper {
`); `);
if (has_css_custom_properties) { if (has_css_custom_properties) {
this.set_css_custom_properties(block, css_custom_properties_wrapper); this.set_css_custom_properties(block, css_custom_properties_wrapper, css_custom_properties_wrapper_element, is_svg_namespace);
} }
block.chunks.create.push(b`@create_component(${name}.$$.fragment);`); block.chunks.create.push(b`@create_component(${name}.$$.fragment);`);
@ -522,7 +527,7 @@ export default class InlineComponentWrapper extends Wrapper {
block.chunks.mount.push(b`@mount_component(${name}, ${mount_target}, ${mount_anchor});`); block.chunks.mount.push(b`@mount_component(${name}, ${mount_target}, ${mount_anchor});`);
if (to_claim) { if (to_claim) {
if (css_custom_properties_wrapper) claim_nodes = this.create_css_custom_properties_wrapper_claim_chunk(block, claim_nodes, css_custom_properties_wrapper); if (css_custom_properties_wrapper) claim_nodes = this.create_css_custom_properties_wrapper_claim_chunk(block, claim_nodes, css_custom_properties_wrapper, css_custom_properties_wrapper_element, is_svg_namespace);
block.chunks.claim.push(b`@claim_component(${name}.$$.fragment, ${claim_nodes});`); block.chunks.claim.push(b`@claim_component(${name}.$$.fragment, ${claim_nodes});`);
} }
@ -568,11 +573,14 @@ export default class InlineComponentWrapper extends Wrapper {
private create_css_custom_properties_wrapper_claim_chunk( private create_css_custom_properties_wrapper_claim_chunk(
block: Block, block: Block,
parent_nodes: Identifier, parent_nodes: Identifier,
css_custom_properties_wrapper: Identifier | null css_custom_properties_wrapper: Identifier | null,
css_custom_properties_wrapper_element: string,
is_svg_namespace: boolean
) { ) {
const nodes = block.get_unique_name(`${css_custom_properties_wrapper.name}_nodes`); const nodes = block.get_unique_name(`${css_custom_properties_wrapper.name}_nodes`);
const claim_element = is_svg_namespace ? x`@claim_svg_element` : x`@claim_element`;
block.chunks.claim.push(b` block.chunks.claim.push(b`
${css_custom_properties_wrapper} = @claim_element(${parent_nodes}, "DIV", { style: true }) ${css_custom_properties_wrapper} = ${claim_element}(${parent_nodes}, "${css_custom_properties_wrapper_element.toUpperCase()}", { style: true })
var ${nodes} = @children(${css_custom_properties_wrapper}); var ${nodes} = @children(${css_custom_properties_wrapper});
`); `);
return nodes; return nodes;
@ -580,14 +588,17 @@ export default class InlineComponentWrapper extends Wrapper {
private set_css_custom_properties( private set_css_custom_properties(
block: Block, block: Block,
css_custom_properties_wrapper: Identifier css_custom_properties_wrapper: Identifier,
css_custom_properties_wrapper_element: string,
is_svg_namespace: boolean
) { ) {
block.chunks.create.push(b`${css_custom_properties_wrapper} = @element("div");`); const element = is_svg_namespace ? x`@svg_element` : x`@element`;
block.chunks.hydrate.push(b`@set_style(${css_custom_properties_wrapper}, "display", "contents");`); block.chunks.create.push(b`${css_custom_properties_wrapper} = ${element}("${css_custom_properties_wrapper_element}");`);
if (!is_svg_namespace) block.chunks.hydrate.push(b`@set_style(${css_custom_properties_wrapper}, "display", "contents");`);
this.node.css_custom_properties.forEach((attr) => { this.node.css_custom_properties.forEach((attr) => {
const dependencies = attr.get_dependencies(); const dependencies = attr.get_dependencies();
const should_cache = attr.should_cache(); const should_cache = attr.should_cache();
const last = should_cache && block.get_unique_name(`${attr.name.replace(/[^a-zA-Z_$]/g, '_')}_last`); const last = should_cache && block.get_unique_name(`${attr.name.replace(regex_invalid_variable_identifier_characters, '_')}_last`);
if (should_cache) block.add_variable(last); if (should_cache) block.add_variable(last);
const value = attr.get_value(block); const value = attr.get_value(block);
const init = should_cache ? x`${last} = ${value}` : value; const init = should_cache ? x`${last} = ${value}` : value;

@ -8,7 +8,7 @@ import Element from '../../nodes/Element';
import MustacheTag from '../../nodes/MustacheTag'; import MustacheTag from '../../nodes/MustacheTag';
import RawMustacheTag from '../../nodes/RawMustacheTag'; import RawMustacheTag from '../../nodes/RawMustacheTag';
import { is_head } from './shared/is_head'; import { is_head } from './shared/is_head';
import { Identifier } from 'estree'; import { Identifier, Node } from 'estree';
export default class RawMustacheTagWrapper extends Tag { export default class RawMustacheTagWrapper extends Tag {
var: Identifier = { type: 'Identifier', name: 'raw' }; var: Identifier = { type: 'Identifier', name: 'raw' };
@ -30,7 +30,7 @@ export default class RawMustacheTagWrapper extends Tag {
const can_use_innerhtml = !in_head && parent_node && !this.prev && !this.next; const can_use_innerhtml = !in_head && parent_node && !this.prev && !this.next;
if (can_use_innerhtml) { if (can_use_innerhtml) {
const insert = content => b`${parent_node}.innerHTML = ${content};`[0]; const insert = (content: Node) => b`${parent_node}.innerHTML = ${content};`[0];
const { init } = this.rename_this_method( const { init } = this.rename_this_method(
block, block,

@ -9,7 +9,7 @@ import add_to_set from '../../utils/add_to_set';
import get_slot_data from '../../utils/get_slot_data'; import get_slot_data from '../../utils/get_slot_data';
import { is_reserved_keyword } from '../../utils/reserved_keywords'; import { is_reserved_keyword } from '../../utils/reserved_keywords';
import is_dynamic from './shared/is_dynamic'; import is_dynamic from './shared/is_dynamic';
import { Identifier, ObjectExpression } from 'estree'; import { Identifier, ObjectExpression, Node } from 'estree';
import create_debugging_comment from './shared/create_debugging_comment'; import create_debugging_comment from './shared/create_debugging_comment';
export default class SlotWrapper extends Wrapper { export default class SlotWrapper extends Wrapper {
@ -75,9 +75,9 @@ export default class SlotWrapper extends Wrapper {
block = this.slot_block; block = this.slot_block;
} }
let get_slot_changes_fn; let get_slot_changes_fn: Identifier | 'null';
let get_slot_spread_changes_fn; let get_slot_spread_changes_fn: Identifier | undefined;
let get_slot_context_fn; let get_slot_context_fn: Identifier | 'null';
if (this.node.values.size > 0) { if (this.node.values.size > 0) {
get_slot_changes_fn = renderer.component.get_unique_name(`get_${sanitize(slot_name)}_slot_changes`); get_slot_changes_fn = renderer.component.get_unique_name(`get_${sanitize(slot_name)}_slot_changes`);
@ -176,7 +176,7 @@ export default class SlotWrapper extends Wrapper {
].filter(Boolean); ].filter(Boolean);
const all_dirty_condition = all_dirty_conditions.length ? all_dirty_conditions.reduce((condition1, condition2) => x`${condition1} || ${condition2}`) : null; const all_dirty_condition = all_dirty_conditions.length ? all_dirty_conditions.reduce((condition1, condition2) => x`${condition1} || ${condition2}`) : null;
let slot_update; let slot_update: Node[];
if (all_dirty_condition) { if (all_dirty_condition) {
const dirty = x`${all_dirty_condition} ? @get_all_dirty_from_scope(${renderer.reference('$$scope')}) : @get_slot_changes(${slot_definition}, ${renderer.reference('$$scope')}, #dirty, ${get_slot_changes_fn})`; const dirty = x`${all_dirty_condition} ? @get_all_dirty_from_scope(${renderer.reference('$$scope')}) : @get_slot_changes(${slot_definition}, ${renderer.reference('$$scope')}, #dirty, ${get_slot_changes_fn})`;

@ -5,6 +5,8 @@ import Wrapper from './shared/Wrapper';
import { x } from 'code-red'; import { x } from 'code-red';
import { Identifier } from 'estree'; import { Identifier } from 'estree';
const regex_non_whitespace_characters = /[\S\u00A0]/;
export default class TextWrapper extends Wrapper { export default class TextWrapper extends Wrapper {
node: Text; node: Text;
data: string; data: string;
@ -27,7 +29,7 @@ export default class TextWrapper extends Wrapper {
use_space() { use_space() {
if (this.renderer.component.component_options.preserveWhitespace) return false; if (this.renderer.component.component_options.preserveWhitespace) return false;
if (/[\S\u00A0]/.test(this.data)) return false; if (regex_non_whitespace_characters.test(this.data)) return false;
return !this.node.within_pre(); return !this.node.within_pre();
} }

@ -45,7 +45,7 @@ export default class WindowWrapper extends Wrapper {
const { renderer } = this; const { renderer } = this;
const { component } = renderer; const { component } = renderer;
const events = {}; const events: Record<string, Array<{ name: string; value: string }>> = {};
const bindings: Record<string, string> = {}; const bindings: Record<string, string> = {};
add_actions(block, '@_window', this.node.actions); add_actions(block, '@_window', this.node.actions);

@ -1,7 +1,7 @@
import { b, x } from 'code-red'; import { b, x } from 'code-red';
import Block from '../../Block'; import Block from '../../Block';
import Action from '../../../nodes/Action'; import Action from '../../../nodes/Action';
import { Expression } from 'estree'; import { Expression, Node } from 'estree';
import is_contextual from '../../../nodes/shared/is_contextual'; import is_contextual from '../../../nodes/shared/is_contextual';
export default function add_actions( export default function add_actions(
@ -12,10 +12,12 @@ export default function add_actions(
actions.forEach(action => add_action(block, target, action)); actions.forEach(action => add_action(block, target, action));
} }
const regex_invalid_variable_identifier_characters = /[^a-zA-Z0-9_$]/g;
export function add_action(block: Block, target: string | Expression, action: Action) { export function add_action(block: Block, target: string | Expression, action: Action) {
const { expression, template_scope } = action; const { expression, template_scope } = action;
let snippet; let snippet: Node | undefined;
let dependencies; let dependencies: string[] | undefined;
if (expression) { if (expression) {
snippet = expression.manipulate(block); snippet = expression.manipulate(block);
@ -23,7 +25,7 @@ export function add_action(block: Block, target: string | Expression, action: Ac
} }
const id = block.get_unique_name( const id = block.get_unique_name(
`${action.name.replace(/[^a-zA-Z0-9_$]/g, '_')}_action` `${action.name.replace(regex_invalid_variable_identifier_characters, '_')}_action`
); );
block.add_variable(id); block.add_variable(id);

@ -1,5 +1,7 @@
import Component from '../../../Component'; import Component from '../../../Component';
import { INode } from '../../../nodes/interfaces'; import { INode } from '../../../nodes/interfaces';
import { regex_whitespace_characters } from '../../../../utils/patterns';
export default function create_debugging_comment( export default function create_debugging_comment(
node: INode, node: INode,
@ -13,7 +15,7 @@ export default function create_debugging_comment(
while (source[c - 1] === '{') c -= 1; while (source[c - 1] === '{') c -= 1;
} }
let d; let d: number;
if (node.type === 'InlineComponent' || node.type === 'Element' || node.type === 'SlotTemplate') { if (node.type === 'InlineComponent' || node.type === 'Element' || node.type === 'SlotTemplate') {
if (node.children.length) { if (node.children.length) {
@ -36,5 +38,5 @@ export default function create_debugging_comment(
const start = locate(c); const start = locate(c);
const loc = `(${start.line}:${start.column})`; const loc = `(${start.line}:${start.column})`;
return `${loc} ${source.slice(c, d)}`.replace(/\s/g, ' '); return `${loc} ${source.slice(c, d)}`.replace(regex_whitespace_characters, ' ');
} }

@ -2,7 +2,7 @@ import Let from '../../../nodes/Let';
import { x, p } from 'code-red'; import { x, p } from 'code-red';
import Block from '../../Block'; import Block from '../../Block';
import TemplateScope from '../../../nodes/shared/TemplateScope'; import TemplateScope from '../../../nodes/shared/TemplateScope';
import { BinaryExpression } from 'estree'; import { BinaryExpression, Identifier } from 'estree';
export function get_slot_definition(block: Block, scope: TemplateScope, lets: Let[]) { export function get_slot_definition(block: Block, scope: TemplateScope, lets: Let[]) {
if (lets.length === 0) return { block, scope }; if (lets.length === 0) return { block, scope };
@ -21,7 +21,7 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le
const value_map = new Map(); const value_map = new Map();
lets.forEach(l => { lets.forEach(l => {
let value; let value: Identifier;
if (l.names.length > 1) { if (l.names.length > 1) {
// more than one, probably destructuring // more than one, probably destructuring
const unique_name = block.get_unique_name(l.names.join('_')).name; const unique_name = block.get_unique_name(l.names.join('_')).name;
@ -86,7 +86,7 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le
elements[g] = grouped[g] elements[g] = grouped[g]
? grouped[g] ? grouped[g]
.map(({ name, n }) => x`${name} ? ${1 << n} : 0`) .map(({ name, n }) => x`${name} ? ${1 << n} : 0`)
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`) .reduce((lhs: ReturnType<typeof x>, rhs: ReturnType<typeof x>) => x`${lhs} | ${rhs}`)
: x`0`; : x`0`;
} }

@ -1,3 +1,5 @@
export function is_head(node) { import { Node } from 'estree';
return node && node.type === 'MemberExpression' && node.object.name === '@_document' && node.property.name === 'head';
export function is_head(node: Node) {
return node && node.type === 'MemberExpression' && node.object['name'] === '@_document' && node.property['name'] === 'head';
} }

@ -8,7 +8,7 @@ import Expression from '../../nodes/shared/Expression';
import remove_whitespace_children from './utils/remove_whitespace_children'; import remove_whitespace_children from './utils/remove_whitespace_children';
import fix_attribute_casing from '../../render_dom/wrappers/Element/fix_attribute_casing'; import fix_attribute_casing from '../../render_dom/wrappers/Element/fix_attribute_casing';
import { namespaces } from '../../../utils/namespaces'; import { namespaces } from '../../../utils/namespaces';
import { start_newline } from '../../../utils/patterns'; import { regex_starts_with_newline } from '../../../utils/patterns';
import { Node, Expression as ESExpression } from 'estree'; import { Node, Expression as ESExpression } from 'estree';
export default function (node: Element, renderer: Renderer, options: RenderOptions) { export default function (node: Element, renderer: Renderer, options: RenderOptions) {
@ -44,7 +44,10 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
class_expression_list.reduce((lhs, rhs) => x`${lhs} + ' ' + ${rhs}`); class_expression_list.reduce((lhs, rhs) => x`${lhs} + ' ' + ${rhs}`);
const style_expression_list = node.styles.map(style_directive => { const style_expression_list = node.styles.map(style_directive => {
const { name, expression: { node: expression } } = style_directive; let { name, important, expression: { node: expression } } = style_directive;
if (important) {
expression = x`${expression} + ' !important'`;
}
return p`"${name}": ${expression}`; return p`"${name}": ${expression}`;
}); });
@ -173,7 +176,7 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
const value_attribute = node.attributes.find(({ name }) => name === 'value'); const value_attribute = node.attributes.find(({ name }) => name === 'value');
if (value_attribute) { if (value_attribute) {
const first = value_attribute.chunks[0]; const first = value_attribute.chunks[0];
if (first && first.type === 'Text' && start_newline.test(first.data)) { if (first && first.type === 'Text' && regex_starts_with_newline.test(first.data)) {
renderer.add_string('\n'); renderer.add_string('\n');
} }
} }
@ -188,7 +191,7 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
// see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element // see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
// see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions // see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions
const first = children[0]; const first = children[0];
if (first && first.type === 'Text' && start_newline.test(first.data)) { if (first && first.type === 'Text' && regex_starts_with_newline.test(first.data)) {
renderer.add_string('\n'); renderer.add_string('\n');
} }
} }

@ -1,6 +1,7 @@
import Renderer, { RenderOptions } from '../Renderer'; import Renderer, { RenderOptions } from '../Renderer';
import Head from '../../nodes/Head'; import Head from '../../nodes/Head';
import { x } from 'code-red'; import { x } from 'code-red';
import { Node } from 'estree';
export default function(node: Head, renderer: Renderer, options: RenderOptions) { export default function(node: Head, renderer: Renderer, options: RenderOptions) {
const head_options = { const head_options = {
@ -11,6 +12,12 @@ export default function(node: Head, renderer: Renderer, options: RenderOptions)
renderer.push(); renderer.push();
renderer.render(node.children, head_options); renderer.render(node.children, head_options);
const result = renderer.pop(); const result = renderer.pop();
let expression: Node = result;
if (options.hydratable) {
const start_comment = `HEAD_${node.id}_START`;
const end_comment = `HEAD_${node.id}_END`;
expression = x`'<!-- ${start_comment} -->' + ${expression} + '<!-- ${end_comment} -->'`;
}
renderer.add_expression(x`$$result.head += '<!-- HEAD_${node.id}_START -->' + ${result} + '<!-- HEAD_${node.id}_END -->', ""`); renderer.add_expression(x`$$result.head += ${expression}, ""`);
} }

@ -3,6 +3,7 @@ import { get_attribute_value } from './shared/get_attribute_value';
import Renderer, { RenderOptions } from '../Renderer'; import Renderer, { RenderOptions } from '../Renderer';
import InlineComponent from '../../nodes/InlineComponent'; import InlineComponent from '../../nodes/InlineComponent';
import { p, x } from 'code-red'; import { p, x } from 'code-red';
import { namespaces } from '../../../utils/namespaces';
function get_prop_value(attribute) { function get_prop_value(attribute) {
if (attribute.is_true) return x`true`; if (attribute.is_true) return x`true`;
@ -88,11 +89,16 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend
}`; }`;
if (node.css_custom_properties.length > 0) { if (node.css_custom_properties.length > 0) {
renderer.add_string('<div style="display: contents;'); if (node.namespace === namespaces.svg) {
node.css_custom_properties.forEach(attr => { renderer.add_string('<g style="');
renderer.add_string(` ${attr.name}:`); } else {
renderer.add_string('<div style="display: contents; ');
}
node.css_custom_properties.forEach((attr, index) => {
renderer.add_string(`${attr.name}:`);
renderer.add_expression(get_attribute_value(attr)); renderer.add_expression(get_attribute_value(attr));
renderer.add_string(';'); renderer.add_string(';');
if (index < node.css_custom_properties.length - 1) renderer.add_string(' ');
}); });
renderer.add_string('">'); renderer.add_string('">');
} }
@ -100,6 +106,10 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend
renderer.add_expression(x`@validate_component(${expression}, "${node.name}").$$render($$result, ${props}, ${bindings}, ${slots})`); renderer.add_expression(x`@validate_component(${expression}, "${node.name}").$$render($$result, ${props}, ${bindings}, ${slots})`);
if (node.css_custom_properties.length > 0) { if (node.css_custom_properties.length > 0) {
if (node.namespace === namespaces.svg) {
renderer.add_string('</g>');
} else {
renderer.add_string('</div>'); renderer.add_string('</div>');
} }
}
} }

@ -4,6 +4,7 @@ import Text from '../../../nodes/Text';
import { x } from 'code-red'; import { x } from 'code-red';
import Expression from '../../../nodes/shared/Expression'; import Expression from '../../../nodes/shared/Expression';
import { Expression as ESTreeExpression } from 'estree'; import { Expression as ESTreeExpression } from 'estree';
import { regex_double_quotes } from '../../../../utils/patterns';
export function get_class_attribute_value(attribute: Attribute): ESTreeExpression { export function get_class_attribute_value(attribute: Attribute): ESTreeExpression {
// handle special case — `class={possiblyUndefined}` with scoped CSS // handle special case — `class={possiblyUndefined}` with scoped CSS
@ -21,7 +22,7 @@ export function get_attribute_value(attribute: Attribute): ESTreeExpression {
return attribute.chunks return attribute.chunks
.map((chunk) => { .map((chunk) => {
return chunk.type === 'Text' return chunk.type === 'Text'
? string_literal(chunk.data.replace(/"/g, '&quot;')) as ESTreeExpression ? string_literal(chunk.data.replace(regex_double_quotes, '&quot;')) as ESTreeExpression
: x`@escape(${chunk.node}, true)`; : x`@escape(${chunk.node}, true)`;
}) })
.reduce((lhs, rhs) => x`${lhs} + ${rhs}`); .reduce((lhs, rhs) => x`${lhs} + ${rhs}`);

@ -1,6 +1,7 @@
import { INode } from '../../../nodes/interfaces'; import { INode } from '../../../nodes/interfaces';
import { trim_end, trim_start } from '../../../../utils/trim'; import { trim_end, trim_start } from '../../../../utils/trim';
import { link } from '../../../../utils/link'; import { link } from '../../../../utils/link';
import { regex_starts_with_whitespace } from '../../../../utils/patterns';
// similar logic from `compile/render_dom/wrappers/Fragment` // similar logic from `compile/render_dom/wrappers/Fragment`
// We want to remove trailing whitespace inside an element/component/block, // We want to remove trailing whitespace inside an element/component/block,
@ -22,7 +23,7 @@ export default function remove_whitespace_children(children: INode[], next?: INo
if (nodes.length === 0) { if (nodes.length === 0) {
const should_trim = next const should_trim = next
? next.type === 'Text' && ? next.type === 'Text' &&
/^\s/.test(next.data) && regex_starts_with_whitespace.test(next.data) &&
trimmable_at(child, next) trimmable_at(child, next)
: !child.has_ancestor('EachBlock'); : !child.has_ancestor('EachBlock');

@ -1,3 +1,10 @@
import { regex_starts_with_underscore, regex_ends_with_underscore } from '../../utils/patterns';
const regex_percentage_characters = /%/g;
const regex_file_ending = /\.[^.]+$/;
const regex_repeated_invalid_variable_identifier_characters = /[^a-zA-Z_$0-9]+/g;
const regex_starts_with_digit = /^(\d)/;
export default function get_name_from_filename(filename: string) { export default function get_name_from_filename(filename: string) {
if (!filename) return null; if (!filename) return null;
@ -12,12 +19,12 @@ export default function get_name_from_filename(filename: string) {
} }
const base = parts.pop() const base = parts.pop()
.replace(/%/g, 'u') .replace(regex_percentage_characters, 'u')
.replace(/\.[^.]+$/, '') .replace(regex_file_ending, '')
.replace(/[^a-zA-Z_$0-9]+/g, '_') .replace(regex_repeated_invalid_variable_identifier_characters, '_')
.replace(/^_/, '') .replace(regex_starts_with_underscore, '')
.replace(/_$/, '') .replace(regex_ends_with_underscore, '')
.replace(/^(\d)/, '_$1'); .replace(regex_starts_with_digit, '_$1');
if (!base) { if (!base) {
throw new Error(`Could not derive component name from file ${filename}`); throw new Error(`Could not derive component name from file ${filename}`);

@ -1,6 +1,9 @@
// https://github.com/darkskyapp/string-hash/blob/master/index.js // https://github.com/darkskyapp/string-hash/blob/master/index.js
const regex_return_characters = /\r/g;
export default function hash(str: string): string { export default function hash(str: string): string {
str = str.replace(/\r/g, ''); str = str.replace(regex_return_characters, '');
let hash = 5381; let hash = 5381;
let i = str.length; let i = str.length;

@ -19,10 +19,14 @@ const escaped = {
'>': '&gt;' '>': '&gt;'
}; };
const regex_html_characters_to_escape = /["'&<>]/g;
export function escape_html(html) { export function escape_html(html) {
return String(html).replace(/["'&<>]/g, match => escaped[match]); return String(html).replace(regex_html_characters_to_escape, match => escaped[match]);
} }
const regex_template_characters_to_escape = /(\${|`|\\)/g;
export function escape_template(str) { export function escape_template(str) {
return str.replace(/(\${|`|\\)/g, '\\$1'); return str.replace(regex_template_characters_to_escape, '\\$1');
} }

@ -107,6 +107,14 @@ export default {
code: `invalid-${slug}-placement`, code: `invalid-${slug}-placement`,
message: `<${name}> tags cannot be inside elements or blocks` message: `<${name}> tags cannot be inside elements or blocks`
}), }),
invalid_logic_block_placement: (location: string, name: string) => ({
code: 'invalid-logic-block-placement',
message: `{#${name}} logic block cannot be ${location}`
}),
invalid_tag_placement: (location: string, name: string) => ({
code: 'invalid-tag-placement',
message: `{@${name}} tag cannot be ${location}`
}),
invalid_ref_directive: (name: string) => ({ invalid_ref_directive: (name: string) => ({
code: 'invalid-ref-directive', code: 'invalid-ref-directive',
message: `The ref directive is no longer supported — use \`bind:this={${name}}\` instead` message: `The ref directive is no longer supported — use \`bind:this={${name}}\` instead`

@ -1,6 +1,6 @@
import { isIdentifierStart, isIdentifierChar } from 'acorn'; import { isIdentifierStart, isIdentifierChar } from 'acorn';
import fragment from './state/fragment'; import fragment from './state/fragment';
import { whitespace } from '../utils/patterns'; import { regex_whitespace } from '../utils/patterns';
import { reserved } from '../utils/names'; import { reserved } from '../utils/names';
import full_char_code_at from '../utils/full_char_code_at'; import full_char_code_at from '../utils/full_char_code_at';
import { TemplateNode, Ast, ParserOptions, Fragment, Style, Script } from '../interfaces'; import { TemplateNode, Ast, ParserOptions, Fragment, Style, Script } from '../interfaces';
@ -15,6 +15,8 @@ interface LastAutoClosedTag {
depth: number; depth: number;
} }
const regex_position_indicator = / \(\d+:\d+\)$/;
export class Parser { export class Parser {
readonly template: string; readonly template: string;
readonly filename?: string; readonly filename?: string;
@ -74,10 +76,10 @@ export class Parser {
if (this.html.children.length) { if (this.html.children.length) {
let start = this.html.children[0].start; let start = this.html.children[0].start;
while (whitespace.test(template[start])) start += 1; while (regex_whitespace.test(template[start])) start += 1;
let end = this.html.children[this.html.children.length - 1].end; let end = this.html.children[this.html.children.length - 1].end;
while (whitespace.test(template[end - 1])) end -= 1; while (regex_whitespace.test(template[end - 1])) end -= 1;
this.html.start = start; this.html.start = start;
this.html.end = end; this.html.end = end;
@ -93,7 +95,7 @@ export class Parser {
acorn_error(err: any) { acorn_error(err: any) {
this.error({ this.error({
code: 'parse-error', code: 'parse-error',
message: err.message.replace(/ \(\d+:\d+\)$/, '') message: err.message.replace(regex_position_indicator, '')
}, err.pos); }, err.pos);
} }
@ -138,7 +140,7 @@ export class Parser {
allow_whitespace() { allow_whitespace() {
while ( while (
this.index < this.template.length && this.index < this.template.length &&
whitespace.test(this.template[this.index]) regex_whitespace.test(this.template[this.index])
) { ) {
this.index++; this.index++;
} }
@ -200,7 +202,7 @@ export class Parser {
} }
require_whitespace() { require_whitespace() {
if (!whitespace.test(this.template[this.index])) { if (!regex_whitespace.test(this.template[this.index])) {
this.error({ this.error({
code: 'missing-whitespace', code: 'missing-whitespace',
message: 'Expected whitespace' message: 'Expected whitespace'

@ -10,6 +10,7 @@ import {
import { parse_expression_at } from '../acorn'; import { parse_expression_at } from '../acorn';
import { Pattern } from 'estree'; import { Pattern } from 'estree';
import parser_errors from '../errors'; import parser_errors from '../errors';
import { regex_not_newline_characters } from '../../utils/patterns';
export default function read_context( export default function read_context(
parser: Parser parser: Parser
@ -65,7 +66,7 @@ export default function read_context(
// so we offset it by removing 1 character in the `space_with_newline` // so we offset it by removing 1 character in the `space_with_newline`
// to achieve that, we remove the 1st space encountered, // to achieve that, we remove the 1st space encountered,
// so it will not affect the `column` of the node // so it will not affect the `column` of the node
let space_with_newline = parser.template.slice(0, start).replace(/[^\n]/g, ' '); let space_with_newline = parser.template.slice(0, start).replace(regex_not_newline_characters, ' ');
const first_space = space_with_newline.indexOf(' '); const first_space = space_with_newline.indexOf(' ');
space_with_newline = space_with_newline.slice(0, first_space) + space_with_newline.slice(first_space + 1); space_with_newline = space_with_newline.slice(0, first_space) + space_with_newline.slice(first_space + 1);

@ -1,7 +1,7 @@
import { parse_expression_at } from '../acorn'; import { parse_expression_at } from '../acorn';
import { Parser } from '../index'; import { Parser } from '../index';
import { Node } from 'estree'; import { Node } from 'estree';
import { whitespace } from '../../utils/patterns'; import { regex_whitespace } from '../../utils/patterns';
import parser_errors from '../errors'; import parser_errors from '../errors';
export default function read_expression(parser: Parser): Node { export default function read_expression(parser: Parser): Node {
@ -20,7 +20,7 @@ export default function read_expression(parser: Parser): Node {
if (char === ')') { if (char === ')') {
num_parens -= 1; num_parens -= 1;
} else if (!whitespace.test(char)) { } else if (!regex_whitespace.test(char)) {
parser.error(parser_errors.unexpected_token(')'), index); parser.error(parser_errors.unexpected_token(')'), index);
} }

@ -3,6 +3,9 @@ import { Parser } from '../index';
import { Script } from '../../interfaces'; import { Script } from '../../interfaces';
import { Node, Program } from 'estree'; import { Node, Program } from 'estree';
import parser_errors from '../errors'; import parser_errors from '../errors';
import { regex_not_newline_characters } from '../../utils/patterns';
const regex_closing_script_tag = /<\/script\s*>/;
function get_context(parser: Parser, attributes: any[], start: number): string { function get_context(parser: Parser, attributes: any[], start: number): string {
const context = attributes.find(attribute => attribute.name === 'context'); const context = attributes.find(attribute => attribute.name === 'context');
@ -23,13 +26,13 @@ function get_context(parser: Parser, attributes: any[], start: number): string {
export default function read_script(parser: Parser, start: number, attributes: Node[]): Script { export default function read_script(parser: Parser, start: number, attributes: Node[]): Script {
const script_start = parser.index; const script_start = parser.index;
const data = parser.read_until(/<\/script\s*>/, parser_errors.unclosed_script); const data = parser.read_until(regex_closing_script_tag, parser_errors.unclosed_script);
if (parser.index >= parser.template.length) { if (parser.index >= parser.template.length) {
parser.error(parser_errors.unclosed_script); parser.error(parser_errors.unclosed_script);
} }
const source = parser.template.slice(0, script_start).replace(/[^\n]/g, ' ') + data; const source = parser.template.slice(0, script_start).replace(regex_not_newline_characters, ' ') + data;
parser.read(/<\/script\s*>/); parser.read(regex_closing_script_tag);
let ast: Program; let ast: Program;

@ -1,14 +1,17 @@
import parse from 'css-tree/lib/parser/index.js'; // @ts-ignore
import parse from 'css-tree/parser';
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
import { Parser } from '../index'; import { Parser } from '../index';
import { Node } from 'estree'; import { Node } from 'estree';
import { Style } from '../../interfaces'; import { Style } from '../../interfaces';
import parser_errors from '../errors'; import parser_errors from '../errors';
const regex_closing_style_tag = /<\/style\s*>/;
export default function read_style(parser: Parser, start: number, attributes: Node[]): Style { export default function read_style(parser: Parser, start: number, attributes: Node[]): Style {
const content_start = parser.index; const content_start = parser.index;
const styles = parser.read_until(/<\/style\s*>/, parser_errors.unclosed_style); const styles = parser.read_until(regex_closing_style_tag, parser_errors.unclosed_style);
if (parser.index >= parser.template.length) { if (parser.index >= parser.template.length) {
parser.error(parser_errors.unclosed_style); parser.error(parser_errors.unclosed_style);
@ -67,7 +70,7 @@ export default function read_style(parser: Parser, start: number, attributes: No
} }
}); });
parser.read(/<\/style\s*>/); parser.read(regex_closing_style_tag);
const end = parser.index; const end = parser.index;
return { return {

@ -1,7 +1,7 @@
import read_context from '../read/context'; import read_context from '../read/context';
import read_expression from '../read/expression'; import read_expression from '../read/expression';
import { closing_tag_omitted } from '../utils/html'; import { closing_tag_omitted } from '../utils/html';
import { whitespace } from '../../utils/patterns'; import { regex_whitespace } from '../../utils/patterns';
import { trim_start, trim_end } from '../../utils/trim'; import { trim_start, trim_end } from '../../utils/trim';
import { to_string } from '../utils/node'; import { to_string } from '../utils/node';
import { Parser } from '../index'; import { Parser } from '../index';
@ -33,6 +33,8 @@ function trim_whitespace(block: TemplateNode, trim_before: boolean, trim_after:
} }
} }
const regex_whitespace_with_closing_curly_brace = /\s*}/;
export default function mustache(parser: Parser) { export default function mustache(parser: Parser) {
const start = parser.index; const start = parser.index;
parser.index += 1; parser.index += 1;
@ -87,8 +89,8 @@ export default function mustache(parser: Parser) {
// strip leading/trailing whitespace as necessary // strip leading/trailing whitespace as necessary
const char_before = parser.template[block.start - 1]; const char_before = parser.template[block.start - 1];
const char_after = parser.template[parser.index]; const char_after = parser.template[parser.index];
const trim_before = !char_before || whitespace.test(char_before); const trim_before = !char_before || regex_whitespace.test(char_before);
const trim_after = !char_after || whitespace.test(char_after); const trim_after = !char_after || regex_whitespace.test(char_after);
trim_whitespace(block, trim_before, trim_after); trim_whitespace(block, trim_before, trim_after);
@ -290,7 +292,7 @@ export default function mustache(parser: Parser) {
const await_block_shorthand = type === 'AwaitBlock' && parser.eat('then'); const await_block_shorthand = type === 'AwaitBlock' && parser.eat('then');
if (await_block_shorthand) { if (await_block_shorthand) {
if (parser.match_regex(/\s*}/)) { if (parser.match_regex(regex_whitespace_with_closing_curly_brace)) {
parser.allow_whitespace(); parser.allow_whitespace();
} else { } else {
parser.require_whitespace(); parser.require_whitespace();
@ -301,7 +303,7 @@ export default function mustache(parser: Parser) {
const await_block_catch_shorthand = !await_block_shorthand && type === 'AwaitBlock' && parser.eat('catch'); const await_block_catch_shorthand = !await_block_shorthand && type === 'AwaitBlock' && parser.eat('catch');
if (await_block_catch_shorthand) { if (await_block_catch_shorthand) {
if (parser.match_regex(/\s*}/)) { if (parser.match_regex(regex_whitespace_with_closing_curly_brace)) {
parser.allow_whitespace(); parser.allow_whitespace();
} else { } else {
parser.require_whitespace(); parser.require_whitespace();
@ -350,7 +352,7 @@ export default function mustache(parser: Parser) {
let identifiers; let identifiers;
// Implies {@debug} which indicates "debug all" // Implies {@debug} which indicates "debug all"
if (parser.read(/\s*}/)) { if (parser.read(regex_whitespace_with_closing_curly_brace)) {
identifiers = []; identifiers = [];
} else { } else {
const expression = read_expression(parser); const expression = read_expression(parser);

@ -53,13 +53,17 @@ function parent_is_head(stack) {
return false; return false;
} }
const regex_closing_textarea_tag = /^<\/textarea(\s[^>]*)?>/i;
const regex_closing_comment = /-->/;
const regex_capital_letter = /[A-Z]/;
export default function tag(parser: Parser) { export default function tag(parser: Parser) {
const start = parser.index++; const start = parser.index++;
let parent = parser.current(); let parent = parser.current();
if (parser.eat('!--')) { if (parser.eat('!--')) {
const data = parser.read_until(/-->/); const data = parser.read_until(regex_closing_comment);
parser.eat('-->', true, parser_errors.unclosed_comment); parser.eat('-->', true, parser_errors.unclosed_comment);
parser.current().children.push({ parser.current().children.push({
@ -104,7 +108,7 @@ export default function tag(parser: Parser) {
const type = meta_tags.has(name) const type = meta_tags.has(name)
? meta_tags.get(name) ? meta_tags.get(name)
: (/[A-Z]/.test(name[0]) || name === 'svelte:self' || name === 'svelte:component') ? 'InlineComponent' : (regex_capital_letter.test(name[0]) || name === 'svelte:self' || name === 'svelte:component') ? 'InlineComponent'
: name === 'svelte:fragment' ? 'SlotTemplate' : name === 'svelte:fragment' ? 'SlotTemplate'
: name === 'title' && parent_is_head(parser.stack) ? 'Title' : name === 'title' && parent_is_head(parser.stack) ? 'Title'
: name === 'slot' && !parser.customElement ? 'Slot' : 'Element'; : name === 'slot' && !parser.customElement ? 'Slot' : 'Element';
@ -218,10 +222,10 @@ export default function tag(parser: Parser) {
// special case // special case
element.children = read_sequence( element.children = read_sequence(
parser, parser,
() => () => regex_closing_textarea_tag.test(parser.template.slice(parser.index)),
/^<\/textarea(\s[^>]*)?>/i.test(parser.template.slice(parser.index)) 'inside <textarea>'
); );
parser.read(/^<\/textarea(\s[^>]*)?>/i); parser.read(regex_closing_textarea_tag);
element.end = parser.index; element.end = parser.index;
} else if (name === 'script' || name === 'style') { } else if (name === 'script' || name === 'style') {
// special case // special case
@ -236,6 +240,8 @@ export default function tag(parser: Parser) {
} }
} }
const regex_whitespace_or_slash_or_closing_tag = /(\s|\/|>)/;
function read_tag_name(parser: Parser) { function read_tag_name(parser: Parser) {
const start = parser.index; const start = parser.index;
@ -265,7 +271,7 @@ function read_tag_name(parser: Parser) {
if (parser.read(SLOT)) return 'svelte:fragment'; if (parser.read(SLOT)) return 'svelte:fragment';
const name = parser.read_until(/(\s|\/|>)/); const name = parser.read_until(regex_whitespace_or_slash_or_closing_tag);
if (meta_tags.has(name)) return name; if (meta_tags.has(name)) return name;
@ -285,6 +291,10 @@ function read_tag_name(parser: Parser) {
return name; return name;
} }
// eslint-disable-next-line no-useless-escape
const regex_token_ending_character = /[\s=\/>"']/;
const regex_quote_characters = /["']/;
function read_attribute(parser: Parser, unique_names: Set<string>) { function read_attribute(parser: Parser, unique_names: Set<string>) {
const start = parser.index; const start = parser.index;
@ -343,8 +353,7 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
} }
} }
// eslint-disable-next-line no-useless-escape const name = parser.read_until(regex_token_ending_character);
const name = parser.read_until(/[\s=\/>"']/);
if (!name) return null; if (!name) return null;
let end = parser.index; let end = parser.index;
@ -359,7 +368,7 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
parser.allow_whitespace(); parser.allow_whitespace();
value = read_attribute_value(parser); value = read_attribute_value(parser);
end = parser.index; end = parser.index;
} else if (parser.match_regex(/["']/)) { } else if (parser.match_regex(regex_quote_characters)) {
parser.error(parser_errors.unexpected_token('='), parser.index); parser.error(parser_errors.unexpected_token('='), parser.index);
} }
@ -386,6 +395,7 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
end, end,
type, type,
name: directive_name, name: directive_name,
modifiers,
value value
}; };
} }
@ -473,7 +483,7 @@ function read_attribute_value(parser: Parser) {
let value; let value;
try { try {
value = read_sequence(parser, () => !!parser.match_regex(regex)); value = read_sequence(parser, () => !!parser.match_regex(regex), 'in attribute value');
} catch (error) { } catch (error) {
if (error.code === 'parse-error') { if (error.code === 'parse-error') {
// if the attribute value didn't close + self-closing tag // if the attribute value didn't close + self-closing tag
@ -495,7 +505,7 @@ function read_attribute_value(parser: Parser) {
return value; return value;
} }
function read_sequence(parser: Parser, done: () => boolean): TemplateNode[] { function read_sequence(parser: Parser, done: () => boolean, location: string): TemplateNode[] {
let current_chunk: Text = { let current_chunk: Text = {
start: parser.index, start: parser.index,
end: null, end: null,
@ -521,6 +531,18 @@ function read_sequence(parser: Parser, done: () => boolean): TemplateNode[] {
flush(parser.index); flush(parser.index);
return chunks; return chunks;
} else if (parser.eat('{')) { } else if (parser.eat('{')) {
if (parser.match('#')) {
const index = parser.index - 1;
parser.eat('#');
const name = parser.read_until(/[^a-z]/);
parser.error(parser_errors.invalid_logic_block_placement(location, name), index);
} else if (parser.match('@')) {
const index = parser.index - 1;
parser.eat('@');
const name = parser.read_until(/[^a-z]/);
parser.error(parser_errors.invalid_tag_placement(location, name), index);
}
flush(parser.index - 1); flush(parser.index - 1);
parser.allow_whitespace(); parser.allow_whitespace();

@ -4,6 +4,7 @@ import { MappedCode, SourceLocation, parse_attached_sourcemap, sourcemap_add_off
import { decode_map } from './decode_sourcemap'; import { decode_map } from './decode_sourcemap';
import { replace_in_code, slice_source } from './replace_in_code'; import { replace_in_code, slice_source } from './replace_in_code';
import { MarkupPreprocessor, Source, Preprocessor, PreprocessorGroup, Processed } from './types'; import { MarkupPreprocessor, Source, Preprocessor, PreprocessorGroup, Processed } from './types';
import { regex_whitespaces } from '../utils/patterns';
export * from './types'; export * from './types';
@ -13,8 +14,10 @@ interface SourceUpdate {
dependencies?: string[]; dependencies?: string[];
} }
const regex_filepath_separator = /[/\\]/;
function get_file_basename(filename: string) { function get_file_basename(filename: string) {
return filename.split(/[/\\]/).pop(); return filename.split(regex_filepath_separator).pop();
} }
/** /**
@ -120,20 +123,26 @@ function processed_tag_to_code(
return tag_open_code.concat(content_code).concat(tag_close_code); return tag_open_code.concat(content_code).concat(tag_close_code);
} }
const regex_quoted_value = /^['"](.*)['"]$/;
function parse_tag_attributes(str: string) { function parse_tag_attributes(str: string) {
// note: won't work with attribute values containing spaces. // note: won't work with attribute values containing spaces.
return str return str
.split(/\s+/) .split(regex_whitespaces)
.filter(Boolean) .filter(Boolean)
.reduce((attrs, attr) => { .reduce((attrs, attr) => {
const i = attr.indexOf('='); const i = attr.indexOf('=');
const [key, value] = i > 0 ? [attr.slice(0, i), attr.slice(i + 1)] : [attr]; const [key, value] = i > 0 ? [attr.slice(0, i), attr.slice(i + 1)] : [attr];
const [, unquoted] = (value && value.match(/^['"](.*)['"]$/)) || []; const [, unquoted] = (value && value.match(regex_quoted_value)) || [];
return { ...attrs, [key]: unquoted ?? value ?? true }; return { ...attrs, [key]: unquoted ?? value ?? true };
}, {}); }, {});
} }
const regex_style_tags = /<!--[^]*?-->|<style(\s[^]*?)?(?:>([^]*?)<\/style>|\/>)/gi;
const regex_script_tags = /<!--[^]*?-->|<script(\s[^]*?)?(?:>([^]*?)<\/script>|\/>)/gi;
/** /**
* Calculate the updates required to process all instances of the specified tag. * Calculate the updates required to process all instances of the specified tag.
*/ */
@ -143,10 +152,7 @@ async function process_tag(
source: Source source: Source
): Promise<SourceUpdate> { ): Promise<SourceUpdate> {
const { filename, source: markup } = source; const { filename, source: markup } = source;
const tag_regex = const tag_regex = tag_name === 'style' ? regex_style_tags : regex_script_tags;
tag_name === 'style'
? /<!--[^]*?-->|<style(\s[^]*?)?(?:>([^]*?)<\/style>|\/>)/gi
: /<!--[^]*?-->|<script(\s[^]*?)?(?:>([^]*?)<\/script>|\/>)/gi;
const dependencies: string[] = []; const dependencies: string[] = [];

@ -1,11 +1,12 @@
import { TemplateNode } from '../interfaces'; import { TemplateNode } from '../interfaces';
import { flatten } from './flatten'; import { flatten } from './flatten';
import { regex_whitespace } from './patterns';
const pattern = /^\s*svelte-ignore\s+([\s\S]+)\s*$/m; const regex_svelte_ignore = /^\s*svelte-ignore\s+([\s\S]+)\s*$/m;
export function extract_svelte_ignore(text: string): string[] { export function extract_svelte_ignore(text: string): string[] {
const match = pattern.exec(text); const match = regex_svelte_ignore.exec(text);
return match ? match[1].split(/[^\S]/).map(x => x.trim()).filter(Boolean) : []; return match ? match[1].split(regex_whitespace).map(x => x.trim()).filter(Boolean) : [];
} }
export function extract_svelte_ignore_from_comments<Node extends { leadingComments?: Array<{value: string}> }>(node: Node): string[] { export function extract_svelte_ignore_from_comments<Node extends { leadingComments?: Array<{value: string}> }>(node: Node): string[] {

@ -1,5 +1,7 @@
const regex_tabs = /^\t+/;
function tabs_to_spaces(str: string) { function tabs_to_spaces(str: string) {
return str.replace(/^\t+/, match => match.split('\t').join(' ')); return str.replace(regex_tabs, match => match.split('\t').join(' '));
} }
export default function get_code_frame( export default function get_code_frame(

@ -61,6 +61,8 @@ function merge_tables<T>(this_table: T[], other_table: T[]): [T[], number[], boo
return [new_table, idx_map, val_changed, idx_changed]; return [new_table, idx_map, val_changed, idx_changed];
} }
const regex_line_token = /([^\d\w\s]|\s+)/g;
export class MappedCode { export class MappedCode {
string: string; string: string;
map: DecodedSourceMap; map: DecodedSourceMap;
@ -195,7 +197,7 @@ export class MappedCode {
const line_list = source.split('\n'); const line_list = source.split('\n');
for (let line = 0; line < line_list.length; line++) { for (let line = 0; line < line_list.length; line++) {
map.mappings.push([]); map.mappings.push([]);
const token_list = line_list[line].split(/([^\d\w\s]|\s+)/g); const token_list = line_list[line].split(regex_line_token);
for (let token = 0, column = 0; token < token_list.length; token++) { for (let token = 0, column = 0; token < token_list.length; token++) {
if (token_list[token] == '') continue; if (token_list[token] == '') continue;
map.mappings[line].push([column, 0, offset.line + line, column]); map.mappings[line].push([column, 0, offset.line + line, column]);
@ -289,6 +291,8 @@ export function apply_preprocessor_sourcemap(filename: string, svelte_map: Sourc
return result_map as SourceMap; return result_map as SourceMap;
} }
const regex_data_uri = /data:(?:application|text)\/json;(?:charset[:=]\S+?;)?base64,(\S*)/;
// parse attached sourcemap in processed.code // parse attached sourcemap in processed.code
export function parse_attached_sourcemap(processed: Processed, tag_name: 'script' | 'style'): void { export function parse_attached_sourcemap(processed: Processed, tag_name: 'script' | 'style'): void {
const r_in = '[#@]\\s*sourceMappingURL\\s*=\\s*(\\S*)'; const r_in = '[#@]\\s*sourceMappingURL\\s*=\\s*(\\S*)';
@ -302,7 +306,7 @@ export function parse_attached_sourcemap(processed: Processed, tag_name: 'script
} }
processed.code = processed.code.replace(regex, (_, match1, match2) => { processed.code = processed.code.replace(regex, (_, match1, match2) => {
const map_url = (tag_name == 'script') ? (match1 || match2) : match1; const map_url = (tag_name == 'script') ? (match1 || match2) : match1;
const map_data = (map_url.match(/data:(?:application|text)\/json;(?:charset[:=]\S+?;)?base64,(\S*)/) || [])[1]; const map_data = (map_url.match(regex_data_uri) || [])[1];
if (map_data) { if (map_data) {
// sourceMappingURL is data URL // sourceMappingURL is data URL
if (processed.map) { if (processed.map) {

@ -1,5 +1,6 @@
import { isIdentifierStart, isIdentifierChar } from 'acorn'; import { isIdentifierStart, isIdentifierChar } from 'acorn';
import full_char_code_at from './full_char_code_at'; import full_char_code_at from './full_char_code_at';
import { regex_starts_with_underscore, regex_ends_with_underscore } from './patterns';
export const reserved = new Set([ export const reserved = new Set([
'arguments', 'arguments',
@ -65,10 +66,13 @@ export function is_valid(str: string): boolean {
return true; return true;
} }
const regex_non_standard_characters = /[^a-zA-Z0-9_]+/g;
const regex_starts_with_number = /^[0-9]/;
export function sanitize(name: string) { export function sanitize(name: string) {
return name return name
.replace(/[^a-zA-Z0-9_]+/g, '_') .replace(regex_non_standard_characters, '_')
.replace(/^_/, '') .replace(regex_starts_with_underscore, '')
.replace(/_$/, '') .replace(regex_ends_with_underscore, '')
.replace(/^[0-9]/, '_$&'); .replace(regex_starts_with_number, '_$&');
} }

@ -25,4 +25,4 @@ export const valid_namespaces = [
xmlns xmlns
]; ];
export const namespaces: Record<string, string> = { foreign, html, mathml, svg, xlink, xml, xmlns }; export const namespaces = { foreign, html, mathml, svg, xlink, xml, xmlns } as const;

@ -1,6 +1,24 @@
export const whitespace = /[ \t\r\n]/; export const regex_whitespace = /\s/;
export const start_whitespace = /^[ \t\r\n]*/; export const regex_whitespaces = /\s+/;
export const end_whitespace = /[ \t\r\n]*$/; export const regex_starts_with_whitespace = /^\s/;
export const start_newline = /^\r?\n/; export const regex_starts_with_whitespaces = /^[ \t\r\n]*/;
export const regex_ends_with_whitespace = /\s$/;
export const regex_ends_with_whitespaces = /[ \t\r\n]*$/;
export const regex_only_whitespaces = /^\s+$/;
export const dimensions = /^(?:offset|client)(?:Width|Height)$/; export const regex_whitespace_characters = /\s/g;
export const regex_non_whitespace_character = /\S/;
export const regex_starts_with_newline = /^\r?\n/;
export const regex_not_newline_characters = /[^\n]/g;
export const regex_double_quotes = /"/g;
export const regex_backslashes = /\\/g;
export const regex_starts_with_underscore = /^_/;
export const regex_ends_with_underscore = /_$/;
export const regex_invalid_variable_identifier_characters = /[^a-zA-Z0-9_$]/g;
export const regex_dimensions = /^(?:offset|client)(?:Width|Height)$/;

@ -1,9 +1,9 @@
import { start_whitespace, end_whitespace } from './patterns'; import { regex_starts_with_whitespaces, regex_ends_with_whitespaces } from './patterns';
export function trim_start(str: string) { export function trim_start(str: string) {
return str.replace(start_whitespace, ''); return str.replace(regex_starts_with_whitespaces, '');
} }
export function trim_end(str: string) { export function trim_end(str: string) {
return str.replace(end_whitespace, ''); return str.replace(regex_ends_with_whitespaces, '');
} }

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

Loading…
Cancel
Save