diff --git a/scripts/packages/build-conditionals/index.mjs b/scripts/packages/build-conditionals/index.mjs index a3620761698..7bc996230c8 100644 --- a/scripts/packages/build-conditionals/index.mjs +++ b/scripts/packages/build-conditionals/index.mjs @@ -35,6 +35,7 @@ export const BUILD = { svg: true, updatable: true, vdomAttribute: true, + vdomXlink: true, vdomClass: true, vdomFunctional: true, vdomKey: true, diff --git a/src/compiler/app-core/build-conditionals.ts b/src/compiler/app-core/build-conditionals.ts index 3159b5804b5..ff4bd4b337d 100644 --- a/src/compiler/app-core/build-conditionals.ts +++ b/src/compiler/app-core/build-conditionals.ts @@ -46,6 +46,7 @@ export function getBuildFeatures(cmps: d.ComponentCompilerMeta[]) { svg: cmps.some(c => c.htmlTagNames.includes('svg')), updatable: cmps.some(c => c.isUpdateable), vdomAttribute: cmps.some(c => c.hasVdomAttribute), + vdomXlink: cmps.some(c => c.hasVdomXlink), vdomClass: cmps.some(c => c.hasVdomClass), vdomFunctional: cmps.some(c => c.hasVdomFunctional), vdomKey: cmps.some(c => c.hasVdomKey), @@ -69,6 +70,7 @@ export function updateComponentBuildConditionals(moduleMap: d.ModuleMap, cmps: d // if the component already has a boolean true value it'll keep it // otherwise we get the boolean value from the imported module cmp.hasVdomAttribute = cmp.hasVdomAttribute || importedModule.hasVdomAttribute; + cmp.hasVdomXlink = cmp.hasVdomXlink || importedModule.hasVdomXlink; cmp.hasVdomClass = cmp.hasVdomClass || importedModule.hasVdomClass; cmp.hasVdomFunctional = cmp.hasVdomFunctional || importedModule.hasVdomFunctional; cmp.hasVdomKey = cmp.hasVdomKey || importedModule.hasVdomKey; diff --git a/src/compiler/browser/build-conditionals-client.ts b/src/compiler/browser/build-conditionals-client.ts index 39447479365..f0e87e8ed68 100644 --- a/src/compiler/browser/build-conditionals-client.ts +++ b/src/compiler/browser/build-conditionals-client.ts @@ -1,6 +1,6 @@ import * as d from '../../declarations'; -export const BUILD: d.Build = { +export const BUILD: Required = { allRenderFn: false, cmpDidLoad: true, cmpDidUnload: true, @@ -9,6 +9,7 @@ export const BUILD: d.Build = { cmpWillLoad: true, cmpWillUpdate: true, cmpWillRender: true, + cmpShouldUpdate: true, connectedCallback: true, cssAnnotations: true, disconnectedCallback: true, @@ -29,6 +30,9 @@ export const BUILD: d.Build = { observeAttribute: true, prop: true, propMutable: true, + propBoolean: true, + propNumber: true, + propString: true, reflect: true, scoped: true, shadowDom: true, @@ -38,6 +42,7 @@ export const BUILD: d.Build = { svg: true, updatable: true, vdomAttribute: true, + vdomXlink: true, vdomClass: true, vdomFunctional: true, vdomKey: true, @@ -52,6 +57,9 @@ export const BUILD: d.Build = { hotModuleReplacement: false, isDebug: false, isDev: false, + cssVarShim: false, + constructableCSS: true, + initializeNextTick: false, hydrateServerSide: false, hydrateClientSide: false, lifecycleDOMEvents: false, diff --git a/src/compiler/build/compiler-ctx.ts b/src/compiler/build/compiler-ctx.ts index 0023bcddd2e..cdb4af11ef5 100644 --- a/src/compiler/build/compiler-ctx.ts +++ b/src/compiler/build/compiler-ctx.ts @@ -96,6 +96,7 @@ export const getModule = (config: d.Config, compilerCtx: d.CompilerCtx, sourceFi excludeFromCollection: false, externalImports: [], hasVdomAttribute: false, + hasVdomXlink: false, hasVdomClass: false, hasVdomFunctional: false, hasVdomKey: false, @@ -131,15 +132,16 @@ export const resetModule = (moduleFile: d.Module) => { moduleFile.originalCollectionComponentPath = null; moduleFile.originalImports.length = 0; - moduleFile.hasVdomAttribute = true; - moduleFile.hasVdomClass = true; - moduleFile.hasVdomFunctional = true; - moduleFile.hasVdomKey = true; - moduleFile.hasVdomListener = true; - moduleFile.hasVdomRef = true; + moduleFile.hasVdomXlink = false; + moduleFile.hasVdomAttribute = false; + moduleFile.hasVdomClass = false; + moduleFile.hasVdomFunctional = false; + moduleFile.hasVdomKey = false; + moduleFile.hasVdomListener = false; + moduleFile.hasVdomRef = false; moduleFile.hasVdomRender = false; - moduleFile.hasVdomStyle = true; - moduleFile.hasVdomText = true; + moduleFile.hasVdomStyle = false; + moduleFile.hasVdomText = false; moduleFile.htmlAttrNames.length = 0; moduleFile.htmlTagNames.length = 0; moduleFile.potentialCmpRefs.length = 0; diff --git a/src/compiler/config/validate-testing.ts b/src/compiler/config/validate-testing.ts index f666199c951..05780e662f7 100644 --- a/src/compiler/config/validate-testing.ts +++ b/src/compiler/config/validate-testing.ts @@ -1,5 +1,4 @@ import * as d from '../../declarations'; -import os from 'os'; import { isOutputTargetDist, isOutputTargetWww } from '../output-targets/output-utils'; import { buildError, buildWarn } from '@utils'; @@ -24,9 +23,6 @@ export function validateTesting(config: d.Config, diagnostics: d.Diagnostic[]) { testing.browserArgs = testing.browserArgs || []; addOption(testing.browserArgs, '--font-render-hinting=medium'); addOption(testing.browserArgs, '--enable-font-antialiasing'); - if (os.platform() === 'win32') { - addOption(testing.browserArgs, '--disable-gpu'); - } if (config.flags.ci) { addOption(testing.browserArgs, '--no-sandbox'); diff --git a/src/compiler/transformers/collections/parse-collection-deprecated.ts b/src/compiler/transformers/collections/parse-collection-deprecated.ts index a326b1b30cf..9ce9f82da19 100644 --- a/src/compiler/transformers/collections/parse-collection-deprecated.ts +++ b/src/compiler/transformers/collections/parse-collection-deprecated.ts @@ -87,6 +87,7 @@ function parseComponentDeprecated(config: d.Config, compilerCtx: d.CompilerCtx, hasState: false, hasStyle: false, hasVdomAttribute: true, + hasVdomXlink: true, hasVdomClass: true, hasVdomFunctional: true, hasVdomKey: true, diff --git a/src/compiler/transformers/static-to-meta/call-expression.ts b/src/compiler/transformers/static-to-meta/call-expression.ts index 6b903fa8634..44038a3bf0a 100644 --- a/src/compiler/transformers/static-to-meta/call-expression.ts +++ b/src/compiler/transformers/static-to-meta/call-expression.ts @@ -7,14 +7,14 @@ import ts from 'typescript'; export const parseCallExpression = (m: d.Module | d.ComponentCompilerMeta, node: ts.CallExpression) => { if (node.arguments != null && node.arguments.length > 0) { - if (node.expression.kind === ts.SyntaxKind.Identifier) { + if (ts.isIdentifier(node.expression)) { // h('tag') - visitCallExpressionArgs(m, node.expression as ts.Identifier, node.arguments); + visitCallExpressionArgs(m, node.expression, node.arguments); - } else if (node.expression.kind === ts.SyntaxKind.PropertyAccessExpression) { + } else if (ts.isPropertyAccessExpression(node.expression)) { // document.createElement('tag') - if ((node.expression as ts.PropertyAccessExpression).name) { - visitCallExpressionArgs(m, (node.expression as ts.PropertyAccessExpression).name as ts.Identifier, node.arguments); + if (node.expression.name) { + visitCallExpressionArgs(m, node.expression.name, node.arguments); } } } diff --git a/src/compiler/transformers/static-to-meta/component.ts b/src/compiler/transformers/static-to-meta/component.ts index 3800aaa9a15..dfd5947b7a0 100644 --- a/src/compiler/transformers/static-to-meta/component.ts +++ b/src/compiler/transformers/static-to-meta/component.ts @@ -18,7 +18,7 @@ import { parseStringLiteral } from './string-literal'; import ts from 'typescript'; -export const parseStaticComponentMeta = (config: d.Config, compilerCtx: d.CompilerCtx, transformCtx: ts.TransformationContext, typeChecker: ts.TypeChecker, cmpNode: ts.ClassDeclaration, moduleFile: d.Module, nodeMap: d.NodeMap, transformOpts: d.TransformOptions, fileCmpNodes: ts.ClassDeclaration[]) => { +export const parseStaticComponentMeta = (config: d.Config, compilerCtx: d.CompilerCtx, typeChecker: ts.TypeChecker, cmpNode: ts.ClassDeclaration, moduleFile: d.Module, nodeMap: d.NodeMap, transformOpts: d.TransformOptions, fileCmpNodes: ts.ClassDeclaration[]) => { if (cmpNode.members == null) { return cmpNode; } @@ -91,6 +91,7 @@ export const parseStaticComponentMeta = (config: d.Config, compilerCtx: d.Compil hasState: false, hasStyle: false, hasVdomAttribute: false, + hasVdomXlink: false, hasVdomClass: false, hasVdomFunctional: false, hasVdomKey: false, @@ -107,16 +108,15 @@ export const parseStaticComponentMeta = (config: d.Config, compilerCtx: d.Compil potentialCmpRefs: [] }; - const visitComponentChildNode = (node: ts.Node): ts.VisitResult => { + const visitComponentChildNode = (node: ts.Node) => { if (ts.isCallExpression(node)) { parseCallExpression(cmp, node); } else if (ts.isStringLiteral(node)) { parseStringLiteral(cmp, node); } - return ts.visitEachChild(node, visitComponentChildNode, transformCtx); + node.forEachChild(visitComponentChildNode); }; - ts.visitEachChild(cmpNode, visitComponentChildNode, transformCtx); - + visitComponentChildNode(cmpNode); parseClassMethods(cmpNode, cmp); cmp.legacyConnect.forEach(({connect}) => { diff --git a/src/compiler/transformers/static-to-meta/vdom.ts b/src/compiler/transformers/static-to-meta/vdom.ts index 824de6cff81..52bde8f71f1 100644 --- a/src/compiler/transformers/static-to-meta/vdom.ts +++ b/src/compiler/transformers/static-to-meta/vdom.ts @@ -10,8 +10,10 @@ export const gatherVdomMeta = (m: d.Module | d.ComponentCompilerMeta, args: ts.N } if (args.length > 1) { - if (ts.isObjectLiteralExpression(args[1])) { - const props: ts.ObjectLiteralElementLike[] = ((args[1] as ts.ObjectLiteralExpression).properties as any); + const objectLiteral = args[1]; + + if (ts.isObjectLiteralExpression(objectLiteral)) { + const props = objectLiteral.properties; const propsWithText = props .filter(p => p.name && (p.name as any).text && (p.name as any).text.length > 0) @@ -35,6 +37,9 @@ export const gatherVdomMeta = (m: d.Module | d.ComponentCompilerMeta, args: ts.N m.hasVdomListener = true; attrs.delete(attr); } + if (attr.startsWith('xlink')) { + m.hasVdomXlink = true; + } }); if (attrs.size > 0) { diff --git a/src/compiler/transformers/static-to-meta/visitor.ts b/src/compiler/transformers/static-to-meta/visitor.ts index fb8065032bd..8c9975a1936 100644 --- a/src/compiler/transformers/static-to-meta/visitor.ts +++ b/src/compiler/transformers/static-to-meta/visitor.ts @@ -17,7 +17,7 @@ export const convertStaticToMeta = (config: d.Config, compilerCtx: d.CompilerCtx const visitNode = (node: ts.Node): ts.VisitResult => { if (ts.isClassDeclaration(node)) { - return parseStaticComponentMeta(config, compilerCtx, transformCtx, typeChecker, node, moduleFile, compilerCtx.nodeMap, transformOpts, fileCmpNodes); + return parseStaticComponentMeta(config, compilerCtx, typeChecker, node, moduleFile, compilerCtx.nodeMap, transformOpts, fileCmpNodes); } else if (ts.isImportDeclaration(node)) { return parseImport(config, compilerCtx, buildCtx, moduleFile, dirPath, node); } else if (ts.isCallExpression(node)) { diff --git a/src/declarations/build-conditionals.ts b/src/declarations/build-conditionals.ts index c06fd0ebf1e..0d2536604b3 100644 --- a/src/declarations/build-conditionals.ts +++ b/src/declarations/build-conditionals.ts @@ -22,6 +22,7 @@ export interface BuildFeatures { vdomRender: boolean; noVdomRender: boolean; vdomAttribute: boolean; + vdomXlink: boolean; vdomClass: boolean; vdomStyle: boolean; vdomKey: boolean; diff --git a/src/declarations/component-compiler-meta.ts b/src/declarations/component-compiler-meta.ts index 01adbb0a49e..5edd8de5261 100644 --- a/src/declarations/component-compiler-meta.ts +++ b/src/declarations/component-compiler-meta.ts @@ -37,6 +37,7 @@ export interface ComponentCompilerFeatures { hasState: boolean; hasStyle: boolean; hasVdomAttribute: boolean; + hasVdomXlink: boolean; hasVdomClass: boolean; hasVdomFunctional: boolean; hasVdomKey: boolean; diff --git a/src/declarations/module.ts b/src/declarations/module.ts index a9bf0e93393..06bcdb57cb4 100644 --- a/src/declarations/module.ts +++ b/src/declarations/module.ts @@ -14,15 +14,6 @@ export interface Module { dtsFilePath: string; excludeFromCollection: boolean; externalImports: string[]; - hasVdomAttribute: boolean; - hasVdomClass: boolean; - hasVdomFunctional: boolean; - hasVdomKey: boolean; - hasVdomListener: boolean; - hasVdomRef: boolean; - hasVdomRender: boolean; - hasVdomStyle: boolean; - hasVdomText: boolean; htmlAttrNames: string[]; htmlTagNames: string[]; isCollectionDependency: boolean; @@ -33,4 +24,16 @@ export interface Module { originalCollectionComponentPath: string; potentialCmpRefs: string[]; sourceFilePath: string; + + // build features + hasVdomAttribute: boolean; + hasVdomXlink: boolean; + hasVdomClass: boolean; + hasVdomFunctional: boolean; + hasVdomKey: boolean; + hasVdomListener: boolean; + hasVdomRef: boolean; + hasVdomRender: boolean; + hasVdomStyle: boolean; + hasVdomText: boolean; } diff --git a/src/mock-doc/attribute.ts b/src/mock-doc/attribute.ts index cda76e41c4c..a42fcfc1a17 100644 --- a/src/mock-doc/attribute.ts +++ b/src/mock-doc/attribute.ts @@ -115,7 +115,7 @@ export class MockAttr { return this._name; } set name(value) { - this._name = value.toLowerCase(); + this._name = value; } get value() { diff --git a/src/mock-doc/test/html-parse.spec.ts b/src/mock-doc/test/html-parse.spec.ts index 75cc55840b4..04590e75983 100644 --- a/src/mock-doc/test/html-parse.spec.ts +++ b/src/mock-doc/test/html-parse.spec.ts @@ -80,6 +80,14 @@ describe('parseHtml', () => { `); }); + it('svg attributes', () => { + doc = new MockDocument(` + + `); + + expect(doc.body.firstElementChild.attributes.item(0).name).toEqual('viewBox'); + }); + it('template', () => { doc = new MockDocument(` diff --git a/src/runtime/get-asset-path.ts b/src/runtime/get-asset-path.ts index 73e9a4351f7..db9246ed7b1 100644 --- a/src/runtime/get-asset-path.ts +++ b/src/runtime/get-asset-path.ts @@ -6,4 +6,3 @@ export const getAssetPath = (path: string) => { ? assetUrl.href : assetUrl.pathname; }; - diff --git a/src/runtime/test/svg-element.spec.tsx b/src/runtime/test/svg-element.spec.tsx index b566bfb1bbd..32d8df0a917 100644 --- a/src/runtime/test/svg-element.spec.tsx +++ b/src/runtime/test/svg-element.spec.tsx @@ -2,46 +2,79 @@ import { Component, h } from '@stencil/core'; import { newSpecPage } from '@stencil/core/testing'; describe('SVG element', () => { - @Component({ tag: 'cmp-a' }) - class CmpA { - render() { - return ( -
- Dude!! - - + it('should render camelCase attributes', async () => { + @Component({ tag: 'cmp-a' }) + class CmpA { + render() { + const A = 'a' as any; + return ( + + + -
- ); + ); + } } - } - - let path: SVGGeometryElement; - beforeEach(async () => { - const page = await newSpecPage({ + const { root } = await newSpecPage({ components: [CmpA], html: `` }); - path = page.root.querySelector('#my-svg-path'); + expect(root).toEqualHtml(` + + + + + + + `); }); - it('allows read access to the ownerSVGElement property', () => { - expect(path.ownerSVGElement).toEqual(null); - }); + describe('path', () => { + @Component({ tag: 'cmp-a' }) + class CmpA { + render() { + return ( +
+ Dude!! + + + +
+ ); + } + } - it('allows read access to the viewportElement property', () => { - expect(path.viewportElement).toEqual(null); - }); + let path: SVGGeometryElement; + beforeEach(async () => { + const page = await newSpecPage({ + components: [CmpA], + html: `` + }); + path = page.root.querySelector('#my-svg-path'); + }); - it('allows access to the getTotalLength() method', () => { - expect(path.getTotalLength()).toEqual(0); - }); + it('path namespace is SVG', () => { + expect(path.namespaceURI).toEqual('http://www.w3.org/2000/svg'); + }); - it('allows access to the isPointInFill() method', () => { - expect(path.isPointInFill()).toEqual(false); - }); + it('allows read access to the ownerSVGElement property', () => { + expect(path.ownerSVGElement).toEqual(null); + }); + + it('allows read access to the viewportElement property', () => { + expect(path.viewportElement).toEqual(null); + }); + + it('allows access to the getTotalLength() method', () => { + expect(path.getTotalLength()).toEqual(0); + }); + + it('allows access to the isPointInFill() method', () => { + expect(path.isPointInFill()).toEqual(false); + }); - it('allows access to the isPointInStroke() method', () => { - expect(path.isPointInStroke()).toEqual(false); + it('allows access to the isPointInStroke() method', () => { + expect(path.isPointInStroke()).toEqual(false); + }); }); }); diff --git a/src/runtime/vdom/set-accessor.ts b/src/runtime/vdom/set-accessor.ts index be75f3a7d42..61b5debcb5d 100644 --- a/src/runtime/vdom/set-accessor.ts +++ b/src/runtime/vdom/set-accessor.ts @@ -94,18 +94,6 @@ export const setAccessor = (elm: HTMLElement, memberName: string, oldValue: any, } else { // Set property if it exists and it's not a SVG const isComplex = isComplexType(newValue); - - /** - * Need to manually update attribute if: - * - memberName is not an attribute - * - if we are rendering the host element in order to reflect attribute - * - if it's a SVG, since properties might not work in - * - if the newValue is null/undefined or 'false'. - */ - const namespace = BUILD.svg && isSvg && (ln !== (ln = ln.replace(/^xlink\:?/, ''))) - ? XLINK_NS - : null; - if ((isProp || (isComplex && newValue !== null)) && !isSvg) { try { if (!elm.tagName.includes('-')) { @@ -121,11 +109,33 @@ export const setAccessor = (elm: HTMLElement, memberName: string, oldValue: any, } catch (e) {} } + /** + * Need to manually update attribute if: + * - memberName is not an attribute + * - if we are rendering the host element in order to reflect attribute + * - if it's a SVG, since properties might not work in + * - if the newValue is null/undefined or 'false'. + */ + let xlink = false; + if (BUILD.vdomXlink) { + if (ln !== (ln = ln.replace(/^xlink\:?/, ''))) { + memberName = ln; + xlink = true; + } + } if (newValue == null || newValue === false) { - elm.removeAttributeNS(namespace, ln); + if (BUILD.vdomXlink && xlink) { + elm.removeAttributeNS(XLINK_NS, memberName); + } else { + elm.removeAttribute(memberName); + } } else if ((!isProp || (flags & VNODE_FLAGS.isHost) || isSvg) && !isComplex) { newValue = newValue === true ? '' : newValue; - elm.setAttributeNS(namespace, ln, newValue); + if (BUILD.vdomXlink && xlink) { + elm.setAttributeNS(XLINK_NS, memberName, newValue); + } else { + elm.setAttribute(memberName, newValue); + } } } }; diff --git a/src/testing/build-conditionals.ts b/src/testing/build-conditionals.ts index e93d9df7e1a..a147c72aeea 100644 --- a/src/testing/build-conditionals.ts +++ b/src/testing/build-conditionals.ts @@ -6,11 +6,7 @@ export const BUILD: d.Build = {}; export function resetBuildConditionals(b: d.Build) { Object.keys(b).forEach(key => { - if (typeof (b as any)[key] === 'boolean') { - (b as any)[key] = true; - } else { - (b as any)[key] = null; - } + (b as any)[key] = true; }); b.isDev = true; @@ -23,6 +19,7 @@ export function resetBuildConditionals(b: d.Build) { b.svg = true; b.updatable = true; b.vdomAttribute = true; + b.vdomXlink = true; b.vdomClass = true; b.vdomStyle = true; b.vdomKey = true; diff --git a/src/testing/spec-page.ts b/src/testing/spec-page.ts index a548048e026..5827091b914 100644 --- a/src/testing/spec-page.ts +++ b/src/testing/spec-page.ts @@ -91,6 +91,7 @@ export async function newSpecPage(opts: d.NewSpecPageOptions): Promise