mirror of https://github.com/sveltejs/svelte
Co-authored-by: Rich Harris <rich.harris@vercel.com> Co-authored-by: Simon Holthausen <simon.holthausen@vercel.com> Co-authored-by: Dominic Gannaway <dg@domgan.com>pull/9382/head
parent
1369aa5cec
commit
fe8a9ce31d
@ -0,0 +1,23 @@
|
|||||||
|
# NOTE: In general this should be kept in sync with .eslintignore
|
||||||
|
|
||||||
|
**/dist/**
|
||||||
|
**/config/**
|
||||||
|
**/build/**
|
||||||
|
**/playgrounds/sandbox/**
|
||||||
|
**/npm/**
|
||||||
|
**/*.js.flow
|
||||||
|
**/*.d.ts
|
||||||
|
**/playwright*/**
|
||||||
|
**/vite.config.js
|
||||||
|
**/vite.prod.config.js
|
||||||
|
**/node_modules
|
||||||
|
|
||||||
|
**/tests/**
|
||||||
|
|
||||||
|
# documentation can contain invalid examples
|
||||||
|
documentation/**
|
||||||
|
|
||||||
|
# contains a fork of the REPL which doesn't adhere to eslint rules
|
||||||
|
sites/svelte-5-preview/**
|
||||||
|
# Wasn't checked previously, reenable at some point
|
||||||
|
sites/svelte.dev/**
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: ['@sveltejs'],
|
||||||
|
|
||||||
|
// TODO: add runes to eslint-plugin-svelte
|
||||||
|
globals: {
|
||||||
|
$state: true,
|
||||||
|
$derived: true,
|
||||||
|
$effect: true,
|
||||||
|
$props: true
|
||||||
|
},
|
||||||
|
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
// scripts and playground should be console logging so don't lint against them
|
||||||
|
files: ['playgrounds/**/*', 'scripts/**/*'],
|
||||||
|
rules: {
|
||||||
|
'no-console': 'off'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// the playgrounds can use public naming conventions since they're examples
|
||||||
|
files: ['playgrounds/**/*'],
|
||||||
|
rules: {
|
||||||
|
'lube/svelte-naming-convention': 'off'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['packages/svelte/src/compiler/**/*'],
|
||||||
|
rules: {
|
||||||
|
'no-var': 'error'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
plugins: ['lube'],
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
'no-console': 'error',
|
||||||
|
'lube/svelte-naming-convention': ['error', { fixSameNames: true }],
|
||||||
|
// eslint isn't that well-versed with JSDoc to know that `foo: /** @type{..} */ (foo)` isn't a violation of this rule, so turn it off
|
||||||
|
'object-shorthand': 'off',
|
||||||
|
'no-var': 'off',
|
||||||
|
|
||||||
|
// TODO: enable these rules and run `pnpm lint:fix`
|
||||||
|
// skipping that for now so as to avoid impacting real work
|
||||||
|
'@typescript-eslint/array-type': 'off',
|
||||||
|
'@typescript-eslint/no-namespace': 'off',
|
||||||
|
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||||
|
'@typescript-eslint/no-unused-vars': 'off',
|
||||||
|
'prefer-const': 'off',
|
||||||
|
'svelte/valid-compile': 'off',
|
||||||
|
quotes: 'off'
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -1,6 +1,27 @@
|
|||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# IDE related
|
||||||
.idea
|
.idea
|
||||||
.DS_Store
|
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/launch.json
|
!.vscode/launch.json
|
||||||
node_modules
|
|
||||||
|
# Test coverage
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
.eslintcache
|
.eslintcache
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# build output
|
||||||
|
dist
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# OS-specific
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
tmp
|
||||||
|
|||||||
@ -0,0 +1,30 @@
|
|||||||
|
# NOTE: In general this should be kept in sync with .eslintignore
|
||||||
|
|
||||||
|
packages/**/dist/*.js
|
||||||
|
packages/**/build/*.js
|
||||||
|
packages/**/npm/**/*
|
||||||
|
packages/**/config/*.js
|
||||||
|
packages/svelte/tests/**/*.svelte
|
||||||
|
packages/svelte/tests/**/_expected*
|
||||||
|
packages/svelte/tests/**/_actual*
|
||||||
|
packages/svelte/tests/**/expected*
|
||||||
|
packages/svelte/tests/**/_output
|
||||||
|
packages/svelte/tests/**/shards/*.test.js
|
||||||
|
packages/svelte/tests/hydration/samples/*/_before.html
|
||||||
|
packages/svelte/tests/hydration/samples/*/_before_head.html
|
||||||
|
packages/svelte/tests/hydration/samples/*/_after.html
|
||||||
|
packages/svelte/tests/hydration/samples/*/_after_head.html
|
||||||
|
packages/svelte/types
|
||||||
|
packages/svelte/compiler.cjs
|
||||||
|
playgrounds/demo/src
|
||||||
|
playgrounds/sandbox/input/**.svelte
|
||||||
|
playgrounds/sandbox/output
|
||||||
|
**/*.md
|
||||||
|
**/node_modules
|
||||||
|
**/.svelte-kit
|
||||||
|
flow-typed
|
||||||
|
.github/CODEOWNERS
|
||||||
|
.prettierignore
|
||||||
|
.eslintignore
|
||||||
|
pnpm-lock.yaml
|
||||||
|
pnpm-workspace.yaml
|
||||||
@ -1,17 +0,0 @@
|
|||||||
**/_actual.js
|
|
||||||
**/expected.js
|
|
||||||
_output
|
|
||||||
test/*/samples/*/output.js
|
|
||||||
|
|
||||||
# automatically generated
|
|
||||||
internal_exports.js
|
|
||||||
|
|
||||||
# output files
|
|
||||||
animate/*.js
|
|
||||||
esing/*.js
|
|
||||||
internal/*.js
|
|
||||||
motion/*.js
|
|
||||||
store/*.js
|
|
||||||
transition/*.js
|
|
||||||
index.js
|
|
||||||
compiler.js
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
extends: ['@sveltejs'],
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/no-non-null-assertion': 'off'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,18 +1,12 @@
|
|||||||
*.map
|
|
||||||
/src/compiler/compile/internal_exports.js
|
|
||||||
/compiler.cjs
|
|
||||||
/scratch/
|
|
||||||
/test/*/samples/_
|
|
||||||
/test/runtime/shards
|
|
||||||
_actual*.*
|
|
||||||
_output
|
|
||||||
/types
|
/types
|
||||||
|
/compiler.cjs
|
||||||
|
|
||||||
action.d.ts
|
action.d.ts
|
||||||
animate.d.ts
|
animate.d.ts
|
||||||
compiler.d.ts
|
compiler.d.ts
|
||||||
easing.d.ts
|
easing.d.ts
|
||||||
index.d.ts
|
index.d.ts
|
||||||
|
legacy.d.ts
|
||||||
motion.d.ts
|
motion.d.ts
|
||||||
store.d.ts
|
store.d.ts
|
||||||
transition.d.ts
|
transition.d.ts
|
||||||
|
|||||||
@ -1,22 +0,0 @@
|
|||||||
/*
|
|
||||||
!/elements
|
|
||||||
!/scripts
|
|
||||||
!/src
|
|
||||||
src/compiler/compile/internal_exports.js
|
|
||||||
src/shared/version.js
|
|
||||||
!/test
|
|
||||||
!documentation
|
|
||||||
!sites
|
|
||||||
sites/svelte.dev/src/lib/generated/*.js
|
|
||||||
sites/svelte.dev/.svelte-kit
|
|
||||||
sites/svelte.dev/.vercel
|
|
||||||
/test/**/*.svelte
|
|
||||||
/test/**/_expected*
|
|
||||||
/test/**/_actual*
|
|
||||||
/test/**/expected*
|
|
||||||
/test/**/_output
|
|
||||||
/test/**/shards/*.test.js
|
|
||||||
/test/hydration/samples/raw-repair/_after.html
|
|
||||||
/types
|
|
||||||
!rollup.config.js
|
|
||||||
!vitest.config.js
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,38 +0,0 @@
|
|||||||
[](https://svelte.dev)
|
|
||||||
|
|
||||||
|
|
||||||
[](https://www.npmjs.com/package/svelte) [](LICENSE.md) [](https://svelte.dev/chat)
|
|
||||||
|
|
||||||
|
|
||||||
## What is Svelte?
|
|
||||||
|
|
||||||
Svelte is a new way to build web applications. It's a compiler that takes your declarative components and converts them into efficient JavaScript that surgically updates the DOM.
|
|
||||||
|
|
||||||
Learn more at the [Svelte website](https://svelte.dev), or stop by the [Discord chatroom](https://svelte.dev/chat).
|
|
||||||
|
|
||||||
## Getting started
|
|
||||||
|
|
||||||
You can play around with Svelte in the [tutorial](https://learn.svelte.dev/), [examples](https://svelte.dev/examples), and [REPL](https://svelte.dev/repl).
|
|
||||||
|
|
||||||
When you're ready to build a full-fledge application, we recommend using [SvelteKit](https://kit.svelte.dev):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm create svelte@latest my-app
|
|
||||||
cd my-app
|
|
||||||
npm install
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
See [the SvelteKit documentation](https://kit.svelte.dev/docs) to learn more.
|
|
||||||
|
|
||||||
## Changelog
|
|
||||||
|
|
||||||
[The Changelog for this package is available on GitHub](https://github.com/sveltejs/svelte/blob/master/packages/svelte/CHANGELOG.md).
|
|
||||||
|
|
||||||
## Supporting Svelte
|
|
||||||
|
|
||||||
Svelte is an MIT-licensed open source project with its ongoing development made possible entirely by fantastic volunteers. If you'd like to support their efforts, please consider:
|
|
||||||
|
|
||||||
- [Becoming a backer on Open Collective](https://opencollective.com/svelte).
|
|
||||||
|
|
||||||
Funds donated via Open Collective will be used for compensating expenses related to Svelte's development.
|
|
||||||
@ -1,142 +1,127 @@
|
|||||||
{
|
{
|
||||||
"name": "svelte",
|
"name": "svelte",
|
||||||
"version": "4.2.3",
|
|
||||||
"description": "Cybernetically enhanced web apps",
|
"description": "Cybernetically enhanced web apps",
|
||||||
|
"license": "MIT",
|
||||||
|
"version": "5.0.0-next.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"module": "src/runtime/index.js",
|
"types": "./types/index.d.ts",
|
||||||
"main": "src/runtime/index.js",
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"src",
|
"src",
|
||||||
"!src/**/tsconfig.json",
|
|
||||||
"types",
|
"types",
|
||||||
"compiler.*",
|
"compiler.cjs",
|
||||||
"register.js",
|
"*.d.ts",
|
||||||
"index.d.ts",
|
|
||||||
"store.d.ts",
|
|
||||||
"animate.d.ts",
|
|
||||||
"transition.d.ts",
|
|
||||||
"easing.d.ts",
|
|
||||||
"motion.d.ts",
|
|
||||||
"action.d.ts",
|
|
||||||
"elements.d.ts",
|
|
||||||
"svelte-html.d.ts",
|
|
||||||
"README.md"
|
"README.md"
|
||||||
],
|
],
|
||||||
|
"module": "src/main/main-client.js",
|
||||||
|
"main": "src/main/main-client.js",
|
||||||
"exports": {
|
"exports": {
|
||||||
"./package.json": "./package.json",
|
|
||||||
".": {
|
".": {
|
||||||
"types": "./types/index.d.ts",
|
"types": "./types/index.d.ts",
|
||||||
"browser": {
|
"browser": "./src/main/main-client.js",
|
||||||
"default": "./src/runtime/index.js"
|
"default": "./src/main/main-server.js"
|
||||||
},
|
|
||||||
"default": "./src/runtime/ssr.js"
|
|
||||||
},
|
|
||||||
"./compiler": {
|
|
||||||
"types": "./types/index.d.ts",
|
|
||||||
"require": "./compiler.cjs",
|
|
||||||
"default": "./src/compiler/index.js"
|
|
||||||
},
|
},
|
||||||
|
"./package.json": "./package.json",
|
||||||
"./action": {
|
"./action": {
|
||||||
"types": "./types/index.d.ts"
|
"types": "./types/index.d.ts"
|
||||||
},
|
},
|
||||||
"./animate": {
|
"./animate": {
|
||||||
"types": "./types/index.d.ts",
|
"types": "./types/index.d.ts",
|
||||||
"default": "./src/runtime/animate/index.js"
|
"default": "./src/animate/index.js"
|
||||||
|
},
|
||||||
|
"./compiler": {
|
||||||
|
"types": "./types/index.d.ts",
|
||||||
|
"require": "./compiler.cjs",
|
||||||
|
"default": "./src/compiler/index.js"
|
||||||
},
|
},
|
||||||
"./easing": {
|
"./easing": {
|
||||||
"types": "./types/index.d.ts",
|
"types": "./types/index.d.ts",
|
||||||
"default": "./src/runtime/easing/index.js"
|
"default": "./src/easing/index.js"
|
||||||
|
},
|
||||||
|
"./elements": {
|
||||||
|
"types": "./elements.d.ts"
|
||||||
},
|
},
|
||||||
"./internal": {
|
"./internal": {
|
||||||
"default": "./src/runtime/internal/index.js"
|
"default": "./src/internal/index.js"
|
||||||
|
},
|
||||||
|
"./internal/disclose-version": {
|
||||||
|
"default": "./src/internal/disclose-version.js"
|
||||||
|
},
|
||||||
|
"./internal/server": {
|
||||||
|
"default": "./src/internal/server/index.js"
|
||||||
|
},
|
||||||
|
"./legacy": {
|
||||||
|
"types": "./types/index.d.ts",
|
||||||
|
"browser": "./src/legacy/legacy-client.js",
|
||||||
|
"default": "./src/legacy/legacy-server.js"
|
||||||
},
|
},
|
||||||
"./motion": {
|
"./motion": {
|
||||||
"types": "./types/index.d.ts",
|
"types": "./types/index.d.ts",
|
||||||
"default": "./src/runtime/motion/index.js"
|
"default": "./src/motion/index.js"
|
||||||
},
|
},
|
||||||
"./store": {
|
"./server": {
|
||||||
"types": "./types/index.d.ts",
|
"types": "./types/index.d.ts",
|
||||||
"default": "./src/runtime/store/index.js"
|
"default": "./src/server/index.js"
|
||||||
},
|
},
|
||||||
"./internal/disclose-version": {
|
"./store": {
|
||||||
"default": "./src/runtime/internal/disclose-version/index.js"
|
"types": "./types/index.d.ts",
|
||||||
|
"default": "./src/store/index.js"
|
||||||
},
|
},
|
||||||
"./transition": {
|
"./transition": {
|
||||||
"types": "./types/index.d.ts",
|
"types": "./types/index.d.ts",
|
||||||
"default": "./src/runtime/transition/index.js"
|
"default": "./src/transition/index.js"
|
||||||
},
|
|
||||||
"./elements": {
|
|
||||||
"types": "./elements.d.ts"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"engines": {
|
|
||||||
"node": ">=16"
|
|
||||||
},
|
|
||||||
"types": "types/index.d.ts",
|
|
||||||
"scripts": {
|
|
||||||
"format": "prettier . --cache --plugin-search-dir=. --write",
|
|
||||||
"check": "tsc --noEmit",
|
|
||||||
"test": "vitest run && echo \"manually check that there are no type errors in test/types by opening the files in there\"",
|
|
||||||
"build": "rollup -c && pnpm types",
|
|
||||||
"generate:version": "node ./scripts/generate-version.js",
|
|
||||||
"dev": "rollup -cw",
|
|
||||||
"posttest": "agadoo src/internal/index.js",
|
|
||||||
"prepublishOnly": "pnpm build",
|
|
||||||
"types": "node ./scripts/generate-dts.js",
|
|
||||||
"lint": "prettier . --cache --plugin-search-dir=. --check && eslint \"{scripts,src,test}/**/*.js\" --cache --fix"
|
|
||||||
},
|
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/sveltejs/svelte.git",
|
"url": "git+https://github.com/sveltejs/svelte.git",
|
||||||
"directory": "packages/svelte"
|
"directory": "packages/svelte"
|
||||||
},
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/sveltejs/svelte/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://svelte.dev",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"UI",
|
"UI",
|
||||||
"framework",
|
"framework",
|
||||||
"templates",
|
"templates",
|
||||||
"templating"
|
"templating"
|
||||||
],
|
],
|
||||||
"author": "Rich Harris",
|
"scripts": {
|
||||||
"license": "MIT",
|
"build": "rollup -c && node scripts/build.js",
|
||||||
"bugs": {
|
"watch": "rollup -cw",
|
||||||
"url": "https://github.com/sveltejs/svelte/issues"
|
"check": "tsc && cd ./tests/types && tsc",
|
||||||
|
"check:watch": "tsc --watch",
|
||||||
|
"generate:version": "node ./scripts/generate-version.js",
|
||||||
|
"prepublishOnly": "pnpm build"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.19",
|
||||||
|
"@playwright/test": "^1.35.1",
|
||||||
|
"@rollup/plugin-commonjs": "^25.0.7",
|
||||||
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
|
"@types/aria-query": "^5.0.3",
|
||||||
|
"@types/estree": "^1.0.5",
|
||||||
|
"dts-buddy": "^0.4.0",
|
||||||
|
"esbuild": "^0.19.2",
|
||||||
|
"rollup": "^4.1.5",
|
||||||
|
"source-map": "^0.7.4",
|
||||||
|
"tiny-glob": "^0.2.9"
|
||||||
},
|
},
|
||||||
"homepage": "https://svelte.dev",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.2.1",
|
"@ampproject/remapping": "^2.2.1",
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.15",
|
"@jridgewell/sourcemap-codec": "^1.4.15",
|
||||||
"@jridgewell/trace-mapping": "^0.3.18",
|
"acorn": "^8.10.0",
|
||||||
"acorn": "^8.9.0",
|
|
||||||
"aria-query": "^5.3.0",
|
"aria-query": "^5.3.0",
|
||||||
"axobject-query": "^3.2.1",
|
"axobject-query": "^4.0.0",
|
||||||
"code-red": "^1.0.3",
|
"esm-env": "^1.0.0",
|
||||||
"css-tree": "^2.3.1",
|
"esrap": "^1.1.1",
|
||||||
"estree-walker": "^3.0.3",
|
|
||||||
"is-reference": "^3.0.1",
|
"is-reference": "^3.0.1",
|
||||||
"locate-character": "^3.0.0",
|
"locate-character": "^3.0.0",
|
||||||
"magic-string": "^0.30.4",
|
"magic-string": "^0.30.4",
|
||||||
"periscopic": "^3.1.0"
|
"zimmerframe": "^1.1.0"
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@playwright/test": "^1.35.1",
|
|
||||||
"@rollup/plugin-commonjs": "^24.1.0",
|
|
||||||
"@rollup/plugin-json": "^6.0.0",
|
|
||||||
"@rollup/plugin-node-resolve": "^15.1.0",
|
|
||||||
"@sveltejs/eslint-config": "^6.0.4",
|
|
||||||
"@types/aria-query": "^5.0.1",
|
|
||||||
"@types/estree": "^1.0.1",
|
|
||||||
"@types/node": "^14.18.51",
|
|
||||||
"agadoo": "^3.0.0",
|
|
||||||
"dts-buddy": "^0.1.7",
|
|
||||||
"esbuild": "^0.18.11",
|
|
||||||
"eslint-plugin-lube": "^0.1.7",
|
|
||||||
"happy-dom": "^9.20.3",
|
|
||||||
"jsdom": "22.0.0",
|
|
||||||
"kleur": "^4.1.5",
|
|
||||||
"rollup": "^3.26.2",
|
|
||||||
"source-map": "^0.7.4",
|
|
||||||
"tiny-glob": "^0.2.9",
|
|
||||||
"typescript": "^5.1.3",
|
|
||||||
"vitest": "^0.33.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,50 +1,17 @@
|
|||||||
import fs from 'node:fs';
|
|
||||||
import { createRequire } from 'node:module';
|
|
||||||
import resolve from '@rollup/plugin-node-resolve';
|
|
||||||
import commonjs from '@rollup/plugin-commonjs';
|
import commonjs from '@rollup/plugin-commonjs';
|
||||||
import json from '@rollup/plugin-json';
|
import resolve from '@rollup/plugin-node-resolve';
|
||||||
|
import terser from '@rollup/plugin-terser';
|
||||||
|
import { defineConfig } from 'rollup';
|
||||||
|
|
||||||
// runs the version generation as a side-effect of importing
|
// runs the version generation as a side-effect of importing
|
||||||
import './scripts/generate-version.js';
|
import './scripts/generate-version.js';
|
||||||
|
|
||||||
const require = createRequire(import.meta.url);
|
export default defineConfig({
|
||||||
|
|
||||||
const internal = await import('./src/runtime/internal/index.js');
|
|
||||||
fs.writeFileSync(
|
|
||||||
'src/compiler/compile/internal_exports.js',
|
|
||||||
`// This file is automatically generated\n` +
|
|
||||||
`export default new Set(${JSON.stringify(Object.keys(internal))});`
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {import("rollup").RollupOptions[]}
|
|
||||||
*/
|
|
||||||
export default [
|
|
||||||
// Generate UMD build of the compiler so that Eslint/Prettier (which need CJS) and REPL (which needs UMD because browser) can use it
|
|
||||||
{
|
|
||||||
input: 'src/compiler/index.js',
|
input: 'src/compiler/index.js',
|
||||||
plugins: [
|
|
||||||
{
|
|
||||||
resolveId(id) {
|
|
||||||
// Must import from the `css-tree` browser bundled distribution due to `createRequire` usage if importing from css-tree directly
|
|
||||||
if (id === 'css-tree') {
|
|
||||||
return require.resolve('./node_modules/css-tree/dist/csstree.esm.js');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resolve(),
|
|
||||||
commonjs({
|
|
||||||
include: ['../../node_modules/**', 'node_modules/**']
|
|
||||||
}),
|
|
||||||
json()
|
|
||||||
],
|
|
||||||
output: {
|
output: {
|
||||||
file: 'compiler.cjs',
|
file: 'compiler.cjs',
|
||||||
format: 'umd',
|
format: 'umd',
|
||||||
name: 'svelte',
|
name: 'svelte'
|
||||||
sourcemap: false,
|
|
||||||
indent: false
|
|
||||||
},
|
},
|
||||||
external: []
|
plugins: [resolve(), commonjs(), terser()]
|
||||||
}
|
});
|
||||||
];
|
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"plugins": ["lube"],
|
|
||||||
"rules": {
|
|
||||||
"lube/svelte-naming-convention": ["error", { "fixSameNames": true }]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
import fs from 'node:fs';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { createBundle } from 'dts-buddy';
|
||||||
|
|
||||||
|
const dir = fileURLToPath(new URL('..', import.meta.url));
|
||||||
|
const pkg = JSON.parse(fs.readFileSync(`${dir}/package.json`, 'utf-8'));
|
||||||
|
|
||||||
|
// For people not using moduleResolution: 'bundler', we need to generate these files. Think about removing this in Svelte 6 or 7
|
||||||
|
// It may look weird, but the imports MUST be ending with index.js to be properly resolved in all TS modes
|
||||||
|
for (const name of ['action', 'animate', 'easing', 'motion', 'store', 'transition', 'legacy']) {
|
||||||
|
fs.writeFileSync(`${name}.d.ts`, "import './types/index.js';");
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync('index.d.ts', "import './types/index.js';");
|
||||||
|
fs.writeFileSync('compiler.d.ts', "import './types/index.js';");
|
||||||
|
|
||||||
|
// TODO: Remove these in Svelte 6. They are here so that tooling (which historically made use of these) can support Svelte 4-6 in one minor version
|
||||||
|
fs.mkdirSync('./types/compiler', { recursive: true });
|
||||||
|
fs.writeFileSync('./types/compiler/preprocess.d.ts', "import '../index.js';");
|
||||||
|
fs.writeFileSync('./types/compiler/interfaces.d.ts', "import '../index.js';");
|
||||||
|
|
||||||
|
await createBundle({
|
||||||
|
output: `${dir}/types/index.d.ts`,
|
||||||
|
modules: {
|
||||||
|
[pkg.name]: `${dir}/src/main/public.d.ts`,
|
||||||
|
[`${pkg.name}/action`]: `${dir}/src/action/public.d.ts`,
|
||||||
|
[`${pkg.name}/animate`]: `${dir}/src/animate/public.d.ts`,
|
||||||
|
[`${pkg.name}/compiler`]: `${dir}/src/compiler/index.js`,
|
||||||
|
[`${pkg.name}/easing`]: `${dir}/src/easing/index.js`,
|
||||||
|
[`${pkg.name}/legacy`]: `${dir}/src/legacy/public.d.ts`,
|
||||||
|
[`${pkg.name}/motion`]: `${dir}/src/motion/public.d.ts`,
|
||||||
|
[`${pkg.name}/server`]: `${dir}/src/server/index.js`,
|
||||||
|
[`${pkg.name}/store`]: `${dir}/src/store/public.d.ts`,
|
||||||
|
[`${pkg.name}/transition`]: `${dir}/src/transition/public.d.ts`,
|
||||||
|
// TODO remove in Svelte 6
|
||||||
|
[`${pkg.name}/types/compiler/preprocess`]: `${dir}/src/compiler/preprocess/legacy-public.d.ts`,
|
||||||
|
[`${pkg.name}/types/compiler/interfaces`]: `${dir}/src/compiler/types/legacy-interfaces.d.ts`
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -1,40 +0,0 @@
|
|||||||
// Compile all Svelte files in a directory to JS and CSS files
|
|
||||||
// Usage: node scripts/compile-test.js <directory>
|
|
||||||
|
|
||||||
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
||||||
import path from 'node:path';
|
|
||||||
import glob from 'tiny-glob/sync.js';
|
|
||||||
import { compile } from '../src/compiler/index.js';
|
|
||||||
|
|
||||||
const cwd = path.resolve(process.argv[2]);
|
|
||||||
|
|
||||||
const options = [
|
|
||||||
['normal', {}],
|
|
||||||
['hydrate', { hydratable: true }],
|
|
||||||
['ssr', { generate: 'ssr' }]
|
|
||||||
];
|
|
||||||
for (const file of glob('**/*.svelte', { cwd })) {
|
|
||||||
const contents = readFileSync(`${cwd}/${file}`, 'utf-8').replace(/\r/g, '');
|
|
||||||
let w;
|
|
||||||
for (const [name, opts] of options) {
|
|
||||||
const dir = `${cwd}/_output/${name}`;
|
|
||||||
|
|
||||||
const { js, css, warnings } = compile(contents, {
|
|
||||||
...opts,
|
|
||||||
filename: file
|
|
||||||
});
|
|
||||||
|
|
||||||
if (warnings.length) {
|
|
||||||
w = warnings;
|
|
||||||
}
|
|
||||||
|
|
||||||
mkdirSync(dir, { recursive: true });
|
|
||||||
js.code && writeFileSync(`${dir}/${file.replace(/\.svelte$/, '.js')}`, js.code);
|
|
||||||
css.code && writeFileSync(`${dir}/${file.replace(/\.svelte$/, '.css')}`, css.code);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (w) {
|
|
||||||
console.log(`Warnings for ${file}:`);
|
|
||||||
console.log(w);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
import * as fs from 'node:fs';
|
|
||||||
import { createBundle } from 'dts-buddy';
|
|
||||||
|
|
||||||
// It may look weird, but the imports MUST be ending with index.js to be properly resolved in all TS modes
|
|
||||||
for (const name of ['action', 'animate', 'easing', 'motion', 'store', 'transition']) {
|
|
||||||
fs.writeFileSync(`${name}.d.ts`, "import './types/index.js';");
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.writeFileSync('index.d.ts', "import './types/index.js';");
|
|
||||||
fs.writeFileSync('compiler.d.ts', "import './types/index.js';");
|
|
||||||
|
|
||||||
// TODO: some way to mark these as deprecated
|
|
||||||
fs.mkdirSync('./types/compiler', { recursive: true });
|
|
||||||
fs.writeFileSync('./types/compiler/preprocess.d.ts', "import '../index.js';");
|
|
||||||
fs.writeFileSync('./types/compiler/interfaces.d.ts', "import '../index.js';");
|
|
||||||
|
|
||||||
await createBundle({
|
|
||||||
output: 'types/index.d.ts',
|
|
||||||
compilerOptions: {
|
|
||||||
strict: true
|
|
||||||
},
|
|
||||||
modules: {
|
|
||||||
svelte: 'src/runtime/public.d.ts',
|
|
||||||
'svelte/compiler': 'src/compiler/public.d.ts',
|
|
||||||
'svelte/types/compiler/preprocess': 'src/compiler/preprocess/public.d.ts',
|
|
||||||
'svelte/types/compiler/interfaces': 'src/compiler/interfaces.d.ts',
|
|
||||||
'svelte/action': 'src/runtime/action/public.d.ts',
|
|
||||||
'svelte/animate': 'src/runtime/animate/public.d.ts',
|
|
||||||
'svelte/easing': 'src/runtime/easing/index.js',
|
|
||||||
'svelte/motion': 'src/runtime/motion/public.d.ts',
|
|
||||||
'svelte/store': 'src/runtime/store/public.d.ts',
|
|
||||||
'svelte/transition': 'src/runtime/transition/public.d.ts'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// There's no way to tell in JS that a class can have arbitrary properties, so we need to add that manually
|
|
||||||
const types = fs.readFileSync('types/index.d.ts', 'utf-8');
|
|
||||||
fs.writeFileSync(
|
|
||||||
'types/index.d.ts',
|
|
||||||
// same line to not affect source map
|
|
||||||
types.replace(/export class SvelteComponent<[^{]*{/, '$& [prop: string]: any;')
|
|
||||||
);
|
|
||||||
@ -1,96 +0,0 @@
|
|||||||
/** ----------------------------------------------------------------------
|
|
||||||
This script gets a list of global objects/functions of browser.
|
|
||||||
This process is simple for now, so it is handled without AST parser.
|
|
||||||
Please run `node scripts/globals-extractor.js` at the project root.
|
|
||||||
|
|
||||||
see: https://github.com/microsoft/TypeScript/tree/main/lib
|
|
||||||
---------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
import http from 'node:https';
|
|
||||||
import fs from 'node:fs';
|
|
||||||
|
|
||||||
const GLOBAL_TS_PATH = './src/compiler/utils/globals.js';
|
|
||||||
|
|
||||||
// MEMO: add additional objects/functions which existed in `src/compiler/utils/names.ts`
|
|
||||||
// before this script was introduced but could not be retrieved by this process.
|
|
||||||
const SPECIALS = ['global', 'globalThis', 'InternalError', 'process', 'undefined'];
|
|
||||||
|
|
||||||
const get_url = (name) =>
|
|
||||||
`https://raw.githubusercontent.com/microsoft/TypeScript/main/src/lib/${name}.d.ts`;
|
|
||||||
const extract_name = (split) => split.match(/^[a-zA-Z0-9_$]+/)[0];
|
|
||||||
|
|
||||||
const extract_functions_and_references = (name, data) => {
|
|
||||||
const functions = [];
|
|
||||||
const references = [];
|
|
||||||
data.split('\n').forEach((line) => {
|
|
||||||
const trimmed = line.trim();
|
|
||||||
const split = trimmed.replace(/[\s+]/, ' ').split(' ');
|
|
||||||
if (split[0] === 'declare' && split[1] !== 'type') {
|
|
||||||
functions.push(extract_name(split[2]));
|
|
||||||
} else if (trimmed.startsWith('/// <reference')) {
|
|
||||||
const matched = trimmed.match(/ lib="(.+)"/);
|
|
||||||
const reference = matched && matched[1];
|
|
||||||
if (reference) references.push(reference);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return { functions, references };
|
|
||||||
};
|
|
||||||
|
|
||||||
const do_get = (url) =>
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
http
|
|
||||||
.get(url, (res) => {
|
|
||||||
let body = '';
|
|
||||||
res.setEncoding('utf8');
|
|
||||||
res.on('data', (chunk) => (body += chunk));
|
|
||||||
res.on('end', () => resolve(body));
|
|
||||||
})
|
|
||||||
.on('error', (e) => {
|
|
||||||
console.error(e.message);
|
|
||||||
reject(e);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetched_names = new Set();
|
|
||||||
const get_functions = async (name) => {
|
|
||||||
const res = [];
|
|
||||||
if (fetched_names.has(name)) return res;
|
|
||||||
fetched_names.add(name);
|
|
||||||
const body = await do_get(get_url(name));
|
|
||||||
const { functions, references } = extract_functions_and_references(name, body);
|
|
||||||
res.push(...functions);
|
|
||||||
const chile_functions = await Promise.all(references.map(get_functions));
|
|
||||||
chile_functions.forEach((i) => res.push(...i));
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
const build_output = (functions) => {
|
|
||||||
const sorted = Array.from(new Set(functions.sort()));
|
|
||||||
return `\
|
|
||||||
/** ----------------------------------------------------------------------
|
|
||||||
This file is automatically generated by \`scripts/globals-extractor.js\`.
|
|
||||||
Generated At: ${new Date().toISOString()}
|
|
||||||
---------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
export default new Set([
|
|
||||||
${sorted.map((i) => `\t'${i}'`).join(',\n')}
|
|
||||||
]);
|
|
||||||
`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const get_exists_globals = () => {
|
|
||||||
const regexp = /^\s*["'](.+)["'],?\s*$/;
|
|
||||||
return fs
|
|
||||||
.readFileSync(GLOBAL_TS_PATH, 'utf8')
|
|
||||||
.split('\n')
|
|
||||||
.filter((line) => line.match(regexp))
|
|
||||||
.map((line) => line.match(regexp)[1]);
|
|
||||||
};
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
const globals = get_exists_globals();
|
|
||||||
const new_globals = await get_functions('es2021.full');
|
|
||||||
globals.forEach((g) => new_globals.push(g));
|
|
||||||
SPECIALS.forEach((g) => new_globals.push(g));
|
|
||||||
fs.writeFileSync(GLOBAL_TS_PATH, build_output(new_globals));
|
|
||||||
})();
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"plugins": ["lube"],
|
|
||||||
"rules": {
|
|
||||||
"lube/svelte-naming-convention": ["error", { "fixSameNames": true }]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
/**
|
||||||
|
* Actions can return an object containing the two properties defined in this interface. Both are optional.
|
||||||
|
* - update: An action can have a parameter. This method will be called whenever that parameter changes,
|
||||||
|
* immediately after Svelte has applied updates to the markup. `ActionReturn` and `ActionReturn<undefined>` both
|
||||||
|
* mean that the action accepts no parameters.
|
||||||
|
* - destroy: Method that is called after the element is unmounted
|
||||||
|
*
|
||||||
|
* Additionally, you can specify which additional attributes and events the action enables on the applied element.
|
||||||
|
* This applies to TypeScript typings only and has no effect at runtime.
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
* ```ts
|
||||||
|
* interface Attributes {
|
||||||
|
* newprop?: string;
|
||||||
|
* 'on:event': (e: CustomEvent<boolean>) => void;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* export function myAction(node: HTMLElement, parameter: Parameter): ActionReturn<Parameter, Attributes> {
|
||||||
|
* // ...
|
||||||
|
* return {
|
||||||
|
* update: (updatedParameter) => {...},
|
||||||
|
* destroy: () => {...}
|
||||||
|
* };
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Docs: https://svelte.dev/docs/svelte-action
|
||||||
|
*/
|
||||||
|
export interface ActionReturn<
|
||||||
|
Parameter = undefined,
|
||||||
|
Attributes extends Record<string, any> = Record<never, any>
|
||||||
|
> {
|
||||||
|
update?: (parameter: Parameter) => void;
|
||||||
|
destroy?: () => void;
|
||||||
|
/**
|
||||||
|
* ### DO NOT USE THIS
|
||||||
|
* This exists solely for type-checking and has no effect at runtime.
|
||||||
|
* Set this through the `Attributes` generic instead.
|
||||||
|
*/
|
||||||
|
$$_attributes?: Attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions are functions that are called when an element is created.
|
||||||
|
* You can use this interface to type such actions.
|
||||||
|
* The following example defines an action that only works on `<div>` elements
|
||||||
|
* and optionally accepts a parameter which it has a default value for:
|
||||||
|
* ```ts
|
||||||
|
* export const myAction: Action<HTMLDivElement, { someProperty: boolean } | undefined> = (node, param = { someProperty: true }) => {
|
||||||
|
* // ...
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
* `Action<HTMLDivElement>` and `Action<HTMLDiveElement, undefined>` both signal that the action accepts no parameters.
|
||||||
|
*
|
||||||
|
* You can return an object with methods `update` and `destroy` from the function and type which additional attributes and events it has.
|
||||||
|
* See interface `ActionReturn` for more details.
|
||||||
|
*
|
||||||
|
* Docs: https://svelte.dev/docs/svelte-action
|
||||||
|
*/
|
||||||
|
export interface Action<
|
||||||
|
Element = HTMLElement,
|
||||||
|
Parameter = undefined,
|
||||||
|
Attributes extends Record<string, any> = Record<never, any>
|
||||||
|
> {
|
||||||
|
<Node extends Element>(
|
||||||
|
...args: undefined extends Parameter
|
||||||
|
? [node: Node, parameter?: Parameter]
|
||||||
|
: [node: Node, parameter: Parameter]
|
||||||
|
): void | ActionReturn<Parameter, Attributes>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementation notes:
|
||||||
|
// - undefined extends X instead of X extends undefined makes this work better with both strict and nonstrict mode
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
import { cubicOut } from '../easing/index.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The flip function calculates the start and end position of an element and animates between them, translating the x and y values.
|
||||||
|
* `flip` stands for [First, Last, Invert, Play](https://aerotwist.com/blog/flip-your-animations/).
|
||||||
|
*
|
||||||
|
* https://svelte.dev/docs/svelte-animate#flip
|
||||||
|
* @param {Element} node
|
||||||
|
* @param {{ from: DOMRect; to: DOMRect }} fromTo
|
||||||
|
* @param {import('./public.js').FlipParams} params
|
||||||
|
* @returns {import('./public.js').AnimationConfig}
|
||||||
|
*/
|
||||||
|
export function flip(node, { from, to }, params = {}) {
|
||||||
|
const style = getComputedStyle(node);
|
||||||
|
const transform = style.transform === 'none' ? '' : style.transform;
|
||||||
|
const [ox, oy] = style.transformOrigin.split(' ').map(parseFloat);
|
||||||
|
const dx = from.left + (from.width * ox) / to.width - (to.left + ox);
|
||||||
|
const dy = from.top + (from.height * oy) / to.height - (to.top + oy);
|
||||||
|
const { delay = 0, duration = (d) => Math.sqrt(d) * 120, easing = cubicOut } = params;
|
||||||
|
return {
|
||||||
|
delay,
|
||||||
|
duration: typeof duration === 'function' ? duration(Math.sqrt(dx * dx + dy * dy)) : duration,
|
||||||
|
easing,
|
||||||
|
css: (t, u) => {
|
||||||
|
const x = u * dx;
|
||||||
|
const y = u * dy;
|
||||||
|
const sx = t + (u * from.width) / to.width;
|
||||||
|
const sy = t + (u * from.height) / to.height;
|
||||||
|
return `transform: ${transform} translate(${x}px, ${y}px) scale(${sx}, ${sy});`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,83 +0,0 @@
|
|||||||
const now = () => performance.now();
|
|
||||||
|
|
||||||
/** @param {any} timings */
|
|
||||||
function collapse_timings(timings) {
|
|
||||||
const result = {};
|
|
||||||
timings.forEach((timing) => {
|
|
||||||
result[timing.label] = Object.assign(
|
|
||||||
{
|
|
||||||
total: timing.end - timing.start
|
|
||||||
},
|
|
||||||
timing.children && collapse_timings(timing.children)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class Stats {
|
|
||||||
/**
|
|
||||||
* @typedef {Object} Timing
|
|
||||||
* @property {string} label
|
|
||||||
* @property {number} start
|
|
||||||
* @property {number} end
|
|
||||||
* @property {Timing[]} children
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** @type {number} */
|
|
||||||
start_time;
|
|
||||||
|
|
||||||
/** @type {Timing} */
|
|
||||||
current_timing;
|
|
||||||
|
|
||||||
/** @type {Timing[]} */
|
|
||||||
current_children;
|
|
||||||
|
|
||||||
/** @type {Timing[]} */
|
|
||||||
timings;
|
|
||||||
|
|
||||||
/** @type {Timing[]} */
|
|
||||||
stack;
|
|
||||||
constructor() {
|
|
||||||
this.start_time = now();
|
|
||||||
this.stack = [];
|
|
||||||
this.current_children = this.timings = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {any} label */
|
|
||||||
start(label) {
|
|
||||||
const timing = {
|
|
||||||
label,
|
|
||||||
start: now(),
|
|
||||||
end: null,
|
|
||||||
children: []
|
|
||||||
};
|
|
||||||
this.current_children.push(timing);
|
|
||||||
this.stack.push(timing);
|
|
||||||
this.current_timing = timing;
|
|
||||||
this.current_children = timing.children;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {any} label */
|
|
||||||
stop(label) {
|
|
||||||
if (label !== this.current_timing.label) {
|
|
||||||
throw new Error(
|
|
||||||
`Mismatched timing labels (expected ${this.current_timing.label}, got ${label})`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.current_timing.end = now();
|
|
||||||
this.stack.pop();
|
|
||||||
this.current_timing = this.stack[this.stack.length - 1];
|
|
||||||
this.current_children = this.current_timing ? this.current_timing.children : this.timings;
|
|
||||||
}
|
|
||||||
render() {
|
|
||||||
const timings = Object.assign(
|
|
||||||
{
|
|
||||||
total: now() - this.start_time
|
|
||||||
},
|
|
||||||
collapse_timings(this.timings)
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
timings
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,356 +0,0 @@
|
|||||||
// All compiler errors should be listed and accessed from here
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export default {
|
|
||||||
invalid_binding_elements: /**
|
|
||||||
* @param {string} element
|
|
||||||
* @param {string} binding
|
|
||||||
*/ (element, binding) => ({
|
|
||||||
code: 'invalid-binding',
|
|
||||||
message: `'${binding}' is not a valid binding on <${element}> elements`
|
|
||||||
}),
|
|
||||||
invalid_binding_element_with: /**
|
|
||||||
* @param {string} elements
|
|
||||||
* @param {string} binding
|
|
||||||
*/ (elements, binding) => ({
|
|
||||||
code: 'invalid-binding',
|
|
||||||
message: `'${binding}' binding can only be used with ${elements}`
|
|
||||||
}),
|
|
||||||
invalid_binding_on: /**
|
|
||||||
* @param {string} binding
|
|
||||||
* @param {string} element
|
|
||||||
* @param {string} [post]
|
|
||||||
*/ (binding, element, post) => ({
|
|
||||||
code: 'invalid-binding',
|
|
||||||
message: `'${binding}' is not a valid binding on ${element}` + (post || '')
|
|
||||||
}),
|
|
||||||
invalid_binding_foreign: /** @param {string} binding */ (binding) => ({
|
|
||||||
code: 'invalid-binding',
|
|
||||||
message: `'${binding}' is not a valid binding. Foreign elements only support bind:this`
|
|
||||||
}),
|
|
||||||
invalid_binding_no_checkbox: /**
|
|
||||||
* @param {string} binding
|
|
||||||
* @param {boolean} is_radio
|
|
||||||
*/ (binding, is_radio) => ({
|
|
||||||
code: 'invalid-binding',
|
|
||||||
message:
|
|
||||||
`'${binding}' binding can only be used with <input type="checkbox">` +
|
|
||||||
(is_radio ? ' — for <input type="radio">, use \'group\' binding' : '')
|
|
||||||
}),
|
|
||||||
invalid_binding: /** @param {string} binding */ (binding) => ({
|
|
||||||
code: 'invalid-binding',
|
|
||||||
message: `'${binding}' is not a valid binding`
|
|
||||||
}),
|
|
||||||
invalid_binding_window: /** @param {string[]} parts */ (parts) => ({
|
|
||||||
code: 'invalid-binding',
|
|
||||||
message: `Bindings on <svelte:window> must be to top-level properties, e.g. '${
|
|
||||||
parts[parts.length - 1]
|
|
||||||
}' rather than '${parts.join('.')}'`
|
|
||||||
}),
|
|
||||||
invalid_binding_let: {
|
|
||||||
code: 'invalid-binding',
|
|
||||||
message: 'Cannot bind to a variable declared with the let: directive'
|
|
||||||
},
|
|
||||||
invalid_binding_await: {
|
|
||||||
code: 'invalid-binding',
|
|
||||||
message: 'Cannot bind to a variable declared with {#await ... then} or {:catch} blocks'
|
|
||||||
},
|
|
||||||
invalid_binding_const: {
|
|
||||||
code: 'invalid-binding',
|
|
||||||
message: 'Cannot bind to a variable declared with {@const ...}'
|
|
||||||
},
|
|
||||||
invalid_binding_writable: {
|
|
||||||
code: 'invalid-binding',
|
|
||||||
message: 'Cannot bind to a variable which is not writable'
|
|
||||||
},
|
|
||||||
binding_undeclared: /** @param {string} name */ (name) => ({
|
|
||||||
code: 'binding-undeclared',
|
|
||||||
message: `${name} is not declared`
|
|
||||||
}),
|
|
||||||
invalid_type: {
|
|
||||||
code: 'invalid-type',
|
|
||||||
message: "'type' attribute cannot be dynamic if input uses two-way binding"
|
|
||||||
},
|
|
||||||
missing_type: {
|
|
||||||
code: 'missing-type',
|
|
||||||
message: "'type' attribute must be specified"
|
|
||||||
},
|
|
||||||
dynamic_multiple_attribute: {
|
|
||||||
code: 'dynamic-multiple-attribute',
|
|
||||||
message: "'multiple' attribute cannot be dynamic if select uses two-way binding"
|
|
||||||
},
|
|
||||||
missing_contenteditable_attribute: {
|
|
||||||
code: 'missing-contenteditable-attribute',
|
|
||||||
message:
|
|
||||||
"'contenteditable' attribute is required for textContent, innerHTML and innerText two-way bindings"
|
|
||||||
},
|
|
||||||
dynamic_contenteditable_attribute: {
|
|
||||||
code: 'dynamic-contenteditable-attribute',
|
|
||||||
message: "'contenteditable' attribute cannot be dynamic if element uses two-way binding"
|
|
||||||
},
|
|
||||||
invalid_event_modifier_combination: /**
|
|
||||||
* @param {string} modifier1
|
|
||||||
* @param {string} modifier2
|
|
||||||
*/ (modifier1, modifier2) => ({
|
|
||||||
code: 'invalid-event-modifier',
|
|
||||||
message: `The '${modifier1}' and '${modifier2}' modifiers cannot be used together`
|
|
||||||
}),
|
|
||||||
invalid_event_modifier_legacy: /** @param {string} modifier */ (modifier) => ({
|
|
||||||
code: 'invalid-event-modifier',
|
|
||||||
message: `The '${modifier}' modifier cannot be used in legacy mode`
|
|
||||||
}),
|
|
||||||
invalid_event_modifier: /** @param {string} valid */ (valid) => ({
|
|
||||||
code: 'invalid-event-modifier',
|
|
||||||
message: `Valid event modifiers are ${valid}`
|
|
||||||
}),
|
|
||||||
invalid_event_modifier_component: {
|
|
||||||
code: 'invalid-event-modifier',
|
|
||||||
message: "Event modifiers other than 'once' can only be used on DOM elements"
|
|
||||||
},
|
|
||||||
textarea_duplicate_value: {
|
|
||||||
code: 'textarea-duplicate-value',
|
|
||||||
message:
|
|
||||||
'A <textarea> can have either a value attribute or (equivalently) child content, but not both'
|
|
||||||
},
|
|
||||||
illegal_attribute: /** @param {string} name */ (name) => ({
|
|
||||||
code: 'illegal-attribute',
|
|
||||||
message: `'${name}' is not a valid attribute name`
|
|
||||||
}),
|
|
||||||
invalid_slot_attribute: {
|
|
||||||
code: 'invalid-slot-attribute',
|
|
||||||
message: 'slot attribute cannot have a dynamic value'
|
|
||||||
},
|
|
||||||
duplicate_slot_attribute: /** @param {string} name */ (name) => ({
|
|
||||||
code: 'duplicate-slot-attribute',
|
|
||||||
message: `Duplicate '${name}' slot`
|
|
||||||
}),
|
|
||||||
invalid_slotted_content: {
|
|
||||||
code: 'invalid-slotted-content',
|
|
||||||
message:
|
|
||||||
"Element with a slot='...' attribute must be a child of a component or a descendant of a custom element"
|
|
||||||
},
|
|
||||||
invalid_attribute_head: {
|
|
||||||
code: 'invalid-attribute',
|
|
||||||
message: '<svelte:head> should not have any attributes or directives'
|
|
||||||
},
|
|
||||||
invalid_action: {
|
|
||||||
code: 'invalid-action',
|
|
||||||
message: 'Actions can only be applied to DOM elements, not components'
|
|
||||||
},
|
|
||||||
invalid_animation: {
|
|
||||||
code: 'invalid-animation',
|
|
||||||
message: 'Animations can only be applied to DOM elements, not components'
|
|
||||||
},
|
|
||||||
invalid_class: {
|
|
||||||
code: 'invalid-class',
|
|
||||||
message: 'Classes can only be applied to DOM elements, not components'
|
|
||||||
},
|
|
||||||
invalid_transition: {
|
|
||||||
code: 'invalid-transition',
|
|
||||||
message: 'Transitions can only be applied to DOM elements, not components'
|
|
||||||
},
|
|
||||||
invalid_let: {
|
|
||||||
code: 'invalid-let',
|
|
||||||
message: 'let directive value must be an identifier or an object/array pattern'
|
|
||||||
},
|
|
||||||
invalid_slot_directive: {
|
|
||||||
code: 'invalid-slot-directive',
|
|
||||||
message: '<slot> cannot have directives'
|
|
||||||
},
|
|
||||||
dynamic_slot_name: {
|
|
||||||
code: 'dynamic-slot-name',
|
|
||||||
message: '<slot> name cannot be dynamic'
|
|
||||||
},
|
|
||||||
invalid_slot_name: {
|
|
||||||
code: 'invalid-slot-name',
|
|
||||||
message: 'default is a reserved word — it cannot be used as a slot name'
|
|
||||||
},
|
|
||||||
invalid_slot_attribute_value_missing: {
|
|
||||||
code: 'invalid-slot-attribute',
|
|
||||||
message: 'slot attribute value is missing'
|
|
||||||
},
|
|
||||||
invalid_slotted_content_fragment: {
|
|
||||||
code: 'invalid-slotted-content',
|
|
||||||
message: '<svelte:fragment> must be a child of a component'
|
|
||||||
},
|
|
||||||
illegal_attribute_title: {
|
|
||||||
code: 'illegal-attribute',
|
|
||||||
message: '<title> cannot have attributes'
|
|
||||||
},
|
|
||||||
illegal_structure_title: {
|
|
||||||
code: 'illegal-structure',
|
|
||||||
message: '<title> can only contain text and {tags}'
|
|
||||||
},
|
|
||||||
duplicate_transition: /**
|
|
||||||
* @param {string} directive
|
|
||||||
* @param {string} parent_directive
|
|
||||||
*/ (directive, parent_directive) => {
|
|
||||||
/** @param {string} _directive */
|
|
||||||
function describe(_directive) {
|
|
||||||
return _directive === 'transition' ? "a 'transition'" : `an '${_directive}'`;
|
|
||||||
}
|
|
||||||
const message =
|
|
||||||
directive === parent_directive
|
|
||||||
? `An element can only have one '${directive}' directive`
|
|
||||||
: `An element cannot have both ${describe(parent_directive)} directive and ${describe(
|
|
||||||
directive
|
|
||||||
)} directive`;
|
|
||||||
return {
|
|
||||||
code: 'duplicate-transition',
|
|
||||||
message
|
|
||||||
};
|
|
||||||
},
|
|
||||||
contextual_store: {
|
|
||||||
code: 'contextual-store',
|
|
||||||
message:
|
|
||||||
'Stores must be declared at the top level of the component (this may change in a future version of Svelte)'
|
|
||||||
},
|
|
||||||
default_export: {
|
|
||||||
code: 'default-export',
|
|
||||||
message: 'A component cannot have a default export'
|
|
||||||
},
|
|
||||||
illegal_declaration: {
|
|
||||||
code: 'illegal-declaration',
|
|
||||||
message: 'The $ prefix is reserved, and cannot be used for variable and import names'
|
|
||||||
},
|
|
||||||
illegal_subscription: {
|
|
||||||
code: 'illegal-subscription',
|
|
||||||
message: 'Cannot reference store value inside <script context="module">'
|
|
||||||
},
|
|
||||||
illegal_global: /** @param {string} name */ (name) => ({
|
|
||||||
code: 'illegal-global',
|
|
||||||
message: `${name} is an illegal variable name`
|
|
||||||
}),
|
|
||||||
illegal_variable_declaration: {
|
|
||||||
code: 'illegal-variable-declaration',
|
|
||||||
message: 'Cannot declare same variable name which is imported inside <script context="module">'
|
|
||||||
},
|
|
||||||
cyclical_reactive_declaration: /** @param {string[]} cycle */ (cycle) => ({
|
|
||||||
code: 'cyclical-reactive-declaration',
|
|
||||||
message: `Cyclical dependency detected: ${cycle.join(' → ')}`
|
|
||||||
}),
|
|
||||||
invalid_tag_property: {
|
|
||||||
code: 'invalid-tag-property',
|
|
||||||
message: "tag name must be two or more words joined by the '-' character"
|
|
||||||
},
|
|
||||||
invalid_customElement_attribute: {
|
|
||||||
code: 'invalid-customElement-attribute',
|
|
||||||
message:
|
|
||||||
"'customElement' must be a string literal defining a valid custom element name or an object of the form " +
|
|
||||||
"{ tag: string; shadow?: 'open' | 'none'; props?: { [key: string]: { attribute?: string; reflect?: boolean; type: .. } } }"
|
|
||||||
},
|
|
||||||
invalid_tag_attribute: {
|
|
||||||
code: 'invalid-tag-attribute',
|
|
||||||
message: "'tag' must be a string literal"
|
|
||||||
},
|
|
||||||
invalid_shadow_attribute: {
|
|
||||||
code: 'invalid-shadow-attribute',
|
|
||||||
message: "'shadow' must be either 'open' or 'none'"
|
|
||||||
},
|
|
||||||
invalid_props_attribute: {
|
|
||||||
code: 'invalid-props-attribute',
|
|
||||||
message:
|
|
||||||
"'props' must be a statically analyzable object literal of the form " +
|
|
||||||
"'{ [key: string]: { attribute?: string; reflect?: boolean; type?: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object' }'"
|
|
||||||
},
|
|
||||||
invalid_namespace_property: /**
|
|
||||||
* @param {string} namespace
|
|
||||||
* @param {string} [suggestion]
|
|
||||||
*/ (namespace, suggestion) => ({
|
|
||||||
code: 'invalid-namespace-property',
|
|
||||||
message:
|
|
||||||
`Invalid namespace '${namespace}'` + (suggestion ? ` (did you mean '${suggestion}'?)` : '')
|
|
||||||
}),
|
|
||||||
invalid_namespace_attribute: {
|
|
||||||
code: 'invalid-namespace-attribute',
|
|
||||||
message: "The 'namespace' attribute must be a string literal representing a valid namespace"
|
|
||||||
},
|
|
||||||
invalid_attribute_value: /** @param {string} name */ (name) => ({
|
|
||||||
code: `invalid-${name}-value`,
|
|
||||||
message: `${name} attribute must be true or false`
|
|
||||||
}),
|
|
||||||
invalid_options_attribute_unknown: /** @param {string} name */ (name) => ({
|
|
||||||
code: 'invalid-options-attribute',
|
|
||||||
message: `<svelte:options> unknown attribute '${name}'`
|
|
||||||
}),
|
|
||||||
invalid_options_attribute: {
|
|
||||||
code: 'invalid-options-attribute',
|
|
||||||
message:
|
|
||||||
"<svelte:options> can only have static 'tag', 'namespace', 'accessors', 'immutable' and 'preserveWhitespace' attributes"
|
|
||||||
},
|
|
||||||
css_invalid_global: {
|
|
||||||
code: 'css-invalid-global',
|
|
||||||
message: ':global(...) can be at the start or end of a selector sequence, but not in the middle'
|
|
||||||
},
|
|
||||||
css_invalid_global_selector: {
|
|
||||||
code: 'css-invalid-global-selector',
|
|
||||||
message: ':global(...) must contain a single selector'
|
|
||||||
},
|
|
||||||
css_invalid_global_selector_position: {
|
|
||||||
code: 'css-invalid-global-selector-position',
|
|
||||||
message:
|
|
||||||
':global(...) not at the start of a selector sequence should not contain type or universal selectors'
|
|
||||||
},
|
|
||||||
css_invalid_selector: /** @param {string} selector */ (selector) => ({
|
|
||||||
code: 'css-invalid-selector',
|
|
||||||
message: `Invalid selector "${selector}"`
|
|
||||||
}),
|
|
||||||
duplicate_animation: {
|
|
||||||
code: 'duplicate-animation',
|
|
||||||
message: "An element can only have one 'animate' directive"
|
|
||||||
},
|
|
||||||
invalid_animation_immediate: {
|
|
||||||
code: 'invalid-animation',
|
|
||||||
message:
|
|
||||||
'An element that uses the animate directive must be the immediate child of a keyed each block'
|
|
||||||
},
|
|
||||||
invalid_animation_key: {
|
|
||||||
code: 'invalid-animation',
|
|
||||||
message:
|
|
||||||
'An element that uses the animate directive must be used inside a keyed each block. Did you forget to add a key to your each block?'
|
|
||||||
},
|
|
||||||
invalid_animation_sole: {
|
|
||||||
code: 'invalid-animation',
|
|
||||||
message:
|
|
||||||
'An element that uses the animate directive must be the sole child of a keyed each block'
|
|
||||||
},
|
|
||||||
invalid_animation_dynamic_element: {
|
|
||||||
code: 'invalid-animation',
|
|
||||||
message: '<svelte:element> cannot have a animate directive'
|
|
||||||
},
|
|
||||||
invalid_directive_value: {
|
|
||||||
code: 'invalid-directive-value',
|
|
||||||
message:
|
|
||||||
'Can only bind to an identifier (e.g. `foo`) or a member expression (e.g. `foo.bar` or `foo[baz]`)'
|
|
||||||
},
|
|
||||||
invalid_const_placement: {
|
|
||||||
code: 'invalid-const-placement',
|
|
||||||
message:
|
|
||||||
'{@const} must be the immediate child of {#if}, {:else if}, {:else}, {#each}, {:then}, {:catch}, <svelte:fragment> or <Component>'
|
|
||||||
},
|
|
||||||
invalid_const_declaration: /** @param {string} name */ (name) => ({
|
|
||||||
code: 'invalid-const-declaration',
|
|
||||||
message: `'${name}' has already been declared`
|
|
||||||
}),
|
|
||||||
invalid_const_update: /** @param {string} name */ (name) => ({
|
|
||||||
code: 'invalid-const-update',
|
|
||||||
message: `'${name}' is declared using {@const ...} and is read-only`
|
|
||||||
}),
|
|
||||||
cyclical_const_tags: /** @param {string[]} cycle */ (cycle) => ({
|
|
||||||
code: 'cyclical-const-tags',
|
|
||||||
message: `Cyclical dependency detected: ${cycle.join(' → ')}`
|
|
||||||
}),
|
|
||||||
invalid_component_style_directive: {
|
|
||||||
code: 'invalid-component-style-directive',
|
|
||||||
message: 'Style directives cannot be used on components'
|
|
||||||
},
|
|
||||||
invalid_var_declaration: {
|
|
||||||
code: 'invalid_var_declaration',
|
|
||||||
message: '"var" scope should not extend outside the reactive block'
|
|
||||||
},
|
|
||||||
invalid_style_directive_modifier: /** @param {string} valid */ (valid) => ({
|
|
||||||
code: 'invalid-style-directive-modifier',
|
|
||||||
message: `Valid modifiers for style directives are: ${valid}`
|
|
||||||
})
|
|
||||||
};
|
|
||||||
@ -1,310 +0,0 @@
|
|||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export default {
|
|
||||||
tag_option_deprecated: {
|
|
||||||
code: 'tag-option-deprecated',
|
|
||||||
message: "'tag' option is deprecated — use 'customElement' instead"
|
|
||||||
},
|
|
||||||
unused_export_let: /**
|
|
||||||
* @param {string} component
|
|
||||||
* @param {string} property
|
|
||||||
*/ (component, property) => ({
|
|
||||||
code: 'unused-export-let',
|
|
||||||
message: `${component} has unused export property '${property}'. If it is for external reference only, please consider using \`export const ${property}\``
|
|
||||||
}),
|
|
||||||
module_script_reactive_declaration: {
|
|
||||||
code: 'module-script-reactive-declaration',
|
|
||||||
message: '$: has no effect in a module script'
|
|
||||||
},
|
|
||||||
non_top_level_reactive_declaration: {
|
|
||||||
code: 'non-top-level-reactive-declaration',
|
|
||||||
message: '$: has no effect outside of the top-level'
|
|
||||||
},
|
|
||||||
module_script_variable_reactive_declaration: /** @param {string[]} names */ (names) => ({
|
|
||||||
code: 'module-script-reactive-declaration',
|
|
||||||
message: `${names.map((name) => `"${name}"`).join(', ')} ${
|
|
||||||
names.length > 1 ? 'are' : 'is'
|
|
||||||
} declared in a module script and will not be reactive`
|
|
||||||
}),
|
|
||||||
missing_declaration: /**
|
|
||||||
* @param {string} name
|
|
||||||
* @param {boolean} has_script
|
|
||||||
*/ (name, has_script) => ({
|
|
||||||
code: 'missing-declaration',
|
|
||||||
message:
|
|
||||||
`'${name}' is not defined` +
|
|
||||||
(has_script
|
|
||||||
? ''
|
|
||||||
: `. Consider adding a <script> block with 'export let ${name}' to declare a prop`)
|
|
||||||
}),
|
|
||||||
missing_custom_element_compile_options: {
|
|
||||||
code: 'missing-custom-element-compile-options',
|
|
||||||
message:
|
|
||||||
"The 'customElement' option is used when generating a custom element. Did you forget the 'customElement: true' compile option?"
|
|
||||||
},
|
|
||||||
css_unused_selector: /** @param {string} selector */ (selector) => ({
|
|
||||||
code: 'css-unused-selector',
|
|
||||||
message: `Unused CSS selector "${selector}"`
|
|
||||||
}),
|
|
||||||
empty_block: {
|
|
||||||
code: 'empty-block',
|
|
||||||
message: 'Empty block'
|
|
||||||
},
|
|
||||||
reactive_component: /** @param {string} name */ (name) => ({
|
|
||||||
code: 'reactive-component',
|
|
||||||
message: `<${name}/> will not be reactive if ${name} changes. Use <svelte:component this={${name}}/> if you want this reactivity.`
|
|
||||||
}),
|
|
||||||
component_name_lowercase: /** @param {string} name */ (name) => ({
|
|
||||||
code: 'component-name-lowercase',
|
|
||||||
message: `<${name}> will be treated as an HTML element unless it begins with a capital letter`
|
|
||||||
}),
|
|
||||||
avoid_is: {
|
|
||||||
code: 'avoid-is',
|
|
||||||
message: "The 'is' attribute is not supported cross-browser and should be avoided"
|
|
||||||
},
|
|
||||||
invalid_html_attribute: /**
|
|
||||||
* @param {string} name
|
|
||||||
* @param {string} suggestion
|
|
||||||
*/ (name, suggestion) => ({
|
|
||||||
code: 'invalid-html-attribute',
|
|
||||||
message: `'${name}' is not a valid HTML attribute. Did you mean '${suggestion}'?`
|
|
||||||
}),
|
|
||||||
a11y_aria_attributes: /** @param {string} name */ (name) => ({
|
|
||||||
code: 'a11y-aria-attributes',
|
|
||||||
message: `A11y: <${name}> should not have aria-* attributes`
|
|
||||||
}),
|
|
||||||
a11y_incorrect_attribute_type: /**
|
|
||||||
* @param {import('aria-query').ARIAPropertyDefinition} schema
|
|
||||||
* @param {string} attribute
|
|
||||||
*/ (schema, attribute) => {
|
|
||||||
let message;
|
|
||||||
switch (schema.type) {
|
|
||||||
case 'boolean':
|
|
||||||
message = `The value of '${attribute}' must be exactly one of true or false`;
|
|
||||||
break;
|
|
||||||
case 'id':
|
|
||||||
message = `The value of '${attribute}' must be a string that represents a DOM element ID`;
|
|
||||||
break;
|
|
||||||
case 'idlist':
|
|
||||||
message = `The value of '${attribute}' must be a space-separated list of strings that represent DOM element IDs`;
|
|
||||||
break;
|
|
||||||
case 'tristate':
|
|
||||||
message = `The value of '${attribute}' must be exactly one of true, false, or mixed`;
|
|
||||||
break;
|
|
||||||
case 'token':
|
|
||||||
message = `The value of '${attribute}' must be exactly one of ${(schema.values || []).join(
|
|
||||||
', '
|
|
||||||
)}`;
|
|
||||||
break;
|
|
||||||
case 'tokenlist':
|
|
||||||
message = `The value of '${attribute}' must be a space-separated list of one or more of ${(
|
|
||||||
schema.values || []
|
|
||||||
).join(', ')}`;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
message = `The value of '${attribute}' must be of type ${schema.type}`;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
code: 'a11y-incorrect-aria-attribute-type',
|
|
||||||
message: `A11y: ${message}`
|
|
||||||
};
|
|
||||||
},
|
|
||||||
a11y_unknown_aria_attribute: /**
|
|
||||||
* @param {string} attribute
|
|
||||||
* @param {string} [suggestion]
|
|
||||||
*/ (attribute, suggestion) => ({
|
|
||||||
code: 'a11y-unknown-aria-attribute',
|
|
||||||
message:
|
|
||||||
`A11y: Unknown aria attribute 'aria-${attribute}'` +
|
|
||||||
(suggestion ? ` (did you mean '${suggestion}'?)` : '')
|
|
||||||
}),
|
|
||||||
a11y_hidden: /** @param {string} name */ (name) => ({
|
|
||||||
code: 'a11y-hidden',
|
|
||||||
message: `A11y: <${name}> element should not be hidden`
|
|
||||||
}),
|
|
||||||
a11y_misplaced_role: /** @param {string} name */ (name) => ({
|
|
||||||
code: 'a11y-misplaced-role',
|
|
||||||
message: `A11y: <${name}> should not have role attribute`
|
|
||||||
}),
|
|
||||||
a11y_unknown_role: /**
|
|
||||||
* @param {string | boolean} role
|
|
||||||
* @param {string} [suggestion]
|
|
||||||
*/ (role, suggestion) => ({
|
|
||||||
code: 'a11y-unknown-role',
|
|
||||||
message: `A11y: Unknown role '${role}'` + (suggestion ? ` (did you mean '${suggestion}'?)` : '')
|
|
||||||
}),
|
|
||||||
a11y_no_abstract_role: /** @param {string | boolean} role */ (role) => ({
|
|
||||||
code: 'a11y-no-abstract-role',
|
|
||||||
message: `A11y: Abstract role '${role}' is forbidden`
|
|
||||||
}),
|
|
||||||
a11y_no_redundant_roles: /** @param {string | boolean} role */ (role) => ({
|
|
||||||
code: 'a11y-no-redundant-roles',
|
|
||||||
message: `A11y: Redundant role '${role}'`
|
|
||||||
}),
|
|
||||||
a11y_no_static_element_interactions: /**
|
|
||||||
* @param {string} element
|
|
||||||
* @param {string[]} handlers
|
|
||||||
*/ (element, handlers) => ({
|
|
||||||
code: 'a11y-no-static-element-interactions',
|
|
||||||
message: `A11y: <${element}> with ${handlers.join(', ')} ${
|
|
||||||
handlers.length === 1 ? 'handler' : 'handlers'
|
|
||||||
} must have an ARIA role`
|
|
||||||
}),
|
|
||||||
a11y_no_interactive_element_to_noninteractive_role: /**
|
|
||||||
* @param {string | boolean} role
|
|
||||||
* @param {string} element
|
|
||||||
*/ (role, element) => ({
|
|
||||||
code: 'a11y-no-interactive-element-to-noninteractive-role',
|
|
||||||
message: `A11y: <${element}> cannot have role '${role}'`
|
|
||||||
}),
|
|
||||||
a11y_no_noninteractive_element_interactions: /** @param {string} element */ (element) => ({
|
|
||||||
code: 'a11y-no-noninteractive-element-interactions',
|
|
||||||
message: `A11y: Non-interactive element <${element}> should not be assigned mouse or keyboard event listeners.`
|
|
||||||
}),
|
|
||||||
a11y_no_noninteractive_element_to_interactive_role: /**
|
|
||||||
* @param {string | boolean} role
|
|
||||||
* @param {string} element
|
|
||||||
*/ (role, element) => ({
|
|
||||||
code: 'a11y-no-noninteractive-element-to-interactive-role',
|
|
||||||
message: `A11y: Non-interactive element <${element}> cannot have interactive role '${role}'`
|
|
||||||
}),
|
|
||||||
a11y_role_has_required_aria_props: /**
|
|
||||||
* @param {string} role
|
|
||||||
* @param {string[]} props
|
|
||||||
*/ (role, props) => ({
|
|
||||||
code: 'a11y-role-has-required-aria-props',
|
|
||||||
message: `A11y: Elements with the ARIA role "${role}" must have the following attributes defined: ${props
|
|
||||||
.map((name) => `"${name}"`)
|
|
||||||
.join(', ')}`
|
|
||||||
}),
|
|
||||||
a11y_role_supports_aria_props: /**
|
|
||||||
* @param {string} attribute
|
|
||||||
* @param {string} role
|
|
||||||
* @param {boolean} is_implicit
|
|
||||||
* @param {string} name
|
|
||||||
*/ (attribute, role, is_implicit, name) => {
|
|
||||||
let message = `The attribute '${attribute}' is not supported by the role '${role}'.`;
|
|
||||||
if (is_implicit) {
|
|
||||||
message += ` This role is implicit on the element <${name}>.`;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
code: 'a11y-role-supports-aria-props',
|
|
||||||
message: `A11y: ${message}`
|
|
||||||
};
|
|
||||||
},
|
|
||||||
a11y_accesskey: {
|
|
||||||
code: 'a11y-accesskey',
|
|
||||||
message: 'A11y: Avoid using accesskey'
|
|
||||||
},
|
|
||||||
a11y_autofocus: {
|
|
||||||
code: 'a11y-autofocus',
|
|
||||||
message: 'A11y: Avoid using autofocus'
|
|
||||||
},
|
|
||||||
a11y_misplaced_scope: {
|
|
||||||
code: 'a11y-misplaced-scope',
|
|
||||||
message: 'A11y: The scope attribute should only be used with <th> elements'
|
|
||||||
},
|
|
||||||
a11y_positive_tabindex: {
|
|
||||||
code: 'a11y-positive-tabindex',
|
|
||||||
message: 'A11y: avoid tabindex values above zero'
|
|
||||||
},
|
|
||||||
a11y_invalid_attribute: /**
|
|
||||||
* @param {string} href_attribute
|
|
||||||
* @param {string} href_value
|
|
||||||
*/ (href_attribute, href_value) => ({
|
|
||||||
code: 'a11y-invalid-attribute',
|
|
||||||
message: `A11y: '${href_value}' is not a valid ${href_attribute} attribute`
|
|
||||||
}),
|
|
||||||
a11y_missing_attribute: /**
|
|
||||||
* @param {string} name
|
|
||||||
* @param {string} article
|
|
||||||
* @param {string} sequence
|
|
||||||
*/ (name, article, sequence) => ({
|
|
||||||
code: 'a11y-missing-attribute',
|
|
||||||
message: `A11y: <${name}> element should have ${article} ${sequence} attribute`
|
|
||||||
}),
|
|
||||||
a11y_autocomplete_valid: /**
|
|
||||||
* @param {null | true | string} type
|
|
||||||
* @param {null | true | string} value
|
|
||||||
*/ (type, value) => ({
|
|
||||||
code: 'a11y-autocomplete-valid',
|
|
||||||
message: `A11y: The value '${value}' is not supported by the attribute 'autocomplete' on element <input type="${
|
|
||||||
type || '...'
|
|
||||||
}">`
|
|
||||||
}),
|
|
||||||
a11y_img_redundant_alt: {
|
|
||||||
code: 'a11y-img-redundant-alt',
|
|
||||||
message: 'A11y: Screenreaders already announce <img> elements as an image.'
|
|
||||||
},
|
|
||||||
a11y_interactive_supports_focus: /** @param {string} role */ (role) => ({
|
|
||||||
code: 'a11y-interactive-supports-focus',
|
|
||||||
message: `A11y: Elements with the '${role}' interactive role must have a tabindex value.`
|
|
||||||
}),
|
|
||||||
a11y_label_has_associated_control: {
|
|
||||||
code: 'a11y-label-has-associated-control',
|
|
||||||
message: 'A11y: A form label must be associated with a control.'
|
|
||||||
},
|
|
||||||
a11y_media_has_caption: {
|
|
||||||
code: 'a11y-media-has-caption',
|
|
||||||
message: 'A11y: <video> elements must have a <track kind="captions">'
|
|
||||||
},
|
|
||||||
a11y_distracting_elements: /** @param {string} name */ (name) => ({
|
|
||||||
code: 'a11y-distracting-elements',
|
|
||||||
message: `A11y: Avoid <${name}> elements`
|
|
||||||
}),
|
|
||||||
a11y_structure_immediate: {
|
|
||||||
code: 'a11y-structure',
|
|
||||||
message: 'A11y: <figcaption> must be an immediate child of <figure>'
|
|
||||||
},
|
|
||||||
a11y_structure_first_or_last: {
|
|
||||||
code: 'a11y-structure',
|
|
||||||
message: 'A11y: <figcaption> must be first or last child of <figure>'
|
|
||||||
},
|
|
||||||
a11y_mouse_events_have_key_events: /**
|
|
||||||
* @param {string} event
|
|
||||||
* @param {string} accompanied_by
|
|
||||||
*/ (event, accompanied_by) => ({
|
|
||||||
code: 'a11y-mouse-events-have-key-events',
|
|
||||||
message: `A11y: on:${event} must be accompanied by on:${accompanied_by}`
|
|
||||||
}),
|
|
||||||
a11y_click_events_have_key_events: {
|
|
||||||
code: 'a11y-click-events-have-key-events',
|
|
||||||
message:
|
|
||||||
'A11y: visible, non-interactive elements with an on:click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as <button type="button"> or <a> might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.'
|
|
||||||
},
|
|
||||||
a11y_missing_content: /** @param {string} name */ (name) => ({
|
|
||||||
code: 'a11y-missing-content',
|
|
||||||
message: `A11y: <${name}> element should have child content`
|
|
||||||
}),
|
|
||||||
a11y_no_noninteractive_tabindex: {
|
|
||||||
code: 'a11y-no-noninteractive-tabindex',
|
|
||||||
message: 'A11y: noninteractive element cannot have nonnegative tabIndex value'
|
|
||||||
},
|
|
||||||
a11y_aria_activedescendant_has_tabindex: {
|
|
||||||
code: 'a11y-aria-activedescendant-has-tabindex',
|
|
||||||
message: 'A11y: Elements with attribute aria-activedescendant should have tabindex value'
|
|
||||||
},
|
|
||||||
redundant_event_modifier_for_touch: {
|
|
||||||
code: 'redundant-event-modifier',
|
|
||||||
message: "Touch event handlers that don't use the 'event' object are passive by default"
|
|
||||||
},
|
|
||||||
redundant_event_modifier_passive: {
|
|
||||||
code: 'redundant-event-modifier',
|
|
||||||
message: 'The passive modifier only works with wheel and touch events'
|
|
||||||
},
|
|
||||||
invalid_rest_eachblock_binding: /** @param {string} rest_element_name */ (rest_element_name) => ({
|
|
||||||
code: 'invalid-rest-eachblock-binding',
|
|
||||||
message: `The rest operator (...) will create a new object and binding '${rest_element_name}' with the original object will not work`
|
|
||||||
}),
|
|
||||||
avoid_mouse_events_on_document: {
|
|
||||||
code: 'avoid-mouse-events-on-document',
|
|
||||||
message:
|
|
||||||
'Mouse enter/leave events on the document are not supported in all browsers and should be avoided'
|
|
||||||
},
|
|
||||||
illegal_attribute_character: {
|
|
||||||
code: 'illegal-attribute-character',
|
|
||||||
message:
|
|
||||||
"Attributes should not contain ':' characters to prevent ambiguity with Svelte directives"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,156 +0,0 @@
|
|||||||
import { b } from 'code-red';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {any} program
|
|
||||||
* @param {import('estree').Identifier} name
|
|
||||||
* @param {string} banner
|
|
||||||
* @param {any} svelte_path
|
|
||||||
* @param {Array<{ name: string; alias: import('estree').Identifier }>} helpers
|
|
||||||
* @param {Array<{ name: string; alias: import('estree').Identifier }>} globals
|
|
||||||
* @param {import('estree').ImportDeclaration[]} imports
|
|
||||||
* @param {Export[]} module_exports
|
|
||||||
* @param {import('estree').ExportNamedDeclaration[]} exports_from
|
|
||||||
*/
|
|
||||||
export default function create_module(
|
|
||||||
program,
|
|
||||||
name,
|
|
||||||
banner,
|
|
||||||
svelte_path = 'svelte',
|
|
||||||
helpers,
|
|
||||||
globals,
|
|
||||||
imports,
|
|
||||||
module_exports,
|
|
||||||
exports_from
|
|
||||||
) {
|
|
||||||
const internal_path = `${svelte_path}/internal`;
|
|
||||||
helpers.sort((a, b) => (a.name < b.name ? -1 : 1));
|
|
||||||
globals.sort((a, b) => (a.name < b.name ? -1 : 1));
|
|
||||||
return esm(
|
|
||||||
program,
|
|
||||||
name,
|
|
||||||
banner,
|
|
||||||
svelte_path,
|
|
||||||
internal_path,
|
|
||||||
helpers,
|
|
||||||
globals,
|
|
||||||
imports,
|
|
||||||
module_exports,
|
|
||||||
exports_from
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {any} source
|
|
||||||
* @param {any} svelte_path
|
|
||||||
*/
|
|
||||||
function edit_source(source, svelte_path) {
|
|
||||||
return source === 'svelte' || source.startsWith('svelte/')
|
|
||||||
? source.replace('svelte', svelte_path)
|
|
||||||
: source;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Array<{ name: string; alias: import('estree').Identifier }>} globals
|
|
||||||
* @param {Array<{ name: string; alias: import('estree').Identifier }>} helpers
|
|
||||||
*/
|
|
||||||
function get_internal_globals(globals, helpers) {
|
|
||||||
return (
|
|
||||||
globals.length > 0 && {
|
|
||||||
type: 'VariableDeclaration',
|
|
||||||
kind: 'const',
|
|
||||||
declarations: [
|
|
||||||
{
|
|
||||||
type: 'VariableDeclarator',
|
|
||||||
id: {
|
|
||||||
type: 'ObjectPattern',
|
|
||||||
properties: globals.map((g) => ({
|
|
||||||
type: 'Property',
|
|
||||||
method: false,
|
|
||||||
shorthand: false,
|
|
||||||
computed: false,
|
|
||||||
key: { type: 'Identifier', name: g.name },
|
|
||||||
value: g.alias,
|
|
||||||
kind: 'init'
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
init: helpers.find(({ name }) => name === 'globals').alias
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {any} program
|
|
||||||
* @param {import('estree').Identifier} name
|
|
||||||
* @param {string} banner
|
|
||||||
* @param {string} svelte_path
|
|
||||||
* @param {string} internal_path
|
|
||||||
* @param {Array<{ name: string; alias: import('estree').Identifier }>} helpers
|
|
||||||
* @param {Array<{ name: string; alias: import('estree').Identifier }>} globals
|
|
||||||
* @param {import('estree').ImportDeclaration[]} imports
|
|
||||||
* @param {Export[]} module_exports
|
|
||||||
* @param {import('estree').ExportNamedDeclaration[]} exports_from
|
|
||||||
*/
|
|
||||||
function esm(
|
|
||||||
program,
|
|
||||||
name,
|
|
||||||
banner,
|
|
||||||
svelte_path,
|
|
||||||
internal_path,
|
|
||||||
helpers,
|
|
||||||
globals,
|
|
||||||
imports,
|
|
||||||
module_exports,
|
|
||||||
exports_from
|
|
||||||
) {
|
|
||||||
const import_declaration = {
|
|
||||||
type: 'ImportDeclaration',
|
|
||||||
specifiers: helpers.map((h) => ({
|
|
||||||
type: 'ImportSpecifier',
|
|
||||||
local: h.alias,
|
|
||||||
imported: { type: 'Identifier', name: h.name }
|
|
||||||
})),
|
|
||||||
source: { type: 'Literal', value: internal_path }
|
|
||||||
};
|
|
||||||
const internal_globals = get_internal_globals(globals, helpers);
|
|
||||||
// edit user imports
|
|
||||||
|
|
||||||
/** @param {any} node */
|
|
||||||
function rewrite_import(node) {
|
|
||||||
const value = edit_source(node.source.value, svelte_path);
|
|
||||||
if (node.source.value !== value) {
|
|
||||||
node.source.value = value;
|
|
||||||
node.source.raw = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
imports.forEach(rewrite_import);
|
|
||||||
exports_from.forEach(rewrite_import);
|
|
||||||
const exports = module_exports.length > 0 && {
|
|
||||||
type: 'ExportNamedDeclaration',
|
|
||||||
specifiers: module_exports.map((x) => ({
|
|
||||||
type: 'Specifier',
|
|
||||||
local: { type: 'Identifier', name: x.name },
|
|
||||||
exported: { type: 'Identifier', name: x.as }
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
program.body = b`
|
|
||||||
/* ${banner} */
|
|
||||||
|
|
||||||
${import_declaration}
|
|
||||||
${internal_globals}
|
|
||||||
${imports}
|
|
||||||
${exports_from}
|
|
||||||
|
|
||||||
${program.body}
|
|
||||||
|
|
||||||
export default ${name};
|
|
||||||
${exports}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} Export
|
|
||||||
* @property {string} name
|
|
||||||
* @property {string} as
|
|
||||||
*/
|
|
||||||
@ -1,833 +0,0 @@
|
|||||||
import { gather_possible_values, UNKNOWN } from './gather_possible_values.js';
|
|
||||||
import compiler_errors from '../compiler_errors.js';
|
|
||||||
import { regex_starts_with_whitespace, regex_ends_with_whitespace } from '../../utils/patterns.js';
|
|
||||||
|
|
||||||
const BlockAppliesToNode = /** @type {const} */ ({
|
|
||||||
NotPossible: 0,
|
|
||||||
Possible: 1,
|
|
||||||
UnknownSelectorType: 2
|
|
||||||
});
|
|
||||||
|
|
||||||
const NodeExist = /** @type {const} */ ({
|
|
||||||
Probably: 0,
|
|
||||||
Definitely: 1
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @typedef {typeof NodeExist[keyof typeof NodeExist]} NodeExistsValue */
|
|
||||||
|
|
||||||
const whitelist_attribute_selector = new Map([
|
|
||||||
['details', new Set(['open'])],
|
|
||||||
['dialog', new Set(['open'])]
|
|
||||||
]);
|
|
||||||
const regex_is_single_css_selector = /[^\\],(?!([^([]+[^\\]|[^([\\])[)\]])/;
|
|
||||||
|
|
||||||
export default class Selector {
|
|
||||||
/** @type {import('./private.js').CssNode} */
|
|
||||||
node;
|
|
||||||
|
|
||||||
/** @type {import('./Stylesheet.js').default} */
|
|
||||||
stylesheet;
|
|
||||||
|
|
||||||
/** @type {Block[]} */
|
|
||||||
blocks;
|
|
||||||
|
|
||||||
/** @type {Block[]} */
|
|
||||||
local_blocks;
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
used;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('./private.js').CssNode} node
|
|
||||||
* @param {import('./Stylesheet.js').default} stylesheet
|
|
||||||
*/
|
|
||||||
constructor(node, stylesheet) {
|
|
||||||
this.node = node;
|
|
||||||
this.stylesheet = stylesheet;
|
|
||||||
this.blocks = group_selectors(node);
|
|
||||||
// take trailing :global(...) selectors out of consideration
|
|
||||||
let i = this.blocks.length;
|
|
||||||
while (i > 0) {
|
|
||||||
if (!this.blocks[i - 1].global) break;
|
|
||||||
i -= 1;
|
|
||||||
}
|
|
||||||
this.local_blocks = this.blocks.slice(0, i);
|
|
||||||
const host_only = this.blocks.length === 1 && this.blocks[0].host;
|
|
||||||
const root_only = this.blocks.length === 1 && this.blocks[0].root;
|
|
||||||
this.used = this.local_blocks.length === 0 || host_only || root_only;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {import('../nodes/Element.js').default} node */
|
|
||||||
apply(node) {
|
|
||||||
/** @type {Array<{ node: import('../nodes/Element.js').default; block: Block }>} */
|
|
||||||
const to_encapsulate = [];
|
|
||||||
apply_selector(this.local_blocks.slice(), node, to_encapsulate);
|
|
||||||
if (to_encapsulate.length > 0) {
|
|
||||||
to_encapsulate.forEach(({ node, block }) => {
|
|
||||||
this.stylesheet.nodes_with_css_class.add(node);
|
|
||||||
block.should_encapsulate = true;
|
|
||||||
});
|
|
||||||
this.used = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {import('magic-string').default} code */
|
|
||||||
minify(code) {
|
|
||||||
/** @type {number} */
|
|
||||||
let c = null;
|
|
||||||
this.blocks.forEach((block, i) => {
|
|
||||||
if (i > 0) {
|
|
||||||
if (block.start - c > 1) {
|
|
||||||
code.update(c, block.start, block.combinator.name || ' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c = block.end;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('magic-string').default} code
|
|
||||||
* @param {string} attr
|
|
||||||
* @param {number} max_amount_class_specificity_increased
|
|
||||||
*/
|
|
||||||
transform(code, attr, max_amount_class_specificity_increased) {
|
|
||||||
const amount_class_specificity_to_increase =
|
|
||||||
max_amount_class_specificity_increased -
|
|
||||||
this.blocks.filter((block) => block.should_encapsulate).length;
|
|
||||||
|
|
||||||
/** @param {import('./private.js').CssNode} selector */
|
|
||||||
function remove_global_pseudo_class(selector) {
|
|
||||||
const first = selector.children[0];
|
|
||||||
const last = selector.children[selector.children.length - 1];
|
|
||||||
code.remove(selector.start, first.start).remove(last.end, selector.end);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Block} block
|
|
||||||
* @param {string} attr
|
|
||||||
*/
|
|
||||||
function encapsulate_block(block, attr) {
|
|
||||||
for (const selector of block.selectors) {
|
|
||||||
if (selector.type === 'PseudoClassSelector' && selector.name === 'global') {
|
|
||||||
remove_global_pseudo_class(selector);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let i = block.selectors.length;
|
|
||||||
while (i--) {
|
|
||||||
const selector = block.selectors[i];
|
|
||||||
if (selector.type === 'PseudoElementSelector' || selector.type === 'PseudoClassSelector') {
|
|
||||||
if (selector.name !== 'root' && selector.name !== 'host') {
|
|
||||||
if (i === 0) code.prependRight(selector.start, attr);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (selector.type === 'TypeSelector' && selector.name === '*') {
|
|
||||||
code.update(selector.start, selector.end, attr);
|
|
||||||
} else {
|
|
||||||
code.appendLeft(selector.end, attr);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.blocks.forEach((block, index) => {
|
|
||||||
if (block.global) {
|
|
||||||
remove_global_pseudo_class(block.selectors[0]);
|
|
||||||
}
|
|
||||||
if (block.should_encapsulate)
|
|
||||||
encapsulate_block(
|
|
||||||
block,
|
|
||||||
index === this.blocks.length - 1
|
|
||||||
? attr.repeat(amount_class_specificity_to_increase + 1)
|
|
||||||
: attr
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {import('../Component.js').default} component */
|
|
||||||
validate(component) {
|
|
||||||
let start = 0;
|
|
||||||
let end = this.blocks.length;
|
|
||||||
for (; start < end; start += 1) {
|
|
||||||
if (!this.blocks[start].global) break;
|
|
||||||
}
|
|
||||||
for (; end > start; end -= 1) {
|
|
||||||
if (!this.blocks[end - 1].global) break;
|
|
||||||
}
|
|
||||||
for (let i = start; i < end; i += 1) {
|
|
||||||
if (this.blocks[i].global) {
|
|
||||||
return component.error(this.blocks[i].selectors[0], compiler_errors.css_invalid_global);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.validate_global_with_multiple_selectors(component);
|
|
||||||
this.validate_global_compound_selector(component);
|
|
||||||
this.validate_invalid_combinator_without_selector(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {import('../Component.js').default} component */
|
|
||||||
validate_global_with_multiple_selectors(component) {
|
|
||||||
if (this.blocks.length === 1 && this.blocks[0].selectors.length === 1) {
|
|
||||||
// standalone :global() with multiple selectors is OK
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const block of this.blocks) {
|
|
||||||
for (const selector of block.selectors) {
|
|
||||||
if (selector.type === 'PseudoClassSelector' && selector.name === 'global') {
|
|
||||||
if (regex_is_single_css_selector.test(selector.children[0].value)) {
|
|
||||||
component.error(selector, compiler_errors.css_invalid_global_selector);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {import('../Component.js').default} component */
|
|
||||||
validate_invalid_combinator_without_selector(component) {
|
|
||||||
for (let i = 0; i < this.blocks.length; i++) {
|
|
||||||
const block = this.blocks[i];
|
|
||||||
if (block.combinator && block.selectors.length === 0) {
|
|
||||||
component.error(
|
|
||||||
this.node,
|
|
||||||
compiler_errors.css_invalid_selector(
|
|
||||||
component.source.slice(this.node.start, this.node.end)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!block.combinator && block.selectors.length === 0) {
|
|
||||||
component.error(
|
|
||||||
this.node,
|
|
||||||
compiler_errors.css_invalid_selector(
|
|
||||||
component.source.slice(this.node.start, this.node.end)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {import('../Component.js').default} component */
|
|
||||||
validate_global_compound_selector(component) {
|
|
||||||
for (const block of this.blocks) {
|
|
||||||
for (let index = 0; index < block.selectors.length; index++) {
|
|
||||||
const selector = block.selectors[index];
|
|
||||||
if (
|
|
||||||
selector.type === 'PseudoClassSelector' &&
|
|
||||||
selector.name === 'global' &&
|
|
||||||
index !== 0 &&
|
|
||||||
selector.children &&
|
|
||||||
selector.children.length > 0 &&
|
|
||||||
!/[.:#\s]/.test(selector.children[0].value[0])
|
|
||||||
) {
|
|
||||||
component.error(selector, compiler_errors.css_invalid_global_selector_position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get_amount_class_specificity_increased() {
|
|
||||||
let count = 0;
|
|
||||||
for (const block of this.blocks) {
|
|
||||||
if (block.should_encapsulate) {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Block[]} blocks
|
|
||||||
* @param {import('../nodes/Element.js').default} node
|
|
||||||
* @param {Array<{ node: import('../nodes/Element.js').default; block: Block }>} to_encapsulate
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
function apply_selector(blocks, node, to_encapsulate) {
|
|
||||||
const block = blocks.pop();
|
|
||||||
if (!block) return false;
|
|
||||||
if (!node) {
|
|
||||||
return (
|
|
||||||
(block.global && blocks.every((block) => block.global)) || (block.host && blocks.length === 0)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
switch (block_might_apply_to_node(block, node)) {
|
|
||||||
case BlockAppliesToNode.NotPossible:
|
|
||||||
return false;
|
|
||||||
case BlockAppliesToNode.UnknownSelectorType:
|
|
||||||
// bail. TODO figure out what these could be
|
|
||||||
to_encapsulate.push({ node, block });
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (block.combinator) {
|
|
||||||
if (block.combinator.type === 'Combinator' && block.combinator.name === ' ') {
|
|
||||||
for (const ancestor_block of blocks) {
|
|
||||||
if (ancestor_block.global) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (ancestor_block.host) {
|
|
||||||
to_encapsulate.push({ node, block });
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
let parent = node;
|
|
||||||
while ((parent = get_element_parent(parent))) {
|
|
||||||
if (
|
|
||||||
block_might_apply_to_node(ancestor_block, parent) !== BlockAppliesToNode.NotPossible
|
|
||||||
) {
|
|
||||||
to_encapsulate.push({ node: parent, block: ancestor_block });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (to_encapsulate.length) {
|
|
||||||
to_encapsulate.push({ node, block });
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (blocks.every((block) => block.global)) {
|
|
||||||
to_encapsulate.push({ node, block });
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} else if (block.combinator.name === '>') {
|
|
||||||
const has_global_parent = blocks.every((block) => block.global);
|
|
||||||
if (has_global_parent || apply_selector(blocks, get_element_parent(node), to_encapsulate)) {
|
|
||||||
to_encapsulate.push({ node, block });
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} else if (block.combinator.name === '+' || block.combinator.name === '~') {
|
|
||||||
const siblings = get_possible_element_siblings(node, block.combinator.name === '+');
|
|
||||||
let has_match = false;
|
|
||||||
// NOTE: if we have :global(), we couldn't figure out what is selected within `:global` due to the
|
|
||||||
// css-tree limitation that does not parse the inner selector of :global
|
|
||||||
// so unless we are sure there will be no sibling to match, we will consider it as matched
|
|
||||||
const has_global = blocks.some((block) => block.global);
|
|
||||||
if (has_global) {
|
|
||||||
if (siblings.size === 0 && get_element_parent(node) !== null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
to_encapsulate.push({ node, block });
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
for (const possible_sibling of siblings.keys()) {
|
|
||||||
if (apply_selector(blocks.slice(), possible_sibling, to_encapsulate)) {
|
|
||||||
to_encapsulate.push({ node, block });
|
|
||||||
has_match = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return has_match;
|
|
||||||
}
|
|
||||||
// TODO other combinators
|
|
||||||
to_encapsulate.push({ node, block });
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
to_encapsulate.push({ node, block });
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const regex_backslash_and_following_character = /\\(.)/g;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Block} block
|
|
||||||
* @param {import('../nodes/Element.js').default} node
|
|
||||||
* @returns {typeof BlockAppliesToNode[keyof typeof BlockAppliesToNode]}
|
|
||||||
*/
|
|
||||||
function block_might_apply_to_node(block, node) {
|
|
||||||
let i = block.selectors.length;
|
|
||||||
while (i--) {
|
|
||||||
const selector = block.selectors[i];
|
|
||||||
const name =
|
|
||||||
typeof selector.name === 'string' &&
|
|
||||||
selector.name.replace(regex_backslash_and_following_character, '$1');
|
|
||||||
if (selector.type === 'PseudoClassSelector' && (name === 'host' || name === 'root')) {
|
|
||||||
return BlockAppliesToNode.NotPossible;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
block.selectors.length === 1 &&
|
|
||||||
selector.type === 'PseudoClassSelector' &&
|
|
||||||
name === 'global'
|
|
||||||
) {
|
|
||||||
return BlockAppliesToNode.NotPossible;
|
|
||||||
}
|
|
||||||
if (selector.type === 'PseudoClassSelector' || selector.type === 'PseudoElementSelector') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (selector.type === 'ClassSelector') {
|
|
||||||
if (
|
|
||||||
!attribute_matches(node, 'class', name, '~=', false) &&
|
|
||||||
!node.classes.some((c) => c.name === name)
|
|
||||||
)
|
|
||||||
return BlockAppliesToNode.NotPossible;
|
|
||||||
} else if (selector.type === 'IdSelector') {
|
|
||||||
if (!attribute_matches(node, 'id', name, '=', false)) return BlockAppliesToNode.NotPossible;
|
|
||||||
} else if (selector.type === 'AttributeSelector') {
|
|
||||||
if (
|
|
||||||
!(
|
|
||||||
whitelist_attribute_selector.has(node.name.toLowerCase()) &&
|
|
||||||
whitelist_attribute_selector
|
|
||||||
.get(node.name.toLowerCase())
|
|
||||||
.has(selector.name.name.toLowerCase())
|
|
||||||
) &&
|
|
||||||
!attribute_matches(
|
|
||||||
node,
|
|
||||||
selector.name.name,
|
|
||||||
selector.value && unquote(selector.value),
|
|
||||||
selector.matcher,
|
|
||||||
selector.flags
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return BlockAppliesToNode.NotPossible;
|
|
||||||
}
|
|
||||||
} else if (selector.type === 'TypeSelector') {
|
|
||||||
if (
|
|
||||||
node.name.toLowerCase() !== name.toLowerCase() &&
|
|
||||||
name !== '*' &&
|
|
||||||
!node.is_dynamic_element
|
|
||||||
)
|
|
||||||
return BlockAppliesToNode.NotPossible;
|
|
||||||
} else {
|
|
||||||
return BlockAppliesToNode.UnknownSelectorType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return BlockAppliesToNode.Possible;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {any} operator
|
|
||||||
* @param {any} expected_value
|
|
||||||
* @param {any} case_insensitive
|
|
||||||
* @param {any} value
|
|
||||||
*/
|
|
||||||
function test_attribute(operator, expected_value, case_insensitive, value) {
|
|
||||||
if (case_insensitive) {
|
|
||||||
expected_value = expected_value.toLowerCase();
|
|
||||||
value = value.toLowerCase();
|
|
||||||
}
|
|
||||||
switch (operator) {
|
|
||||||
case '=':
|
|
||||||
return value === expected_value;
|
|
||||||
case '~=':
|
|
||||||
return value.split(/\s/).includes(expected_value);
|
|
||||||
case '|=':
|
|
||||||
return `${value}-`.startsWith(`${expected_value}-`);
|
|
||||||
case '^=':
|
|
||||||
return value.startsWith(expected_value);
|
|
||||||
case '$=':
|
|
||||||
return value.endsWith(expected_value);
|
|
||||||
case '*=':
|
|
||||||
return value.includes(expected_value);
|
|
||||||
default:
|
|
||||||
throw new Error("this shouldn't happen");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('./private.js').CssNode} node
|
|
||||||
* @param {string} name
|
|
||||||
* @param {string} expected_value
|
|
||||||
* @param {string} operator
|
|
||||||
* @param {boolean} case_insensitive
|
|
||||||
*/
|
|
||||||
function attribute_matches(node, name, expected_value, operator, case_insensitive) {
|
|
||||||
const spread = node.attributes.find((attr) => attr.type === 'Spread');
|
|
||||||
if (spread) return true;
|
|
||||||
if (node.bindings.some((binding) => binding.name === name)) return true;
|
|
||||||
const attr = node.attributes.find((attr) => attr.name === name);
|
|
||||||
if (!attr) return false;
|
|
||||||
if (attr.is_true) return operator === null;
|
|
||||||
if (expected_value == null) return true;
|
|
||||||
if (attr.chunks.length === 1) {
|
|
||||||
const value = attr.chunks[0];
|
|
||||||
if (!value) return false;
|
|
||||||
if (value.type === 'Text')
|
|
||||||
return test_attribute(operator, expected_value, case_insensitive, value.data);
|
|
||||||
}
|
|
||||||
const possible_values = new Set();
|
|
||||||
let prev_values = [];
|
|
||||||
for (const chunk of attr.chunks) {
|
|
||||||
const current_possible_values = new Set();
|
|
||||||
if (chunk.type === 'Text') {
|
|
||||||
current_possible_values.add(chunk.data);
|
|
||||||
} else {
|
|
||||||
gather_possible_values(chunk.node, current_possible_values);
|
|
||||||
}
|
|
||||||
// impossible to find out all combinations
|
|
||||||
if (current_possible_values.has(UNKNOWN)) return true;
|
|
||||||
if (prev_values.length > 0) {
|
|
||||||
const start_with_space = [];
|
|
||||||
const remaining = [];
|
|
||||||
current_possible_values.forEach((current_possible_value) => {
|
|
||||||
if (regex_starts_with_whitespace.test(current_possible_value)) {
|
|
||||||
start_with_space.push(current_possible_value);
|
|
||||||
} else {
|
|
||||||
remaining.push(current_possible_value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (remaining.length > 0) {
|
|
||||||
if (start_with_space.length > 0) {
|
|
||||||
prev_values.forEach((prev_value) => possible_values.add(prev_value));
|
|
||||||
}
|
|
||||||
const combined = [];
|
|
||||||
prev_values.forEach((prev_value) => {
|
|
||||||
remaining.forEach((value) => {
|
|
||||||
combined.push(prev_value + value);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
prev_values = combined;
|
|
||||||
start_with_space.forEach((value) => {
|
|
||||||
if (regex_ends_with_whitespace.test(value)) {
|
|
||||||
possible_values.add(value);
|
|
||||||
} else {
|
|
||||||
prev_values.push(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
prev_values.forEach((prev_value) => possible_values.add(prev_value));
|
|
||||||
prev_values = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
current_possible_values.forEach((current_possible_value) => {
|
|
||||||
if (regex_ends_with_whitespace.test(current_possible_value)) {
|
|
||||||
possible_values.add(current_possible_value);
|
|
||||||
} else {
|
|
||||||
prev_values.push(current_possible_value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (prev_values.length < current_possible_values.size) {
|
|
||||||
prev_values.push(' ');
|
|
||||||
}
|
|
||||||
if (prev_values.length > 20) {
|
|
||||||
// might grow exponentially, bail out
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prev_values.forEach((prev_value) => possible_values.add(prev_value));
|
|
||||||
if (possible_values.has(UNKNOWN)) return true;
|
|
||||||
for (const value of possible_values) {
|
|
||||||
if (test_attribute(operator, expected_value, case_insensitive, value)) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {import('./private.js').CssNode} value */
|
|
||||||
function unquote(value) {
|
|
||||||
if (value.type === 'Identifier') return value.name;
|
|
||||||
const str = value.value;
|
|
||||||
if ((str[0] === str[str.length - 1] && str[0] === "'") || str[0] === '"') {
|
|
||||||
return str.slice(1, str.length - 1);
|
|
||||||
}
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../nodes/Element.js').default} node
|
|
||||||
* @returns {any}
|
|
||||||
*/
|
|
||||||
function get_element_parent(node) {
|
|
||||||
/** @type {import('../nodes/interfaces.js').INode} */
|
|
||||||
let parent = node;
|
|
||||||
while ((parent = parent.parent) && parent.type !== 'Element');
|
|
||||||
return /** @type {import('../nodes/Element.js').default | null} */ (parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the given node's previous sibling in the DOM
|
|
||||||
*
|
|
||||||
* The Svelte <slot> is just a placeholder and is not actually real. Any children nodes
|
|
||||||
* in <slot> are 'flattened' and considered as the same level as the <slot>'s siblings
|
|
||||||
*
|
|
||||||
* e.g.
|
|
||||||
* <h1>Heading 1</h1>
|
|
||||||
* <slot>
|
|
||||||
* <h2>Heading 2</h2>
|
|
||||||
* </slot>
|
|
||||||
*
|
|
||||||
* is considered to look like:
|
|
||||||
* <h1>Heading 1</h1>
|
|
||||||
* <h2>Heading 2</h2>
|
|
||||||
* @param {import('../nodes/interfaces.js').INode} node
|
|
||||||
* @returns {import('../nodes/interfaces.js').INode}
|
|
||||||
*/
|
|
||||||
function find_previous_sibling(node) {
|
|
||||||
/** @type {import('../nodes/interfaces.js').INode} */
|
|
||||||
let current_node = node;
|
|
||||||
do {
|
|
||||||
if (current_node.type === 'Slot') {
|
|
||||||
const slot_children = current_node.children;
|
|
||||||
if (slot_children.length > 0) {
|
|
||||||
current_node = slot_children.slice(-1)[0]; // go to its last child first
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (!current_node.prev && current_node.parent && current_node.parent.type === 'Slot') {
|
|
||||||
current_node = current_node.parent;
|
|
||||||
}
|
|
||||||
current_node = current_node.prev;
|
|
||||||
} while (current_node && current_node.type === 'Slot');
|
|
||||||
return current_node;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../nodes/interfaces.js').INode} node
|
|
||||||
* @param {boolean} adjacent_only
|
|
||||||
* @returns {Map<import('../nodes/Element.js').default, NodeExistsValue>}
|
|
||||||
*/
|
|
||||||
function get_possible_element_siblings(node, adjacent_only) {
|
|
||||||
/** @type {Map<import('../nodes/Element.js').default, NodeExistsValue>} */
|
|
||||||
const result = new Map();
|
|
||||||
|
|
||||||
/** @type {import('../nodes/interfaces.js').INode} */
|
|
||||||
let prev = node;
|
|
||||||
while ((prev = find_previous_sibling(prev))) {
|
|
||||||
if (prev.type === 'Element') {
|
|
||||||
if (
|
|
||||||
!prev.attributes.find(
|
|
||||||
(attr) => attr.type === 'Attribute' && attr.name.toLowerCase() === 'slot'
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
result.set(prev, NodeExist.Definitely);
|
|
||||||
}
|
|
||||||
if (adjacent_only) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (prev.type === 'EachBlock' || prev.type === 'IfBlock' || prev.type === 'AwaitBlock') {
|
|
||||||
const possible_last_child = get_possible_last_child(prev, adjacent_only);
|
|
||||||
add_to_map(possible_last_child, result);
|
|
||||||
if (adjacent_only && has_definite_elements(possible_last_child)) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!prev || !adjacent_only) {
|
|
||||||
/** @type {import('../nodes/interfaces.js').INode} */
|
|
||||||
let parent = node;
|
|
||||||
let skip_each_for_last_child = node.type === 'ElseBlock';
|
|
||||||
while (
|
|
||||||
(parent = parent.parent) &&
|
|
||||||
(parent.type === 'EachBlock' ||
|
|
||||||
parent.type === 'IfBlock' ||
|
|
||||||
parent.type === 'ElseBlock' ||
|
|
||||||
parent.type === 'AwaitBlock')
|
|
||||||
) {
|
|
||||||
const possible_siblings = get_possible_element_siblings(parent, adjacent_only);
|
|
||||||
add_to_map(possible_siblings, result);
|
|
||||||
if (parent.type === 'EachBlock') {
|
|
||||||
// first child of each block can select the last child of each block as previous sibling
|
|
||||||
if (skip_each_for_last_child) {
|
|
||||||
skip_each_for_last_child = false;
|
|
||||||
} else {
|
|
||||||
add_to_map(get_possible_last_child(parent, adjacent_only), result);
|
|
||||||
}
|
|
||||||
} else if (parent.type === 'ElseBlock') {
|
|
||||||
skip_each_for_last_child = true;
|
|
||||||
parent = parent.parent;
|
|
||||||
}
|
|
||||||
if (adjacent_only && has_definite_elements(possible_siblings)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../nodes/EachBlock.js').default | import('../nodes/IfBlock.js').default | import('../nodes/AwaitBlock.js').default} block
|
|
||||||
* @param {boolean} adjacent_only
|
|
||||||
* @returns {Map<import('../nodes/Element.js').default, NodeExistsValue>}
|
|
||||||
*/
|
|
||||||
function get_possible_last_child(block, adjacent_only) {
|
|
||||||
/** @typedef {Map<import('../nodes/Element.js').default, NodeExistsValue>} NodeMap */
|
|
||||||
|
|
||||||
/** @type {NodeMap} */
|
|
||||||
const result = new Map();
|
|
||||||
if (block.type === 'EachBlock') {
|
|
||||||
/** @type {NodeMap} */
|
|
||||||
const each_result = loop_child(block.children, adjacent_only);
|
|
||||||
|
|
||||||
/** @type {NodeMap} */
|
|
||||||
const else_result = block.else ? loop_child(block.else.children, adjacent_only) : new Map();
|
|
||||||
const not_exhaustive = !has_definite_elements(else_result);
|
|
||||||
if (not_exhaustive) {
|
|
||||||
mark_as_probably(each_result);
|
|
||||||
mark_as_probably(else_result);
|
|
||||||
}
|
|
||||||
add_to_map(each_result, result);
|
|
||||||
add_to_map(else_result, result);
|
|
||||||
} else if (block.type === 'IfBlock') {
|
|
||||||
/** @type {NodeMap} */
|
|
||||||
const if_result = loop_child(block.children, adjacent_only);
|
|
||||||
|
|
||||||
/** @type {NodeMap} */
|
|
||||||
const else_result = block.else ? loop_child(block.else.children, adjacent_only) : new Map();
|
|
||||||
const not_exhaustive = !has_definite_elements(if_result) || !has_definite_elements(else_result);
|
|
||||||
if (not_exhaustive) {
|
|
||||||
mark_as_probably(if_result);
|
|
||||||
mark_as_probably(else_result);
|
|
||||||
}
|
|
||||||
add_to_map(if_result, result);
|
|
||||||
add_to_map(else_result, result);
|
|
||||||
} else if (block.type === 'AwaitBlock') {
|
|
||||||
/** @type {NodeMap} */
|
|
||||||
const pending_result = block.pending
|
|
||||||
? loop_child(block.pending.children, adjacent_only)
|
|
||||||
: new Map();
|
|
||||||
|
|
||||||
/** @type {NodeMap} */
|
|
||||||
const then_result = block.then ? loop_child(block.then.children, adjacent_only) : new Map();
|
|
||||||
|
|
||||||
/** @type {NodeMap} */
|
|
||||||
const catch_result = block.catch ? loop_child(block.catch.children, adjacent_only) : new Map();
|
|
||||||
const not_exhaustive =
|
|
||||||
!has_definite_elements(pending_result) ||
|
|
||||||
!has_definite_elements(then_result) ||
|
|
||||||
!has_definite_elements(catch_result);
|
|
||||||
if (not_exhaustive) {
|
|
||||||
mark_as_probably(pending_result);
|
|
||||||
mark_as_probably(then_result);
|
|
||||||
mark_as_probably(catch_result);
|
|
||||||
}
|
|
||||||
add_to_map(pending_result, result);
|
|
||||||
add_to_map(then_result, result);
|
|
||||||
add_to_map(catch_result, result);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Map<import('../nodes/Element.js').default, NodeExistsValue>} result
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
function has_definite_elements(result) {
|
|
||||||
if (result.size === 0) return false;
|
|
||||||
for (const exist of result.values()) {
|
|
||||||
if (exist === NodeExist.Definitely) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Map<import('../nodes/Element.js').default, NodeExistsValue>} from
|
|
||||||
* @param {Map<import('../nodes/Element.js').default, NodeExistsValue>} to
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
function add_to_map(from, to) {
|
|
||||||
from.forEach((exist, element) => {
|
|
||||||
to.set(element, higher_existence(exist, to.get(element)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {NodeExistsValue | null} exist1
|
|
||||||
* @param {NodeExistsValue | null} exist2
|
|
||||||
* @returns {NodeExistsValue}
|
|
||||||
*/
|
|
||||||
function higher_existence(exist1, exist2) {
|
|
||||||
if (exist1 === undefined || exist2 === undefined) return exist1 || exist2;
|
|
||||||
return exist1 > exist2 ? exist1 : exist2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {Map<import('../nodes/Element.js').default, NodeExistsValue>} result */
|
|
||||||
function mark_as_probably(result) {
|
|
||||||
for (const key of result.keys()) {
|
|
||||||
result.set(key, NodeExist.Probably);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../nodes/interfaces.js').INode[]} children
|
|
||||||
* @param {boolean} adjacent_only
|
|
||||||
*/
|
|
||||||
function loop_child(children, adjacent_only) {
|
|
||||||
/** @type {Map<import('../nodes/Element.js').default, NodeExistsValue>} */
|
|
||||||
const result = new Map();
|
|
||||||
for (let i = children.length - 1; i >= 0; i--) {
|
|
||||||
const child = children[i];
|
|
||||||
if (child.type === 'Element') {
|
|
||||||
result.set(child, NodeExist.Definitely);
|
|
||||||
if (adjacent_only) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
child.type === 'EachBlock' ||
|
|
||||||
child.type === 'IfBlock' ||
|
|
||||||
child.type === 'AwaitBlock'
|
|
||||||
) {
|
|
||||||
const child_result = get_possible_last_child(child, adjacent_only);
|
|
||||||
add_to_map(child_result, result);
|
|
||||||
if (adjacent_only && has_definite_elements(child_result)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Block {
|
|
||||||
/** @type {boolean} */
|
|
||||||
host;
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
root;
|
|
||||||
|
|
||||||
/** @type {import('./private.js').CssNode} */
|
|
||||||
combinator;
|
|
||||||
|
|
||||||
/** @type {import('./private.js').CssNode[]} */
|
|
||||||
selectors;
|
|
||||||
|
|
||||||
/** @type {number} */
|
|
||||||
start;
|
|
||||||
|
|
||||||
/** @type {number} */
|
|
||||||
end;
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
should_encapsulate;
|
|
||||||
|
|
||||||
/** @param {import('./private.js').CssNode} combinator */
|
|
||||||
constructor(combinator) {
|
|
||||||
this.combinator = combinator;
|
|
||||||
this.host = false;
|
|
||||||
this.root = false;
|
|
||||||
this.selectors = [];
|
|
||||||
this.start = null;
|
|
||||||
this.end = null;
|
|
||||||
this.should_encapsulate = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {import('./private.js').CssNode} selector */
|
|
||||||
add(selector) {
|
|
||||||
if (this.selectors.length === 0) {
|
|
||||||
this.start = selector.start;
|
|
||||||
this.host = selector.type === 'PseudoClassSelector' && selector.name === 'host';
|
|
||||||
}
|
|
||||||
this.root = this.root || (selector.type === 'PseudoClassSelector' && selector.name === 'root');
|
|
||||||
this.selectors.push(selector);
|
|
||||||
this.end = selector.end;
|
|
||||||
}
|
|
||||||
get global() {
|
|
||||||
return (
|
|
||||||
this.selectors.length >= 1 &&
|
|
||||||
this.selectors[0].type === 'PseudoClassSelector' &&
|
|
||||||
this.selectors[0].name === 'global' &&
|
|
||||||
this.selectors.every(
|
|
||||||
(selector) =>
|
|
||||||
selector.type === 'PseudoClassSelector' || selector.type === 'PseudoElementSelector'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {import('./private.js').CssNode} selector */
|
|
||||||
function group_selectors(selector) {
|
|
||||||
/** @type {Block} */
|
|
||||||
let block = new Block(null);
|
|
||||||
const blocks = [block];
|
|
||||||
selector.children.forEach((child) => {
|
|
||||||
if (child.type === 'WhiteSpace' || child.type === 'Combinator') {
|
|
||||||
block = new Block(child);
|
|
||||||
blocks.push(block);
|
|
||||||
} else {
|
|
||||||
block.add(child);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return blocks;
|
|
||||||
}
|
|
||||||
@ -1,514 +0,0 @@
|
|||||||
import MagicString from 'magic-string';
|
|
||||||
import { walk } from 'estree-walker';
|
|
||||||
import Selector from './Selector.js';
|
|
||||||
import hash from '../utils/hash.js';
|
|
||||||
import compiler_warnings from '../compiler_warnings.js';
|
|
||||||
import { extract_ignores_above_position } from '../../utils/extract_svelte_ignore.js';
|
|
||||||
import { push_array } from '../../utils/push_array.js';
|
|
||||||
import { regex_only_whitespaces, regex_whitespace } from '../../utils/patterns.js';
|
|
||||||
|
|
||||||
const regex_css_browser_prefix = /^-((webkit)|(moz)|(o)|(ms))-/;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} name
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
function remove_css_prefix(name) {
|
|
||||||
return name.replace(regex_css_browser_prefix, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {import('./private.js').CssNode} node */
|
|
||||||
const is_keyframes_node = (node) => remove_css_prefix(node.name) === 'keyframes';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('./private.js').CssNode} param
|
|
||||||
* @returns {true}
|
|
||||||
*/
|
|
||||||
const at_rule_has_declaration = ({ block }) =>
|
|
||||||
block && block.children && block.children.find((node) => node.type === 'Declaration');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('magic-string').default} code
|
|
||||||
* @param {number} start
|
|
||||||
* @param {Declaration[]} declarations
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
function minify_declarations(code, start, declarations) {
|
|
||||||
let c = start;
|
|
||||||
declarations.forEach((declaration, i) => {
|
|
||||||
const separator = i > 0 ? ';' : '';
|
|
||||||
if (declaration.node.start - c > separator.length) {
|
|
||||||
code.update(c, declaration.node.start, separator);
|
|
||||||
}
|
|
||||||
declaration.minify(code);
|
|
||||||
c = declaration.node.end;
|
|
||||||
});
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
class Rule {
|
|
||||||
/** @type {import('./Selector.js').default[]} */
|
|
||||||
selectors;
|
|
||||||
|
|
||||||
/** @type {Declaration[]} */
|
|
||||||
declarations;
|
|
||||||
|
|
||||||
/** @type {import('./private.js').CssNode} */
|
|
||||||
node;
|
|
||||||
|
|
||||||
/** @type {Atrule} */
|
|
||||||
parent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('./private.js').CssNode} node
|
|
||||||
* @param {any} stylesheet
|
|
||||||
* @param {Atrule} [parent]
|
|
||||||
*/
|
|
||||||
constructor(node, stylesheet, parent) {
|
|
||||||
this.node = node;
|
|
||||||
this.parent = parent;
|
|
||||||
this.selectors = node.prelude.children.map((node) => new Selector(node, stylesheet));
|
|
||||||
this.declarations = node.block.children.map((node) => new Declaration(node));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {import('../nodes/Element.js').default} node */
|
|
||||||
apply(node) {
|
|
||||||
this.selectors.forEach((selector) => selector.apply(node)); // TODO move the logic in here?
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {boolean} dev */
|
|
||||||
is_used(dev) {
|
|
||||||
if (this.parent && this.parent.node.type === 'Atrule' && is_keyframes_node(this.parent.node))
|
|
||||||
return true;
|
|
||||||
if (this.declarations.length === 0) return dev;
|
|
||||||
return this.selectors.some((s) => s.used);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('magic-string').default} code
|
|
||||||
* @param {boolean} _dev
|
|
||||||
*/
|
|
||||||
minify(code, _dev) {
|
|
||||||
let c = this.node.start;
|
|
||||||
let started = false;
|
|
||||||
this.selectors.forEach((selector) => {
|
|
||||||
if (selector.used) {
|
|
||||||
const separator = started ? ',' : '';
|
|
||||||
if (selector.node.start - c > separator.length) {
|
|
||||||
code.update(c, selector.node.start, separator);
|
|
||||||
}
|
|
||||||
selector.minify(code);
|
|
||||||
c = selector.node.end;
|
|
||||||
started = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
code.remove(c, this.node.block.start);
|
|
||||||
c = this.node.block.start + 1;
|
|
||||||
c = minify_declarations(code, c, this.declarations);
|
|
||||||
code.remove(c, this.node.block.end - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('magic-string').default} code
|
|
||||||
* @param {string} id
|
|
||||||
* @param {Map<string, string>} keyframes
|
|
||||||
* @param {number} max_amount_class_specificity_increased
|
|
||||||
*/
|
|
||||||
transform(code, id, keyframes, max_amount_class_specificity_increased) {
|
|
||||||
if (this.parent && this.parent.node.type === 'Atrule' && is_keyframes_node(this.parent.node))
|
|
||||||
return true;
|
|
||||||
const attr = `.${id}`;
|
|
||||||
this.selectors.forEach((selector) =>
|
|
||||||
selector.transform(code, attr, max_amount_class_specificity_increased)
|
|
||||||
);
|
|
||||||
this.declarations.forEach((declaration) => declaration.transform(code, keyframes));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {import('../Component.js').default} component */
|
|
||||||
validate(component) {
|
|
||||||
this.selectors.forEach((selector) => {
|
|
||||||
selector.validate(component);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {(selector: import('./Selector.js').default) => void} handler */
|
|
||||||
warn_on_unused_selector(handler) {
|
|
||||||
this.selectors.forEach((selector) => {
|
|
||||||
if (!selector.used) handler(selector);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
get_max_amount_class_specificity_increased() {
|
|
||||||
return Math.max(
|
|
||||||
...this.selectors.map((selector) => selector.get_amount_class_specificity_increased())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
class Declaration {
|
|
||||||
/** @type {import('./private.js').CssNode} */
|
|
||||||
node;
|
|
||||||
|
|
||||||
/** @param {import('./private.js').CssNode} node */
|
|
||||||
constructor(node) {
|
|
||||||
this.node = node;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('magic-string').default} code
|
|
||||||
* @param {Map<string, string>} keyframes
|
|
||||||
*/
|
|
||||||
transform(code, keyframes) {
|
|
||||||
const property = this.node.property && remove_css_prefix(this.node.property.toLowerCase());
|
|
||||||
if (property === 'animation' || property === 'animation-name') {
|
|
||||||
this.node.value.children.forEach((block) => {
|
|
||||||
if (block.type === 'Identifier') {
|
|
||||||
const name = block.name;
|
|
||||||
if (keyframes.has(name)) {
|
|
||||||
code.update(block.start, block.end, keyframes.get(name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {import('magic-string').default} code */
|
|
||||||
minify(code) {
|
|
||||||
if (!this.node.property) return; // @apply, and possibly other weird cases?
|
|
||||||
const c = this.node.start + this.node.property.length;
|
|
||||||
const first = this.node.value.children ? this.node.value.children[0] : this.node.value;
|
|
||||||
// Don't minify whitespace in custom properties, since some browsers (Chromium < 99)
|
|
||||||
// treat --foo: ; and --foo:; differently
|
|
||||||
if (first.type === 'Raw' && regex_only_whitespaces.test(first.value)) return;
|
|
||||||
let start = first.start;
|
|
||||||
while (regex_whitespace.test(code.original[start])) start += 1;
|
|
||||||
if (start - c > 1) {
|
|
||||||
code.update(c, start, ':');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
class Atrule {
|
|
||||||
/** @type {import('./private.js').CssNode} */
|
|
||||||
node;
|
|
||||||
|
|
||||||
/** @type {Array<Atrule | Rule>} */
|
|
||||||
children;
|
|
||||||
|
|
||||||
/** @type {Declaration[]} */
|
|
||||||
declarations;
|
|
||||||
|
|
||||||
/** @param {import('./private.js').CssNode} node */
|
|
||||||
constructor(node) {
|
|
||||||
this.node = node;
|
|
||||||
this.children = [];
|
|
||||||
this.declarations = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {import('../nodes/Element.js').default} node */
|
|
||||||
apply(node) {
|
|
||||||
if (
|
|
||||||
this.node.name === 'container' ||
|
|
||||||
this.node.name === 'media' ||
|
|
||||||
this.node.name === 'supports' ||
|
|
||||||
this.node.name === 'layer'
|
|
||||||
) {
|
|
||||||
this.children.forEach((child) => {
|
|
||||||
child.apply(node);
|
|
||||||
});
|
|
||||||
} else if (is_keyframes_node(this.node)) {
|
|
||||||
this.children.forEach((/** @type {Rule} */ rule) => {
|
|
||||||
rule.selectors.forEach((selector) => {
|
|
||||||
selector.used = true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {boolean} _dev */
|
|
||||||
is_used(_dev) {
|
|
||||||
return true; // TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('magic-string').default} code
|
|
||||||
* @param {boolean} dev
|
|
||||||
*/
|
|
||||||
minify(code, dev) {
|
|
||||||
if (this.node.name === 'media') {
|
|
||||||
const expression_char = code.original[this.node.prelude.start];
|
|
||||||
let c = this.node.start + (expression_char === '(' ? 6 : 7);
|
|
||||||
if (this.node.prelude.start > c) code.remove(c, this.node.prelude.start);
|
|
||||||
this.node.prelude.children.forEach((query) => {
|
|
||||||
// TODO minify queries
|
|
||||||
c = query.end;
|
|
||||||
});
|
|
||||||
code.remove(c, this.node.block.start);
|
|
||||||
} else if (this.node.name === 'supports') {
|
|
||||||
let c = this.node.start + 9;
|
|
||||||
if (this.node.prelude.start - c > 1) code.update(c, this.node.prelude.start, ' ');
|
|
||||||
this.node.prelude.children.forEach((query) => {
|
|
||||||
// TODO minify queries
|
|
||||||
c = query.end;
|
|
||||||
});
|
|
||||||
code.remove(c, this.node.block.start);
|
|
||||||
} else {
|
|
||||||
let c = this.node.start + this.node.name.length + 1;
|
|
||||||
if (this.node.prelude) {
|
|
||||||
if (this.node.prelude.start - c > 1) code.update(c, this.node.prelude.start, ' ');
|
|
||||||
c = this.node.prelude.end;
|
|
||||||
}
|
|
||||||
if (this.node.block && this.node.block.start - c > 0) {
|
|
||||||
code.remove(c, this.node.block.start);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO other atrules
|
|
||||||
if (this.node.block) {
|
|
||||||
let c = this.node.block.start + 1;
|
|
||||||
if (this.declarations.length) {
|
|
||||||
c = minify_declarations(code, c, this.declarations);
|
|
||||||
// if the atrule has children, leave the last declaration semicolon alone
|
|
||||||
if (this.children.length) c++;
|
|
||||||
}
|
|
||||||
this.children.forEach((child) => {
|
|
||||||
if (child.is_used(dev)) {
|
|
||||||
code.remove(c, child.node.start);
|
|
||||||
child.minify(code, dev);
|
|
||||||
c = child.node.end;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
code.remove(c, this.node.block.end - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('magic-string').default} code
|
|
||||||
* @param {string} id
|
|
||||||
* @param {Map<string, string>} keyframes
|
|
||||||
* @param {number} max_amount_class_specificity_increased
|
|
||||||
*/
|
|
||||||
transform(code, id, keyframes, max_amount_class_specificity_increased) {
|
|
||||||
if (is_keyframes_node(this.node)) {
|
|
||||||
this.node.prelude.children.forEach(({ type, name, start, end }) => {
|
|
||||||
if (type === 'Identifier') {
|
|
||||||
if (name.startsWith('-global-')) {
|
|
||||||
code.remove(start, start + 8);
|
|
||||||
this.children.forEach((/** @type {Rule} */ rule) => {
|
|
||||||
rule.selectors.forEach((selector) => {
|
|
||||||
selector.used = true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
code.update(start, end, keyframes.get(name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.children.forEach((child) => {
|
|
||||||
child.transform(code, id, keyframes, max_amount_class_specificity_increased);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {import('../Component.js').default} component */
|
|
||||||
validate(component) {
|
|
||||||
this.children.forEach((child) => {
|
|
||||||
child.validate(component);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {(selector: import('./Selector.js').default) => void} handler */
|
|
||||||
warn_on_unused_selector(handler) {
|
|
||||||
if (this.node.name !== 'media') return;
|
|
||||||
this.children.forEach((child) => {
|
|
||||||
child.warn_on_unused_selector(handler);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
get_max_amount_class_specificity_increased() {
|
|
||||||
return Math.max(
|
|
||||||
...this.children.map((rule) => rule.get_max_amount_class_specificity_increased())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {any} params */
|
|
||||||
const get_default_css_hash = ({ css, hash }) => {
|
|
||||||
return `svelte-${hash(css)}`;
|
|
||||||
};
|
|
||||||
export default class Stylesheet {
|
|
||||||
/** @type {string} */
|
|
||||||
source;
|
|
||||||
|
|
||||||
/** @type {import('../../interfaces.js').Ast} */
|
|
||||||
ast;
|
|
||||||
|
|
||||||
/** @type {string} */
|
|
||||||
filename;
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
dev;
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
has_styles;
|
|
||||||
|
|
||||||
/** @type {string} */
|
|
||||||
id;
|
|
||||||
|
|
||||||
/** @type {Array<Rule | Atrule>} */
|
|
||||||
children = [];
|
|
||||||
|
|
||||||
/** @type {Map<string, string>} */
|
|
||||||
keyframes = new Map();
|
|
||||||
|
|
||||||
/** @type {Set<import('./private.js').CssNode>} */
|
|
||||||
nodes_with_css_class = new Set();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {{
|
|
||||||
* source: string;
|
|
||||||
* ast: import('../../interfaces.js').Ast;
|
|
||||||
* filename: string | undefined;
|
|
||||||
* component_name: string | undefined;
|
|
||||||
* dev: boolean;
|
|
||||||
* get_css_hash: import('../../interfaces.js').CssHashGetter;
|
|
||||||
* }} params
|
|
||||||
*/
|
|
||||||
constructor({ source, ast, component_name, filename, dev, get_css_hash = get_default_css_hash }) {
|
|
||||||
this.source = source;
|
|
||||||
this.ast = ast;
|
|
||||||
this.filename = filename;
|
|
||||||
this.dev = dev;
|
|
||||||
if (ast.css && ast.css.children.length) {
|
|
||||||
this.id = get_css_hash({
|
|
||||||
filename,
|
|
||||||
name: component_name,
|
|
||||||
css: ast.css.content.styles,
|
|
||||||
hash
|
|
||||||
});
|
|
||||||
this.has_styles = true;
|
|
||||||
|
|
||||||
/** @type {Atrule[]} */
|
|
||||||
const stack = [];
|
|
||||||
let depth = 0;
|
|
||||||
|
|
||||||
/** @type {Atrule} */
|
|
||||||
let current_atrule = null;
|
|
||||||
walk(/** @type {any} */ (ast.css), {
|
|
||||||
enter: (/** @type {any} */ node) => {
|
|
||||||
if (node.type === 'Atrule') {
|
|
||||||
const atrule = new Atrule(node);
|
|
||||||
stack.push(atrule);
|
|
||||||
if (current_atrule) {
|
|
||||||
current_atrule.children.push(atrule);
|
|
||||||
} else if (depth <= 1) {
|
|
||||||
this.children.push(atrule);
|
|
||||||
}
|
|
||||||
if (is_keyframes_node(node)) {
|
|
||||||
node.prelude.children.forEach((expression) => {
|
|
||||||
if (expression.type === 'Identifier' && !expression.name.startsWith('-global-')) {
|
|
||||||
this.keyframes.set(expression.name, `${this.id}-${expression.name}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (at_rule_has_declaration(node)) {
|
|
||||||
const at_rule_declarations = node.block.children
|
|
||||||
.filter((node) => node.type === 'Declaration')
|
|
||||||
.map((node) => new Declaration(node));
|
|
||||||
push_array(atrule.declarations, at_rule_declarations);
|
|
||||||
}
|
|
||||||
current_atrule = atrule;
|
|
||||||
}
|
|
||||||
if (node.type === 'Rule') {
|
|
||||||
const rule = new Rule(node, this, current_atrule);
|
|
||||||
if (current_atrule) {
|
|
||||||
current_atrule.children.push(rule);
|
|
||||||
} else if (depth <= 1) {
|
|
||||||
this.children.push(rule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
depth += 1;
|
|
||||||
},
|
|
||||||
leave: (/** @type {any} */ node) => {
|
|
||||||
if (node.type === 'Atrule') {
|
|
||||||
stack.pop();
|
|
||||||
current_atrule = stack[stack.length - 1];
|
|
||||||
}
|
|
||||||
depth -= 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.has_styles = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {import('../nodes/Element.js').default} node */
|
|
||||||
apply(node) {
|
|
||||||
if (!this.has_styles) return;
|
|
||||||
for (let i = 0; i < this.children.length; i += 1) {
|
|
||||||
const child = this.children[i];
|
|
||||||
child.apply(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reify() {
|
|
||||||
this.nodes_with_css_class.forEach((node) => {
|
|
||||||
node.add_css_class();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {string} file */
|
|
||||||
render(file) {
|
|
||||||
if (!this.has_styles) {
|
|
||||||
return { code: null, map: null };
|
|
||||||
}
|
|
||||||
const code = new MagicString(this.source);
|
|
||||||
walk(/** @type {any} */ (this.ast.css), {
|
|
||||||
enter: (/** @type {any} */ node) => {
|
|
||||||
code.addSourcemapLocation(node.start);
|
|
||||||
code.addSourcemapLocation(node.end);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const max = Math.max(
|
|
||||||
...this.children.map((rule) => rule.get_max_amount_class_specificity_increased())
|
|
||||||
);
|
|
||||||
this.children.forEach((child) => {
|
|
||||||
child.transform(code, this.id, this.keyframes, max);
|
|
||||||
});
|
|
||||||
let c = 0;
|
|
||||||
this.children.forEach((child) => {
|
|
||||||
if (child.is_used(this.dev)) {
|
|
||||||
code.remove(c, child.node.start);
|
|
||||||
child.minify(code, this.dev);
|
|
||||||
c = child.node.end;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
code.remove(c, this.source.length);
|
|
||||||
return {
|
|
||||||
code: code.toString(),
|
|
||||||
map: code.generateMap({
|
|
||||||
includeContent: true,
|
|
||||||
source: this.filename,
|
|
||||||
file
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {import('../Component.js').default} component */
|
|
||||||
validate(component) {
|
|
||||||
this.children.forEach((child) => {
|
|
||||||
child.validate(component);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {import('../Component.js').default} component */
|
|
||||||
warn_on_unused_selectors(component) {
|
|
||||||
const ignores = !this.ast.css
|
|
||||||
? []
|
|
||||||
: extract_ignores_above_position(this.ast.css.start, this.ast.html.children);
|
|
||||||
component.push_ignores(ignores);
|
|
||||||
this.children.forEach((child) => {
|
|
||||||
child.warn_on_unused_selector((selector) => {
|
|
||||||
component.warn(
|
|
||||||
selector.node,
|
|
||||||
compiler_warnings.css_unused_selector(
|
|
||||||
this.source.slice(selector.node.start, selector.node.end)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
component.pop_ignores();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
export const UNKNOWN = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import("estree").Node} node
|
|
||||||
* @param {Set<string | {}>} set
|
|
||||||
*/
|
|
||||||
export function gather_possible_values(node, set) {
|
|
||||||
if (node.type === 'Literal') {
|
|
||||||
set.add(node.value);
|
|
||||||
} else if (node.type === 'ConditionalExpression') {
|
|
||||||
gather_possible_values(node.consequent, set);
|
|
||||||
gather_possible_values(node.alternate, set);
|
|
||||||
} else {
|
|
||||||
set.add(UNKNOWN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
export interface CssNode {
|
|
||||||
type: string;
|
|
||||||
start: number;
|
|
||||||
end: number;
|
|
||||||
[prop_name: string]: any;
|
|
||||||
}
|
|
||||||
@ -1,155 +0,0 @@
|
|||||||
import Stats from '../Stats.js';
|
|
||||||
import parse from '../parse/index.js';
|
|
||||||
import render_dom from './render_dom/index.js';
|
|
||||||
import render_ssr from './render_ssr/index.js';
|
|
||||||
import Component from './Component.js';
|
|
||||||
import fuzzymatch from '../utils/fuzzymatch.js';
|
|
||||||
import get_name_from_filename from './utils/get_name_from_filename.js';
|
|
||||||
import { valid_namespaces } from '../utils/namespaces.js';
|
|
||||||
|
|
||||||
const valid_options = [
|
|
||||||
'name',
|
|
||||||
'filename',
|
|
||||||
'sourcemap',
|
|
||||||
'enableSourcemap',
|
|
||||||
'generate',
|
|
||||||
'errorMode',
|
|
||||||
'varsReport',
|
|
||||||
'outputFilename',
|
|
||||||
'cssOutputFilename',
|
|
||||||
'sveltePath',
|
|
||||||
'dev',
|
|
||||||
'accessors',
|
|
||||||
'immutable',
|
|
||||||
'hydratable',
|
|
||||||
'legacy',
|
|
||||||
'customElement',
|
|
||||||
'namespace',
|
|
||||||
'tag',
|
|
||||||
'css',
|
|
||||||
'loopGuardTimeout',
|
|
||||||
'preserveComments',
|
|
||||||
'preserveWhitespace',
|
|
||||||
'cssHash',
|
|
||||||
'discloseVersion'
|
|
||||||
];
|
|
||||||
const valid_css_values = [true, false, 'injected', 'external', 'none'];
|
|
||||||
const regex_valid_identifier = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/;
|
|
||||||
const regex_starts_with_lowercase_character = /^[a-z]/;
|
|
||||||
|
|
||||||
let warned_of_format = false;
|
|
||||||
let warned_boolean_css = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../interfaces.js').CompileOptions} options
|
|
||||||
* @param {import('../interfaces.js').Warning[]} warnings
|
|
||||||
*/
|
|
||||||
function validate_options(options, warnings) {
|
|
||||||
if (/** @type {any} */ (options).format) {
|
|
||||||
if (!warned_of_format) {
|
|
||||||
warned_of_format = true;
|
|
||||||
console.warn(
|
|
||||||
'The format option has been removed in Svelte 4, the compiler only outputs ESM now. Remove "format" from your compiler options. ' +
|
|
||||||
'If you did not set this yourself, bump the version of your bundler plugin (vite-plugin-svelte/rollup-plugin-svelte/svelte-loader)'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
delete (/** @type {any} */ (options).format);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { name, filename, loopGuardTimeout, dev, namespace, css } = options;
|
|
||||||
Object.keys(options).forEach((key) => {
|
|
||||||
if (!valid_options.includes(key)) {
|
|
||||||
const match = fuzzymatch(key, valid_options);
|
|
||||||
let message = `Unrecognized option '${key}'`;
|
|
||||||
if (match) message += ` (did you mean '${match}'?)`;
|
|
||||||
throw new Error(message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (name && !regex_valid_identifier.test(name)) {
|
|
||||||
throw new Error(`options.name must be a valid identifier (got '${name}')`);
|
|
||||||
}
|
|
||||||
if (name && regex_starts_with_lowercase_character.test(name)) {
|
|
||||||
const message = 'options.name should be capitalised';
|
|
||||||
warnings.push({
|
|
||||||
code: 'options-lowercase-name',
|
|
||||||
message,
|
|
||||||
filename,
|
|
||||||
toString: () => message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (loopGuardTimeout && !dev) {
|
|
||||||
const message = 'options.loopGuardTimeout is for options.dev = true only';
|
|
||||||
warnings.push({
|
|
||||||
code: 'options-loop-guard-timeout',
|
|
||||||
message,
|
|
||||||
filename,
|
|
||||||
toString: () => message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (css === true || css === false) {
|
|
||||||
options.css = css === true ? 'injected' : 'external';
|
|
||||||
if (!warned_boolean_css) {
|
|
||||||
console.warn(
|
|
||||||
`compilerOptions.css as a boolean is deprecated. Use '${options.css}' instead of ${css}.`
|
|
||||||
);
|
|
||||||
warned_boolean_css = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!valid_css_values.includes(options.css)) {
|
|
||||||
throw new Error(
|
|
||||||
`compilerOptions.css must be 'injected', 'external' or 'none' (got '${options.css}').`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (namespace && valid_namespaces.indexOf(namespace) === -1) {
|
|
||||||
const match = fuzzymatch(namespace, valid_namespaces);
|
|
||||||
if (match) {
|
|
||||||
throw new Error(`Invalid namespace '${namespace}' (did you mean '${match}'?)`);
|
|
||||||
} else {
|
|
||||||
throw new Error(`Invalid namespace '${namespace}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.discloseVersion == undefined) {
|
|
||||||
options.discloseVersion = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* `compile` takes your component source code, and turns it into a JavaScript module that exports a class.
|
|
||||||
*
|
|
||||||
* https://svelte.dev/docs/svelte-compiler#svelte-compile
|
|
||||||
* @param {string} source
|
|
||||||
* @param {import('../interfaces.js').CompileOptions} options
|
|
||||||
*/
|
|
||||||
export default function compile(source, options = {}) {
|
|
||||||
options = Object.assign(
|
|
||||||
{ generate: 'dom', dev: false, enableSourcemap: true, css: 'injected' },
|
|
||||||
options
|
|
||||||
);
|
|
||||||
const stats = new Stats();
|
|
||||||
const warnings = [];
|
|
||||||
validate_options(options, warnings);
|
|
||||||
stats.start('parse');
|
|
||||||
const ast = parse(source, options);
|
|
||||||
stats.stop('parse');
|
|
||||||
stats.start('create component');
|
|
||||||
const component = new Component(
|
|
||||||
ast,
|
|
||||||
source,
|
|
||||||
options.name || get_name_from_filename(options.filename) || 'Component',
|
|
||||||
options,
|
|
||||||
stats,
|
|
||||||
warnings
|
|
||||||
);
|
|
||||||
stats.stop('create component');
|
|
||||||
const result =
|
|
||||||
options.generate === false
|
|
||||||
? null
|
|
||||||
: options.generate === 'ssr'
|
|
||||||
? render_ssr(component, options)
|
|
||||||
: render_dom(component, options);
|
|
||||||
return component.generate(result);
|
|
||||||
}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
// This file is automatically generated
|
|
||||||
export default new Set(["HtmlTag","HtmlTagHydration","ResizeObserverSingleton","SvelteComponent","SvelteComponentDev","SvelteComponentTyped","SvelteElement","action_destroyer","add_attribute","add_classes","add_flush_callback","add_iframe_resize_listener","add_location","add_render_callback","add_styles","add_transform","afterUpdate","append","append_dev","append_empty_stylesheet","append_hydration","append_hydration_dev","append_styles","assign","attr","attr_dev","attribute_to_object","beforeUpdate","bind","binding_callbacks","blank_object","bubble","check_outros","children","claim_comment","claim_component","claim_element","claim_html_tag","claim_space","claim_svg_element","claim_text","clear_loops","comment","component_subscribe","compute_rest_props","compute_slots","construct_svelte_component","construct_svelte_component_dev","contenteditable_truthy_values","createEventDispatcher","create_animation","create_bidirectional_transition","create_component","create_custom_element","create_in_transition","create_out_transition","create_slot","create_ssr_component","current_component","custom_event","dataset_dev","debug","destroy_block","destroy_component","destroy_each","detach","detach_after_dev","detach_before_dev","detach_between_dev","detach_dev","dirty_components","dispatch_dev","each","element","element_is","empty","end_hydrating","ensure_array_like","ensure_array_like_dev","escape","escape_attribute_value","escape_object","exclude_internal_props","fix_and_destroy_block","fix_and_outro_and_destroy_block","fix_position","flush","flush_render_callbacks","getAllContexts","getContext","get_all_dirty_from_scope","get_binding_group_value","get_current_component","get_custom_elements_slots","get_root_for_style","get_slot_changes","get_spread_object","get_spread_update","get_store_value","get_svelte_dataset","globals","group_outros","handle_promise","hasContext","has_prop","head_selector","identity","init","init_binding_group","init_binding_group_dynamic","insert","insert_dev","insert_hydration","insert_hydration_dev","intros","invalid_attribute_name_character","is_client","is_crossorigin","is_empty","is_function","is_promise","is_void","listen","listen_dev","loop","loop_guard","merge_ssr_styles","missing_component","mount_component","noop","not_equal","now","null_to_empty","object_without_properties","onDestroy","onMount","once","outro_and_destroy_block","prevent_default","prop_dev","query_selector_all","raf","resize_observer_border_box","resize_observer_content_box","resize_observer_device_pixel_content_box","run","run_all","safe_not_equal","schedule_update","select_multiple_value","select_option","select_options","select_value","self","setContext","set_attributes","set_current_component","set_custom_element_data","set_custom_element_data_map","set_data","set_data_contenteditable","set_data_contenteditable_dev","set_data_dev","set_data_maybe_contenteditable","set_data_maybe_contenteditable_dev","set_dynamic_element_data","set_input_type","set_input_value","set_now","set_raf","set_store_value","set_style","set_svg_attributes","space","split_css_unit","spread","src_url_equal","srcset_url_equal","start_hydrating","stop_immediate_propagation","stop_propagation","subscribe","svg_element","text","tick","time_ranges_to_array","to_number","toggle_class","transition_in","transition_out","trusted","update_await_block_branch","update_keyed_each","update_slot","update_slot_base","validate_component","validate_dynamic_element","validate_each_keys","validate_slots","validate_store","validate_void_dynamic_element","xlink_attr"]);
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
import Node from './shared/Node.js';
|
|
||||||
import Expression from './shared/Expression.js';
|
|
||||||
|
|
||||||
/** @extends Node<'Action'> */
|
|
||||||
export default class Action extends Node {
|
|
||||||
/** @type {string} */
|
|
||||||
name;
|
|
||||||
|
|
||||||
/** @type {import('./shared/Expression.js').default} */
|
|
||||||
expression;
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
uses_context;
|
|
||||||
|
|
||||||
/** @type {import('./shared/TemplateScope.js').default} */
|
|
||||||
template_scope;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component *
|
|
||||||
* @param {import('./shared/Node.js').default} parent *
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope *
|
|
||||||
* @param {import('../../interfaces.js').Directive} info undefined
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
const object = info.name.split('.')[0];
|
|
||||||
component.warn_if_undefined(object, info, scope);
|
|
||||||
this.name = info.name;
|
|
||||||
component.add_reference(/** @type {any} */ (this), object);
|
|
||||||
this.expression = info.expression
|
|
||||||
? new Expression(component, this, scope, info.expression)
|
|
||||||
: null;
|
|
||||||
this.template_scope = scope;
|
|
||||||
this.uses_context = this.expression && this.expression.uses_context;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
import Node from './shared/Node.js';
|
|
||||||
import Expression from './shared/Expression.js';
|
|
||||||
import compiler_errors from '../compiler_errors.js';
|
|
||||||
|
|
||||||
/** @extends Node<'Animation'> */
|
|
||||||
export default class Animation extends Node {
|
|
||||||
/** @type {string} */
|
|
||||||
name;
|
|
||||||
|
|
||||||
/** @type {import('./shared/Expression.js').default} */
|
|
||||||
expression;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component *
|
|
||||||
* @param {import('./Element.js').default} parent *
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope *
|
|
||||||
* @param {import('../../interfaces.js').TemplateNode} info undefined
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
component.warn_if_undefined(info.name, info, scope);
|
|
||||||
this.name = info.name;
|
|
||||||
component.add_reference(/** @type {any} */ (this), info.name.split('.')[0]);
|
|
||||||
if (parent.animation) {
|
|
||||||
component.error(this, compiler_errors.duplicate_animation);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const block = parent.parent;
|
|
||||||
if (!block || block.type !== 'EachBlock') {
|
|
||||||
// TODO can we relax the 'immediate child' rule?
|
|
||||||
component.error(this, compiler_errors.invalid_animation_immediate);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!block.key) {
|
|
||||||
component.error(this, compiler_errors.invalid_animation_key);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/** @type {import('./EachBlock.js').default} */ (block).has_animation = true;
|
|
||||||
this.expression = info.expression
|
|
||||||
? new Expression(component, this, scope, info.expression, true)
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,136 +0,0 @@
|
|||||||
import { string_literal } from '../utils/stringify.js';
|
|
||||||
import add_to_set from '../utils/add_to_set.js';
|
|
||||||
import Node from './shared/Node.js';
|
|
||||||
import Expression from './shared/Expression.js';
|
|
||||||
import { x } from 'code-red';
|
|
||||||
import compiler_warnings from '../compiler_warnings.js';
|
|
||||||
|
|
||||||
/** @extends Node<'Attribute' | 'Spread', import('./Element.js').default> */
|
|
||||||
export default class Attribute extends Node {
|
|
||||||
/** @type {import('./shared/TemplateScope.js').default} */
|
|
||||||
scope;
|
|
||||||
|
|
||||||
/** @type {string} */
|
|
||||||
name;
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
is_spread;
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
is_true;
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
is_static;
|
|
||||||
|
|
||||||
/** @type {import('./shared/Expression.js').default} */
|
|
||||||
expression;
|
|
||||||
|
|
||||||
/** @type {Array<import('./Text.js').default | import('./shared/Expression.js').default>} */
|
|
||||||
chunks;
|
|
||||||
|
|
||||||
/** @type {Set<string>} */
|
|
||||||
dependencies;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('./shared/Node.js').default} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope
|
|
||||||
* @param {import('../../interfaces.js').TemplateNode} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.scope = scope;
|
|
||||||
|
|
||||||
if (info.type === 'Spread') {
|
|
||||||
this.name = null;
|
|
||||||
this.is_spread = true;
|
|
||||||
this.is_true = false;
|
|
||||||
this.expression = new Expression(component, this, scope, info.expression);
|
|
||||||
this.dependencies = this.expression.dependencies;
|
|
||||||
this.chunks = null;
|
|
||||||
this.is_static = false;
|
|
||||||
} else {
|
|
||||||
this.name = info.name;
|
|
||||||
this.is_true = info.value === true;
|
|
||||||
this.is_static = true;
|
|
||||||
this.dependencies = new Set();
|
|
||||||
this.chunks = this.is_true
|
|
||||||
? []
|
|
||||||
: info.value.map((node) => {
|
|
||||||
if (node.type === 'Text') return node;
|
|
||||||
this.is_static = false;
|
|
||||||
const expression = new Expression(component, this, scope, node.expression);
|
|
||||||
add_to_set(this.dependencies, expression.dependencies);
|
|
||||||
return expression;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.dependencies.size > 0) {
|
|
||||||
parent.cannot_use_innerhtml();
|
|
||||||
parent.not_static_content();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Svelte 5: Think about moving this into the parser and make it an error
|
|
||||||
if (
|
|
||||||
this.name &&
|
|
||||||
this.name.includes(':') &&
|
|
||||||
!this.name.startsWith('xmlns:') &&
|
|
||||||
!this.name.startsWith('xlink:') &&
|
|
||||||
!this.name.startsWith('xml:')
|
|
||||||
) {
|
|
||||||
component.warn(this, compiler_warnings.illegal_attribute_character);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
get_dependencies() {
|
|
||||||
if (this.is_spread) return this.expression.dynamic_dependencies();
|
|
||||||
|
|
||||||
/** @type {Set<string>} */
|
|
||||||
const dependencies = new Set();
|
|
||||||
this.chunks.forEach((chunk) => {
|
|
||||||
if (chunk.type === 'Expression') {
|
|
||||||
add_to_set(dependencies, chunk.dynamic_dependencies());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return Array.from(dependencies);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {any} block */
|
|
||||||
get_value(block) {
|
|
||||||
if (this.is_true) return x`true`;
|
|
||||||
if (this.chunks.length === 0) return x`""`;
|
|
||||||
if (this.chunks.length === 1) {
|
|
||||||
return this.chunks[0].type === 'Text'
|
|
||||||
? string_literal(/** @type {import('./Text.js').default} */ (this.chunks[0]).data)
|
|
||||||
: /** @type {import('./shared/Expression.js').default} */ (this.chunks[0]).manipulate(
|
|
||||||
block
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let expression = this.chunks
|
|
||||||
.map(
|
|
||||||
/** @param {any} chunk */ (chunk) =>
|
|
||||||
chunk.type === 'Text' ? string_literal(chunk.data) : chunk.manipulate(block)
|
|
||||||
)
|
|
||||||
.reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
|
|
||||||
if (this.chunks[0].type !== 'Text') {
|
|
||||||
expression = x`"" + ${expression}`;
|
|
||||||
}
|
|
||||||
return expression;
|
|
||||||
}
|
|
||||||
get_static_value() {
|
|
||||||
if (!this.is_static) return null;
|
|
||||||
return this.is_true
|
|
||||||
? true
|
|
||||||
: this.chunks[0]
|
|
||||||
? // method should be called only when `is_static = true`
|
|
||||||
/** @type {import('./Text.js').default} */ (this.chunks[0]).data
|
|
||||||
: '';
|
|
||||||
}
|
|
||||||
should_cache() {
|
|
||||||
return this.is_static
|
|
||||||
? false
|
|
||||||
: this.chunks.length === 1
|
|
||||||
? // @ts-ignore todo: probably error
|
|
||||||
this.chunks[0].node.type !== 'Identifier' || this.scope.names.has(this.chunks[0].node.name)
|
|
||||||
: true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,74 +0,0 @@
|
|||||||
import Node from './shared/Node.js';
|
|
||||||
import PendingBlock from './PendingBlock.js';
|
|
||||||
import ThenBlock from './ThenBlock.js';
|
|
||||||
import CatchBlock from './CatchBlock.js';
|
|
||||||
import Expression from './shared/Expression.js';
|
|
||||||
import { unpack_destructuring } from './shared/Context.js';
|
|
||||||
|
|
||||||
/** @extends Node<'AwaitBlock'> */
|
|
||||||
export default class AwaitBlock extends Node {
|
|
||||||
/** @type {import('./shared/Expression.js').default} */
|
|
||||||
expression;
|
|
||||||
|
|
||||||
/** @type {import('./shared/Context.js').Context[]} */
|
|
||||||
then_contexts;
|
|
||||||
|
|
||||||
/** @type {import('./shared/Context.js').Context[]} */
|
|
||||||
catch_contexts;
|
|
||||||
|
|
||||||
/** @type {import('estree').Node | null} */
|
|
||||||
then_node;
|
|
||||||
|
|
||||||
/** @type {import('estree').Node | null} */
|
|
||||||
catch_node;
|
|
||||||
|
|
||||||
/** @type {import('./PendingBlock.js').default} */
|
|
||||||
pending;
|
|
||||||
|
|
||||||
/** @type {import('./ThenBlock.js').default} */
|
|
||||||
then;
|
|
||||||
|
|
||||||
/** @type {import('./CatchBlock.js').default} */
|
|
||||||
catch;
|
|
||||||
|
|
||||||
/** @type {Map<string, import('estree').Node>} */
|
|
||||||
context_rest_properties = new Map();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('./shared/Node.js').default} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope
|
|
||||||
* @param {import('../../interfaces.js').TemplateNode} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.cannot_use_innerhtml();
|
|
||||||
this.not_static_content();
|
|
||||||
this.expression = new Expression(component, this, scope, info.expression);
|
|
||||||
this.then_node = info.value;
|
|
||||||
this.catch_node = info.error;
|
|
||||||
if (this.then_node) {
|
|
||||||
this.then_contexts = [];
|
|
||||||
unpack_destructuring({
|
|
||||||
contexts: this.then_contexts,
|
|
||||||
node: info.value,
|
|
||||||
scope,
|
|
||||||
component,
|
|
||||||
context_rest_properties: this.context_rest_properties
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (this.catch_node) {
|
|
||||||
this.catch_contexts = [];
|
|
||||||
unpack_destructuring({
|
|
||||||
contexts: this.catch_contexts,
|
|
||||||
node: info.error,
|
|
||||||
scope,
|
|
||||||
component,
|
|
||||||
context_rest_properties: this.context_rest_properties
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.pending = new PendingBlock(component, this, scope, info.pending);
|
|
||||||
this.then = new ThenBlock(component, this, scope, info.then);
|
|
||||||
this.catch = new CatchBlock(component, this, scope, info.catch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,132 +0,0 @@
|
|||||||
import Node from './shared/Node.js';
|
|
||||||
import get_object from '../utils/get_object.js';
|
|
||||||
import Expression from './shared/Expression.js';
|
|
||||||
import { regex_dimensions, regex_box_size } from '../../utils/patterns.js';
|
|
||||||
import { clone } from '../../utils/clone.js';
|
|
||||||
import compiler_errors from '../compiler_errors.js';
|
|
||||||
import compiler_warnings from '../compiler_warnings.js';
|
|
||||||
|
|
||||||
// TODO this should live in a specific binding
|
|
||||||
const read_only_media_attributes = new Set([
|
|
||||||
'duration',
|
|
||||||
'buffered',
|
|
||||||
'seekable',
|
|
||||||
'played',
|
|
||||||
'seeking',
|
|
||||||
'ended',
|
|
||||||
'videoHeight',
|
|
||||||
'videoWidth',
|
|
||||||
'naturalWidth',
|
|
||||||
'naturalHeight',
|
|
||||||
'readyState'
|
|
||||||
]);
|
|
||||||
|
|
||||||
/** @extends Node<'Binding'> */
|
|
||||||
export default class Binding extends Node {
|
|
||||||
/** @type {string} */
|
|
||||||
name;
|
|
||||||
|
|
||||||
/** @type {import('./shared/Expression.js').default} */
|
|
||||||
expression;
|
|
||||||
|
|
||||||
/** @type {import('estree').Node} */
|
|
||||||
raw_expression; // TODO exists only for bind:this — is there a more elegant solution?
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
is_contextual;
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
is_readonly;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('./Element.js').default | import('./InlineComponent.js').default | import('./Window.js').default | import('./Document.js').default} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope
|
|
||||||
* @param {import('../../interfaces.js').TemplateNode} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
if (info.expression.type !== 'Identifier' && info.expression.type !== 'MemberExpression') {
|
|
||||||
component.error(info, compiler_errors.invalid_directive_value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.name = info.name;
|
|
||||||
this.expression = new Expression(component, this, scope, info.expression);
|
|
||||||
this.raw_expression = clone(info.expression);
|
|
||||||
const { name } = get_object(this.expression.node);
|
|
||||||
this.is_contextual = Array.from(this.expression.references).some((name) =>
|
|
||||||
scope.names.has(name)
|
|
||||||
);
|
|
||||||
if (this.is_contextual) this.validate_binding_rest_properties(scope);
|
|
||||||
// make sure we track this as a mutable ref
|
|
||||||
if (scope.is_let(name)) {
|
|
||||||
component.error(this, compiler_errors.invalid_binding_let);
|
|
||||||
return;
|
|
||||||
} else if (scope.names.has(name)) {
|
|
||||||
if (scope.is_await(name)) {
|
|
||||||
component.error(this, compiler_errors.invalid_binding_await);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (scope.is_const(name)) {
|
|
||||||
component.error(this, compiler_errors.invalid_binding_const);
|
|
||||||
}
|
|
||||||
scope.dependencies_for_name.get(name).forEach((name) => {
|
|
||||||
const variable = component.var_lookup.get(name);
|
|
||||||
if (variable) {
|
|
||||||
variable.mutated = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const variable = component.var_lookup.get(name);
|
|
||||||
if (!variable || variable.global) {
|
|
||||||
component.error(
|
|
||||||
/** @type {any} */ (this.expression.node),
|
|
||||||
compiler_errors.binding_undeclared(name)
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
variable[this.expression.node.type === 'MemberExpression' ? 'mutated' : 'reassigned'] = true;
|
|
||||||
if (info.expression.type === 'Identifier' && !variable.writable) {
|
|
||||||
component.error(
|
|
||||||
/** @type {any} */ (this.expression.node),
|
|
||||||
compiler_errors.invalid_binding_writable
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const type = parent.get_static_attribute_value('type');
|
|
||||||
this.is_readonly =
|
|
||||||
regex_dimensions.test(this.name) ||
|
|
||||||
regex_box_size.test(this.name) ||
|
|
||||||
(is_element(parent) &&
|
|
||||||
((parent.is_media_node() && read_only_media_attributes.has(this.name)) ||
|
|
||||||
(parent.name === 'input' && type === 'file'))) /* TODO others? */;
|
|
||||||
}
|
|
||||||
is_readonly_media_attribute() {
|
|
||||||
return read_only_media_attributes.has(this.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {import('./shared/TemplateScope.js').default} scope */
|
|
||||||
validate_binding_rest_properties(scope) {
|
|
||||||
this.expression.references.forEach((name) => {
|
|
||||||
const each_block = scope.get_owner(name);
|
|
||||||
if (each_block && each_block.type === 'EachBlock') {
|
|
||||||
const rest_node = each_block.context_rest_properties.get(name);
|
|
||||||
if (rest_node) {
|
|
||||||
this.component.warn(
|
|
||||||
/** @type {any} */ (rest_node),
|
|
||||||
compiler_warnings.invalid_rest_eachblock_binding(name)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('./shared/Node.js').default} node
|
|
||||||
* @returns {node is import('./Element.js').default}
|
|
||||||
*/
|
|
||||||
function is_element(node) {
|
|
||||||
return !!(/** @type {any} */ (node).is_media_node);
|
|
||||||
}
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
import Node from './shared/Node.js';
|
|
||||||
import EventHandler from './EventHandler.js';
|
|
||||||
import Action from './Action.js';
|
|
||||||
|
|
||||||
/** @extends Node<'Body'> */
|
|
||||||
export default class Body extends Node {
|
|
||||||
/** @type {import('./EventHandler.js').default[]} */
|
|
||||||
handlers = [];
|
|
||||||
|
|
||||||
/** @type {import('./Action.js').default[]} */
|
|
||||||
actions = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('./shared/Node.js').default} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope
|
|
||||||
* @param {import('../../interfaces.js').Element} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
info.attributes.forEach((node) => {
|
|
||||||
if (node.type === 'EventHandler') {
|
|
||||||
this.handlers.push(new EventHandler(component, this, scope, node));
|
|
||||||
} else if (node.type === 'Action') {
|
|
||||||
this.actions.push(new Action(component, this, scope, node));
|
|
||||||
} else {
|
|
||||||
// TODO there shouldn't be anything else here...
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
import AbstractBlock from './shared/AbstractBlock.js';
|
|
||||||
import get_const_tags from './shared/get_const_tags.js';
|
|
||||||
|
|
||||||
/** @extends AbstractBlock<'CatchBlock'> */
|
|
||||||
export default class CatchBlock extends AbstractBlock {
|
|
||||||
/** @type {import('./shared/TemplateScope.js').default} */
|
|
||||||
scope;
|
|
||||||
|
|
||||||
/** @type {import('./ConstTag.js').default[]} */
|
|
||||||
const_tags;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('./AwaitBlock.js').default} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope
|
|
||||||
* @param {import('../../interfaces.js').TemplateNode} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.scope = scope.child();
|
|
||||||
if (parent.catch_node) {
|
|
||||||
parent.catch_contexts.forEach((context) => {
|
|
||||||
if (context.type !== 'DestructuredVariable') return;
|
|
||||||
this.scope.add(context.key.name, parent.expression.dependencies, this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
[this.const_tags, this.children] = get_const_tags(info.children, component, this, parent);
|
|
||||||
if (!info.skip) {
|
|
||||||
this.warn_if_empty_block();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
import Node from './shared/Node.js';
|
|
||||||
import Expression from './shared/Expression.js';
|
|
||||||
|
|
||||||
/** @extends Node<'Class'> */
|
|
||||||
export default class Class extends Node {
|
|
||||||
/** @type {string} */
|
|
||||||
name;
|
|
||||||
|
|
||||||
/** @type {import('./shared/Expression.js').default} */
|
|
||||||
expression;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('./shared/Node.js').default} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope
|
|
||||||
* @param {import('../../interfaces.js').TemplateNode} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.name = info.name;
|
|
||||||
this.expression = info.expression
|
|
||||||
? new Expression(component, this, scope, info.expression)
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
import Node from './shared/Node.js';
|
|
||||||
|
|
||||||
/** @extends Node<'Comment'> */
|
|
||||||
export default class Comment extends Node {
|
|
||||||
/** @type {string} */
|
|
||||||
data;
|
|
||||||
|
|
||||||
/** @type {string[]} */
|
|
||||||
ignores;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('./shared/Node.js').default} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope
|
|
||||||
* @param {import('../../interfaces.js').TemplateNode} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.data = info.data;
|
|
||||||
this.ignores = info.ignores;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,104 +0,0 @@
|
|||||||
import Node from './shared/Node.js';
|
|
||||||
import Expression from './shared/Expression.js';
|
|
||||||
import { unpack_destructuring } from './shared/Context.js';
|
|
||||||
import { walk } from 'estree-walker';
|
|
||||||
import { extract_identifiers } from 'periscopic';
|
|
||||||
import is_reference from 'is-reference';
|
|
||||||
import get_object from '../utils/get_object.js';
|
|
||||||
import compiler_errors from '../compiler_errors.js';
|
|
||||||
|
|
||||||
const allowed_parents = new Set([
|
|
||||||
'EachBlock',
|
|
||||||
'CatchBlock',
|
|
||||||
'ThenBlock',
|
|
||||||
'InlineComponent',
|
|
||||||
'SlotTemplate',
|
|
||||||
'IfBlock',
|
|
||||||
'ElseBlock'
|
|
||||||
]);
|
|
||||||
|
|
||||||
/** @extends Node<'ConstTag'> */
|
|
||||||
export default class ConstTag extends Node {
|
|
||||||
/** @type {import('./shared/Expression.js').default} */
|
|
||||||
expression;
|
|
||||||
|
|
||||||
/** @type {import('./shared/Context.js').Context[]} */
|
|
||||||
contexts = [];
|
|
||||||
|
|
||||||
/** @type {import('../../interfaces.js').ConstTag} */
|
|
||||||
node;
|
|
||||||
|
|
||||||
/** @type {import('./shared/TemplateScope.js').default} */
|
|
||||||
scope;
|
|
||||||
|
|
||||||
/** @type {Map<string, import('estree').Node>} */
|
|
||||||
context_rest_properties = new Map();
|
|
||||||
|
|
||||||
/** @type {Set<string>} */
|
|
||||||
assignees = new Set();
|
|
||||||
|
|
||||||
/** @type {Set<string>} */
|
|
||||||
dependencies = new Set();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('./interfaces.js').INodeAllowConstTag} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope
|
|
||||||
* @param {import('../../interfaces.js').ConstTag} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
if (!allowed_parents.has(parent.type)) {
|
|
||||||
component.error(info, compiler_errors.invalid_const_placement);
|
|
||||||
}
|
|
||||||
this.node = info;
|
|
||||||
this.scope = scope;
|
|
||||||
const { assignees, dependencies } = this;
|
|
||||||
extract_identifiers(info.expression.left).forEach(({ name }) => {
|
|
||||||
assignees.add(name);
|
|
||||||
const owner = this.scope.get_owner(name);
|
|
||||||
if (owner === parent) {
|
|
||||||
component.error(info, compiler_errors.invalid_const_declaration(name));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
walk(info.expression.right, {
|
|
||||||
/**
|
|
||||||
* @type {import('estree-walker').SyncHandler}
|
|
||||||
*/
|
|
||||||
enter(node, parent) {
|
|
||||||
if (
|
|
||||||
is_reference(
|
|
||||||
/** @type {import('is-reference').NodeWithPropertyDefinition} */ (node),
|
|
||||||
/** @type {import('is-reference').NodeWithPropertyDefinition} */ (parent)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
const identifier = get_object(node);
|
|
||||||
const { name } = identifier;
|
|
||||||
dependencies.add(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
parse_expression() {
|
|
||||||
unpack_destructuring({
|
|
||||||
contexts: this.contexts,
|
|
||||||
node: this.node.expression.left,
|
|
||||||
scope: this.scope,
|
|
||||||
component: this.component,
|
|
||||||
context_rest_properties: this.context_rest_properties
|
|
||||||
});
|
|
||||||
this.expression = new Expression(this.component, this, this.scope, this.node.expression.right);
|
|
||||||
this.contexts.forEach((context) => {
|
|
||||||
if (context.type !== 'DestructuredVariable') return;
|
|
||||||
const owner = this.scope.get_owner(context.key.name);
|
|
||||||
if (owner && owner.type === 'ConstTag' && owner.parent === this.parent) {
|
|
||||||
this.component.error(
|
|
||||||
this.node,
|
|
||||||
compiler_errors.invalid_const_declaration(context.key.name)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.scope.add(context.key.name, this.expression.dependencies, this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
import Node from './shared/Node.js';
|
|
||||||
import Expression from './shared/Expression.js';
|
|
||||||
|
|
||||||
/** @extends Node<'DebugTag'> */
|
|
||||||
export default class DebugTag extends Node {
|
|
||||||
/** @type {import('./shared/Expression.js').default[]} */
|
|
||||||
expressions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('./interfaces.js').INode} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope
|
|
||||||
* @param {import('../../interfaces.js').TemplateNode} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.expressions = info.identifiers.map(
|
|
||||||
/** @param {import('estree').Node} node */ (node) => {
|
|
||||||
return new Expression(component, parent, scope, node);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
import Node from './shared/Node.js';
|
|
||||||
import Binding from './Binding.js';
|
|
||||||
import EventHandler from './EventHandler.js';
|
|
||||||
import fuzzymatch from '../../utils/fuzzymatch.js';
|
|
||||||
import Action from './Action.js';
|
|
||||||
import list from '../../utils/list.js';
|
|
||||||
import compiler_warnings from '../compiler_warnings.js';
|
|
||||||
import compiler_errors from '../compiler_errors.js';
|
|
||||||
|
|
||||||
const valid_bindings = ['fullscreenElement', 'visibilityState'];
|
|
||||||
|
|
||||||
/** @extends Node<'Document'> */
|
|
||||||
export default class Document extends Node {
|
|
||||||
/** @type {import('./EventHandler.js').default[]} */
|
|
||||||
handlers = [];
|
|
||||||
|
|
||||||
/** @type {import('./Binding.js').default[]} */
|
|
||||||
bindings = [];
|
|
||||||
|
|
||||||
/** @type {import('./Action.js').default[]} */
|
|
||||||
actions = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('./shared/Node.js').default} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope
|
|
||||||
* @param {import('../../interfaces.js').Element} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
info.attributes.forEach((node) => {
|
|
||||||
if (node.type === 'EventHandler') {
|
|
||||||
this.handlers.push(new EventHandler(component, this, scope, node));
|
|
||||||
} else if (node.type === 'Binding') {
|
|
||||||
if (!~valid_bindings.indexOf(node.name)) {
|
|
||||||
const match = fuzzymatch(node.name, valid_bindings);
|
|
||||||
if (match) {
|
|
||||||
return component.error(
|
|
||||||
node,
|
|
||||||
compiler_errors.invalid_binding_on(
|
|
||||||
node.name,
|
|
||||||
'<svelte:document>',
|
|
||||||
` (did you mean '${match}'?)`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return component.error(
|
|
||||||
node,
|
|
||||||
compiler_errors.invalid_binding_on(
|
|
||||||
node.name,
|
|
||||||
'<svelte:document>',
|
|
||||||
` — valid bindings are ${list(valid_bindings)}`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.bindings.push(new Binding(component, this, scope, node));
|
|
||||||
} else if (node.type === 'Action') {
|
|
||||||
this.actions.push(new Action(component, this, scope, node));
|
|
||||||
} else {
|
|
||||||
// TODO there shouldn't be anything else here...
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.validate();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @private */
|
|
||||||
validate() {
|
|
||||||
const handlers_map = new Set();
|
|
||||||
this.handlers.forEach((handler) => handlers_map.add(handler.name));
|
|
||||||
if (handlers_map.has('mouseenter') || handlers_map.has('mouseleave')) {
|
|
||||||
this.component.warn(this, compiler_warnings.avoid_mouse_events_on_document);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,114 +0,0 @@
|
|||||||
import ElseBlock from './ElseBlock.js';
|
|
||||||
import Expression from './shared/Expression.js';
|
|
||||||
import AbstractBlock from './shared/AbstractBlock.js';
|
|
||||||
import { unpack_destructuring } from './shared/Context.js';
|
|
||||||
import compiler_errors from '../compiler_errors.js';
|
|
||||||
import get_const_tags from './shared/get_const_tags.js';
|
|
||||||
|
|
||||||
/** @extends AbstractBlock<'EachBlock'> */
|
|
||||||
export default class EachBlock extends AbstractBlock {
|
|
||||||
/** @type {import('./shared/Expression.js').default} */
|
|
||||||
expression;
|
|
||||||
|
|
||||||
/** @type {import('estree').Node} */
|
|
||||||
context_node;
|
|
||||||
|
|
||||||
/** @type {string} */
|
|
||||||
iterations;
|
|
||||||
|
|
||||||
/** @type {string} */
|
|
||||||
index;
|
|
||||||
|
|
||||||
/** @type {string} */
|
|
||||||
context;
|
|
||||||
|
|
||||||
/** @type {import('./shared/Expression.js').default} */
|
|
||||||
key;
|
|
||||||
|
|
||||||
/** @type {import('./shared/TemplateScope.js').default} */
|
|
||||||
scope;
|
|
||||||
|
|
||||||
/** @type {import('./shared/Context.js').Context[]} */
|
|
||||||
contexts;
|
|
||||||
|
|
||||||
/** @type {import('./ConstTag.js').default[]} */
|
|
||||||
const_tags;
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
has_animation;
|
|
||||||
/** */
|
|
||||||
has_binding = false;
|
|
||||||
/** */
|
|
||||||
has_index_binding = false;
|
|
||||||
|
|
||||||
/** @type {Map<string, import('estree').Node>} */
|
|
||||||
context_rest_properties;
|
|
||||||
|
|
||||||
/** @type {import('./ElseBlock.js').default} */
|
|
||||||
else;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('estree').Node} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope
|
|
||||||
* @param {import('../../interfaces.js').TemplateNode} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.cannot_use_innerhtml();
|
|
||||||
this.not_static_content();
|
|
||||||
this.expression = new Expression(component, this, scope, info.expression);
|
|
||||||
this.context = info.context.name || 'each'; // TODO this is used to facilitate binding; currently fails with destructuring
|
|
||||||
this.context_node = info.context;
|
|
||||||
this.index = info.index;
|
|
||||||
this.scope = scope.child();
|
|
||||||
this.context_rest_properties = new Map();
|
|
||||||
this.contexts = [];
|
|
||||||
unpack_destructuring({
|
|
||||||
contexts: this.contexts,
|
|
||||||
node: info.context,
|
|
||||||
scope,
|
|
||||||
component,
|
|
||||||
context_rest_properties: this.context_rest_properties
|
|
||||||
});
|
|
||||||
this.contexts.forEach((context) => {
|
|
||||||
if (context.type !== 'DestructuredVariable') return;
|
|
||||||
this.scope.add(context.key.name, this.expression.dependencies, this);
|
|
||||||
});
|
|
||||||
if (this.index) {
|
|
||||||
// index can only change if this is a keyed each block
|
|
||||||
const dependencies = info.key ? this.expression.dependencies : new Set([]);
|
|
||||||
this.scope.add(this.index, dependencies, this);
|
|
||||||
}
|
|
||||||
this.key = info.key ? new Expression(component, this, this.scope, info.key) : null;
|
|
||||||
this.has_animation = false;
|
|
||||||
[this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
|
|
||||||
if (this.has_animation) {
|
|
||||||
this.children = this.children.filter(
|
|
||||||
(child) => !is_empty_node(child) && !is_comment_node(child)
|
|
||||||
);
|
|
||||||
if (this.children.length !== 1) {
|
|
||||||
const child = this.children.find(
|
|
||||||
(child) => !!(/** @type {import('./Element.js').default} */ (child).animation)
|
|
||||||
);
|
|
||||||
component.error(
|
|
||||||
/** @type {import('./Element.js').default} */ (child).animation,
|
|
||||||
compiler_errors.invalid_animation_sole
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.warn_if_empty_block();
|
|
||||||
this.else = info.else ? new ElseBlock(component, this, this.scope, info.else) : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {import('./interfaces.js').INode} node */
|
|
||||||
function is_empty_node(node) {
|
|
||||||
return node.type === 'Text' && node.data.trim() === '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {import('./interfaces.js').INode} node */
|
|
||||||
function is_comment_node(node) {
|
|
||||||
return node.type === 'Comment';
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,24 +0,0 @@
|
|||||||
import AbstractBlock from './shared/AbstractBlock.js';
|
|
||||||
import get_const_tags from './shared/get_const_tags.js';
|
|
||||||
|
|
||||||
/** @extends AbstractBlock<'ElseBlock'> */
|
|
||||||
export default class ElseBlock extends AbstractBlock {
|
|
||||||
/** @type {import('./shared/TemplateScope.js').default} */
|
|
||||||
scope;
|
|
||||||
|
|
||||||
/** @type {import('./ConstTag.js').default[]} */
|
|
||||||
const_tags;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('./shared/Node.js').default} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope
|
|
||||||
* @param {import('../../interfaces.js').TemplateNode} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.scope = scope.child();
|
|
||||||
[this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
|
|
||||||
this.warn_if_empty_block();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,82 +0,0 @@
|
|||||||
import Node from './shared/Node.js';
|
|
||||||
import Expression from './shared/Expression.js';
|
|
||||||
import { sanitize } from '../../utils/names.js';
|
|
||||||
|
|
||||||
const regex_contains_term_function_expression = /FunctionExpression/;
|
|
||||||
|
|
||||||
/** @extends Node<'EventHandler'> */
|
|
||||||
export default class EventHandler extends Node {
|
|
||||||
/** @type {string} */
|
|
||||||
name;
|
|
||||||
|
|
||||||
/** @type {Set<string>} */
|
|
||||||
modifiers;
|
|
||||||
|
|
||||||
/** @type {import('./shared/Expression.js').default} */
|
|
||||||
expression;
|
|
||||||
|
|
||||||
/** @type {import('estree').Identifier} */
|
|
||||||
handler_name;
|
|
||||||
/** */
|
|
||||||
uses_context = false;
|
|
||||||
/** */
|
|
||||||
can_make_passive = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('./shared/Node.js').default} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} template_scope
|
|
||||||
* @param {import('../../interfaces.js').TemplateNode} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, template_scope, info) {
|
|
||||||
super(component, parent, template_scope, info);
|
|
||||||
this.name = info.name;
|
|
||||||
this.modifiers = new Set(info.modifiers);
|
|
||||||
if (info.expression) {
|
|
||||||
this.expression = new Expression(component, this, template_scope, info.expression);
|
|
||||||
this.uses_context = this.expression.uses_context;
|
|
||||||
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
|
|
||||||
// `event` is passed to another function, we can make it passive
|
|
||||||
this.can_make_passive = true;
|
|
||||||
} else if (info.expression.type === 'Identifier') {
|
|
||||||
let node = component.node_for_declaration.get(info.expression.name);
|
|
||||||
if (node) {
|
|
||||||
if (node.type === 'VariableDeclaration') {
|
|
||||||
// for `const handleClick = () => {...}`, we want the [arrow] function expression node
|
|
||||||
const declarator = node.declarations.find(
|
|
||||||
(d) => /** @type {import('estree').Identifier} */ (d.id).name === info.expression.name
|
|
||||||
);
|
|
||||||
node = declarator && declarator.init;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
node &&
|
|
||||||
(node.type === 'FunctionExpression' ||
|
|
||||||
node.type === 'FunctionDeclaration' ||
|
|
||||||
node.type === 'ArrowFunctionExpression') &&
|
|
||||||
node.params.length === 0
|
|
||||||
) {
|
|
||||||
this.can_make_passive = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.handler_name = component.get_unique_name(`${sanitize(this.name)}_handler`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @returns {boolean} */
|
|
||||||
get reassigned() {
|
|
||||||
if (!this.expression) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const node = this.expression.node;
|
|
||||||
if (regex_contains_term_function_expression.test(node.type)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return this.expression.dynamic_dependencies().length > 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
import Node from './shared/Node.js';
|
|
||||||
import map_children from './shared/map_children.js';
|
|
||||||
import TemplateScope from './shared/TemplateScope.js';
|
|
||||||
|
|
||||||
/** @extends Node<'Fragment'> */
|
|
||||||
export default class Fragment extends Node {
|
|
||||||
/** @type {import('../render_dom/Block.js').default} */
|
|
||||||
block;
|
|
||||||
|
|
||||||
/** @type {import('./interfaces.js').INode[]} */
|
|
||||||
children;
|
|
||||||
|
|
||||||
/** @type {import('./shared/TemplateScope.js').default} */
|
|
||||||
scope;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('../../interfaces.js').TemplateNode} info
|
|
||||||
*/
|
|
||||||
constructor(component, info) {
|
|
||||||
const scope = new TemplateScope();
|
|
||||||
super(component, null, scope, info);
|
|
||||||
this.scope = scope;
|
|
||||||
this.children = map_children(component, this, scope, info.children);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
import Node from './shared/Node.js';
|
|
||||||
import map_children from './shared/map_children.js';
|
|
||||||
import hash from '../utils/hash.js';
|
|
||||||
import compiler_errors from '../compiler_errors.js';
|
|
||||||
import { regex_non_whitespace_character } from '../../utils/patterns.js';
|
|
||||||
|
|
||||||
/** @extends Node<'Head'> */
|
|
||||||
export default class Head extends Node {
|
|
||||||
/** @type {any[]} */
|
|
||||||
children; // TODO
|
|
||||||
|
|
||||||
/** @type {string} */
|
|
||||||
id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('./shared/Node.js').default} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope
|
|
||||||
* @param {import('../../interfaces.js').TemplateNode} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.cannot_use_innerhtml();
|
|
||||||
if (info.attributes.length) {
|
|
||||||
component.error(info.attributes[0], compiler_errors.invalid_attribute_head);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.children = map_children(
|
|
||||||
component,
|
|
||||||
parent,
|
|
||||||
scope,
|
|
||||||
info.children.filter((child) => {
|
|
||||||
return child.type !== 'Text' || regex_non_whitespace_character.test(child.data);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
if (this.children.length > 0) {
|
|
||||||
this.id = `svelte-${hash(this.component.source.slice(this.start, this.end))}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
import ElseBlock from './ElseBlock.js';
|
|
||||||
import Expression from './shared/Expression.js';
|
|
||||||
import AbstractBlock from './shared/AbstractBlock.js';
|
|
||||||
import get_const_tags from './shared/get_const_tags.js';
|
|
||||||
|
|
||||||
/** @extends AbstractBlock<'IfBlock'> */
|
|
||||||
export default class IfBlock extends AbstractBlock {
|
|
||||||
/** @type {import('./shared/Expression.js').default} */
|
|
||||||
expression;
|
|
||||||
|
|
||||||
/** @type {import('./ElseBlock.js').default} */
|
|
||||||
else;
|
|
||||||
|
|
||||||
/** @type {import('./shared/TemplateScope.js').default} */
|
|
||||||
scope;
|
|
||||||
|
|
||||||
/** @type {import('./ConstTag.js').default[]} */
|
|
||||||
const_tags;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('./shared/Node.js').default} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope
|
|
||||||
* @param {import('../../interfaces.js').TemplateNode} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.scope = scope.child();
|
|
||||||
this.cannot_use_innerhtml();
|
|
||||||
this.not_static_content();
|
|
||||||
this.expression = new Expression(component, this, this.scope, info.expression);
|
|
||||||
[this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
|
|
||||||
this.else = info.else ? new ElseBlock(component, this, scope, info.else) : null;
|
|
||||||
this.warn_if_empty_block();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,199 +0,0 @@
|
|||||||
import Node from './shared/Node.js';
|
|
||||||
import Attribute from './Attribute.js';
|
|
||||||
import map_children from './shared/map_children.js';
|
|
||||||
import Binding from './Binding.js';
|
|
||||||
import EventHandler from './EventHandler.js';
|
|
||||||
import Expression from './shared/Expression.js';
|
|
||||||
import compiler_errors from '../compiler_errors.js';
|
|
||||||
import { regex_only_whitespaces } from '../../utils/patterns.js';
|
|
||||||
|
|
||||||
/** @extends Node<'InlineComponent'> */
|
|
||||||
export default class InlineComponent extends Node {
|
|
||||||
/** @type {string} */
|
|
||||||
name;
|
|
||||||
|
|
||||||
/** @type {import('./shared/Expression.js').default} */
|
|
||||||
expression;
|
|
||||||
|
|
||||||
/** @type {import('./Binding.js').default[]} */
|
|
||||||
bindings = [];
|
|
||||||
|
|
||||||
/** @type {import('./EventHandler.js').default[]} */
|
|
||||||
handlers = [];
|
|
||||||
|
|
||||||
/** @type {import('./Attribute.js').default[]} */
|
|
||||||
css_custom_properties = [];
|
|
||||||
|
|
||||||
/** @type {import('./interfaces.js').INode[]} */
|
|
||||||
children;
|
|
||||||
|
|
||||||
/** @type {import('./shared/TemplateScope.js').default} */
|
|
||||||
scope;
|
|
||||||
|
|
||||||
/** @type {string} */
|
|
||||||
namespace;
|
|
||||||
|
|
||||||
/** @type {Attribute[]} */
|
|
||||||
let_attributes;
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('./shared/Node.js').default} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope
|
|
||||||
* @param {import('../../interfaces.js').TemplateNode} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.cannot_use_innerhtml();
|
|
||||||
this.not_static_content();
|
|
||||||
if (info.name !== 'svelte:component' && info.name !== 'svelte:self') {
|
|
||||||
const name = info.name.split('.')[0]; // accommodate namespaces
|
|
||||||
component.warn_if_undefined(name, info, scope);
|
|
||||||
component.add_reference(/** @type {any} */ (this), name);
|
|
||||||
}
|
|
||||||
this.name = info.name;
|
|
||||||
this.namespace = get_namespace(parent, component.namespace);
|
|
||||||
this.expression =
|
|
||||||
this.name === 'svelte:component'
|
|
||||||
? new Expression(component, this, scope, info.expression)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const let_attributes = (this.let_attributes = []);
|
|
||||||
info.attributes.forEach(
|
|
||||||
/** @param {import('../../interfaces.js').BaseDirective | import('../../interfaces.js').Attribute | import('../../interfaces.js').SpreadAttribute} node */ (
|
|
||||||
node
|
|
||||||
) => {
|
|
||||||
/* eslint-disable no-fallthrough */
|
|
||||||
switch (node.type) {
|
|
||||||
case 'Action':
|
|
||||||
return component.error(node, compiler_errors.invalid_action);
|
|
||||||
case 'Attribute':
|
|
||||||
if (node.name.startsWith('--')) {
|
|
||||||
this.css_custom_properties.push(new Attribute(component, this, scope, node));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// fallthrough
|
|
||||||
case 'Spread':
|
|
||||||
this.attributes.push(new Attribute(component, this, scope, node));
|
|
||||||
break;
|
|
||||||
case 'Binding':
|
|
||||||
this.bindings.push(new Binding(component, this, scope, node));
|
|
||||||
break;
|
|
||||||
case 'Class':
|
|
||||||
return component.error(node, compiler_errors.invalid_class);
|
|
||||||
case 'EventHandler':
|
|
||||||
this.handlers.push(new EventHandler(component, this, scope, node));
|
|
||||||
break;
|
|
||||||
case 'Let':
|
|
||||||
let_attributes.push(node);
|
|
||||||
break;
|
|
||||||
case 'Transition':
|
|
||||||
return component.error(node, compiler_errors.invalid_transition);
|
|
||||||
case 'StyleDirective':
|
|
||||||
return component.error(node, compiler_errors.invalid_component_style_directive);
|
|
||||||
case 'Animation':
|
|
||||||
return component.error(node, compiler_errors.invalid_animation);
|
|
||||||
default:
|
|
||||||
throw new Error(`Not implemented: ${node.type}`);
|
|
||||||
}
|
|
||||||
/* eslint-enable no-fallthrough */
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
this.scope = scope;
|
|
||||||
|
|
||||||
this.handlers.forEach((handler) => {
|
|
||||||
handler.modifiers.forEach((modifier) => {
|
|
||||||
if (modifier !== 'once') {
|
|
||||||
return component.error(handler, compiler_errors.invalid_event_modifier_component);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const children = [];
|
|
||||||
for (let i = info.children.length - 1; i >= 0; i--) {
|
|
||||||
const child = info.children[i];
|
|
||||||
if (child.type === 'SlotTemplate') {
|
|
||||||
children.push(child);
|
|
||||||
info.children.splice(i, 1);
|
|
||||||
} else if (
|
|
||||||
(child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') &&
|
|
||||||
child.attributes.find((attribute) => attribute.name === 'slot')
|
|
||||||
) {
|
|
||||||
const slot_template = {
|
|
||||||
start: child.start,
|
|
||||||
end: child.end,
|
|
||||||
type: 'SlotTemplate',
|
|
||||||
name: 'svelte:fragment',
|
|
||||||
attributes: [],
|
|
||||||
children: [child]
|
|
||||||
};
|
|
||||||
// transfer attributes
|
|
||||||
for (let i = child.attributes.length - 1; i >= 0; i--) {
|
|
||||||
const attribute = child.attributes[i];
|
|
||||||
if (attribute.type === 'Let') {
|
|
||||||
slot_template.attributes.push(attribute);
|
|
||||||
child.attributes.splice(i, 1);
|
|
||||||
} else if (attribute.type === 'Attribute' && attribute.name === 'slot') {
|
|
||||||
slot_template.attributes.push(attribute);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// transfer const
|
|
||||||
for (let i = child.children.length - 1; i >= 0; i--) {
|
|
||||||
const child_child = child.children[i];
|
|
||||||
if (child_child.type === 'ConstTag') {
|
|
||||||
slot_template.children.push(child_child);
|
|
||||||
child.children.splice(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
children.push(slot_template);
|
|
||||||
info.children.splice(i, 1);
|
|
||||||
} else if (child.type === 'Comment' && children.length > 0) {
|
|
||||||
children[children.length - 1].children.unshift(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (info.children.some((node) => not_whitespace_text(node))) {
|
|
||||||
children.push({
|
|
||||||
start: info.start,
|
|
||||||
end: info.end,
|
|
||||||
type: 'SlotTemplate',
|
|
||||||
name: 'svelte:fragment',
|
|
||||||
attributes: [],
|
|
||||||
children: info.children
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (let_attributes.length) {
|
|
||||||
// copy let: attribute from <Component /> to <svelte:fragment slot="default" />
|
|
||||||
// as they are for `slot="default"` only
|
|
||||||
children.forEach((child) => {
|
|
||||||
const slot = child.attributes.find((attribute) => attribute.name === 'slot');
|
|
||||||
if (!slot || slot.value[0].data === 'default') {
|
|
||||||
child.attributes.push(...let_attributes);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.children = map_children(component, this, this.scope, children);
|
|
||||||
}
|
|
||||||
get slot_template_name() {
|
|
||||||
return /** @type {string} */ (
|
|
||||||
this.attributes.find((attribute) => attribute.name === 'slot').get_static_value()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {any} node */
|
|
||||||
function not_whitespace_text(node) {
|
|
||||||
return !(node.type === 'Text' && regex_only_whitespaces.test(node.data));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('./shared/Node.js').default} parent
|
|
||||||
* @param {string} explicit_namespace
|
|
||||||
*/
|
|
||||||
function get_namespace(parent, explicit_namespace) {
|
|
||||||
const parent_element = parent.find_nearest(/^Element/);
|
|
||||||
if (!parent_element) {
|
|
||||||
return explicit_namespace;
|
|
||||||
}
|
|
||||||
return parent_element.namespace;
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
import Expression from './shared/Expression.js';
|
|
||||||
import map_children from './shared/map_children.js';
|
|
||||||
import AbstractBlock from './shared/AbstractBlock.js';
|
|
||||||
|
|
||||||
/** @extends AbstractBlock<'KeyBlock'> */
|
|
||||||
export default class KeyBlock extends AbstractBlock {
|
|
||||||
/** @type {import('./shared/Expression.js').default} */
|
|
||||||
expression;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('./shared/Node.js').default} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope
|
|
||||||
* @param {import('../../interfaces.js').TemplateNode} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.cannot_use_innerhtml();
|
|
||||||
this.not_static_content();
|
|
||||||
this.expression = new Expression(component, this, scope, info.expression);
|
|
||||||
this.children = map_children(component, this, scope, info.children);
|
|
||||||
this.warn_if_empty_block();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
import Node from './shared/Node.js';
|
|
||||||
import { walk } from 'estree-walker';
|
|
||||||
import compiler_errors from '../compiler_errors.js';
|
|
||||||
|
|
||||||
const applicable = new Set(['Identifier', 'ObjectExpression', 'ArrayExpression', 'Property']);
|
|
||||||
|
|
||||||
/** @extends Node<'Let'> */
|
|
||||||
export default class Let extends Node {
|
|
||||||
/** @type {import('estree').Identifier} */
|
|
||||||
name;
|
|
||||||
|
|
||||||
/** @type {import('estree').Identifier} */
|
|
||||||
value;
|
|
||||||
|
|
||||||
/** @type {string[]} */
|
|
||||||
names = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('./shared/Node.js').default} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope
|
|
||||||
* @param {import('../../interfaces.js').TemplateNode} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.name = { type: 'Identifier', name: info.name };
|
|
||||||
const { names } = this;
|
|
||||||
if (info.expression) {
|
|
||||||
this.value = info.expression;
|
|
||||||
walk(info.expression, {
|
|
||||||
/** @param {import('estree').Identifier | import('estree').BasePattern} node */
|
|
||||||
enter(node) {
|
|
||||||
if (!applicable.has(node.type)) {
|
|
||||||
return component.error(/** @type {any} */ (node), compiler_errors.invalid_let);
|
|
||||||
}
|
|
||||||
if (node.type === 'Identifier') {
|
|
||||||
names.push(/** @type {import('estree').Identifier} */ (node).name);
|
|
||||||
}
|
|
||||||
// slightly unfortunate hack
|
|
||||||
if (node.type === 'ArrayExpression') {
|
|
||||||
node.type = 'ArrayPattern';
|
|
||||||
}
|
|
||||||
if (node.type === 'ObjectExpression') {
|
|
||||||
node.type = 'ObjectPattern';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
names.push(this.name.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
import Tag from './shared/Tag.js';
|
|
||||||
|
|
||||||
/** @extends Tag<'MustacheTag'> */
|
|
||||||
export default class MustacheTag extends Tag {}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
import Node from './shared/Node.js';
|
|
||||||
|
|
||||||
/** @extends Node<'Options'> */
|
|
||||||
export default class Options extends Node {}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
import map_children from './shared/map_children.js';
|
|
||||||
import AbstractBlock from './shared/AbstractBlock.js';
|
|
||||||
|
|
||||||
/** @extends AbstractBlock<'PendingBlock'> */
|
|
||||||
export default class PendingBlock extends AbstractBlock {
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('./shared/Node.js').default} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope
|
|
||||||
* @param {import('../../interfaces.js').TemplateNode} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.children = map_children(component, parent, scope, info.children);
|
|
||||||
if (!info.skip) {
|
|
||||||
this.warn_if_empty_block();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
import Tag from './shared/Tag.js';
|
|
||||||
|
|
||||||
/** @extends Tag<'RawMustacheTag'> */
|
|
||||||
export default class RawMustacheTag extends Tag {
|
|
||||||
/**
|
|
||||||
* @param {any} component
|
|
||||||
* @param {any} parent
|
|
||||||
* @param {any} scope
|
|
||||||
* @param {any} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.cannot_use_innerhtml();
|
|
||||||
this.not_static_content();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
import Element from './Element.js';
|
|
||||||
import Attribute from './Attribute.js';
|
|
||||||
import compiler_errors from '../compiler_errors.js';
|
|
||||||
|
|
||||||
/** @extends Element */
|
|
||||||
export default class Slot extends Element {
|
|
||||||
/** @type {'Slot'} */
|
|
||||||
// @ts-ignore Slot elements have the 'Slot' type, but TypeScript doesn't allow us to have 'Slot' when it extends Element
|
|
||||||
type = 'Slot';
|
|
||||||
|
|
||||||
/** @type {string} */
|
|
||||||
slot_name;
|
|
||||||
|
|
||||||
/** @type {Map<string, import('./Attribute.js').default>} */
|
|
||||||
values = new Map();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('./interfaces.js').INode} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope
|
|
||||||
* @param {import('../../interfaces.js').TemplateNode} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
info.attributes.forEach((attr) => {
|
|
||||||
if (attr.type !== 'Attribute' && attr.type !== 'Spread') {
|
|
||||||
return component.error(attr, compiler_errors.invalid_slot_directive);
|
|
||||||
}
|
|
||||||
if (attr.name === 'name') {
|
|
||||||
if (attr.value.length !== 1 || attr.value[0].type !== 'Text') {
|
|
||||||
return component.error(attr, compiler_errors.dynamic_slot_name);
|
|
||||||
}
|
|
||||||
this.slot_name = attr.value[0].data;
|
|
||||||
if (this.slot_name === 'default') {
|
|
||||||
return component.error(attr, compiler_errors.invalid_slot_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.values.set(attr.name, new Attribute(component, this, scope, attr));
|
|
||||||
});
|
|
||||||
if (!this.slot_name) this.slot_name = 'default';
|
|
||||||
|
|
||||||
component.slots.set(this.slot_name, this);
|
|
||||||
this.cannot_use_innerhtml();
|
|
||||||
this.not_static_content();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
import Node from './shared/Node.js';
|
|
||||||
import Let from './Let.js';
|
|
||||||
import Attribute from './Attribute.js';
|
|
||||||
import compiler_errors from '../compiler_errors.js';
|
|
||||||
import get_const_tags from './shared/get_const_tags.js';
|
|
||||||
|
|
||||||
/** @extends Node<'SlotTemplate'> */
|
|
||||||
export default class SlotTemplate extends Node {
|
|
||||||
/** @type {import('./shared/TemplateScope.js').default} */
|
|
||||||
scope;
|
|
||||||
|
|
||||||
/** @type {import('./interfaces.js').INode[]} */
|
|
||||||
children;
|
|
||||||
|
|
||||||
/** @type {import('./Let.js').default[]} */
|
|
||||||
lets = [];
|
|
||||||
|
|
||||||
/** @type {import('./ConstTag.js').default[]} */
|
|
||||||
const_tags;
|
|
||||||
|
|
||||||
/** @type {import('./Attribute.js').default} */
|
|
||||||
slot_attribute;
|
|
||||||
|
|
||||||
/** @type {string} */
|
|
||||||
slot_template_name = 'default';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('./interfaces.js').INode} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope
|
|
||||||
* @param {any} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.validate_slot_template_placement();
|
|
||||||
scope = scope.child();
|
|
||||||
info.attributes.forEach((node) => {
|
|
||||||
switch (node.type) {
|
|
||||||
case 'Let': {
|
|
||||||
const l = new Let(component, this, scope, node);
|
|
||||||
this.lets.push(l);
|
|
||||||
const dependencies = new Set([l.name.name]);
|
|
||||||
l.names.forEach((name) => {
|
|
||||||
scope.add(name, dependencies, this);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'Attribute': {
|
|
||||||
if (node.name === 'slot') {
|
|
||||||
this.slot_attribute = new Attribute(component, this, scope, node);
|
|
||||||
if (!this.slot_attribute.is_static) {
|
|
||||||
return component.error(node, compiler_errors.invalid_slot_attribute);
|
|
||||||
}
|
|
||||||
const value = this.slot_attribute.get_static_value();
|
|
||||||
if (typeof value === 'boolean') {
|
|
||||||
return component.error(node, compiler_errors.invalid_slot_attribute_value_missing);
|
|
||||||
}
|
|
||||||
this.slot_template_name = /** @type {string} */ (value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
throw new Error(`Invalid attribute '${node.name}' in <svelte:fragment>`);
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new Error(`Not implemented: ${node.type}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.scope = scope;
|
|
||||||
[this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
|
|
||||||
}
|
|
||||||
validate_slot_template_placement() {
|
|
||||||
if (this.parent.type !== 'InlineComponent') {
|
|
||||||
return this.component.error(this, compiler_errors.invalid_slotted_content_fragment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
import list from '../../utils/list.js';
|
|
||||||
import compiler_errors from '../compiler_errors.js';
|
|
||||||
import { nodes_to_template_literal } from '../utils/nodes_to_template_literal.js';
|
|
||||||
import Expression from './shared/Expression.js';
|
|
||||||
import Node from './shared/Node.js';
|
|
||||||
|
|
||||||
const valid_modifiers = new Set(['important']);
|
|
||||||
|
|
||||||
/** @extends Node<'StyleDirective'> */
|
|
||||||
export default class StyleDirective extends Node {
|
|
||||||
/** @type {string} */
|
|
||||||
name;
|
|
||||||
|
|
||||||
/** @type {Set<string>} */
|
|
||||||
modifiers;
|
|
||||||
|
|
||||||
/** @type {import('./shared/Expression.js').default} */
|
|
||||||
expression;
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
should_cache;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('./shared/Node.js').default} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope
|
|
||||||
* @param {import('../../interfaces.js').TemplateNode} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
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
|
|
||||||
// the StyleDirective going forward.
|
|
||||||
if (info.value === true || (info.value.length === 1 && info.value[0].type === 'MustacheTag')) {
|
|
||||||
const identifier =
|
|
||||||
info.value === true
|
|
||||||
? {
|
|
||||||
type: 'Identifier',
|
|
||||||
start: info.end - info.name.length,
|
|
||||||
end: info.end,
|
|
||||||
name: info.name
|
|
||||||
}
|
|
||||||
: info.value[0].expression;
|
|
||||||
this.expression = new Expression(component, this, scope, identifier);
|
|
||||||
this.should_cache = false;
|
|
||||||
} else {
|
|
||||||
const raw_expression = nodes_to_template_literal(info.value);
|
|
||||||
this.expression = new Expression(component, this, scope, raw_expression);
|
|
||||||
this.should_cache = raw_expression.expressions.length > 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
get important() {
|
|
||||||
return this.modifiers.has('important');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
import Node from './shared/Node.js';
|
|
||||||
import { regex_non_whitespace_character } from '../../utils/patterns.js';
|
|
||||||
|
|
||||||
// Whitespace inside one of these elements will not result in
|
|
||||||
// a whitespace node being created in any circumstances. (This
|
|
||||||
// list is almost certainly very incomplete)
|
|
||||||
const elements_without_text = new Set(['audio', 'datalist', 'dl', 'optgroup', 'select', 'video']);
|
|
||||||
const regex_ends_with_svg = /svg$/;
|
|
||||||
const regex_non_whitespace_characters = /[\S\u00A0]/;
|
|
||||||
|
|
||||||
/** @extends Node<'Text'> */
|
|
||||||
export default class Text extends Node {
|
|
||||||
/** @type {string} */
|
|
||||||
data;
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
synthetic;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('./interfaces.js').INode} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope
|
|
||||||
* @param {import('../../interfaces.js').TemplateNode} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.data = info.data;
|
|
||||||
this.synthetic = info.synthetic || false;
|
|
||||||
}
|
|
||||||
should_skip() {
|
|
||||||
if (regex_non_whitespace_character.test(this.data)) return false;
|
|
||||||
const parent_element = this.find_nearest(/(?:Element|InlineComponent|SlotTemplate|Head)/);
|
|
||||||
if (!parent_element) return false;
|
|
||||||
if (parent_element.type === 'Head') return true;
|
|
||||||
if (parent_element.type === 'InlineComponent')
|
|
||||||
return parent_element.children.length === 1 && this === parent_element.children[0];
|
|
||||||
// svg namespace exclusions
|
|
||||||
if (regex_ends_with_svg.test(parent_element.namespace)) {
|
|
||||||
if (this.prev && this.prev.type === 'Element' && this.prev.name === 'tspan') return false;
|
|
||||||
}
|
|
||||||
return parent_element.namespace || elements_without_text.has(parent_element.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @returns {boolean} */
|
|
||||||
keep_space() {
|
|
||||||
if (this.component.component_options.preserveWhitespace) return true;
|
|
||||||
return this.within_pre();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @returns {boolean} */
|
|
||||||
within_pre() {
|
|
||||||
let node = this.parent;
|
|
||||||
while (node) {
|
|
||||||
if (node.type === 'Element' && node.name === 'pre') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
node = node.parent;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @returns {boolean} */
|
|
||||||
use_space() {
|
|
||||||
if (this.component.compile_options.preserveWhitespace) return false;
|
|
||||||
if (regex_non_whitespace_characters.test(this.data)) return false;
|
|
||||||
return !this.within_pre();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
import AbstractBlock from './shared/AbstractBlock.js';
|
|
||||||
import get_const_tags from './shared/get_const_tags.js';
|
|
||||||
|
|
||||||
/** @extends AbstractBlock<'ThenBlock'> */
|
|
||||||
export default class ThenBlock extends AbstractBlock {
|
|
||||||
/** @type {import('./shared/TemplateScope.js').default} */
|
|
||||||
scope;
|
|
||||||
|
|
||||||
/** @type {import('./ConstTag.js').default[]} */
|
|
||||||
const_tags;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('./AwaitBlock.js').default} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope
|
|
||||||
* @param {import('../../interfaces.js').TemplateNode} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.scope = scope.child();
|
|
||||||
if (parent.then_node) {
|
|
||||||
parent.then_contexts.forEach((context) => {
|
|
||||||
if (context.type !== 'DestructuredVariable') return;
|
|
||||||
this.scope.add(context.key.name, parent.expression.dependencies, this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
[this.const_tags, this.children] = get_const_tags(info.children, component, this, parent);
|
|
||||||
if (!info.skip) {
|
|
||||||
this.warn_if_empty_block();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
import Node from './shared/Node.js';
|
|
||||||
import map_children from './shared/map_children.js';
|
|
||||||
import compiler_errors from '../compiler_errors.js';
|
|
||||||
|
|
||||||
/** @extends Node<'Title'> */
|
|
||||||
export default class Title extends Node {
|
|
||||||
/** @type {import('./shared/map_children.js').Children} */
|
|
||||||
children;
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
should_cache;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('./shared/Node.js').default} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope
|
|
||||||
* @param {import('../../interfaces.js').TemplateNode} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
this.children = map_children(component, parent, scope, info.children);
|
|
||||||
if (info.attributes.length > 0) {
|
|
||||||
component.error(info.attributes[0], compiler_errors.illegal_attribute_title);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
info.children.forEach((child) => {
|
|
||||||
if (child.type !== 'Text' && child.type !== 'MustacheTag') {
|
|
||||||
return component.error(child, compiler_errors.illegal_structure_title);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.should_cache =
|
|
||||||
info.children.length === 1
|
|
||||||
? info.children[0].type !== 'Identifier' || scope.names.has(info.children[0].name)
|
|
||||||
: true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
import Node from './shared/Node.js';
|
|
||||||
import Expression from './shared/Expression.js';
|
|
||||||
import compiler_errors from '../compiler_errors.js';
|
|
||||||
|
|
||||||
/** @extends Node<'Transition'> */
|
|
||||||
export default class Transition extends Node {
|
|
||||||
/** @type {string} */
|
|
||||||
name;
|
|
||||||
|
|
||||||
/** @type {string} */
|
|
||||||
directive;
|
|
||||||
|
|
||||||
/** @type {import('./shared/Expression.js').default} */
|
|
||||||
expression;
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
is_local;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('./Element.js').default} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope
|
|
||||||
* @param {import('../../interfaces.js').TemplateNode} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
component.warn_if_undefined(info.name, info, scope);
|
|
||||||
this.name = info.name;
|
|
||||||
component.add_reference(/** @type {any} */ (this), info.name.split('.')[0]);
|
|
||||||
this.directive = info.intro && info.outro ? 'transition' : info.intro ? 'in' : 'out';
|
|
||||||
this.is_local = !info.modifiers.includes('global');
|
|
||||||
if ((info.intro && parent.intro) || (info.outro && parent.outro)) {
|
|
||||||
const parent_transition = parent.intro || parent.outro;
|
|
||||||
component.error(
|
|
||||||
info,
|
|
||||||
compiler_errors.duplicate_transition(this.directive, parent_transition.directive)
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.expression = info.expression
|
|
||||||
? new Expression(component, this, scope, info.expression)
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,84 +0,0 @@
|
|||||||
import Node from './shared/Node.js';
|
|
||||||
import Binding from './Binding.js';
|
|
||||||
import EventHandler from './EventHandler.js';
|
|
||||||
import flatten_reference from '../utils/flatten_reference.js';
|
|
||||||
import fuzzymatch from '../../utils/fuzzymatch.js';
|
|
||||||
import list from '../../utils/list.js';
|
|
||||||
import Action from './Action.js';
|
|
||||||
import compiler_errors from '../compiler_errors.js';
|
|
||||||
|
|
||||||
const valid_bindings = [
|
|
||||||
'innerWidth',
|
|
||||||
'innerHeight',
|
|
||||||
'outerWidth',
|
|
||||||
'outerHeight',
|
|
||||||
'scrollX',
|
|
||||||
'scrollY',
|
|
||||||
'devicePixelRatio',
|
|
||||||
'online'
|
|
||||||
];
|
|
||||||
|
|
||||||
/** @extends Node<'Window'> */
|
|
||||||
export default class Window extends Node {
|
|
||||||
/** @type {import('./EventHandler.js').default[]} */
|
|
||||||
handlers = [];
|
|
||||||
|
|
||||||
/** @type {import('./Binding.js').default[]} */
|
|
||||||
bindings = [];
|
|
||||||
|
|
||||||
/** @type {import('./Action.js').default[]} */
|
|
||||||
actions = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('./shared/Node.js').default} parent
|
|
||||||
* @param {import('./shared/TemplateScope.js').default} scope
|
|
||||||
* @param {import('../../interfaces.js').TemplateNode} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
info.attributes.forEach((node) => {
|
|
||||||
if (node.type === 'EventHandler') {
|
|
||||||
this.handlers.push(new EventHandler(component, this, scope, node));
|
|
||||||
} else if (node.type === 'Binding') {
|
|
||||||
if (node.expression.type !== 'Identifier') {
|
|
||||||
const { parts } = flatten_reference(node.expression);
|
|
||||||
// TODO is this constraint necessary?
|
|
||||||
return component.error(node.expression, compiler_errors.invalid_binding_window(parts));
|
|
||||||
}
|
|
||||||
if (!~valid_bindings.indexOf(node.name)) {
|
|
||||||
const match =
|
|
||||||
node.name === 'width'
|
|
||||||
? 'innerWidth'
|
|
||||||
: node.name === 'height'
|
|
||||||
? 'innerHeight'
|
|
||||||
: fuzzymatch(node.name, valid_bindings);
|
|
||||||
if (match) {
|
|
||||||
return component.error(
|
|
||||||
node,
|
|
||||||
compiler_errors.invalid_binding_on(
|
|
||||||
node.name,
|
|
||||||
'<svelte:window>',
|
|
||||||
` (did you mean '${match}'?)`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return component.error(
|
|
||||||
node,
|
|
||||||
compiler_errors.invalid_binding_on(
|
|
||||||
node.name,
|
|
||||||
'<svelte:window>',
|
|
||||||
` — valid bindings are ${list(valid_bindings)}`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.bindings.push(new Binding(component, this, scope, node));
|
|
||||||
} else if (node.type === 'Action') {
|
|
||||||
this.actions.push(new Action(component, this, scope, node));
|
|
||||||
} else {
|
|
||||||
// TODO there shouldn't be anything else here...
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,83 +0,0 @@
|
|||||||
import Tag from './shared/Tag';
|
|
||||||
import Action from './Action';
|
|
||||||
import Animation from './Animation';
|
|
||||||
import Attribute from './Attribute';
|
|
||||||
import AwaitBlock from './AwaitBlock';
|
|
||||||
import Binding from './Binding';
|
|
||||||
import Body from './Body';
|
|
||||||
import CatchBlock from './CatchBlock';
|
|
||||||
import Class from './Class';
|
|
||||||
import StyleDirective from './StyleDirective';
|
|
||||||
import Comment from './Comment';
|
|
||||||
import ConstTag from './ConstTag';
|
|
||||||
import DebugTag from './DebugTag';
|
|
||||||
import Document from './Document';
|
|
||||||
import EachBlock from './EachBlock';
|
|
||||||
import Element from './Element';
|
|
||||||
import ElseBlock from './ElseBlock';
|
|
||||||
import EventHandler from './EventHandler';
|
|
||||||
import Fragment from './Fragment';
|
|
||||||
import Head from './Head';
|
|
||||||
import IfBlock from './IfBlock';
|
|
||||||
import InlineComponent from './InlineComponent';
|
|
||||||
import KeyBlock from './KeyBlock';
|
|
||||||
import Let from './Let';
|
|
||||||
import MustacheTag from './MustacheTag';
|
|
||||||
import Options from './Options';
|
|
||||||
import PendingBlock from './PendingBlock';
|
|
||||||
import RawMustacheTag from './RawMustacheTag';
|
|
||||||
import Slot from './Slot';
|
|
||||||
import SlotTemplate from './SlotTemplate';
|
|
||||||
import Text from './Text';
|
|
||||||
import ThenBlock from './ThenBlock';
|
|
||||||
import Title from './Title';
|
|
||||||
import Transition from './Transition';
|
|
||||||
import Window from './Window';
|
|
||||||
|
|
||||||
// note: to write less types each of types in union below should have type defined as literal
|
|
||||||
// https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#discriminating-unions
|
|
||||||
export type INode =
|
|
||||||
| Action
|
|
||||||
| Animation
|
|
||||||
| Attribute
|
|
||||||
| AwaitBlock
|
|
||||||
| Binding
|
|
||||||
| Body
|
|
||||||
| CatchBlock
|
|
||||||
| Class
|
|
||||||
| Comment
|
|
||||||
| ConstTag
|
|
||||||
| DebugTag
|
|
||||||
| Document
|
|
||||||
| EachBlock
|
|
||||||
| Element
|
|
||||||
| ElseBlock
|
|
||||||
| EventHandler
|
|
||||||
| Fragment
|
|
||||||
| Head
|
|
||||||
| IfBlock
|
|
||||||
| InlineComponent
|
|
||||||
| KeyBlock
|
|
||||||
| Let
|
|
||||||
| MustacheTag
|
|
||||||
| Options
|
|
||||||
| PendingBlock
|
|
||||||
| RawMustacheTag
|
|
||||||
| Slot
|
|
||||||
| SlotTemplate
|
|
||||||
| StyleDirective
|
|
||||||
| Tag
|
|
||||||
| Text
|
|
||||||
| ThenBlock
|
|
||||||
| Title
|
|
||||||
| Transition
|
|
||||||
| Window;
|
|
||||||
|
|
||||||
export type INodeAllowConstTag =
|
|
||||||
| IfBlock
|
|
||||||
| ElseBlock
|
|
||||||
| EachBlock
|
|
||||||
| CatchBlock
|
|
||||||
| ThenBlock
|
|
||||||
| InlineComponent
|
|
||||||
| SlotTemplate;
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
import Node from './Node.js';
|
|
||||||
import compiler_warnings from '../../compiler_warnings.js';
|
|
||||||
|
|
||||||
const regex_non_whitespace_characters = /[^ \r\n\f\v\t]/;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template {string} Type
|
|
||||||
* @extends Node<Type>
|
|
||||||
*/
|
|
||||||
export default class AbstractBlock extends Node {
|
|
||||||
/** @type {import('../../render_dom/Block.js').default} */
|
|
||||||
block;
|
|
||||||
|
|
||||||
/** @type {import('../interfaces.js').INode[]} */
|
|
||||||
children;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../../Component.js').default} component
|
|
||||||
* @param {any} parent
|
|
||||||
* @param {any} scope
|
|
||||||
* @param {any} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
}
|
|
||||||
warn_if_empty_block() {
|
|
||||||
if (!this.children || this.children.length > 1) return;
|
|
||||||
const child = this.children[0];
|
|
||||||
if (!child || (child.type === 'Text' && !regex_non_whitespace_characters.test(child.data))) {
|
|
||||||
this.component.warn(this, compiler_warnings.empty_block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,249 +0,0 @@
|
|||||||
import { x } from 'code-red';
|
|
||||||
import { walk } from 'estree-walker';
|
|
||||||
import is_reference from 'is-reference';
|
|
||||||
import { clone } from '../../../utils/clone.js';
|
|
||||||
import flatten_reference from '../../utils/flatten_reference.js';
|
|
||||||
/**
|
|
||||||
* @param {{
|
|
||||||
* contexts: Context[];
|
|
||||||
* node: import('estree').Pattern;
|
|
||||||
* modifier?: DestructuredVariable['modifier'];
|
|
||||||
* default_modifier?: DestructuredVariable['default_modifier'];
|
|
||||||
* scope: import('./TemplateScope.js').default;
|
|
||||||
* component: import('../../Component.js').default;
|
|
||||||
* context_rest_properties: Map<string, import('estree').Node>;
|
|
||||||
* in_rest_element?: boolean;
|
|
||||||
* }} params
|
|
||||||
*/
|
|
||||||
export function unpack_destructuring({
|
|
||||||
contexts,
|
|
||||||
node,
|
|
||||||
modifier = (node) => node,
|
|
||||||
default_modifier = (node) => node,
|
|
||||||
scope,
|
|
||||||
component,
|
|
||||||
context_rest_properties,
|
|
||||||
in_rest_element = false
|
|
||||||
}) {
|
|
||||||
if (!node) return;
|
|
||||||
if (node.type === 'Identifier') {
|
|
||||||
contexts.push({
|
|
||||||
type: 'DestructuredVariable',
|
|
||||||
key: /** @type {import('estree').Identifier} */ (node),
|
|
||||||
modifier,
|
|
||||||
default_modifier
|
|
||||||
});
|
|
||||||
if (in_rest_element) {
|
|
||||||
context_rest_properties.set(node.name, node);
|
|
||||||
}
|
|
||||||
component.used_names.add(node.name);
|
|
||||||
} else if (node.type === 'ArrayPattern') {
|
|
||||||
node.elements.forEach((element, i) => {
|
|
||||||
if (!element) {
|
|
||||||
return;
|
|
||||||
} else if (element.type === 'RestElement') {
|
|
||||||
unpack_destructuring({
|
|
||||||
contexts,
|
|
||||||
node: element.argument,
|
|
||||||
modifier: (node) =>
|
|
||||||
/** @type {import('estree').Node} */ (x`${modifier(node)}.slice(${i})`),
|
|
||||||
default_modifier,
|
|
||||||
scope,
|
|
||||||
component,
|
|
||||||
context_rest_properties,
|
|
||||||
in_rest_element: true
|
|
||||||
});
|
|
||||||
} else if (element.type === 'AssignmentPattern') {
|
|
||||||
const n = contexts.length;
|
|
||||||
mark_referenced(element.right, scope, component);
|
|
||||||
unpack_destructuring({
|
|
||||||
contexts,
|
|
||||||
node: element.left,
|
|
||||||
modifier: (node) => x`${modifier(node)}[${i}]`,
|
|
||||||
default_modifier: (node, to_ctx) =>
|
|
||||||
/** @type {import('estree').Node} */ (
|
|
||||||
x`${node} !== undefined ? ${node} : ${update_reference(
|
|
||||||
contexts,
|
|
||||||
n,
|
|
||||||
element.right,
|
|
||||||
to_ctx
|
|
||||||
)}`
|
|
||||||
),
|
|
||||||
scope,
|
|
||||||
component,
|
|
||||||
context_rest_properties,
|
|
||||||
in_rest_element
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
unpack_destructuring({
|
|
||||||
contexts,
|
|
||||||
node: element,
|
|
||||||
modifier: (node) => /** @type {import('estree').Node} */ (x`${modifier(node)}[${i}]`),
|
|
||||||
default_modifier,
|
|
||||||
scope,
|
|
||||||
component,
|
|
||||||
context_rest_properties,
|
|
||||||
in_rest_element
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (node.type === 'ObjectPattern') {
|
|
||||||
const used_properties = [];
|
|
||||||
node.properties.forEach((property) => {
|
|
||||||
if (property.type === 'RestElement') {
|
|
||||||
unpack_destructuring({
|
|
||||||
contexts,
|
|
||||||
node: property.argument,
|
|
||||||
modifier: (node) =>
|
|
||||||
/** @type {import('estree').Node} */ (
|
|
||||||
x`@object_without_properties(${modifier(node)}, [${used_properties}])`
|
|
||||||
),
|
|
||||||
default_modifier,
|
|
||||||
scope,
|
|
||||||
component,
|
|
||||||
context_rest_properties,
|
|
||||||
in_rest_element: true
|
|
||||||
});
|
|
||||||
} else if (property.type === 'Property') {
|
|
||||||
const key = property.key;
|
|
||||||
const value = property.value;
|
|
||||||
|
|
||||||
/** @type {(node: import('estree').Node) => import('estree').Node} */
|
|
||||||
let new_modifier;
|
|
||||||
if (property.computed) {
|
|
||||||
// e.g { [computedProperty]: ... }
|
|
||||||
const property_name = component.get_unique_name('computed_property');
|
|
||||||
contexts.push({
|
|
||||||
type: 'ComputedProperty',
|
|
||||||
property_name,
|
|
||||||
key
|
|
||||||
});
|
|
||||||
new_modifier = (node) => x`${modifier(node)}[${property_name}]`;
|
|
||||||
used_properties.push(x`${property_name}`);
|
|
||||||
} else if (key.type === 'Identifier') {
|
|
||||||
// e.g. { someProperty: ... }
|
|
||||||
const property_name = key.name;
|
|
||||||
new_modifier = (node) => x`${modifier(node)}.${property_name}`;
|
|
||||||
used_properties.push(x`"${property_name}"`);
|
|
||||||
} else if (key.type === 'Literal') {
|
|
||||||
// e.g. { "property-in-quotes": ... } or { 14: ... }
|
|
||||||
const property_name = key.value;
|
|
||||||
new_modifier = (node) => x`${modifier(node)}["${property_name}"]`;
|
|
||||||
used_properties.push(x`"${property_name}"`);
|
|
||||||
}
|
|
||||||
if (value.type === 'AssignmentPattern') {
|
|
||||||
// e.g. { property = default } or { property: newName = default }
|
|
||||||
const n = contexts.length;
|
|
||||||
mark_referenced(value.right, scope, component);
|
|
||||||
unpack_destructuring({
|
|
||||||
contexts,
|
|
||||||
node: value.left,
|
|
||||||
modifier: new_modifier,
|
|
||||||
default_modifier: (node, to_ctx) =>
|
|
||||||
/** @type {import('estree').Node} */ (
|
|
||||||
x`${node} !== undefined ? ${node} : ${update_reference(
|
|
||||||
contexts,
|
|
||||||
n,
|
|
||||||
value.right,
|
|
||||||
to_ctx
|
|
||||||
)}`
|
|
||||||
),
|
|
||||||
scope,
|
|
||||||
component,
|
|
||||||
context_rest_properties,
|
|
||||||
in_rest_element
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// e.g. { property } or { property: newName }
|
|
||||||
unpack_destructuring({
|
|
||||||
contexts,
|
|
||||||
node: value,
|
|
||||||
modifier: new_modifier,
|
|
||||||
default_modifier,
|
|
||||||
scope,
|
|
||||||
component,
|
|
||||||
context_rest_properties,
|
|
||||||
in_rest_element
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Context[]} contexts
|
|
||||||
* @param {number} n
|
|
||||||
* @param {import('estree').Expression} expression
|
|
||||||
* @param {(name: string) => import('estree').Node} to_ctx
|
|
||||||
* @returns {import('estree').Node}
|
|
||||||
*/
|
|
||||||
function update_reference(contexts, n, expression, to_ctx) {
|
|
||||||
/** @param {import('estree').Identifier} node */
|
|
||||||
const find_from_context = (node) => {
|
|
||||||
for (let i = n; i < contexts.length; i++) {
|
|
||||||
const cur_context = contexts[i];
|
|
||||||
if (cur_context.type !== 'DestructuredVariable') continue;
|
|
||||||
const { key } = cur_context;
|
|
||||||
if (node.name === key.name) {
|
|
||||||
throw new Error(`Cannot access '${node.name}' before initialization`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return to_ctx(node.name);
|
|
||||||
};
|
|
||||||
if (expression.type === 'Identifier') {
|
|
||||||
return find_from_context(expression);
|
|
||||||
}
|
|
||||||
// NOTE: avoid unnecessary deep clone?
|
|
||||||
expression = /** @type {import('estree').Expression} */ (clone(expression));
|
|
||||||
walk(expression, {
|
|
||||||
enter(node, parent) {
|
|
||||||
if (
|
|
||||||
is_reference(
|
|
||||||
/** @type {import('is-reference').NodeWithPropertyDefinition} */ (node),
|
|
||||||
/** @type {import('is-reference').NodeWithPropertyDefinition} */ (parent)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
this.replace(find_from_context(/** @type {import('estree').Identifier} */ (node)));
|
|
||||||
this.skip();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('estree').Node} node
|
|
||||||
* @param {import('./TemplateScope.js').default} scope
|
|
||||||
* @param {import('../../Component.js').default} component
|
|
||||||
*/
|
|
||||||
function mark_referenced(node, scope, component) {
|
|
||||||
walk(node, {
|
|
||||||
enter(node, parent) {
|
|
||||||
if (is_reference(node, parent)) {
|
|
||||||
const { name } = flatten_reference(node);
|
|
||||||
if (!scope.is_let(name) && !scope.names.has(name)) {
|
|
||||||
component.add_reference(node, name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @typedef {DestructuredVariable | ComputedProperty} Context */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} ComputedProperty
|
|
||||||
* @property {'ComputedProperty'} type
|
|
||||||
* @property {import('estree').Identifier} property_name
|
|
||||||
* @property {import('estree').Expression|import('estree').PrivateIdentifier} key
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} DestructuredVariable
|
|
||||||
* @property {'DestructuredVariable'} type
|
|
||||||
* @property {import('estree').Identifier} key
|
|
||||||
* @property {string} [name]
|
|
||||||
* @property {(node:import('estree').Node)=>import('estree').Node} modifier
|
|
||||||
* @property {(node:import('estree').Node,to_ctx:(name:string)=>import('estree').Node)=>import('estree').Node} default_modifier
|
|
||||||
*/
|
|
||||||
@ -1,474 +0,0 @@
|
|||||||
import { walk } from 'estree-walker';
|
|
||||||
import is_reference from 'is-reference';
|
|
||||||
import flatten_reference from '../../utils/flatten_reference.js';
|
|
||||||
import { create_scopes, extract_names } from '../../utils/scope.js';
|
|
||||||
import { sanitize } from '../../../utils/names.js';
|
|
||||||
import get_object from '../../utils/get_object.js';
|
|
||||||
import is_dynamic from '../../render_dom/wrappers/shared/is_dynamic.js';
|
|
||||||
import { b } from 'code-red';
|
|
||||||
import { invalidate } from '../../render_dom/invalidate.js';
|
|
||||||
import { is_reserved_keyword } from '../../utils/reserved_keywords.js';
|
|
||||||
import replace_object from '../../utils/replace_object.js';
|
|
||||||
import is_contextual from './is_contextual.js';
|
|
||||||
import { clone } from '../../../utils/clone.js';
|
|
||||||
import compiler_errors from '../../compiler_errors.js';
|
|
||||||
|
|
||||||
const regex_contains_term_function_expression = /FunctionExpression/;
|
|
||||||
|
|
||||||
export default class Expression {
|
|
||||||
/** @type {'Expression'} */
|
|
||||||
type = 'Expression';
|
|
||||||
|
|
||||||
/** @type {import('../../Component.js').default} */
|
|
||||||
component;
|
|
||||||
|
|
||||||
/** @type {import('../interfaces.js').INode} */
|
|
||||||
owner;
|
|
||||||
|
|
||||||
/** @type {import('estree').Node} */
|
|
||||||
node;
|
|
||||||
|
|
||||||
/** @type {Set<string>} */
|
|
||||||
references = new Set();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dependencies declared in the script block
|
|
||||||
* @type {Set<string>}
|
|
||||||
*/
|
|
||||||
dependencies = new Set();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dependencies declared in the HTML-like template section
|
|
||||||
* @type {Set<string>}
|
|
||||||
*/
|
|
||||||
contextual_dependencies = new Set();
|
|
||||||
|
|
||||||
/** @type {import('./TemplateScope.js').default} */
|
|
||||||
template_scope;
|
|
||||||
|
|
||||||
/** @type {import('../../utils/scope.js').Scope} */
|
|
||||||
scope;
|
|
||||||
|
|
||||||
/** @type {WeakMap<import('estree').Node, import('../../utils/scope.js').Scope>} */
|
|
||||||
scope_map;
|
|
||||||
|
|
||||||
/** @type {Array<import('estree').Node | import('estree').Node[]>} */
|
|
||||||
declarations = [];
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
uses_context = false;
|
|
||||||
|
|
||||||
/** @type {import('estree').Node} */
|
|
||||||
manipulated;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../../Component.js').default} component *
|
|
||||||
* @param {import('../interfaces.js').INode} owner *
|
|
||||||
* @param {import('./TemplateScope.js').default} template_scope *
|
|
||||||
* @param {import('estree').Node} info *
|
|
||||||
* @param {boolean} [lazy] undefined
|
|
||||||
*/
|
|
||||||
constructor(component, owner, template_scope, info, lazy) {
|
|
||||||
// TODO revert to direct property access in prod?
|
|
||||||
Object.defineProperties(this, {
|
|
||||||
component: {
|
|
||||||
value: component
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.node = info;
|
|
||||||
this.template_scope = template_scope;
|
|
||||||
this.owner = owner;
|
|
||||||
const { dependencies, contextual_dependencies, references } = this;
|
|
||||||
let { map, scope } = create_scopes(info);
|
|
||||||
this.scope = scope;
|
|
||||||
this.scope_map = map;
|
|
||||||
const expression = this;
|
|
||||||
let function_expression;
|
|
||||||
|
|
||||||
// discover dependencies, but don't change the code yet
|
|
||||||
walk(info, {
|
|
||||||
/**
|
|
||||||
* @param {any} node
|
|
||||||
* @param {import('estree').Node} parent
|
|
||||||
* @param {string} key
|
|
||||||
*/
|
|
||||||
enter(node, parent, key) {
|
|
||||||
// don't manipulate shorthand props twice
|
|
||||||
if (key === 'key' && /** @type {import('estree').Property} */ (parent).shorthand) return;
|
|
||||||
// don't manipulate `import.meta`, `new.target`
|
|
||||||
if (node.type === 'MetaProperty') return this.skip();
|
|
||||||
if (map.has(node)) {
|
|
||||||
scope = map.get(node);
|
|
||||||
}
|
|
||||||
if (!function_expression && regex_contains_term_function_expression.test(node.type)) {
|
|
||||||
function_expression = node;
|
|
||||||
}
|
|
||||||
if (is_reference(node, parent)) {
|
|
||||||
const { name, nodes } = flatten_reference(node);
|
|
||||||
references.add(name);
|
|
||||||
if (scope.has(name)) return;
|
|
||||||
if (name[0] === '$') {
|
|
||||||
const store_name = name.slice(1);
|
|
||||||
if (template_scope.names.has(store_name) || scope.has(store_name)) {
|
|
||||||
return component.error(node, compiler_errors.contextual_store);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (template_scope.is_let(name)) {
|
|
||||||
if (!lazy) {
|
|
||||||
contextual_dependencies.add(name);
|
|
||||||
dependencies.add(name);
|
|
||||||
}
|
|
||||||
} else if (template_scope.names.has(name)) {
|
|
||||||
expression.uses_context = true;
|
|
||||||
contextual_dependencies.add(name);
|
|
||||||
const owner = template_scope.get_owner(name);
|
|
||||||
const is_index = owner.type === 'EachBlock' && owner.key && name === owner.index;
|
|
||||||
if (!lazy || is_index) {
|
|
||||||
template_scope.dependencies_for_name
|
|
||||||
.get(name)
|
|
||||||
.forEach((name) => dependencies.add(name));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!lazy) {
|
|
||||||
const variable = component.var_lookup.get(name);
|
|
||||||
if (!variable || !variable.imported || variable.mutated || variable.reassigned) {
|
|
||||||
dependencies.add(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
component.add_reference(node, name);
|
|
||||||
component.warn_if_undefined(name, nodes[0], template_scope, owner);
|
|
||||||
}
|
|
||||||
this.skip();
|
|
||||||
}
|
|
||||||
// track any assignments from template expressions as mutable
|
|
||||||
let names;
|
|
||||||
let deep = false;
|
|
||||||
if (function_expression) {
|
|
||||||
if (node.type === 'AssignmentExpression') {
|
|
||||||
deep = node.left.type === 'MemberExpression';
|
|
||||||
names = extract_names(deep ? get_object(node.left) : node.left);
|
|
||||||
} else if (node.type === 'UpdateExpression') {
|
|
||||||
deep = node.argument.type === 'MemberExpression';
|
|
||||||
names = extract_names(get_object(node.argument));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (names) {
|
|
||||||
names.forEach((name) => {
|
|
||||||
if (template_scope.names.has(name)) {
|
|
||||||
if (template_scope.is_const(name)) {
|
|
||||||
component.error(node, compiler_errors.invalid_const_update(name));
|
|
||||||
}
|
|
||||||
template_scope.dependencies_for_name.get(name).forEach((name) => {
|
|
||||||
const variable = component.var_lookup.get(name);
|
|
||||||
if (variable) variable[deep ? 'mutated' : 'reassigned'] = true;
|
|
||||||
});
|
|
||||||
const each_block = template_scope.get_owner(name);
|
|
||||||
/** @type {import('../EachBlock.js').default} */ (each_block).has_binding = true;
|
|
||||||
} else {
|
|
||||||
component.add_reference(node, name);
|
|
||||||
const variable = component.var_lookup.get(name);
|
|
||||||
if (variable) {
|
|
||||||
variable[deep ? 'mutated' : 'reassigned'] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const declaration = scope.find_owner(name)?.declarations.get(name);
|
|
||||||
if (declaration) {
|
|
||||||
if (
|
|
||||||
/** @type {import('estree').VariableDeclaration} */ (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'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/** @type {import('estree-walker').SyncHandler} */
|
|
||||||
leave(node) {
|
|
||||||
if (map.has(node)) {
|
|
||||||
scope = scope.parent;
|
|
||||||
}
|
|
||||||
if (node === function_expression) {
|
|
||||||
function_expression = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
dynamic_dependencies() {
|
|
||||||
return Array.from(this.dependencies).filter((name) => {
|
|
||||||
if (this.template_scope.is_let(name)) return true;
|
|
||||||
if (is_reserved_keyword(name)) return true;
|
|
||||||
const variable = this.component.var_lookup.get(name);
|
|
||||||
return is_dynamic(variable);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
dynamic_contextual_dependencies() {
|
|
||||||
return Array.from(this.contextual_dependencies).filter((name) => {
|
|
||||||
return Array.from(this.template_scope.dependencies_for_name.get(name)).some(
|
|
||||||
(variable_name) => {
|
|
||||||
const variable = this.component.var_lookup.get(variable_name);
|
|
||||||
return is_dynamic(variable);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// TODO move this into a render-dom wrapper?
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../../render_dom/Block.js').default} [block]
|
|
||||||
* @param {string | void} [ctx]
|
|
||||||
*/
|
|
||||||
manipulate(block, ctx) {
|
|
||||||
// TODO ideally we wouldn't end up calling this method
|
|
||||||
// multiple times
|
|
||||||
if (this.manipulated) return this.manipulated;
|
|
||||||
const { component, declarations, scope_map: map, template_scope, owner } = this;
|
|
||||||
let scope = this.scope;
|
|
||||||
|
|
||||||
/** @type {import('estree').FunctionExpression | import('estree').ArrowFunctionExpression | null} */
|
|
||||||
let function_expression;
|
|
||||||
|
|
||||||
/** @type {Set<string>} */
|
|
||||||
let dependencies;
|
|
||||||
|
|
||||||
/** @type {Set<string>} */
|
|
||||||
let contextual_dependencies;
|
|
||||||
const node = walk(this.node, {
|
|
||||||
/** @type {import('estree-walker').SyncHandler} */
|
|
||||||
enter(node, parent) {
|
|
||||||
if (node.type === 'Property' && node.shorthand) {
|
|
||||||
node.value = clone(node.value);
|
|
||||||
node.shorthand = false;
|
|
||||||
}
|
|
||||||
if (map.has(node)) {
|
|
||||||
scope = map.get(node);
|
|
||||||
}
|
|
||||||
if (node.type === 'Identifier' && is_reference(node, parent)) {
|
|
||||||
const { name } = flatten_reference(node);
|
|
||||||
if (scope.has(name)) return;
|
|
||||||
if (function_expression) {
|
|
||||||
if (template_scope.names.has(name)) {
|
|
||||||
contextual_dependencies.add(name);
|
|
||||||
template_scope.dependencies_for_name.get(name).forEach((dependency) => {
|
|
||||||
dependencies.add(dependency);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
dependencies.add(name);
|
|
||||||
component.add_reference(node, name); // TODO is this redundant/misplaced?
|
|
||||||
}
|
|
||||||
} else if (is_contextual(component, template_scope, name)) {
|
|
||||||
const reference = block.renderer.reference(node, ctx);
|
|
||||||
this.replace(reference);
|
|
||||||
}
|
|
||||||
this.skip();
|
|
||||||
}
|
|
||||||
if (!function_expression) {
|
|
||||||
if (node.type === 'AssignmentExpression') {
|
|
||||||
// TODO should this be a warning/error? `<p>{foo = 1}</p>`
|
|
||||||
}
|
|
||||||
if (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {
|
|
||||||
function_expression = node;
|
|
||||||
dependencies = new Set();
|
|
||||||
contextual_dependencies = new Set();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/** @type {import('estree-walker').SyncHandler} */
|
|
||||||
leave(node, parent) {
|
|
||||||
if (map.has(node)) scope = scope.parent;
|
|
||||||
if (node === function_expression) {
|
|
||||||
const id = component.get_unique_name(sanitize(get_function_name(node, owner)));
|
|
||||||
const declaration = b`const ${id} = ${node}`;
|
|
||||||
const extract_functions = () => {
|
|
||||||
const deps = Array.from(contextual_dependencies);
|
|
||||||
const function_expression = /** @type {import('estree').FunctionExpression} */ (node);
|
|
||||||
const has_args = function_expression.params.length > 0;
|
|
||||||
function_expression.params = [
|
|
||||||
...deps.map(
|
|
||||||
(name) => /** @type {import('estree').Identifier} */ ({ type: 'Identifier', name })
|
|
||||||
),
|
|
||||||
...function_expression.params
|
|
||||||
];
|
|
||||||
const context_args = deps.map((name) => block.renderer.reference(name, ctx));
|
|
||||||
component.partly_hoisted.push(declaration);
|
|
||||||
block.renderer.add_to_context(id.name);
|
|
||||||
const callee = block.renderer.reference(id);
|
|
||||||
this.replace(id);
|
|
||||||
const func_declaration = has_args
|
|
||||||
? b`function ${id}(...args) {
|
|
||||||
return ${callee}(${context_args}, ...args);
|
|
||||||
}`
|
|
||||||
: b`function ${id}() {
|
|
||||||
return ${callee}(${context_args});
|
|
||||||
}`;
|
|
||||||
return { deps, func_declaration };
|
|
||||||
};
|
|
||||||
if (owner.type === 'ConstTag') {
|
|
||||||
// we need a combo block/init recipe
|
|
||||||
if (contextual_dependencies.size === 0) {
|
|
||||||
let child_scope = scope;
|
|
||||||
walk(node, {
|
|
||||||
/** @type {import('estree-walker').SyncHandler} */
|
|
||||||
enter(node, parent) {
|
|
||||||
if (map.has(node)) child_scope = map.get(node);
|
|
||||||
if (node.type === 'Identifier' && is_reference(node, parent)) {
|
|
||||||
if (child_scope.has(node.name)) return;
|
|
||||||
this.replace(block.renderer.reference(node, ctx));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/** @param {import('estree').Node} node */
|
|
||||||
leave(node) {
|
|
||||||
if (map.has(node)) child_scope = child_scope.parent;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const { func_declaration } = extract_functions();
|
|
||||||
this.replace(func_declaration[0]);
|
|
||||||
}
|
|
||||||
} else if (dependencies.size === 0 && contextual_dependencies.size === 0) {
|
|
||||||
// we can hoist this out of the component completely
|
|
||||||
component.fully_hoisted.push(declaration);
|
|
||||||
this.replace(id);
|
|
||||||
component.add_var(node, {
|
|
||||||
name: id.name,
|
|
||||||
internal: true,
|
|
||||||
hoistable: true,
|
|
||||||
referenced: true
|
|
||||||
});
|
|
||||||
} else if (contextual_dependencies.size === 0) {
|
|
||||||
// function can be hoisted inside the component init
|
|
||||||
component.partly_hoisted.push(declaration);
|
|
||||||
block.renderer.add_to_context(id.name);
|
|
||||||
this.replace(block.renderer.reference(id));
|
|
||||||
} else {
|
|
||||||
// we need a combo block/init recipe
|
|
||||||
const { deps, func_declaration } = extract_functions();
|
|
||||||
if (owner.type === 'Attribute' && owner.parent.name === 'slot') {
|
|
||||||
/** @type {Set<import('../interfaces.js').INode>} */
|
|
||||||
const dep_scopes = new Set(deps.map((name) => template_scope.get_owner(name)));
|
|
||||||
// find the nearest scopes
|
|
||||||
|
|
||||||
/** @type {import('../interfaces.js').INode} */
|
|
||||||
let node = owner.parent;
|
|
||||||
while (node && !dep_scopes.has(node)) {
|
|
||||||
node = node.parent;
|
|
||||||
}
|
|
||||||
const func_expression = func_declaration[0];
|
|
||||||
|
|
||||||
if (node.type === 'SlotTemplate') {
|
|
||||||
// <svelte:fragment let:data />
|
|
||||||
this.replace(func_expression);
|
|
||||||
} else {
|
|
||||||
// {#each}, {#await}
|
|
||||||
const func_id = component.get_unique_name(id.name + '_func');
|
|
||||||
block.renderer.add_to_context(func_id.name, true);
|
|
||||||
// rename #ctx -> child_ctx;
|
|
||||||
walk(func_expression, {
|
|
||||||
/** @param {import('estree').Node} node */
|
|
||||||
enter(node) {
|
|
||||||
if (node.type === 'Identifier' && node.name === '#ctx') {
|
|
||||||
node.name = 'child_ctx';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// add to get_xxx_context
|
|
||||||
// child_ctx[x] = function () { ... }
|
|
||||||
/** @type {import('../EachBlock.js').default} */ (
|
|
||||||
template_scope.get_owner(deps[0])
|
|
||||||
).contexts.push({
|
|
||||||
type: 'DestructuredVariable',
|
|
||||||
key: func_id,
|
|
||||||
modifier: () => func_expression,
|
|
||||||
default_modifier: (node) => node
|
|
||||||
});
|
|
||||||
this.replace(block.renderer.reference(func_id));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
declarations.push(func_declaration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function_expression = null;
|
|
||||||
dependencies = null;
|
|
||||||
contextual_dependencies = null;
|
|
||||||
if (parent && parent.type === 'Property') {
|
|
||||||
parent.method = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') {
|
|
||||||
const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument;
|
|
||||||
const object_name = get_object(assignee).name;
|
|
||||||
if (scope.has(object_name)) return;
|
|
||||||
// normally (`a = 1`, `b.c = 2`), there'll be a single name
|
|
||||||
// (a or b). In destructuring cases (`[d, e] = [e, d]`) there
|
|
||||||
// may be more, in which case we need to tack the extra ones
|
|
||||||
// onto the initial function call
|
|
||||||
const names = new Set(extract_names(/** @type {import('estree').Node} */ (assignee)));
|
|
||||||
|
|
||||||
/** @type {Set<string>} */
|
|
||||||
const traced = new Set();
|
|
||||||
names.forEach((name) => {
|
|
||||||
const dependencies = template_scope.dependencies_for_name.get(name);
|
|
||||||
if (dependencies) {
|
|
||||||
dependencies.forEach((name) => traced.add(name));
|
|
||||||
} else {
|
|
||||||
traced.add(name);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const context = block.bindings.get(object_name);
|
|
||||||
if (context) {
|
|
||||||
// for `{#each array as item}`
|
|
||||||
// replace `item = 1` to `each_array[each_index] = 1`, this allow us to mutate the array
|
|
||||||
// rather than mutating the local `item` variable
|
|
||||||
const { snippet, object, property } = context;
|
|
||||||
|
|
||||||
/** @type {any} */
|
|
||||||
const replaced = replace_object(assignee, snippet);
|
|
||||||
if (node.type === 'AssignmentExpression') {
|
|
||||||
node.left = replaced;
|
|
||||||
} else {
|
|
||||||
node.argument = replaced;
|
|
||||||
}
|
|
||||||
contextual_dependencies.add(object.name);
|
|
||||||
contextual_dependencies.add(property.name);
|
|
||||||
}
|
|
||||||
this.replace(invalidate(block.renderer, scope, node, traced));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (declarations.length > 0) {
|
|
||||||
block.maintain_context = true;
|
|
||||||
declarations.forEach((declaration) => {
|
|
||||||
block.chunks.init.push(declaration);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return (this.manipulated = /** @type {import('estree').Node} */ (node));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('estree').Node} _node
|
|
||||||
* @param {import('../interfaces.js').INode} parent
|
|
||||||
*/
|
|
||||||
function get_function_name(_node, parent) {
|
|
||||||
if (parent.type === 'EventHandler') {
|
|
||||||
return `${parent.name}_handler`;
|
|
||||||
}
|
|
||||||
if (parent.type === 'Action') {
|
|
||||||
return `${parent.name}_function`;
|
|
||||||
}
|
|
||||||
return 'func';
|
|
||||||
}
|
|
||||||
@ -1,113 +0,0 @@
|
|||||||
/**
|
|
||||||
* @template {string} Type
|
|
||||||
* @template {import('../interfaces.js').INode} [Parent=import('../interfaces.js').INode]
|
|
||||||
*/
|
|
||||||
export default class Node {
|
|
||||||
/**
|
|
||||||
* @readonly
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
start;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @readonly
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
end;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @readonly
|
|
||||||
* @type {import('../../Component.js').default}
|
|
||||||
*/
|
|
||||||
component;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @readonly
|
|
||||||
* @type {Parent}
|
|
||||||
*/
|
|
||||||
parent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @readonly
|
|
||||||
* @type {Type}
|
|
||||||
*/
|
|
||||||
type;
|
|
||||||
|
|
||||||
/** @type {import('../interfaces.js').INode} */
|
|
||||||
prev;
|
|
||||||
|
|
||||||
/** @type {import('../interfaces.js').INode} */
|
|
||||||
next;
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
can_use_innerhtml;
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
is_static_content;
|
|
||||||
|
|
||||||
/** @type {string} */
|
|
||||||
var;
|
|
||||||
|
|
||||||
/** @type {import('../Attribute.js').default[]} */
|
|
||||||
attributes = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../../Component.js').default} component
|
|
||||||
* @param {Node} parent
|
|
||||||
* @param {any} _scope
|
|
||||||
* @param {import('../../../interfaces.js').TemplateNode} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, _scope, info) {
|
|
||||||
this.start = info.start;
|
|
||||||
this.end = info.end;
|
|
||||||
this.type = /** @type {Type} */ (info.type);
|
|
||||||
// this makes properties non-enumerable, which makes logging
|
|
||||||
// bearable. might have a performance cost. TODO remove in prod?
|
|
||||||
Object.defineProperties(this, {
|
|
||||||
component: {
|
|
||||||
value: component
|
|
||||||
},
|
|
||||||
parent: {
|
|
||||||
value: parent
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.can_use_innerhtml = true;
|
|
||||||
this.is_static_content = true;
|
|
||||||
}
|
|
||||||
cannot_use_innerhtml() {
|
|
||||||
if (this.can_use_innerhtml !== false) {
|
|
||||||
this.can_use_innerhtml = false;
|
|
||||||
if (this.parent) this.parent.cannot_use_innerhtml();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
not_static_content() {
|
|
||||||
this.is_static_content = false;
|
|
||||||
if (this.parent) this.parent.not_static_content();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {RegExp} selector */
|
|
||||||
find_nearest(selector) {
|
|
||||||
if (selector.test(this.type)) return this;
|
|
||||||
if (this.parent) return this.parent.find_nearest(selector);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {string} name */
|
|
||||||
get_static_attribute_value(name) {
|
|
||||||
const attribute = this.attributes.find(
|
|
||||||
/** @param {import('../Attribute.js').default} attr */
|
|
||||||
(attr) => attr.type === 'Attribute' && attr.name.toLowerCase() === name
|
|
||||||
);
|
|
||||||
if (!attribute) return null;
|
|
||||||
if (attribute.is_true) return true;
|
|
||||||
if (attribute.chunks.length === 0) return '';
|
|
||||||
if (attribute.chunks.length === 1 && attribute.chunks[0].type === 'Text') {
|
|
||||||
return /** @type {import('../Text.js').default} */ (attribute.chunks[0]).data;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {string} type */
|
|
||||||
has_ancestor(type) {
|
|
||||||
return this.parent ? this.parent.type === type || this.parent.has_ancestor(type) : false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
import Node from './Node.js';
|
|
||||||
import Expression from './Expression.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template {'MustacheTag' | 'RawMustacheTag'} [Type='MustacheTag' | 'RawMustacheTag']
|
|
||||||
* @extends Node<Type>
|
|
||||||
*/
|
|
||||||
export default class Tag extends Node {
|
|
||||||
/** @type {import('./Expression.js').default} */
|
|
||||||
expression;
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
should_cache;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {any} component
|
|
||||||
* @param {any} parent
|
|
||||||
* @param {any} scope
|
|
||||||
* @param {any} info
|
|
||||||
*/
|
|
||||||
constructor(component, parent, scope, info) {
|
|
||||||
super(component, parent, scope, info);
|
|
||||||
component.tags.push(this);
|
|
||||||
this.cannot_use_innerhtml();
|
|
||||||
this.expression = new Expression(component, this, scope, info.expression);
|
|
||||||
this.should_cache =
|
|
||||||
info.expression.type !== 'Identifier' ||
|
|
||||||
(this.expression.dependencies.size && scope.names.has(info.expression.name));
|
|
||||||
}
|
|
||||||
is_dependencies_static() {
|
|
||||||
return (
|
|
||||||
this.expression.dynamic_contextual_dependencies().length === 0 &&
|
|
||||||
this.expression.dynamic_dependencies().length === 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
check_if_content_dynamic() {
|
|
||||||
if (!this.is_dependencies_static()) {
|
|
||||||
this.not_static_content();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,83 +0,0 @@
|
|||||||
/** The scope of constructs within the Svelte template */
|
|
||||||
export default class TemplateScope {
|
|
||||||
/**
|
|
||||||
* @typedef {import('../EachBlock').default
|
|
||||||
* | import('../ThenBlock').default
|
|
||||||
* | import('../CatchBlock').default
|
|
||||||
* | import('../InlineComponent').default
|
|
||||||
* | import('../Element').default
|
|
||||||
* | import('../SlotTemplate').default
|
|
||||||
* | import('../ConstTag').default} NodeWithScope
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** @type {Set<string>} */
|
|
||||||
names;
|
|
||||||
|
|
||||||
/** @type {Map<string, Set<string>>} */
|
|
||||||
dependencies_for_name;
|
|
||||||
|
|
||||||
/** @type {Map<string, NodeWithScope>} */
|
|
||||||
owners = new Map();
|
|
||||||
|
|
||||||
/** @type {TemplateScope} */
|
|
||||||
parent;
|
|
||||||
|
|
||||||
/** @param {TemplateScope} [parent] undefined */
|
|
||||||
constructor(parent) {
|
|
||||||
this.parent = parent;
|
|
||||||
this.names = new Set(parent ? parent.names : []);
|
|
||||||
this.dependencies_for_name = new Map(parent ? parent.dependencies_for_name : []);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {any} name
|
|
||||||
* @param {Set<string>} dependencies
|
|
||||||
* @param {any} owner
|
|
||||||
*/
|
|
||||||
add(name, dependencies, owner) {
|
|
||||||
this.names.add(name);
|
|
||||||
this.dependencies_for_name.set(name, dependencies);
|
|
||||||
this.owners.set(name, owner);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
child() {
|
|
||||||
const child = new TemplateScope(this);
|
|
||||||
return child;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {string} name */
|
|
||||||
is_top_level(name) {
|
|
||||||
return !this.parent || (!this.names.has(name) && this.parent.is_top_level(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} name
|
|
||||||
* @returns {NodeWithScope}
|
|
||||||
*/
|
|
||||||
get_owner(name) {
|
|
||||||
return this.owners.get(name) || (this.parent && this.parent.get_owner(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {string} name */
|
|
||||||
is_let(name) {
|
|
||||||
const owner = this.get_owner(name);
|
|
||||||
return (
|
|
||||||
owner &&
|
|
||||||
(owner.type === 'Element' ||
|
|
||||||
owner.type === 'InlineComponent' ||
|
|
||||||
owner.type === 'SlotTemplate')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {string} name */
|
|
||||||
is_await(name) {
|
|
||||||
const owner = this.get_owner(name);
|
|
||||||
return owner && (owner.type === 'ThenBlock' || owner.type === 'CatchBlock');
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {string} name */
|
|
||||||
is_const(name) {
|
|
||||||
const owner = this.get_owner(name);
|
|
||||||
return owner && owner.type === 'ConstTag';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
import ConstTag from '../ConstTag.js';
|
|
||||||
import map_children from './map_children.js';
|
|
||||||
import check_graph_for_cycles from '../../utils/check_graph_for_cycles.js';
|
|
||||||
import compiler_errors from '../../compiler_errors.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../../../interfaces.js').TemplateNode[]} children
|
|
||||||
* @param {import('../../Component.js').default} component
|
|
||||||
* @param {import('../interfaces.js').INodeAllowConstTag} node
|
|
||||||
* @param {import('../interfaces.js').INode} parent
|
|
||||||
* @returns {[ConstTag[], Array<Exclude<import('../interfaces.js').INode, ConstTag>>]}
|
|
||||||
*/
|
|
||||||
export default function get_const_tags(children, component, node, parent) {
|
|
||||||
/** @type {import('../../../interfaces.js').ConstTag[]} */
|
|
||||||
const const_tags = [];
|
|
||||||
|
|
||||||
/** @type {Array<Exclude<import('../../../interfaces.js').TemplateNode, import('../../../interfaces.js').ConstTag>>} */
|
|
||||||
const others = [];
|
|
||||||
for (const child of children) {
|
|
||||||
if (child.type === 'ConstTag') {
|
|
||||||
const_tags.push(/** @type {import('../../../interfaces.js').ConstTag} */ (child));
|
|
||||||
} else {
|
|
||||||
others.push(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const consts_nodes = const_tags.map((tag) => new ConstTag(component, node, node.scope, tag));
|
|
||||||
const sorted_consts_nodes = sort_consts_nodes(consts_nodes, component);
|
|
||||||
sorted_consts_nodes.forEach((node) => node.parse_expression());
|
|
||||||
const children_nodes = map_children(component, parent, node.scope, others);
|
|
||||||
return [
|
|
||||||
sorted_consts_nodes,
|
|
||||||
/** @type {Array<Exclude<import('../interfaces.js').INode, ConstTag>>} */ (children_nodes)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {ConstTag[]} consts_nodes
|
|
||||||
* @param {import('../../Component.js').default} component
|
|
||||||
*/
|
|
||||||
function sort_consts_nodes(consts_nodes, component) {
|
|
||||||
/** @typedef {{ assignees: Set<string>; dependencies: Set<string>; node: ConstTag; }} ConstNode */
|
|
||||||
|
|
||||||
/** @type {ConstNode[]} */
|
|
||||||
const sorted_consts_nodes = [];
|
|
||||||
|
|
||||||
/** @type {ConstNode[]} */
|
|
||||||
const unsorted_consts_nodes = consts_nodes.map((node) => {
|
|
||||||
return {
|
|
||||||
assignees: node.assignees,
|
|
||||||
dependencies: node.dependencies,
|
|
||||||
node
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const lookup = new Map();
|
|
||||||
unsorted_consts_nodes.forEach((node) => {
|
|
||||||
node.assignees.forEach((name) => {
|
|
||||||
if (!lookup.has(name)) {
|
|
||||||
lookup.set(name, []);
|
|
||||||
}
|
|
||||||
lookup.get(name).push(node);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const cycle = check_graph_for_cycles(
|
|
||||||
unsorted_consts_nodes.reduce((acc, node) => {
|
|
||||||
node.assignees.forEach((v) => {
|
|
||||||
node.dependencies.forEach((w) => {
|
|
||||||
if (!node.assignees.has(w)) {
|
|
||||||
acc.push([v, w]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return acc;
|
|
||||||
}, [])
|
|
||||||
);
|
|
||||||
if (cycle && cycle.length) {
|
|
||||||
const node_list = lookup.get(cycle[0]);
|
|
||||||
const node = node_list[0];
|
|
||||||
component.error(node.node, compiler_errors.cyclical_const_tags(cycle));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {ConstNode} node */
|
|
||||||
const add_node = (node) => {
|
|
||||||
if (sorted_consts_nodes.includes(node)) return;
|
|
||||||
node.dependencies.forEach((name) => {
|
|
||||||
if (node.assignees.has(name)) return;
|
|
||||||
const earlier_nodes = lookup.get(name);
|
|
||||||
if (earlier_nodes) {
|
|
||||||
earlier_nodes.forEach(add_node);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
sorted_consts_nodes.push(node);
|
|
||||||
};
|
|
||||||
unsorted_consts_nodes.forEach(add_node);
|
|
||||||
return sorted_consts_nodes.map((node) => node.node);
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
import { is_reserved_keyword } from '../../utils/reserved_keywords.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../../Component.js').default} component
|
|
||||||
* @param {import('./TemplateScope.js').default} scope
|
|
||||||
* @param {string} name
|
|
||||||
*/
|
|
||||||
export default function is_contextual(component, scope, name) {
|
|
||||||
if (is_reserved_keyword(name)) return true;
|
|
||||||
// if it's a name below root scope, it's contextual
|
|
||||||
if (!scope.is_top_level(name)) return true;
|
|
||||||
const variable = component.var_lookup.get(name);
|
|
||||||
// hoistables, module declarations, and imports are non-contextual
|
|
||||||
if (!variable || variable.hoistable) return false;
|
|
||||||
// assume contextual
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@ -1,96 +0,0 @@
|
|||||||
import AwaitBlock from '../AwaitBlock.js';
|
|
||||||
import Body from '../Body.js';
|
|
||||||
import ConstTag from '../ConstTag.js';
|
|
||||||
import Comment from '../Comment.js';
|
|
||||||
import EachBlock from '../EachBlock.js';
|
|
||||||
import Document from '../Document.js';
|
|
||||||
import Element from '../Element.js';
|
|
||||||
import Head from '../Head.js';
|
|
||||||
import IfBlock from '../IfBlock.js';
|
|
||||||
import InlineComponent from '../InlineComponent.js';
|
|
||||||
import KeyBlock from '../KeyBlock.js';
|
|
||||||
import MustacheTag from '../MustacheTag.js';
|
|
||||||
import Options from '../Options.js';
|
|
||||||
import RawMustacheTag from '../RawMustacheTag.js';
|
|
||||||
import DebugTag from '../DebugTag.js';
|
|
||||||
import Slot from '../Slot.js';
|
|
||||||
import SlotTemplate from '../SlotTemplate.js';
|
|
||||||
import Text from '../Text.js';
|
|
||||||
import Title from '../Title.js';
|
|
||||||
import Window from '../Window.js';
|
|
||||||
import { push_array } from '../../../utils/push_array.js';
|
|
||||||
|
|
||||||
/** @typedef {ReturnType<typeof map_children>} Children */
|
|
||||||
|
|
||||||
/** @param {any} type */
|
|
||||||
function get_constructor(type) {
|
|
||||||
switch (type) {
|
|
||||||
case 'AwaitBlock':
|
|
||||||
return AwaitBlock;
|
|
||||||
case 'Body':
|
|
||||||
return Body;
|
|
||||||
case 'Comment':
|
|
||||||
return Comment;
|
|
||||||
case 'ConstTag':
|
|
||||||
return ConstTag;
|
|
||||||
case 'Document':
|
|
||||||
return Document;
|
|
||||||
case 'EachBlock':
|
|
||||||
return EachBlock;
|
|
||||||
case 'Element':
|
|
||||||
return Element;
|
|
||||||
case 'Head':
|
|
||||||
return Head;
|
|
||||||
case 'IfBlock':
|
|
||||||
return IfBlock;
|
|
||||||
case 'InlineComponent':
|
|
||||||
return InlineComponent;
|
|
||||||
case 'KeyBlock':
|
|
||||||
return KeyBlock;
|
|
||||||
case 'MustacheTag':
|
|
||||||
return MustacheTag;
|
|
||||||
case 'Options':
|
|
||||||
return Options;
|
|
||||||
case 'RawMustacheTag':
|
|
||||||
return RawMustacheTag;
|
|
||||||
case 'DebugTag':
|
|
||||||
return DebugTag;
|
|
||||||
case 'Slot':
|
|
||||||
return Slot;
|
|
||||||
case 'SlotTemplate':
|
|
||||||
return SlotTemplate;
|
|
||||||
case 'Text':
|
|
||||||
return Text;
|
|
||||||
case 'Title':
|
|
||||||
return Title;
|
|
||||||
case 'Window':
|
|
||||||
return Window;
|
|
||||||
default:
|
|
||||||
throw new Error(`Not implemented: ${type}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {any} component
|
|
||||||
* @param {any} parent
|
|
||||||
* @param {any} scope
|
|
||||||
* @param {import('../../../interfaces.js').TemplateNode[]} children
|
|
||||||
*/
|
|
||||||
export default function map_children(component, parent, scope, children) {
|
|
||||||
let last = null;
|
|
||||||
let ignores = [];
|
|
||||||
return children.map((child) => {
|
|
||||||
const constructor = get_constructor(child.type);
|
|
||||||
const use_ignores = child.type !== 'Text' && child.type !== 'Comment' && ignores.length;
|
|
||||||
if (use_ignores) component.push_ignores(ignores);
|
|
||||||
const node = new constructor(component, parent, scope, child);
|
|
||||||
if (use_ignores) component.pop_ignores(), (ignores = []);
|
|
||||||
if (node.type === 'Comment' && node.ignores.length) {
|
|
||||||
push_array(ignores, node.ignores);
|
|
||||||
}
|
|
||||||
if (last) last.next = node;
|
|
||||||
node.prev = last;
|
|
||||||
last = node;
|
|
||||||
return node;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,532 +0,0 @@
|
|||||||
import { b, x } from 'code-red';
|
|
||||||
import { is_head } from './wrappers/shared/is_head.js';
|
|
||||||
import { regex_double_quotes } from '../../utils/patterns.js';
|
|
||||||
import { flatten } from '../../utils/flatten.js';
|
|
||||||
|
|
||||||
export default class Block {
|
|
||||||
/**
|
|
||||||
* @typedef {Object} Bindings
|
|
||||||
* @property {import('estree').Identifier} object
|
|
||||||
* @property {import('estree').Identifier} property
|
|
||||||
* @property {import('estree').Node} snippet
|
|
||||||
* @property {string} store
|
|
||||||
* @property {(node:import('estree').Node) => import('estree').Node} modifier
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @typedef {Object} BlockOptions
|
|
||||||
* @property {Block} [parent]
|
|
||||||
* @property {import('estree').Identifier} name
|
|
||||||
* @property {string} type
|
|
||||||
* @property {import('./Renderer.js').default} [renderer]
|
|
||||||
* @property {string} [comment]
|
|
||||||
* @property {import('estree').Identifier} [key]
|
|
||||||
* @property {Map<string,Bindings>} [bindings]
|
|
||||||
* @property {Set<string>} [dependencies]
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** @type {Block} */
|
|
||||||
parent;
|
|
||||||
|
|
||||||
/** @type {import('./Renderer.js').default} */
|
|
||||||
renderer;
|
|
||||||
|
|
||||||
/** @type {import('estree').Identifier} */
|
|
||||||
name;
|
|
||||||
|
|
||||||
/** @type {string} */
|
|
||||||
type;
|
|
||||||
|
|
||||||
/** @type {string} */
|
|
||||||
comment;
|
|
||||||
|
|
||||||
/** @type {import('./wrappers/shared/Wrapper.js').default[]} */
|
|
||||||
wrappers;
|
|
||||||
|
|
||||||
/** @type {import('estree').Identifier} */
|
|
||||||
key;
|
|
||||||
|
|
||||||
/** @type {import('estree').Identifier} */
|
|
||||||
first;
|
|
||||||
|
|
||||||
/** @type {Set<string>} */
|
|
||||||
dependencies = new Set();
|
|
||||||
|
|
||||||
/** @type {Map<string, Bindings>} */
|
|
||||||
bindings;
|
|
||||||
|
|
||||||
/** @type {Set<string>} */
|
|
||||||
binding_group_initialised = new Set();
|
|
||||||
|
|
||||||
/** @type {Set<import('./Renderer.js').BindingGroup>} */
|
|
||||||
binding_groups = new Set();
|
|
||||||
/**
|
|
||||||
* @type {{
|
|
||||||
* declarations: Array<import('estree').Node | import('estree').Node[]>;
|
|
||||||
* init: Array<import('estree').Node | import('estree').Node[]>;
|
|
||||||
* create: Array<import('estree').Node | import('estree').Node[]>;
|
|
||||||
* claim: Array<import('estree').Node | import('estree').Node[]>;
|
|
||||||
* hydrate: Array<import('estree').Node | import('estree').Node[]>;
|
|
||||||
* mount: Array<import('estree').Node | import('estree').Node[]>;
|
|
||||||
* measure: Array<import('estree').Node | import('estree').Node[]>;
|
|
||||||
* restore_measurements: Array<import('estree').Node | import('estree').Node[]>;
|
|
||||||
* fix: Array<import('estree').Node | import('estree').Node[]>;
|
|
||||||
* animate: Array<import('estree').Node | import('estree').Node[]>;
|
|
||||||
* intro: Array<import('estree').Node | import('estree').Node[]>;
|
|
||||||
* update: Array<import('estree').Node | import('estree').Node[]>;
|
|
||||||
* outro: Array<import('estree').Node | import('estree').Node[]>;
|
|
||||||
* destroy: Array<import('estree').Node | import('estree').Node[]>;
|
|
||||||
* }}
|
|
||||||
*/
|
|
||||||
chunks;
|
|
||||||
|
|
||||||
/** @type {import('estree').Node[]} */
|
|
||||||
event_listeners = [];
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
maintain_context;
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
has_animation;
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
has_intros;
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
has_outros;
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
has_intro_method; // could have the method without the transition, due to siblings
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
has_outro_method;
|
|
||||||
|
|
||||||
/** @type {number} */
|
|
||||||
outros;
|
|
||||||
|
|
||||||
/** @type {Map<string, import('estree').Identifier>} */
|
|
||||||
aliases;
|
|
||||||
|
|
||||||
/** @type {Map<string, { id: import('estree').Identifier; init?: import('estree').Node }>} */
|
|
||||||
variables = new Map();
|
|
||||||
|
|
||||||
/** @type {(name: string) => import('estree').Identifier} */
|
|
||||||
get_unique_name;
|
|
||||||
/** */
|
|
||||||
has_update_method = false;
|
|
||||||
|
|
||||||
/** @type {{ element_var: string; condition_expression?: any }} */
|
|
||||||
autofocus;
|
|
||||||
|
|
||||||
/** @param {BlockOptions} options */
|
|
||||||
constructor(options) {
|
|
||||||
this.parent = options.parent;
|
|
||||||
this.renderer = options.renderer;
|
|
||||||
this.name = options.name;
|
|
||||||
this.type = options.type;
|
|
||||||
this.comment = options.comment;
|
|
||||||
this.wrappers = [];
|
|
||||||
// for keyed each blocks
|
|
||||||
this.key = options.key;
|
|
||||||
this.first = null;
|
|
||||||
this.bindings = options.bindings;
|
|
||||||
this.chunks = {
|
|
||||||
declarations: [],
|
|
||||||
init: [],
|
|
||||||
create: [],
|
|
||||||
claim: [],
|
|
||||||
hydrate: [],
|
|
||||||
mount: [],
|
|
||||||
measure: [],
|
|
||||||
restore_measurements: [],
|
|
||||||
fix: [],
|
|
||||||
animate: [],
|
|
||||||
intro: [],
|
|
||||||
update: [],
|
|
||||||
outro: [],
|
|
||||||
destroy: []
|
|
||||||
};
|
|
||||||
this.has_animation = false;
|
|
||||||
this.has_intro_method = false; // a block could have an intro method but not intro transitions, e.g. if a sibling block has intros
|
|
||||||
this.has_outro_method = false;
|
|
||||||
this.outros = 0;
|
|
||||||
this.get_unique_name = this.renderer.component.get_unique_name_maker();
|
|
||||||
this.aliases = new Map();
|
|
||||||
if (this.key) this.aliases.set('key', this.get_unique_name('key'));
|
|
||||||
}
|
|
||||||
|
|
||||||
assign_variable_names() {
|
|
||||||
/** @type {Set<string>} */
|
|
||||||
const seen = new Set();
|
|
||||||
|
|
||||||
/** @type {Set<string>} */
|
|
||||||
const dupes = new Set();
|
|
||||||
let i = this.wrappers.length;
|
|
||||||
while (i--) {
|
|
||||||
const wrapper = this.wrappers[i];
|
|
||||||
if (!wrapper.var) continue;
|
|
||||||
if (seen.has(wrapper.var.name)) {
|
|
||||||
dupes.add(wrapper.var.name);
|
|
||||||
}
|
|
||||||
seen.add(wrapper.var.name);
|
|
||||||
}
|
|
||||||
const counts = new Map();
|
|
||||||
i = this.wrappers.length;
|
|
||||||
while (i--) {
|
|
||||||
const wrapper = this.wrappers[i];
|
|
||||||
if (!wrapper.var) continue;
|
|
||||||
let suffix = '';
|
|
||||||
if (dupes.has(wrapper.var.name)) {
|
|
||||||
const i = counts.get(wrapper.var.name) || 0;
|
|
||||||
counts.set(wrapper.var.name, i + 1);
|
|
||||||
suffix = i;
|
|
||||||
}
|
|
||||||
wrapper.var.name = this.get_unique_name(wrapper.var.name + suffix).name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {Set<string>} dependencies */
|
|
||||||
add_dependencies(dependencies) {
|
|
||||||
dependencies.forEach((dependency) => {
|
|
||||||
this.dependencies.add(dependency);
|
|
||||||
});
|
|
||||||
this.has_update_method = true;
|
|
||||||
if (this.parent) {
|
|
||||||
this.parent.add_dependencies(dependencies);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('estree').Identifier} id
|
|
||||||
* @param {import('estree').Node} render_statement
|
|
||||||
* @param {import('estree').Node} claim_statement
|
|
||||||
* @param {import('estree').Node} parent_node
|
|
||||||
* @param {boolean} [no_detach]
|
|
||||||
*/
|
|
||||||
add_element(id, render_statement, claim_statement, parent_node, no_detach) {
|
|
||||||
this.add_variable(id);
|
|
||||||
this.chunks.create.push(b`${id} = ${render_statement};`);
|
|
||||||
if (this.renderer.options.hydratable) {
|
|
||||||
this.chunks.claim.push(b`${id} = ${claim_statement || render_statement};`);
|
|
||||||
}
|
|
||||||
if (parent_node) {
|
|
||||||
this.chunks.mount.push(b`@append(${parent_node}, ${id});`);
|
|
||||||
if (is_head(parent_node) && !no_detach) this.chunks.destroy.push(b`@detach(${id});`);
|
|
||||||
} else {
|
|
||||||
this.chunks.mount.push(b`@insert(#target, ${id}, #anchor);`);
|
|
||||||
if (!no_detach) this.chunks.destroy.push(b`if (detaching) @detach(${id});`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {boolean} [local] */
|
|
||||||
add_intro(local) {
|
|
||||||
this.has_intros = this.has_intro_method = true;
|
|
||||||
if (!local && this.parent) this.parent.add_intro();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {boolean} [local] */
|
|
||||||
add_outro(local) {
|
|
||||||
this.has_outros = this.has_outro_method = true;
|
|
||||||
this.outros += 1;
|
|
||||||
if (!local && this.parent) this.parent.add_outro();
|
|
||||||
}
|
|
||||||
|
|
||||||
add_animation() {
|
|
||||||
this.has_animation = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('estree').Identifier} id
|
|
||||||
* @param {import('estree').Node} [init]
|
|
||||||
*/
|
|
||||||
add_variable(id, init) {
|
|
||||||
if (this.variables.has(id.name)) {
|
|
||||||
throw new Error(`Variable '${id.name}' already initialised with a different value`);
|
|
||||||
}
|
|
||||||
this.variables.set(id.name, { id, init });
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {string} name */
|
|
||||||
alias(name) {
|
|
||||||
if (!this.aliases.has(name)) {
|
|
||||||
this.aliases.set(name, this.get_unique_name(name));
|
|
||||||
}
|
|
||||||
return this.aliases.get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {BlockOptions} options */
|
|
||||||
child(options) {
|
|
||||||
return new Block(Object.assign({}, this, { key: null }, options, { parent: this }));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {any} [key] */
|
|
||||||
get_contents(key) {
|
|
||||||
const { dev } = this.renderer.options;
|
|
||||||
if (this.has_outros) {
|
|
||||||
this.add_variable({ type: 'Identifier', name: '#current' });
|
|
||||||
if (this.chunks.intro.length > 0) {
|
|
||||||
this.chunks.intro.push(b`#current = true;`);
|
|
||||||
this.chunks.mount.push(b`#current = true;`);
|
|
||||||
}
|
|
||||||
if (this.chunks.outro.length > 0) {
|
|
||||||
this.chunks.outro.push(b`#current = false;`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.autofocus) {
|
|
||||||
if (this.autofocus.condition_expression) {
|
|
||||||
this.chunks.mount.push(
|
|
||||||
b`if (${this.autofocus.condition_expression}) ${this.autofocus.element_var}.focus();`
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.chunks.mount.push(b`${this.autofocus.element_var}.focus();`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.render_binding_groups();
|
|
||||||
this.render_listeners();
|
|
||||||
|
|
||||||
/** @type {Record<string, any>} */
|
|
||||||
const properties = {};
|
|
||||||
const noop = x`@noop`;
|
|
||||||
properties.key = key;
|
|
||||||
if (this.first) {
|
|
||||||
properties.first = x`null`;
|
|
||||||
this.chunks.hydrate.push(b`this.first = ${this.first};`);
|
|
||||||
}
|
|
||||||
if (this.chunks.create.length === 0 && this.chunks.hydrate.length === 0) {
|
|
||||||
properties.create = noop;
|
|
||||||
} else {
|
|
||||||
const hydrate =
|
|
||||||
this.chunks.hydrate.length > 0 &&
|
|
||||||
(this.renderer.options.hydratable ? b`this.h();` : this.chunks.hydrate);
|
|
||||||
properties.create = x`function #create() {
|
|
||||||
${this.chunks.create}
|
|
||||||
${hydrate}
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
if (this.renderer.options.hydratable || this.chunks.claim.length > 0) {
|
|
||||||
if (this.chunks.claim.length === 0 && this.chunks.hydrate.length === 0) {
|
|
||||||
properties.claim = noop;
|
|
||||||
} else {
|
|
||||||
properties.claim = x`function #claim(#nodes) {
|
|
||||||
${this.chunks.claim}
|
|
||||||
${this.renderer.options.hydratable && this.chunks.hydrate.length > 0 && b`this.h();`}
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.renderer.options.hydratable && this.chunks.hydrate.length > 0) {
|
|
||||||
properties.hydrate = x`function #hydrate() {
|
|
||||||
${this.chunks.hydrate}
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
if (this.chunks.mount.length === 0) {
|
|
||||||
properties.mount = noop;
|
|
||||||
} else if (this.event_listeners.length === 0) {
|
|
||||||
properties.mount = x`function #mount(#target, #anchor) {
|
|
||||||
${this.chunks.mount}
|
|
||||||
}`;
|
|
||||||
} else {
|
|
||||||
properties.mount = x`function #mount(#target, #anchor) {
|
|
||||||
${this.chunks.mount}
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
if (this.has_update_method || this.maintain_context) {
|
|
||||||
if (this.chunks.update.length === 0 && !this.maintain_context) {
|
|
||||||
properties.update = noop;
|
|
||||||
} else {
|
|
||||||
const ctx = this.maintain_context ? x`#new_ctx` : x`#ctx`;
|
|
||||||
|
|
||||||
/** @type {import('estree').Identifier | import('estree').ArrayPattern} */
|
|
||||||
let dirty = { type: 'Identifier', name: '#dirty' };
|
|
||||||
if (!this.renderer.context_overflow && !this.parent) {
|
|
||||||
dirty = { type: 'ArrayPattern', elements: [dirty] };
|
|
||||||
}
|
|
||||||
properties.update = x`function #update(${ctx}, ${dirty}) {
|
|
||||||
${this.maintain_context && b`#ctx = ${ctx};`}
|
|
||||||
${this.chunks.update}
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.has_animation) {
|
|
||||||
properties.measure = x`function #measure() {
|
|
||||||
${this.chunks.measure}
|
|
||||||
}`;
|
|
||||||
if (this.chunks.restore_measurements.length) {
|
|
||||||
properties.restore_measurements = x`function #restore_measurements(#measurement) {
|
|
||||||
${this.chunks.restore_measurements}
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
properties.fix = x`function #fix() {
|
|
||||||
${this.chunks.fix}
|
|
||||||
}`;
|
|
||||||
properties.animate = x`function #animate() {
|
|
||||||
${this.chunks.animate}
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
if (this.has_intro_method || this.has_outro_method) {
|
|
||||||
if (this.chunks.intro.length === 0) {
|
|
||||||
properties.intro = noop;
|
|
||||||
} else {
|
|
||||||
properties.intro = x`function #intro(#local) {
|
|
||||||
${this.has_outros && b`if (#current) return;`}
|
|
||||||
${this.chunks.intro}
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
if (this.chunks.outro.length === 0) {
|
|
||||||
properties.outro = noop;
|
|
||||||
} else {
|
|
||||||
properties.outro = x`function #outro(#local) {
|
|
||||||
${this.chunks.outro}
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.chunks.destroy.length === 0) {
|
|
||||||
properties.destroy = noop;
|
|
||||||
} else {
|
|
||||||
const dispose_elements = [];
|
|
||||||
// Coalesce if blocks with the same condition
|
|
||||||
const others = flatten(this.chunks.destroy).filter(
|
|
||||||
/** @param {import('estree').Node} node */
|
|
||||||
(node) => {
|
|
||||||
if (
|
|
||||||
node.type === 'IfStatement' &&
|
|
||||||
node.test.type === 'Identifier' &&
|
|
||||||
node.test.name === 'detaching'
|
|
||||||
) {
|
|
||||||
dispose_elements.push(node.consequent);
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
properties.destroy = x`function #destroy(detaching) {
|
|
||||||
${dispose_elements.length ? b`if (detaching) { ${dispose_elements} }` : null}
|
|
||||||
${others}
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
if (!this.renderer.component.compile_options.dev) {
|
|
||||||
// allow shorthand names
|
|
||||||
for (const name in properties) {
|
|
||||||
const property = properties[name];
|
|
||||||
if (property) property.id = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @type {any} */
|
|
||||||
const return_value = x`{
|
|
||||||
key: ${properties.key},
|
|
||||||
first: ${properties.first},
|
|
||||||
c: ${properties.create},
|
|
||||||
l: ${properties.claim},
|
|
||||||
h: ${properties.hydrate},
|
|
||||||
m: ${properties.mount},
|
|
||||||
p: ${properties.update},
|
|
||||||
r: ${properties.measure},
|
|
||||||
s: ${properties.restore_measurements},
|
|
||||||
f: ${properties.fix},
|
|
||||||
a: ${properties.animate},
|
|
||||||
i: ${properties.intro},
|
|
||||||
o: ${properties.outro},
|
|
||||||
d: ${properties.destroy}
|
|
||||||
}`;
|
|
||||||
const block = dev && this.get_unique_name('block');
|
|
||||||
const body = b`
|
|
||||||
${this.chunks.declarations}
|
|
||||||
|
|
||||||
${Array.from(this.variables.values()).map(({ id, init }) => {
|
|
||||||
return init ? b`let ${id} = ${init}` : b`let ${id}`;
|
|
||||||
})}
|
|
||||||
|
|
||||||
${this.chunks.init}
|
|
||||||
|
|
||||||
${
|
|
||||||
dev
|
|
||||||
? b`
|
|
||||||
const ${block} = ${return_value};
|
|
||||||
@dispatch_dev("SvelteRegisterBlock", {
|
|
||||||
block: ${block},
|
|
||||||
id: ${this.name || 'create_fragment'}.name,
|
|
||||||
type: "${this.type}",
|
|
||||||
source: "${this.comment ? this.comment.replace(regex_double_quotes, '\\"') : ''}",
|
|
||||||
ctx: #ctx
|
|
||||||
});
|
|
||||||
return ${block};`
|
|
||||||
: b`
|
|
||||||
return ${return_value};`
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @returns {boolean} */
|
|
||||||
has_content() {
|
|
||||||
return (
|
|
||||||
!!this.first ||
|
|
||||||
this.event_listeners.length > 0 ||
|
|
||||||
this.chunks.intro.length > 0 ||
|
|
||||||
this.chunks.outro.length > 0 ||
|
|
||||||
this.chunks.create.length > 0 ||
|
|
||||||
this.chunks.hydrate.length > 0 ||
|
|
||||||
this.chunks.claim.length > 0 ||
|
|
||||||
this.chunks.mount.length > 0 ||
|
|
||||||
this.chunks.update.length > 0 ||
|
|
||||||
this.chunks.destroy.length > 0 ||
|
|
||||||
this.has_animation
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const key = this.key && this.get_unique_name('key');
|
|
||||||
|
|
||||||
/** @type {any[]} */
|
|
||||||
const args = [x`#ctx`];
|
|
||||||
if (key) args.unshift(key);
|
|
||||||
const fn = b`function ${this.name}(${args}) {
|
|
||||||
${this.get_contents(key)}
|
|
||||||
}`;
|
|
||||||
return this.comment
|
|
||||||
? b`
|
|
||||||
// ${this.comment}
|
|
||||||
${fn}`
|
|
||||||
: fn;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {string} chunk */
|
|
||||||
render_listeners(chunk = '') {
|
|
||||||
if (this.event_listeners.length > 0) {
|
|
||||||
this.add_variable({ type: 'Identifier', name: '#mounted' });
|
|
||||||
this.chunks.destroy.push(b`#mounted = false`);
|
|
||||||
|
|
||||||
/** @type {import('estree').Identifier} */
|
|
||||||
const dispose = {
|
|
||||||
type: 'Identifier',
|
|
||||||
name: `#dispose${chunk}`
|
|
||||||
};
|
|
||||||
this.add_variable(dispose);
|
|
||||||
if (this.event_listeners.length === 1) {
|
|
||||||
this.chunks.mount.push(b`
|
|
||||||
if (!#mounted) {
|
|
||||||
${dispose} = ${this.event_listeners[0]};
|
|
||||||
#mounted = true;
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
this.chunks.destroy.push(b`${dispose}();`);
|
|
||||||
} else {
|
|
||||||
this.chunks.mount.push(b`
|
|
||||||
if (!#mounted) {
|
|
||||||
${dispose} = [
|
|
||||||
${this.event_listeners}
|
|
||||||
];
|
|
||||||
#mounted = true;
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
this.chunks.destroy.push(b`@run_all(${dispose});`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
render_binding_groups() {
|
|
||||||
for (const binding_group of this.binding_groups) {
|
|
||||||
binding_group.render(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,335 +0,0 @@
|
|||||||
import Block from './Block.js';
|
|
||||||
import FragmentWrapper from './wrappers/Fragment.js';
|
|
||||||
import { x } from 'code-red';
|
|
||||||
import flatten_reference from '../utils/flatten_reference.js';
|
|
||||||
import { reserved_keywords } from '../utils/reserved_keywords.js';
|
|
||||||
import { renderer_invalidate } from './invalidate.js';
|
|
||||||
|
|
||||||
export default class Renderer {
|
|
||||||
/**
|
|
||||||
* @typedef {Object} ContextMember
|
|
||||||
* @property {string} name
|
|
||||||
* @property {import('estree').Literal} index
|
|
||||||
* @property {boolean} is_contextual
|
|
||||||
* @property {boolean} is_non_contextual
|
|
||||||
* @property {import('../../interfaces.js').Var} variable
|
|
||||||
* @property {number} priority
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Array<{
|
|
||||||
* n: number;
|
|
||||||
* names: string[];
|
|
||||||
* }>} BitMasks
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** @type {import('../Component.js').default} */
|
|
||||||
component; // TODO Maybe Renderer shouldn't know about Component?
|
|
||||||
|
|
||||||
/** @type {import('../../interfaces.js').CompileOptions} */
|
|
||||||
options;
|
|
||||||
|
|
||||||
/** @type {ContextMember[]} */
|
|
||||||
context = [];
|
|
||||||
|
|
||||||
/** @type {ContextMember[]} */
|
|
||||||
initial_context = [];
|
|
||||||
|
|
||||||
/** @type {Map<string, ContextMember>} */
|
|
||||||
context_lookup = new Map();
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
context_overflow;
|
|
||||||
|
|
||||||
/** @type {Array<import('./Block.js').default | import('estree').Node | import('estree').Node[]>} */
|
|
||||||
blocks = [];
|
|
||||||
|
|
||||||
/** @type {Set<string>} */
|
|
||||||
readonly = new Set();
|
|
||||||
|
|
||||||
/** @type {Array<import('estree').Node | import('estree').Node[]>} */
|
|
||||||
meta_bindings = []; // initial values for e.g. window.innerWidth, if there's a <svelte:window> meta tag
|
|
||||||
|
|
||||||
/** @type {Map<string, BindingGroup>} */
|
|
||||||
binding_groups = new Map();
|
|
||||||
|
|
||||||
/** @type {import('./Block.js').default} */
|
|
||||||
block;
|
|
||||||
|
|
||||||
/** @type {import('./wrappers/Fragment.js').default} */
|
|
||||||
fragment;
|
|
||||||
|
|
||||||
/** @type {import('estree').Identifier} */
|
|
||||||
file_var;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use this for stack traces. It is 1-based and acts on pre-processed sources.
|
|
||||||
* Use `meta_locate` for metadata on DOM elements.
|
|
||||||
* @type {(c: number) => { line: number; column: number }}
|
|
||||||
*/
|
|
||||||
locate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use this for metadata on DOM elements. It is 1-based and acts on sources that have not been pre-processed.
|
|
||||||
* Use `locate` for source mappings.
|
|
||||||
* @type {(c: number) => { line: number; column: number }}
|
|
||||||
*/
|
|
||||||
meta_locate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('../../interfaces.js').CompileOptions} options
|
|
||||||
*/
|
|
||||||
constructor(component, options) {
|
|
||||||
this.component = component;
|
|
||||||
this.options = options;
|
|
||||||
this.locate = component.locate; // TODO messy
|
|
||||||
this.meta_locate = component.meta_locate; // TODO messy
|
|
||||||
this.file_var = options.dev && this.component.get_unique_name('file');
|
|
||||||
component.vars
|
|
||||||
.filter((v) => !v.hoistable || (v.export_name && !v.module))
|
|
||||||
.forEach((v) => this.add_to_context(v.name));
|
|
||||||
// ensure store values are included in context
|
|
||||||
component.vars.filter((v) => v.subscribable).forEach((v) => this.add_to_context(`$${v.name}`));
|
|
||||||
reserved_keywords.forEach((keyword) => {
|
|
||||||
if (component.var_lookup.has(keyword)) {
|
|
||||||
this.add_to_context(keyword);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (component.slots.size > 0) {
|
|
||||||
this.add_to_context('$$scope');
|
|
||||||
this.add_to_context('#slots');
|
|
||||||
}
|
|
||||||
// main block
|
|
||||||
this.block = new Block({
|
|
||||||
renderer: this,
|
|
||||||
name: null,
|
|
||||||
type: 'component',
|
|
||||||
key: null,
|
|
||||||
bindings: new Map(),
|
|
||||||
dependencies: new Set()
|
|
||||||
});
|
|
||||||
this.block.has_update_method = true;
|
|
||||||
this.fragment = new FragmentWrapper(
|
|
||||||
this,
|
|
||||||
this.block,
|
|
||||||
component.fragment.children,
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
// TODO messy
|
|
||||||
this.blocks.forEach((block) => {
|
|
||||||
if (block instanceof Block) {
|
|
||||||
block.assign_variable_names();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.block.assign_variable_names();
|
|
||||||
this.fragment.render(this.block, null, /** @type {import('estree').Identifier} */ (x`#nodes`));
|
|
||||||
this.context_overflow = this.context.length > 31;
|
|
||||||
this.context.forEach((member) => {
|
|
||||||
const { variable } = member;
|
|
||||||
if (variable) {
|
|
||||||
member.priority += 2;
|
|
||||||
if (variable.mutated || variable.reassigned) member.priority += 4;
|
|
||||||
// these determine whether variable is included in initial context
|
|
||||||
// array, so must have the highest priority
|
|
||||||
if (variable.is_reactive_dependency && (variable.mutated || variable.reassigned))
|
|
||||||
member.priority += 16;
|
|
||||||
if (variable.export_name) member.priority += 32;
|
|
||||||
if (variable.referenced) member.priority += 64;
|
|
||||||
} else if (member.is_non_contextual) {
|
|
||||||
// determine whether variable is included in initial context
|
|
||||||
// array, so must have the highest priority
|
|
||||||
member.priority += 8;
|
|
||||||
}
|
|
||||||
if (!member.is_contextual) {
|
|
||||||
member.priority += 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.context.sort(
|
|
||||||
(a, b) =>
|
|
||||||
b.priority - a.priority ||
|
|
||||||
/** @type {number} */ (a.index.value) - /** @type {number} */ (b.index.value)
|
|
||||||
);
|
|
||||||
this.context.forEach((member, i) => (member.index.value = i));
|
|
||||||
let i = this.context.length;
|
|
||||||
while (i--) {
|
|
||||||
const member = this.context[i];
|
|
||||||
if (member.variable) {
|
|
||||||
if (
|
|
||||||
member.variable.referenced ||
|
|
||||||
member.variable.export_name ||
|
|
||||||
(member.variable.is_reactive_dependency &&
|
|
||||||
(member.variable.mutated || member.variable.reassigned))
|
|
||||||
)
|
|
||||||
break;
|
|
||||||
} else if (member.is_non_contextual) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.initial_context = this.context.slice(0, i + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} name
|
|
||||||
* @param {any} contextual
|
|
||||||
*/
|
|
||||||
add_to_context(name, contextual = false) {
|
|
||||||
if (!this.context_lookup.has(name)) {
|
|
||||||
/** @type {ContextMember} */
|
|
||||||
const member = {
|
|
||||||
name,
|
|
||||||
index: { type: 'Literal', value: this.context.length },
|
|
||||||
is_contextual: false,
|
|
||||||
is_non_contextual: false,
|
|
||||||
variable: null,
|
|
||||||
priority: 0
|
|
||||||
};
|
|
||||||
this.context_lookup.set(name, member);
|
|
||||||
this.context.push(member);
|
|
||||||
}
|
|
||||||
const member = this.context_lookup.get(name);
|
|
||||||
if (contextual) {
|
|
||||||
member.is_contextual = true;
|
|
||||||
} else {
|
|
||||||
member.is_non_contextual = true;
|
|
||||||
member.variable = this.component.var_lookup.get(name);
|
|
||||||
}
|
|
||||||
return member;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} name
|
|
||||||
* @param {unknown} [value]
|
|
||||||
* @param {boolean} main_execution_context
|
|
||||||
*/
|
|
||||||
invalidate(name, value, main_execution_context = false) {
|
|
||||||
return renderer_invalidate(this, name, value, main_execution_context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string[]} names
|
|
||||||
* @param {any} is_reactive_declaration
|
|
||||||
* @returns {import('estree').Expression}
|
|
||||||
*/
|
|
||||||
dirty(names, is_reactive_declaration = false) {
|
|
||||||
const renderer = this;
|
|
||||||
const dirty = /** @type {| import('estree').Identifier
|
|
||||||
| import('estree').MemberExpression} */ (
|
|
||||||
is_reactive_declaration ? x`$$self.$$.dirty` : x`#dirty`
|
|
||||||
);
|
|
||||||
const get_bitmask = () => {
|
|
||||||
/** @type {BitMasks} */
|
|
||||||
const bitmask = [];
|
|
||||||
names.forEach((name) => {
|
|
||||||
const member = renderer.context_lookup.get(name);
|
|
||||||
if (!member) return;
|
|
||||||
if (member.index.value === -1) {
|
|
||||||
throw new Error('unset index');
|
|
||||||
}
|
|
||||||
const value = /** @type {number} */ (member.index.value);
|
|
||||||
const i = (value / 31) | 0;
|
|
||||||
const n = 1 << value % 31;
|
|
||||||
if (!bitmask[i]) bitmask[i] = { n: 0, names: [] };
|
|
||||||
bitmask[i].n |= n;
|
|
||||||
bitmask[i].names.push(name);
|
|
||||||
});
|
|
||||||
return bitmask;
|
|
||||||
};
|
|
||||||
// TODO: context-overflow make it less gross
|
|
||||||
return /** @type {any} */ ({
|
|
||||||
// Using a ParenthesizedExpression allows us to create
|
|
||||||
// the expression lazily. TODO would be better if
|
|
||||||
// context was determined before rendering, so that
|
|
||||||
// this indirection was unnecessary
|
|
||||||
type: 'ParenthesizedExpression',
|
|
||||||
get expression() {
|
|
||||||
const bitmask = get_bitmask();
|
|
||||||
if (!bitmask.length) {
|
|
||||||
return /** @type {import('estree').BinaryExpression} */ (
|
|
||||||
x`${dirty} & /*${names.join(', ')}*/ 0`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (renderer.context_overflow) {
|
|
||||||
return bitmask
|
|
||||||
.map((b, i) => ({ b, i }))
|
|
||||||
.filter(({ b }) => b)
|
|
||||||
.map(({ b, i }) => x`${dirty}[${i}] & /*${b.names.join(', ')}*/ ${b.n}`)
|
|
||||||
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`);
|
|
||||||
}
|
|
||||||
return /** @type {import('estree').BinaryExpression} */ (
|
|
||||||
x`${dirty} & /*${names.join(', ')}*/ ${bitmask[0].n}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// NOTE: this method may be called before this.context_overflow / this.context is fully defined
|
|
||||||
// therefore, they can only be evaluated later in a getter function
|
|
||||||
|
|
||||||
/** @returns {import('estree').UnaryExpression | import('estree').ArrayExpression} */
|
|
||||||
get_initial_dirty() {
|
|
||||||
const _this = this;
|
|
||||||
// TODO: context-overflow make it less gross
|
|
||||||
|
|
||||||
/** @type {import('estree').UnaryExpression} */
|
|
||||||
const val = /** @type {import('estree').UnaryExpression} */ (x`-1`);
|
|
||||||
return {
|
|
||||||
get type() {
|
|
||||||
return _this.context_overflow ? 'ArrayExpression' : 'UnaryExpression';
|
|
||||||
},
|
|
||||||
// as [-1]
|
|
||||||
get elements() {
|
|
||||||
const elements = [];
|
|
||||||
for (let i = 0; i < _this.context.length; i += 31) {
|
|
||||||
elements.push(val);
|
|
||||||
}
|
|
||||||
return elements;
|
|
||||||
},
|
|
||||||
// as -1
|
|
||||||
operator: val.operator,
|
|
||||||
prefix: val.prefix,
|
|
||||||
argument: val.argument
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string | import('estree').Identifier | import('estree').MemberExpression} node
|
|
||||||
* @param {string | void} ctx
|
|
||||||
*/
|
|
||||||
reference(node, ctx = '#ctx') {
|
|
||||||
if (typeof node === 'string') {
|
|
||||||
node = { type: 'Identifier', name: node };
|
|
||||||
}
|
|
||||||
const { name, nodes } = flatten_reference(node);
|
|
||||||
const member = this.context_lookup.get(name);
|
|
||||||
// TODO is this correct?
|
|
||||||
if (this.component.var_lookup.get(name)) {
|
|
||||||
this.component.add_reference(node, name);
|
|
||||||
}
|
|
||||||
if (member !== undefined) {
|
|
||||||
const replacement = /** @type {import('estree').MemberExpression} */ (
|
|
||||||
x`/*${member.name}*/ ${ctx}[${member.index}]`
|
|
||||||
);
|
|
||||||
if (nodes[0].loc) replacement.object.loc = nodes[0].loc;
|
|
||||||
nodes[0] = replacement;
|
|
||||||
return nodes.reduce((lhs, rhs) => x`${lhs}.${rhs}`);
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {import('./Block.js').default | import('estree').Node | import('estree').Node[]} block */
|
|
||||||
remove_block(block) {
|
|
||||||
this.blocks.splice(this.blocks.indexOf(block), 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} BindingGroup
|
|
||||||
* @property {(to_reference?:boolean)=>import('estree').Node} binding_group
|
|
||||||
* @property {string[]} contexts
|
|
||||||
* @property {Set<string>} list_dependencies
|
|
||||||
* @property {string} keypath
|
|
||||||
* @property {(block:Block,element:import('estree').PrivateIdentifier) => void} add_element
|
|
||||||
* @property {(block:Block)=>void} render
|
|
||||||
*/
|
|
||||||
@ -1,623 +0,0 @@
|
|||||||
import { b, x, p } from 'code-red';
|
|
||||||
import Renderer from './Renderer.js';
|
|
||||||
import { walk } from 'estree-walker';
|
|
||||||
import { extract_names } from 'periscopic';
|
|
||||||
import { invalidate } from './invalidate.js';
|
|
||||||
import { apply_preprocessor_sourcemap } from '../../utils/mapped_code.js';
|
|
||||||
import { flatten } from '../../utils/flatten.js';
|
|
||||||
import check_enable_sourcemap from '../utils/check_enable_sourcemap.js';
|
|
||||||
import { push_array } from '../../utils/push_array.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../Component.js').default} component
|
|
||||||
* @param {import('../../interfaces.js').CompileOptions} options
|
|
||||||
* @returns {{ js: import('estree').Node[]; css: import('../../interfaces.js').CssResult; }}
|
|
||||||
*/
|
|
||||||
export default function dom(component, options) {
|
|
||||||
const { name } = component;
|
|
||||||
const renderer = new Renderer(component, options);
|
|
||||||
const { block } = renderer;
|
|
||||||
block.has_outro_method = true;
|
|
||||||
/** @type {import('estree').Node[][]} */
|
|
||||||
const body = [];
|
|
||||||
if (renderer.file_var) {
|
|
||||||
const file = component.file ? x`"${component.file}"` : x`undefined`;
|
|
||||||
body.push(b`const ${renderer.file_var} = ${file};`);
|
|
||||||
}
|
|
||||||
const css = component.stylesheet.render(options.filename);
|
|
||||||
const css_sourcemap_enabled = check_enable_sourcemap(options.enableSourcemap, 'css');
|
|
||||||
if (css_sourcemap_enabled) {
|
|
||||||
css.map = apply_preprocessor_sourcemap(
|
|
||||||
options.filename,
|
|
||||||
css.map,
|
|
||||||
/** @type {string | import('@ampproject/remapping').RawSourceMap | import('@ampproject/remapping').DecodedSourceMap} */ (
|
|
||||||
options.sourcemap
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
css.map = null;
|
|
||||||
}
|
|
||||||
const styles =
|
|
||||||
css_sourcemap_enabled && component.stylesheet.has_styles && options.dev
|
|
||||||
? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */`
|
|
||||||
: css.code;
|
|
||||||
const add_css = component.get_unique_name('add_css');
|
|
||||||
const should_add_css = !!styles && (options.customElement || options.css === 'injected');
|
|
||||||
if (should_add_css) {
|
|
||||||
body.push(b`
|
|
||||||
function ${add_css}(target) {
|
|
||||||
@append_styles(target, "${component.stylesheet.id}", "${styles}");
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
// fix order
|
|
||||||
// TODO the deconflicted names of blocks are reversed... should set them here
|
|
||||||
const blocks = renderer.blocks.slice().reverse();
|
|
||||||
push_array(
|
|
||||||
body,
|
|
||||||
blocks.map((block) => {
|
|
||||||
// TODO this is a horrible mess — renderer.blocks
|
|
||||||
// contains a mixture of Blocks and Nodes
|
|
||||||
if (/** @type {import('./Block.js').default} */ (block).render)
|
|
||||||
return /** @type {import('./Block.js').default} */ (block).render();
|
|
||||||
return block;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
if (options.dev && !options.hydratable) {
|
|
||||||
block.chunks.claim.push(
|
|
||||||
b`throw new @_Error("options.hydrate only works if the component was compiled with the \`hydratable: true\` option");`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const uses_slots = component.var_lookup.has('$$slots');
|
|
||||||
|
|
||||||
/** @type {import('estree').Node[] | undefined} */
|
|
||||||
let compute_slots;
|
|
||||||
if (uses_slots) {
|
|
||||||
compute_slots = b`
|
|
||||||
const $$slots = @compute_slots(#slots);
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
const uses_props = component.var_lookup.has('$$props');
|
|
||||||
const uses_rest = component.var_lookup.has('$$restProps');
|
|
||||||
const $$props = uses_props || uses_rest ? '$$new_props' : '$$props';
|
|
||||||
const props = component.vars.filter((variable) => !variable.module && variable.export_name);
|
|
||||||
const writable_props = props.filter((variable) => variable.writable);
|
|
||||||
const omit_props_names = component.get_unique_name('omit_props_names');
|
|
||||||
const compute_rest = x`@compute_rest_props($$props, ${omit_props_names.name})`;
|
|
||||||
const rest = uses_rest
|
|
||||||
? b`
|
|
||||||
const ${omit_props_names.name} = [${props.map((prop) => `"${prop.export_name}"`).join(',')}];
|
|
||||||
let $$restProps = ${compute_rest};
|
|
||||||
`
|
|
||||||
: null;
|
|
||||||
const set =
|
|
||||||
uses_props || uses_rest || writable_props.length > 0 || component.slots.size > 0
|
|
||||||
? x`
|
|
||||||
${$$props} => {
|
|
||||||
${
|
|
||||||
uses_props &&
|
|
||||||
renderer.invalidate(
|
|
||||||
'$$props',
|
|
||||||
x`$$props = @assign(@assign({}, $$props), @exclude_internal_props($$new_props))`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
${
|
|
||||||
uses_rest &&
|
|
||||||
!uses_props &&
|
|
||||||
x`$$props = @assign(@assign({}, $$props), @exclude_internal_props($$new_props))`
|
|
||||||
}
|
|
||||||
${uses_rest && renderer.invalidate('$$restProps', x`$$restProps = ${compute_rest}`)}
|
|
||||||
${writable_props.map(
|
|
||||||
(prop) =>
|
|
||||||
b`if ('${prop.export_name}' in ${$$props}) ${renderer.invalidate(
|
|
||||||
prop.name,
|
|
||||||
x`${prop.name} = ${$$props}.${prop.export_name}`
|
|
||||||
)};`
|
|
||||||
)}
|
|
||||||
${
|
|
||||||
component.slots.size > 0 &&
|
|
||||||
b`if ('$$scope' in ${$$props}) ${renderer.invalidate(
|
|
||||||
'$$scope',
|
|
||||||
x`$$scope = ${$$props}.$$scope`
|
|
||||||
)};`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
: null;
|
|
||||||
const accessors = [];
|
|
||||||
const not_equal = component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`;
|
|
||||||
|
|
||||||
/** @type {import('estree').Node[] | import('estree').Node} */
|
|
||||||
let missing_props_check;
|
|
||||||
|
|
||||||
/** @type {import('estree').Expression} */
|
|
||||||
let inject_state;
|
|
||||||
|
|
||||||
/** @type {import('estree').Expression} */
|
|
||||||
let capture_state;
|
|
||||||
|
|
||||||
/** @type {import('estree').Node[] | import('estree').Node} */
|
|
||||||
let props_inject;
|
|
||||||
props.forEach((prop) => {
|
|
||||||
const variable = component.var_lookup.get(prop.name);
|
|
||||||
if (!variable.writable || component.component_options.accessors) {
|
|
||||||
accessors.push({
|
|
||||||
type: 'MethodDefinition',
|
|
||||||
kind: 'get',
|
|
||||||
key: { type: 'Identifier', name: prop.export_name },
|
|
||||||
value: x`function() {
|
|
||||||
return ${
|
|
||||||
prop.hoistable
|
|
||||||
? prop.name
|
|
||||||
: x`this.$$.ctx[${renderer.context_lookup.get(prop.name).index}]`
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
});
|
|
||||||
} else if (component.compile_options.dev) {
|
|
||||||
accessors.push({
|
|
||||||
type: 'MethodDefinition',
|
|
||||||
kind: 'get',
|
|
||||||
key: { type: 'Identifier', name: prop.export_name },
|
|
||||||
value: x`function() {
|
|
||||||
throw new @_Error("<${component.tag}>: Props cannot be read directly from the component instance unless compiling with 'accessors: true' or '<svelte:options accessors/>'");
|
|
||||||
}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (component.component_options.accessors) {
|
|
||||||
if (variable.writable && !renderer.readonly.has(prop.name)) {
|
|
||||||
accessors.push({
|
|
||||||
type: 'MethodDefinition',
|
|
||||||
kind: 'set',
|
|
||||||
key: { type: 'Identifier', name: prop.export_name },
|
|
||||||
value: x`function(${prop.name}) {
|
|
||||||
this.$$set({ ${prop.export_name}: ${prop.name} });
|
|
||||||
@flush();
|
|
||||||
}`
|
|
||||||
});
|
|
||||||
} else if (component.compile_options.dev) {
|
|
||||||
accessors.push({
|
|
||||||
type: 'MethodDefinition',
|
|
||||||
kind: 'set',
|
|
||||||
key: { type: 'Identifier', name: prop.export_name },
|
|
||||||
value: x`function(value) {
|
|
||||||
throw new @_Error("<${component.tag}>: Cannot set read-only property '${prop.export_name}'");
|
|
||||||
}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (component.compile_options.dev) {
|
|
||||||
accessors.push({
|
|
||||||
type: 'MethodDefinition',
|
|
||||||
kind: 'set',
|
|
||||||
key: { type: 'Identifier', name: prop.export_name },
|
|
||||||
value: x`function(value) {
|
|
||||||
throw new @_Error("<${component.tag}>: Props cannot be set directly on the component instance unless compiling with 'accessors: true' or '<svelte:options accessors/>'");
|
|
||||||
}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
component.instance_exports_from.forEach((exports_from) => {
|
|
||||||
const import_declaration = {
|
|
||||||
...exports_from,
|
|
||||||
type: 'ImportDeclaration',
|
|
||||||
specifiers: [],
|
|
||||||
source: exports_from.source
|
|
||||||
};
|
|
||||||
component.imports.push(/** @type {import('estree').ImportDeclaration} */ (import_declaration));
|
|
||||||
exports_from.specifiers.forEach((specifier) => {
|
|
||||||
if (component.component_options.accessors) {
|
|
||||||
const name = component.get_unique_name(specifier.exported.name);
|
|
||||||
import_declaration.specifiers.push({
|
|
||||||
...specifier,
|
|
||||||
type: 'ImportSpecifier',
|
|
||||||
imported: specifier.local,
|
|
||||||
local: name
|
|
||||||
});
|
|
||||||
accessors.push({
|
|
||||||
type: 'MethodDefinition',
|
|
||||||
kind: 'get',
|
|
||||||
key: { type: 'Identifier', name: specifier.exported.name },
|
|
||||||
value: x`function() {
|
|
||||||
return ${name}
|
|
||||||
}`
|
|
||||||
});
|
|
||||||
} else if (component.compile_options.dev) {
|
|
||||||
accessors.push({
|
|
||||||
type: 'MethodDefinition',
|
|
||||||
kind: 'get',
|
|
||||||
key: { type: 'Identifier', name: specifier.exported.name },
|
|
||||||
value: x`function() {
|
|
||||||
throw new @_Error("<${component.tag}>: Props cannot be read directly from the component instance unless compiling with 'accessors: true' or '<svelte:options accessors/>'");
|
|
||||||
}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
if (component.compile_options.dev) {
|
|
||||||
// checking that expected ones were passed
|
|
||||||
const expected = props.filter((prop) => prop.writable && !prop.initialised);
|
|
||||||
if (expected.length) {
|
|
||||||
missing_props_check = b`
|
|
||||||
$$self.$$.on_mount.push(function () {
|
|
||||||
${expected.map(
|
|
||||||
(prop) => b`
|
|
||||||
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}'");
|
|
||||||
}`
|
|
||||||
)}
|
|
||||||
});
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
const capturable_vars = component.vars.filter(
|
|
||||||
(v) => !v.internal && !v.global && !v.name.startsWith('$$')
|
|
||||||
);
|
|
||||||
if (capturable_vars.length > 0) {
|
|
||||||
capture_state = x`() => ({ ${capturable_vars.map((prop) => p`${prop.name}`)} })`;
|
|
||||||
}
|
|
||||||
const injectable_vars = capturable_vars.filter(
|
|
||||||
(v) => !v.module && v.writable && v.name[0] !== '$'
|
|
||||||
);
|
|
||||||
if (uses_props || injectable_vars.length > 0) {
|
|
||||||
inject_state = x`
|
|
||||||
${$$props} => {
|
|
||||||
${
|
|
||||||
uses_props &&
|
|
||||||
renderer.invalidate('$$props', x`$$props = @assign(@assign({}, $$props), $$new_props)`)
|
|
||||||
}
|
|
||||||
${injectable_vars.map(
|
|
||||||
(v) =>
|
|
||||||
b`if ('${v.name}' in $$props) ${renderer.invalidate(
|
|
||||||
v.name,
|
|
||||||
x`${v.name} = ${$$props}.${v.name}`
|
|
||||||
)};`
|
|
||||||
)}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
props_inject = b`
|
|
||||||
if ($$props && "$$inject" in $$props) {
|
|
||||||
$$self.$inject_state($$props.$$inject);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// instrument assignments
|
|
||||||
if (component.ast.instance) {
|
|
||||||
let scope = component.instance_scope;
|
|
||||||
const map = component.instance_scope_map;
|
|
||||||
|
|
||||||
/** @type {import('estree').Node | null} */
|
|
||||||
let execution_context = null;
|
|
||||||
walk(component.ast.instance.content, {
|
|
||||||
enter(node) {
|
|
||||||
if (map.has(node)) {
|
|
||||||
scope = /** @type {import('periscopic').Scope} */ (map.get(node));
|
|
||||||
if (!execution_context && !scope.block) {
|
|
||||||
execution_context = node;
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
!execution_context &&
|
|
||||||
node.type === 'LabeledStatement' &&
|
|
||||||
node.label.name === '$'
|
|
||||||
) {
|
|
||||||
execution_context = node;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
leave(node) {
|
|
||||||
if (map.has(node)) {
|
|
||||||
scope = scope.parent;
|
|
||||||
}
|
|
||||||
if (execution_context === node) {
|
|
||||||
execution_context = null;
|
|
||||||
}
|
|
||||||
if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') {
|
|
||||||
const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument;
|
|
||||||
// normally (`a = 1`, `b.c = 2`), there'll be a single name
|
|
||||||
// (a or b). In destructuring cases (`[d, e] = [e, d]`) there
|
|
||||||
// may be more, in which case we need to tack the extra ones
|
|
||||||
// onto the initial function call
|
|
||||||
const names = new Set(extract_names(/** @type {import('estree').Node} */ (assignee)));
|
|
||||||
this.replace(invalidate(renderer, scope, node, names, execution_context === null));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
component.rewrite_props(({ name, reassigned, export_name }) => {
|
|
||||||
const value = `$${name}`;
|
|
||||||
const i = renderer.context_lookup.get(`$${name}`).index;
|
|
||||||
const insert =
|
|
||||||
reassigned || export_name
|
|
||||||
? b`${`$$subscribe_${name}`}()`
|
|
||||||
: b`@component_subscribe($$self, ${name}, #value => $$invalidate(${i}, ${value} = #value))`;
|
|
||||||
if (component.compile_options.dev) {
|
|
||||||
return b`@validate_store(${name}, '${name}'); ${insert}`;
|
|
||||||
}
|
|
||||||
return insert;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const args = [x`$$self`];
|
|
||||||
const has_invalidate =
|
|
||||||
props.length > 0 ||
|
|
||||||
component.has_reactive_assignments ||
|
|
||||||
component.slots.size > 0 ||
|
|
||||||
capture_state ||
|
|
||||||
inject_state;
|
|
||||||
if (has_invalidate) {
|
|
||||||
args.push(x`$$props`, x`$$invalidate`);
|
|
||||||
} else if (component.compile_options.dev) {
|
|
||||||
// $$props arg is still needed for unknown prop check
|
|
||||||
args.push(x`$$props`);
|
|
||||||
}
|
|
||||||
// has_create_fragment is intentionally to be true in dev mode.
|
|
||||||
const has_create_fragment = component.compile_options.dev || block.has_content();
|
|
||||||
if (has_create_fragment) {
|
|
||||||
body.push(b`
|
|
||||||
function create_fragment(#ctx) {
|
|
||||||
${block.get_contents()}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
body.push(b`
|
|
||||||
${component.extract_javascript(component.ast.module)}
|
|
||||||
|
|
||||||
${component.fully_hoisted}
|
|
||||||
`);
|
|
||||||
const filtered_props = props.filter((prop) => {
|
|
||||||
const variable = component.var_lookup.get(prop.name);
|
|
||||||
if (variable.hoistable) return false;
|
|
||||||
return prop.name[0] !== '$';
|
|
||||||
});
|
|
||||||
const reactive_stores = component.vars.filter(
|
|
||||||
(variable) => variable.name[0] === '$' && variable.name[1] !== '$'
|
|
||||||
);
|
|
||||||
const instance_javascript = component.extract_javascript(component.ast.instance);
|
|
||||||
const has_definition =
|
|
||||||
component.compile_options.dev ||
|
|
||||||
(instance_javascript && instance_javascript.length > 0) ||
|
|
||||||
filtered_props.length > 0 ||
|
|
||||||
uses_props ||
|
|
||||||
component.partly_hoisted.length > 0 ||
|
|
||||||
renderer.initial_context.length > 0 ||
|
|
||||||
component.reactive_declarations.length > 0 ||
|
|
||||||
capture_state ||
|
|
||||||
inject_state;
|
|
||||||
const definition = has_definition
|
|
||||||
? component.alias('instance')
|
|
||||||
: { type: 'Literal', value: null };
|
|
||||||
const reactive_store_subscriptions = reactive_stores
|
|
||||||
.filter((store) => {
|
|
||||||
const variable = component.var_lookup.get(store.name.slice(1));
|
|
||||||
return !variable || variable.hoistable;
|
|
||||||
})
|
|
||||||
.map(
|
|
||||||
({ name }) => b`
|
|
||||||
${component.compile_options.dev && b`@validate_store(${name.slice(1)}, '${name.slice(1)}');`}
|
|
||||||
@component_subscribe($$self, ${name.slice(1)}, $$value => $$invalidate(${
|
|
||||||
renderer.context_lookup.get(name).index
|
|
||||||
}, ${name} = $$value));
|
|
||||||
`
|
|
||||||
);
|
|
||||||
const resubscribable_reactive_store_unsubscribers = reactive_stores
|
|
||||||
.filter((store) => {
|
|
||||||
const variable = component.var_lookup.get(store.name.slice(1));
|
|
||||||
return variable && (variable.reassigned || variable.export_name);
|
|
||||||
})
|
|
||||||
.map(({ name }) => b`$$self.$$.on_destroy.push(() => ${`$$unsubscribe_${name.slice(1)}`}());`);
|
|
||||||
if (has_definition) {
|
|
||||||
/** @type {import('estree').Node | import('estree').Node[]} */
|
|
||||||
const reactive_declarations = [];
|
|
||||||
|
|
||||||
/** @type {import('estree').Node[]} */
|
|
||||||
const fixed_reactive_declarations = []; // not really 'reactive' but whatever
|
|
||||||
component.reactive_declarations.forEach((d) => {
|
|
||||||
const dependencies = Array.from(d.dependencies);
|
|
||||||
const uses_rest_or_props = !!dependencies.find((n) => n === '$$props' || n === '$$restProps');
|
|
||||||
const writable = dependencies.filter((n) => {
|
|
||||||
const variable = component.var_lookup.get(n);
|
|
||||||
return variable && (variable.export_name || variable.mutated || variable.reassigned);
|
|
||||||
});
|
|
||||||
const condition =
|
|
||||||
!uses_rest_or_props && writable.length > 0 && renderer.dirty(writable, true);
|
|
||||||
let statement = d.node; // TODO remove label (use d.node.body) if it's not referenced
|
|
||||||
if (condition)
|
|
||||||
statement = /** @type {import('estree').Statement} */ (
|
|
||||||
b`if (${condition}) { ${statement} }`[0]
|
|
||||||
);
|
|
||||||
if (condition || uses_rest_or_props) {
|
|
||||||
reactive_declarations.push(statement);
|
|
||||||
} else {
|
|
||||||
fixed_reactive_declarations.push(statement);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const injected = Array.from(component.injected_reactive_declaration_vars).filter((name) => {
|
|
||||||
const variable = component.var_lookup.get(name);
|
|
||||||
return variable.injected && variable.name[0] !== '$';
|
|
||||||
});
|
|
||||||
const reactive_store_declarations = reactive_stores.map((variable) => {
|
|
||||||
const $name = variable.name;
|
|
||||||
const name = $name.slice(1);
|
|
||||||
const store = component.var_lookup.get(name);
|
|
||||||
if (store && (store.reassigned || store.export_name)) {
|
|
||||||
const unsubscribe = `$$unsubscribe_${name}`;
|
|
||||||
const subscribe = `$$subscribe_${name}`;
|
|
||||||
const i = renderer.context_lookup.get($name).index;
|
|
||||||
return b`let ${$name}, ${unsubscribe} = @noop, ${subscribe} = () => (${unsubscribe}(), ${unsubscribe} = @subscribe(${name}, $$value => $$invalidate(${i}, ${$name} = $$value)), ${name})`;
|
|
||||||
}
|
|
||||||
return b`let ${$name};`;
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @type {import('estree').Node[] | undefined} */
|
|
||||||
let unknown_props_check;
|
|
||||||
if (component.compile_options.dev && !(uses_props || uses_rest)) {
|
|
||||||
unknown_props_check = b`
|
|
||||||
const writable_props = [${writable_props.map((prop) => x`'${prop.export_name}'`)}];
|
|
||||||
@_Object.keys($$props).forEach(key => {
|
|
||||||
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== '$$' && key !== 'slot') @_console.warn(\`<${
|
|
||||||
component.tag
|
|
||||||
}> was created with unknown prop '\${key}'\`);
|
|
||||||
});
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
const return_value = {
|
|
||||||
type: 'ArrayExpression',
|
|
||||||
elements: renderer.initial_context.map(
|
|
||||||
(member) =>
|
|
||||||
/** @type {import('estree').Expression} */ ({
|
|
||||||
type: 'Identifier',
|
|
||||||
name: member.name
|
|
||||||
})
|
|
||||||
)
|
|
||||||
};
|
|
||||||
body.push(b`
|
|
||||||
function ${definition}(${args}) {
|
|
||||||
${injected.map((name) => b`let ${name};`)}
|
|
||||||
|
|
||||||
${rest}
|
|
||||||
|
|
||||||
${reactive_store_declarations}
|
|
||||||
|
|
||||||
${reactive_store_subscriptions}
|
|
||||||
|
|
||||||
${resubscribable_reactive_store_unsubscribers}
|
|
||||||
|
|
||||||
${
|
|
||||||
component.slots.size || component.compile_options.dev || uses_slots
|
|
||||||
? b`let { $$slots: #slots = {}, $$scope } = $$props;`
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
${
|
|
||||||
component.compile_options.dev &&
|
|
||||||
b`@validate_slots('${component.tag}', #slots, [${[...component.slots.keys()]
|
|
||||||
.map((key) => `'${key}'`)
|
|
||||||
.join(',')}]);`
|
|
||||||
}
|
|
||||||
${compute_slots}
|
|
||||||
|
|
||||||
${instance_javascript}
|
|
||||||
|
|
||||||
${missing_props_check}
|
|
||||||
${unknown_props_check}
|
|
||||||
|
|
||||||
${
|
|
||||||
renderer.binding_groups.size > 0 &&
|
|
||||||
b`const $$binding_groups = [${[...renderer.binding_groups.keys()].map((_) => x`[]`)}];`
|
|
||||||
}
|
|
||||||
|
|
||||||
${component.partly_hoisted}
|
|
||||||
|
|
||||||
${set && b`$$self.$$set = ${set};`}
|
|
||||||
|
|
||||||
${capture_state && b`$$self.$capture_state = ${capture_state};`}
|
|
||||||
|
|
||||||
${inject_state && b`$$self.$inject_state = ${inject_state};`}
|
|
||||||
|
|
||||||
${/* before reactive declarations */ props_inject}
|
|
||||||
|
|
||||||
${
|
|
||||||
reactive_declarations.length > 0 &&
|
|
||||||
b`
|
|
||||||
$$self.$$.update = () => {
|
|
||||||
${reactive_declarations}
|
|
||||||
};
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
${fixed_reactive_declarations}
|
|
||||||
|
|
||||||
${uses_props && b`$$props = @exclude_internal_props($$props);`}
|
|
||||||
|
|
||||||
return ${return_value};
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
const prop_indexes = /** @type {import('estree').ObjectExpression} */ (
|
|
||||||
x`{
|
|
||||||
${props
|
|
||||||
.filter((v) => v.export_name && !v.module)
|
|
||||||
.map((v) => p`${v.export_name}: ${renderer.context_lookup.get(v.name).index}`)}
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
let dirty;
|
|
||||||
if (renderer.context_overflow) {
|
|
||||||
dirty = x`[]`;
|
|
||||||
for (let i = 0; i < renderer.context.length; i += 31) {
|
|
||||||
/** @type {any} */ (dirty).elements.push(x`-1`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const superclass = {
|
|
||||||
type: 'Identifier',
|
|
||||||
name: options.dev ? '@SvelteComponentDev' : '@SvelteComponent'
|
|
||||||
};
|
|
||||||
const optional_parameters = [];
|
|
||||||
if (should_add_css) {
|
|
||||||
optional_parameters.push(add_css);
|
|
||||||
} else if (dirty) {
|
|
||||||
optional_parameters.push(x`null`);
|
|
||||||
}
|
|
||||||
if (dirty) {
|
|
||||||
optional_parameters.push(dirty);
|
|
||||||
}
|
|
||||||
const declaration = /** @type {import('estree').ClassDeclaration} */ (
|
|
||||||
b`
|
|
||||||
class ${name} extends ${superclass} {
|
|
||||||
constructor(options) {
|
|
||||||
super(${options.dev && 'options'});
|
|
||||||
@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 });`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`[0]
|
|
||||||
);
|
|
||||||
push_array(declaration.body.body, accessors);
|
|
||||||
body.push(/** @type {any} */ (declaration));
|
|
||||||
if (options.customElement) {
|
|
||||||
const props_str = writable_props.reduce((def, prop) => {
|
|
||||||
def[prop.export_name] =
|
|
||||||
component.component_options.customElement?.props?.[prop.export_name] || {};
|
|
||||||
if (prop.is_boolean && !def[prop.export_name].type) {
|
|
||||||
def[prop.export_name].type = 'Boolean';
|
|
||||||
}
|
|
||||||
return def;
|
|
||||||
}, {});
|
|
||||||
const slots_str = [...component.slots.keys()].map((key) => `"${key}"`).join(',');
|
|
||||||
const accessors_str = accessors
|
|
||||||
.filter(
|
|
||||||
(accessor) =>
|
|
||||||
accessor.kind === 'get' &&
|
|
||||||
!writable_props.some((prop) => prop.export_name === accessor.key.name)
|
|
||||||
)
|
|
||||||
.map((accessor) => `"${accessor.key.name}"`)
|
|
||||||
.join(',');
|
|
||||||
const use_shadow_dom =
|
|
||||||
component.component_options.customElement?.shadow !== 'none' ? 'true' : 'false';
|
|
||||||
|
|
||||||
const create_ce = x`@create_custom_element(${name}, ${JSON.stringify(
|
|
||||||
props_str
|
|
||||||
)}, [${slots_str}], [${accessors_str}], ${use_shadow_dom}, ${
|
|
||||||
component.component_options.customElement?.extend
|
|
||||||
})`;
|
|
||||||
|
|
||||||
if (component.component_options.customElement?.tag) {
|
|
||||||
body.push(
|
|
||||||
b`@_customElements.define("${component.component_options.customElement.tag}", ${create_ce});`
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
body.push(b`${create_ce}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.discloseVersion === true) {
|
|
||||||
component.imports.unshift({
|
|
||||||
type: 'ImportDeclaration',
|
|
||||||
specifiers: [],
|
|
||||||
source: {
|
|
||||||
type: 'Literal',
|
|
||||||
value: `${options.sveltePath ?? 'svelte'}/internal/disclose-version`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return { js: flatten(body), css };
|
|
||||||
}
|
|
||||||
@ -1,147 +0,0 @@
|
|||||||
import { nodes_match } from '../../utils/nodes_match.js';
|
|
||||||
import { x } from 'code-red';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('./Renderer.js').default} renderer
|
|
||||||
* @param {import('periscopic').Scope} scope
|
|
||||||
* @param {import('estree').Node} node
|
|
||||||
* @param {Set<string>} names
|
|
||||||
* @param {boolean} main_execution_context
|
|
||||||
* @returns {any}
|
|
||||||
*/
|
|
||||||
export function invalidate(renderer, scope, node, names, main_execution_context = false) {
|
|
||||||
const { component } = renderer;
|
|
||||||
const [head, ...tail] = /** @type {import('../../interfaces.js').Var[]} */ (
|
|
||||||
Array.from(names)
|
|
||||||
.filter((name) => {
|
|
||||||
const owner = scope.find_owner(name);
|
|
||||||
return !owner || owner === component.instance_scope;
|
|
||||||
})
|
|
||||||
.map((name) => component.var_lookup.get(name))
|
|
||||||
.filter((variable) => {
|
|
||||||
return (
|
|
||||||
variable &&
|
|
||||||
!variable.hoistable &&
|
|
||||||
!variable.global &&
|
|
||||||
!variable.module &&
|
|
||||||
(variable.referenced ||
|
|
||||||
variable.subscribable ||
|
|
||||||
variable.is_reactive_dependency ||
|
|
||||||
variable.export_name ||
|
|
||||||
variable.name[0] === '$')
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../../interfaces.js').Var} variable
|
|
||||||
* @param {import('estree').Expression} [node]
|
|
||||||
*/
|
|
||||||
function get_invalidated(variable, node) {
|
|
||||||
if (main_execution_context && !variable.subscribable && variable.name[0] !== '$') {
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
return renderer_invalidate(renderer, variable.name, undefined, main_execution_context);
|
|
||||||
}
|
|
||||||
if (!head) {
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
component.has_reactive_assignments = true;
|
|
||||||
if (
|
|
||||||
node.type === 'AssignmentExpression' &&
|
|
||||||
node.operator === '=' &&
|
|
||||||
nodes_match(node.left, node.right, ['trailingComments', 'leadingComments']) &&
|
|
||||||
tail.length === 0
|
|
||||||
) {
|
|
||||||
return get_invalidated(head, node);
|
|
||||||
}
|
|
||||||
const is_store_value = head.name[0] === '$' && head.name[1] !== '$';
|
|
||||||
const extra_args = tail.map((variable) => get_invalidated(variable)).filter(Boolean);
|
|
||||||
if (is_store_value) {
|
|
||||||
return x`@set_store_value(${head.name.slice(1)}, ${node}, ${head.name}, ${extra_args})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
let invalidate;
|
|
||||||
if (!main_execution_context) {
|
|
||||||
const pass_value =
|
|
||||||
extra_args.length > 0 ||
|
|
||||||
(node.type === 'AssignmentExpression' && node.left.type !== 'Identifier') ||
|
|
||||||
(node.type === 'UpdateExpression' && (!node.prefix || node.argument.type !== 'Identifier'));
|
|
||||||
if (pass_value) {
|
|
||||||
extra_args.unshift({
|
|
||||||
type: 'Identifier',
|
|
||||||
name: head.name
|
|
||||||
});
|
|
||||||
}
|
|
||||||
invalidate = x`$$invalidate(${
|
|
||||||
renderer.context_lookup.get(head.name).index
|
|
||||||
}, ${node}, ${extra_args})`;
|
|
||||||
} else {
|
|
||||||
// skip `$$invalidate` if it is in the main execution context
|
|
||||||
invalidate = extra_args.length ? [node, ...extra_args] : node;
|
|
||||||
}
|
|
||||||
if (head.subscribable && head.reassigned) {
|
|
||||||
const subscribe = `$$subscribe_${head.name}`;
|
|
||||||
invalidate = x`${subscribe}(${invalidate})`;
|
|
||||||
}
|
|
||||||
return invalidate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('./Renderer.js').default} renderer
|
|
||||||
* @param {string} name
|
|
||||||
* @param {any} [value]
|
|
||||||
* @param {boolean} [main_execution_context]
|
|
||||||
* @returns {import('estree').Node}
|
|
||||||
*/
|
|
||||||
export function renderer_invalidate(renderer, name, value, main_execution_context = false) {
|
|
||||||
const variable = renderer.component.var_lookup.get(name);
|
|
||||||
if (variable && variable.subscribable && (variable.reassigned || variable.export_name)) {
|
|
||||||
if (main_execution_context) {
|
|
||||||
return x`${`$$subscribe_${name}`}(${value || name})`;
|
|
||||||
} else {
|
|
||||||
const member = renderer.context_lookup.get(name);
|
|
||||||
return x`${`$$subscribe_${name}`}($$invalidate(${member.index}, ${value || name}))`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (name[0] === '$' && name[1] !== '$') {
|
|
||||||
return x`${name.slice(1)}.set(${value || name})`;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
variable &&
|
|
||||||
(variable.module ||
|
|
||||||
(!variable.referenced &&
|
|
||||||
!variable.is_reactive_dependency &&
|
|
||||||
!variable.export_name &&
|
|
||||||
!name.startsWith('$$')))
|
|
||||||
) {
|
|
||||||
return value || name;
|
|
||||||
}
|
|
||||||
if (value) {
|
|
||||||
if (main_execution_context) {
|
|
||||||
return x`${value}`;
|
|
||||||
} else {
|
|
||||||
const member = renderer.context_lookup.get(name);
|
|
||||||
return x`$$invalidate(${member.index}, ${value})`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (main_execution_context) return;
|
|
||||||
// if this is a reactive declaration, invalidate dependencies recursively
|
|
||||||
const deps = new Set([name]);
|
|
||||||
deps.forEach((name) => {
|
|
||||||
const reactive_declarations = renderer.component.reactive_declarations.filter((x) =>
|
|
||||||
x.assignees.has(name)
|
|
||||||
);
|
|
||||||
reactive_declarations.forEach((declaration) => {
|
|
||||||
declaration.dependencies.forEach((name) => {
|
|
||||||
deps.add(name);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// TODO ideally globals etc wouldn't be here in the first place
|
|
||||||
const filtered = Array.from(deps).filter((n) => renderer.context_lookup.has(n));
|
|
||||||
if (!filtered.length) return null;
|
|
||||||
return filtered
|
|
||||||
.map((n) => x`$$invalidate(${renderer.context_lookup.get(n).index}, ${n})`)
|
|
||||||
.reduce((lhs, rhs) => x`${lhs}, ${rhs}`);
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue