From a7eff8894fac1254466b885665ada783e82e3cb8 Mon Sep 17 00:00:00 2001 From: halfnelson Date: Tue, 26 Jan 2021 07:25:10 +1000 Subject: [PATCH] add `foreign` namespace to preserve attribute case and skip HTML-specific a11y validations (#5652) --- CHANGELOG.md | 1 + site/content/docs/02-template-syntax.md | 2 +- site/content/docs/04-compile-time.md | 2 +- src/compiler/compile/Component.ts | 3 +- src/compiler/compile/index.ts | 13 +- src/compiler/compile/nodes/Element.ts | 281 ++++++++++-------- .../render_dom/wrappers/Element/Attribute.ts | 28 +- src/compiler/interfaces.ts | 1 + src/compiler/utils/namespaces.ts | 7 +- .../_config.js | 20 ++ .../main.svelte | 3 + .../_config.js | 18 ++ .../main.svelte | 4 + test/validator/index.ts | 18 ++ .../a11y-in-foreign-namespace/input.svelte | 7 + .../a11y-in-foreign-namespace/warnings.json | 1 + .../errors.json | 15 + .../input.svelte | 6 + 18 files changed, 291 insertions(+), 139 deletions(-) create mode 100644 test/runtime/samples/attribute-casing-foreign-namespace-compiler-option/_config.js create mode 100644 test/runtime/samples/attribute-casing-foreign-namespace-compiler-option/main.svelte create mode 100644 test/runtime/samples/attribute-casing-foreign-namespace/_config.js create mode 100644 test/runtime/samples/attribute-casing-foreign-namespace/main.svelte create mode 100644 test/validator/samples/a11y-in-foreign-namespace/input.svelte create mode 100644 test/validator/samples/a11y-in-foreign-namespace/warnings.json create mode 100644 test/validator/samples/binding-invalid-foreign-namespace/errors.json create mode 100644 test/validator/samples/binding-invalid-foreign-namespace/input.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index f4f648916ffa..53175a203cba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased * Allow multiple instances of the same action on an element ([#5516](https://github.com/sveltejs/svelte/pull/5516)) +* Support `foreign` namespace, which disables certain HTML5-specific behaviour and checks ([#5652](https://github.com/sveltejs/svelte/pull/5652)) * Support inline comment sourcemaps in code from preprocessors ([#5854](https://github.com/sveltejs/svelte/pull/5854)) ## 3.31.2 diff --git a/site/content/docs/02-template-syntax.md b/site/content/docs/02-template-syntax.md index 6f1a2da61eb8..7288b8e8dd28 100644 --- a/site/content/docs/02-template-syntax.md +++ b/site/content/docs/02-template-syntax.md @@ -1530,7 +1530,7 @@ The `` element provides a place to specify per-component compile * `immutable={false}` — the default. Svelte will be more conservative about whether or not mutable objects have changed * `accessors={true}` — adds getters and setters for the component's props * `accessors={false}` — the default -* `namespace="..."` — the namespace where this component will be used, most commonly "svg" +* `namespace="..."` — the namespace where this component will be used, most commonly "svg"; use the "foreign" namespace to opt out of case-insensitive attribute names and HTML-specific warnings * `tag="..."` — the name to use when compiling this component as a custom element ```sv diff --git a/site/content/docs/04-compile-time.md b/site/content/docs/04-compile-time.md index 9fa0f9219505..48f3ae2c4eaa 100644 --- a/site/content/docs/04-compile-time.md +++ b/site/content/docs/04-compile-time.md @@ -80,7 +80,7 @@ The following options can be passed to the compiler. None are required: | `outputFilename` | `null` | A `string` used for your JavaScript sourcemap. | `cssOutputFilename` | `null` | A `string` used for your CSS sourcemap. | `sveltePath` | `"svelte"` | The location of the `svelte` package. Any imports from `svelte` or `svelte/[module]` will be modified accordingly. - +| `namespace` | `"html"` | The namespace of the element; e.g., `"mathml"`, `"svg"`, `"foreign"`. --- diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index 5fb81e7457af..d2b22c65e157 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -1349,7 +1349,8 @@ function process_component_options(component: Component, nodes) { 'accessors' in component.compile_options ? component.compile_options.accessors : !!component.compile_options.customElement, - preserveWhitespace: !!component.compile_options.preserveWhitespace + preserveWhitespace: !!component.compile_options.preserveWhitespace, + namespace: component.compile_options.namespace }; const node = nodes.find(node => node.name === 'svelte:options'); diff --git a/src/compiler/compile/index.ts b/src/compiler/compile/index.ts index 842539fcde88..9f9b31917e38 100644 --- a/src/compiler/compile/index.ts +++ b/src/compiler/compile/index.ts @@ -6,6 +6,7 @@ import { CompileOptions, Warning } from '../interfaces'; import Component from './Component'; import fuzzymatch from '../utils/fuzzymatch'; import get_name_from_filename from './utils/get_name_from_filename'; +import { valid_namespaces } from '../utils/namespaces'; const valid_options = [ 'format', @@ -22,6 +23,7 @@ const valid_options = [ 'hydratable', 'legacy', 'customElement', + 'namespace', 'tag', 'css', 'loopGuardTimeout', @@ -30,7 +32,7 @@ const valid_options = [ ]; function validate_options(options: CompileOptions, warnings: Warning[]) { - const { name, filename, loopGuardTimeout, dev } = options; + const { name, filename, loopGuardTimeout, dev, namespace } = options; Object.keys(options).forEach(key => { if (!valid_options.includes(key)) { @@ -65,6 +67,15 @@ function validate_options(options: CompileOptions, warnings: Warning[]) { toString: () => message }); } + + 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}'`); + } + } } export default function compile(source: string, options: CompileOptions = {}) { diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index b12e616f4dfb..8c5b4cb9f5c4 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -136,44 +136,45 @@ export default class Element extends Node { this.namespace = get_namespace(parent as Element, this, component.namespace); - if (this.name === 'textarea') { - if (info.children.length > 0) { - const value_attribute = info.attributes.find(node => node.name === 'value'); - if (value_attribute) { - component.error(value_attribute, { - code: 'textarea-duplicate-value', - message: 'A