feat(ivy): added new namespace and element instructions to JIT environment (#23899)

PR Close #23899
This commit is contained in:
Ben Lesh
2018-06-01 16:00:51 -07:00
committed by Victor Berchet
parent 1007d1ad27
commit acf270d724
18 changed files with 126 additions and 75 deletions

View File

@ -136,7 +136,8 @@ export function compileComponentFromMetadata(
const templateFunctionExpression =
new TemplateDefinitionBuilder(
constantPool, CONTEXT_NAME, BindingScope.ROOT_SCOPE, 0, templateTypeName, templateName,
meta.viewQueries, directiveMatcher, directivesUsed, meta.pipes, pipesUsed)
meta.viewQueries, directiveMatcher, directivesUsed, meta.pipes, pipesUsed,
R3.namespaceHTML)
.buildTemplateFunction(
template.nodes, [], template.hasNgContent, template.ngContentSelectors);
@ -443,4 +444,4 @@ function typeMapToExpressionMap(
const entries = Array.from(map).map(
([key, type]): [string, o.Expression] => [key, outputCtx.importExpr(type)]);
return new Map(entries);
}
}

View File

@ -18,8 +18,8 @@ import * as html from '../../ml_parser/ast';
import {HtmlParser} from '../../ml_parser/html_parser';
import {WhitespaceVisitor} from '../../ml_parser/html_whitespaces';
import {DEFAULT_INTERPOLATION_CONFIG} from '../../ml_parser/interpolation_config';
import {splitNsName} from '../../ml_parser/tags';
import * as o from '../../output/output_ast';
import {ExternalReference} from '../../output/output_ast';
import {ParseError, ParseSourceSpan} from '../../parse_util';
import {DomElementSchemaRegistry} from '../../schema/dom_element_schema_registry';
import {CssSelector, SelectorMatcher} from '../../selector';
@ -52,7 +52,6 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
private _valueConverter: ValueConverter;
private _unsupported = unsupported;
private _bindingScope: BindingScope;
private _namespace = R3.namespaceHTML;
// Whether we are inside a translatable element (`<p i18n>... somewhere here ... </p>)
private _inI18nSection: boolean = false;
@ -68,7 +67,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
parentBindingScope: BindingScope, private level = 0, private contextName: string|null,
private templateName: string|null, private viewQueries: R3QueryMetadata[],
private directiveMatcher: SelectorMatcher|null, private directives: Set<o.Expression>,
private pipeTypeByName: Map<string, o.Expression>, private pipes: Set<o.Expression>) {
private pipeTypeByName: Map<string, o.Expression>, private pipes: Set<o.Expression>,
private _namespace: o.ExternalReference) {
this._bindingScope =
parentBindingScope.nestedScope((lhsVar: o.ReadVarExpr, expression: o.Expression) => {
this._bindingCode.push(
@ -91,6 +91,10 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
buildTemplateFunction(
nodes: t.Node[], variables: t.Variable[], hasNgContent: boolean = false,
ngContentSelectors: string[] = []): o.FunctionExpr {
if (this._namespace !== R3.namespaceHTML) {
this.instruction(this._creationCode, null, this._namespace);
}
// Create variable bindings
for (const variable of variables) {
const variableName = variable.name;
@ -224,20 +228,20 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
/**
* Gets the namespace instruction function based on the current element
* @param ivyElementName An system element name, can include colons like :svg:svg
* @param namespaceKey A system key for a namespace (e.g. 'svg' or 'math')
*/
getNamespaceInstruction(ivyElementName: string) {
switch (ivyElementName) {
case ':svg:svg':
getNamespaceInstruction(namespaceKey: string|null) {
switch (namespaceKey) {
case 'svg':
return R3.namespaceSVG;
case ':math:math':
case 'math':
return R3.namespaceMathML;
default:
return this._namespace;
return R3.namespaceHTML;
}
}
addNamespaceInstruction(nsInstruction: ExternalReference, element: t.Element) {
addNamespaceInstruction(nsInstruction: o.ExternalReference, element: t.Element) {
this._namespace = nsInstruction;
this.instruction(this._creationCode, element.sourceSpan, nsInstruction);
}
@ -249,7 +253,9 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
const outputAttrs: {[name: string]: string} = {};
const attrI18nMetas: {[name: string]: string} = {};
let i18nMeta: string = '';
let i18nMeta = '';
const [namespaceKey, elementName] = splitNsName(element.name);
// Elements inside i18n sections are replaced with placeholders
// TODO(vicb): nested elements are a WIP in this phase
@ -291,7 +297,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
// Element creation mode
const parameters: o.Expression[] = [
o.literal(elementIndex),
o.literal(element.name),
o.literal(elementName),
];
// Add the attributes
@ -343,7 +349,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
const implicit = o.variable(CONTEXT_NAME);
const wasInNamespace = this._namespace;
const currentNamespace = this.getNamespaceInstruction(element.name);
const currentNamespace = this.getNamespaceInstruction(namespaceKey);
// If the namespace is changing now, include an instruction to change it
// during element creation.
if (currentNamespace !== wasInNamespace) {
@ -359,14 +366,6 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
this._creationCode, element.sourceSpan, R3.elementStart,
...trimTrailingNulls(parameters));
// If the element happens to be an SVG <foreignObject>, we need to switch
// to the HTML namespace inside of it
if (element.name === ':svg:foreignObject') {
// NOTE(benlesh): this may cause extremem corner-case bugs if someone was to do something
// like <math>...<foreignObject></foreignObject>...</math>.
this.addNamespaceInstruction(R3.namespaceHTML, element);
}
// Generate Listeners (outputs)
element.outputs.forEach((outputAst: t.BoundEvent) => {
const elName = sanitizeIdentifier(element.name);
@ -423,7 +422,6 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
}
// Restore the state before exiting this node
this._inI18nSection = wasInI18nSection;
this._namespace = wasInNamespace;
}
visitTemplate(template: t.Template) {
@ -484,7 +482,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
// Create the template function
const templateVisitor = new TemplateDefinitionBuilder(
this.constantPool, templateContext, this._bindingScope, this.level + 1, contextName,
templateName, [], this.directiveMatcher, this.directives, this.pipeTypeByName, this.pipes);
templateName, [], this.directiveMatcher, this.directives, this.pipeTypeByName, this.pipes,
this._namespace);
const templateFunctionExpr =
templateVisitor.buildTemplateFunction(template.children, template.variables);
this._postfixCode.push(templateFunctionExpr.toDeclStmt(templateName, null));

View File

@ -95,8 +95,8 @@ describe('compiler compliance', () => {
if (rf & 1) {
$r3$.ɵE(0, 'div', $e0_attrs$);
$r3$.ɵNS();
$r3$.ɵE(1, ':svg:svg');
$r3$.ɵEe(2, ':svg:circle', $e2_attrs$);
$r3$.ɵE(1, 'svg');
$r3$.ɵEe(2, 'circle', $e2_attrs$);
$r3$.ɵe();
$r3$.ɵe();
}
@ -110,6 +110,54 @@ describe('compiler compliance', () => {
expectEmit(result.source, template, 'Incorrect template');
});
it('should enter and leave the SVG namespace appropriately', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`<div class="my-app" title="Hello"><svg><circle cx="50" cy="100" r="25"/></svg><p>TEST 2</p></div>\`
})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
// The factory should look like this:
const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }';
// The template should look like this (where IDENT is a wild card for an identifier):
const template = `
const $c1$ = ['class', 'my-app', 'title', 'Hello'];
template: function MyComponent_Template(rf: IDENT, ctx: IDENT) {
if (rf & 1) {
$r3$.ɵE(0, 'div', $e0_attrs$);
$r3$.ɵNS();
$r3$.ɵE(1, 'svg');
$r3$.ɵEe(2, 'circle', $e2_attrs$);
$r3$.ɵe();
$r3$.ɵNH();
$r3$.ɵE(3, 'p');
$r3$.ɵT(4, 'TEST 2');
$r3$.ɵe();
$r3$.ɵe();
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, factory, 'Incorrect factory');
expectEmit(result.source, template, 'Incorrect template');
});
it('should bind to element properties', () => {
const files = {
app: {
@ -1274,7 +1322,7 @@ describe('compiler compliance', () => {
expectEmit(source, MyComponentDefinition, 'Invalid component definition');
});
it('should support a let variable and reference for SVG', () => {
it('should support embedded views in the SVG namespace', () => {
const files = {
app: {
...shared,
@ -1321,15 +1369,16 @@ describe('compiler compliance', () => {
template: function MyComponent_Template(rf:IDENT,ctx:IDENT){
if (rf & 1) {
$r3$.ɵNS();
$r3$.ɵE(0,':svg:svg');
$r3$.ɵE(0,'svg');
$r3$.ɵC(1,MyComponent__svg_g_Template_1,null,$_c0$);
$r3$.ɵe();
}
if (rf & 2) { $r3$.ɵp(1,'forOf',$r3$.ɵb(ctx.items)); }
function MyComponent__svg_g_Template_1(rf:IDENT,ctx0:IDENT) {
if (rf & 1) {
$r3$E(0,':svg:g');
$r3$.ɵEe(1,':svg:circle');
$r3$NS();
$r3$.ɵE(0,'g');
$r3$.ɵEe(1,'circle');
$r3$.ɵe();
}
}