From 1f59f2f04dfe18a5c8dfe52a8268b498535f4df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=A1ko=20Hevery?= Date: Thu, 16 Aug 2018 14:37:21 -0700 Subject: [PATCH] fix(core): size regression with closure compiler (#25531) By pulling in `compiler` into `core` the `compiler` was not 100% tree-shakable and about 8KB of code was retained when tree-shaken with closure. PR Close #25531 --- packages/compiler/src/ml_parser/html_tags.ts | 117 ++++++++++-------- .../compiler/src/render3/view/template.ts | 8 +- .../src/schema/dom_element_schema_registry.ts | 4 +- .../src/schema/dom_security_schema.ts | 72 ++++++----- .../src/template_parser/template_parser.ts | 12 +- .../hello_world_r2/bundle.golden_symbols.json | 9 +- 6 files changed, 123 insertions(+), 99 deletions(-) diff --git a/packages/compiler/src/ml_parser/html_tags.ts b/packages/compiler/src/ml_parser/html_tags.ts index 9d3da64f4e..0b49ab978d 100644 --- a/packages/compiler/src/ml_parser/html_tags.ts +++ b/packages/compiler/src/ml_parser/html_tags.ts @@ -69,63 +69,72 @@ export class HtmlTagDefinition implements TagDefinition { } } +let _DEFAULT_TAG_DEFINITION !: HtmlTagDefinition; + // see http://www.w3.org/TR/html51/syntax.html#optional-tags // This implementation does not fully conform to the HTML5 spec. -const TAG_DEFINITIONS: {[key: string]: HtmlTagDefinition} = { - 'base': new HtmlTagDefinition({isVoid: true}), - 'meta': new HtmlTagDefinition({isVoid: true}), - 'area': new HtmlTagDefinition({isVoid: true}), - 'embed': new HtmlTagDefinition({isVoid: true}), - 'link': new HtmlTagDefinition({isVoid: true}), - 'img': new HtmlTagDefinition({isVoid: true}), - 'input': new HtmlTagDefinition({isVoid: true}), - 'param': new HtmlTagDefinition({isVoid: true}), - 'hr': new HtmlTagDefinition({isVoid: true}), - 'br': new HtmlTagDefinition({isVoid: true}), - 'source': new HtmlTagDefinition({isVoid: true}), - 'track': new HtmlTagDefinition({isVoid: true}), - 'wbr': new HtmlTagDefinition({isVoid: true}), - 'p': new HtmlTagDefinition({ - closedByChildren: [ - 'address', 'article', 'aside', 'blockquote', 'div', 'dl', 'fieldset', 'footer', 'form', - 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', - 'main', 'nav', 'ol', 'p', 'pre', 'section', 'table', 'ul' - ], - closedByParent: true - }), - 'thead': new HtmlTagDefinition({closedByChildren: ['tbody', 'tfoot']}), - 'tbody': new HtmlTagDefinition({closedByChildren: ['tbody', 'tfoot'], closedByParent: true}), - 'tfoot': new HtmlTagDefinition({closedByChildren: ['tbody'], closedByParent: true}), - 'tr': new HtmlTagDefinition({ - closedByChildren: ['tr'], - requiredParents: ['tbody', 'tfoot', 'thead'], - closedByParent: true - }), - 'td': new HtmlTagDefinition({closedByChildren: ['td', 'th'], closedByParent: true}), - 'th': new HtmlTagDefinition({closedByChildren: ['td', 'th'], closedByParent: true}), - 'col': new HtmlTagDefinition({requiredParents: ['colgroup'], isVoid: true}), - 'svg': new HtmlTagDefinition({implicitNamespacePrefix: 'svg'}), - 'math': new HtmlTagDefinition({implicitNamespacePrefix: 'math'}), - 'li': new HtmlTagDefinition({closedByChildren: ['li'], closedByParent: true}), - 'dt': new HtmlTagDefinition({closedByChildren: ['dt', 'dd']}), - 'dd': new HtmlTagDefinition({closedByChildren: ['dt', 'dd'], closedByParent: true}), - 'rb': new HtmlTagDefinition({closedByChildren: ['rb', 'rt', 'rtc', 'rp'], closedByParent: true}), - 'rt': new HtmlTagDefinition({closedByChildren: ['rb', 'rt', 'rtc', 'rp'], closedByParent: true}), - 'rtc': new HtmlTagDefinition({closedByChildren: ['rb', 'rtc', 'rp'], closedByParent: true}), - 'rp': new HtmlTagDefinition({closedByChildren: ['rb', 'rt', 'rtc', 'rp'], closedByParent: true}), - 'optgroup': new HtmlTagDefinition({closedByChildren: ['optgroup'], closedByParent: true}), - 'option': new HtmlTagDefinition({closedByChildren: ['option', 'optgroup'], closedByParent: true}), - 'pre': new HtmlTagDefinition({ignoreFirstLf: true}), - 'listing': new HtmlTagDefinition({ignoreFirstLf: true}), - 'style': new HtmlTagDefinition({contentType: TagContentType.RAW_TEXT}), - 'script': new HtmlTagDefinition({contentType: TagContentType.RAW_TEXT}), - 'title': new HtmlTagDefinition({contentType: TagContentType.ESCAPABLE_RAW_TEXT}), - 'textarea': - new HtmlTagDefinition({contentType: TagContentType.ESCAPABLE_RAW_TEXT, ignoreFirstLf: true}), -}; - -const _DEFAULT_TAG_DEFINITION = new HtmlTagDefinition(); +let TAG_DEFINITIONS !: {[key: string]: HtmlTagDefinition}; export function getHtmlTagDefinition(tagName: string): HtmlTagDefinition { + if (!TAG_DEFINITIONS) { + _DEFAULT_TAG_DEFINITION = new HtmlTagDefinition(); + TAG_DEFINITIONS = { + 'base': new HtmlTagDefinition({isVoid: true}), + 'meta': new HtmlTagDefinition({isVoid: true}), + 'area': new HtmlTagDefinition({isVoid: true}), + 'embed': new HtmlTagDefinition({isVoid: true}), + 'link': new HtmlTagDefinition({isVoid: true}), + 'img': new HtmlTagDefinition({isVoid: true}), + 'input': new HtmlTagDefinition({isVoid: true}), + 'param': new HtmlTagDefinition({isVoid: true}), + 'hr': new HtmlTagDefinition({isVoid: true}), + 'br': new HtmlTagDefinition({isVoid: true}), + 'source': new HtmlTagDefinition({isVoid: true}), + 'track': new HtmlTagDefinition({isVoid: true}), + 'wbr': new HtmlTagDefinition({isVoid: true}), + 'p': new HtmlTagDefinition({ + closedByChildren: [ + 'address', 'article', 'aside', 'blockquote', 'div', 'dl', 'fieldset', + 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', + 'h6', 'header', 'hgroup', 'hr', 'main', 'nav', 'ol', + 'p', 'pre', 'section', 'table', 'ul' + ], + closedByParent: true + }), + 'thead': new HtmlTagDefinition({closedByChildren: ['tbody', 'tfoot']}), + 'tbody': new HtmlTagDefinition({closedByChildren: ['tbody', 'tfoot'], closedByParent: true}), + 'tfoot': new HtmlTagDefinition({closedByChildren: ['tbody'], closedByParent: true}), + 'tr': new HtmlTagDefinition({ + closedByChildren: ['tr'], + requiredParents: ['tbody', 'tfoot', 'thead'], + closedByParent: true + }), + 'td': new HtmlTagDefinition({closedByChildren: ['td', 'th'], closedByParent: true}), + 'th': new HtmlTagDefinition({closedByChildren: ['td', 'th'], closedByParent: true}), + 'col': new HtmlTagDefinition({requiredParents: ['colgroup'], isVoid: true}), + 'svg': new HtmlTagDefinition({implicitNamespacePrefix: 'svg'}), + 'math': new HtmlTagDefinition({implicitNamespacePrefix: 'math'}), + 'li': new HtmlTagDefinition({closedByChildren: ['li'], closedByParent: true}), + 'dt': new HtmlTagDefinition({closedByChildren: ['dt', 'dd']}), + 'dd': new HtmlTagDefinition({closedByChildren: ['dt', 'dd'], closedByParent: true}), + 'rb': new HtmlTagDefinition( + {closedByChildren: ['rb', 'rt', 'rtc', 'rp'], closedByParent: true}), + 'rt': new HtmlTagDefinition( + {closedByChildren: ['rb', 'rt', 'rtc', 'rp'], closedByParent: true}), + 'rtc': new HtmlTagDefinition({closedByChildren: ['rb', 'rtc', 'rp'], closedByParent: true}), + 'rp': new HtmlTagDefinition( + {closedByChildren: ['rb', 'rt', 'rtc', 'rp'], closedByParent: true}), + 'optgroup': new HtmlTagDefinition({closedByChildren: ['optgroup'], closedByParent: true}), + 'option': + new HtmlTagDefinition({closedByChildren: ['option', 'optgroup'], closedByParent: true}), + 'pre': new HtmlTagDefinition({ignoreFirstLf: true}), + 'listing': new HtmlTagDefinition({ignoreFirstLf: true}), + 'style': new HtmlTagDefinition({contentType: TagContentType.RAW_TEXT}), + 'script': new HtmlTagDefinition({contentType: TagContentType.RAW_TEXT}), + 'title': new HtmlTagDefinition({contentType: TagContentType.ESCAPABLE_RAW_TEXT}), + 'textarea': new HtmlTagDefinition( + {contentType: TagContentType.ESCAPABLE_RAW_TEXT, ignoreFirstLf: true}), + }; + } return TAG_DEFINITIONS[tagName.toLowerCase()] || _DEFAULT_TAG_DEFINITION; } diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index 28968f4c88..09827c1cbc 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -1078,8 +1078,14 @@ export class BindingScope implements LocalResolver { private map = new Map(); private referenceNameIndex = 0; private restoreViewVariable: o.ReadVarExpr|null = null; + private static _ROOT_SCOPE: BindingScope; - static ROOT_SCOPE = new BindingScope().set(0, '$event', o.variable('$event')); + static get ROOT_SCOPE(): BindingScope { + if (!BindingScope._ROOT_SCOPE) { + BindingScope._ROOT_SCOPE = new BindingScope().set(0, '$event', o.variable('$event')); + } + return BindingScope._ROOT_SCOPE; + } private constructor(public bindingLevel: number = 0, private parent: BindingScope|null = null) {} diff --git a/packages/compiler/src/schema/dom_element_schema_registry.ts b/packages/compiler/src/schema/dom_element_schema_registry.ts index 7382670a94..9562a3c0d2 100644 --- a/packages/compiler/src/schema/dom_element_schema_registry.ts +++ b/packages/compiler/src/schema/dom_element_schema_registry.ts @@ -342,11 +342,11 @@ export class DomElementSchemaRegistry extends ElementSchemaRegistry { // property names do not have a security impact. tagName = tagName.toLowerCase(); propName = propName.toLowerCase(); - let ctx = SECURITY_SCHEMA[tagName + '|' + propName]; + let ctx = SECURITY_SCHEMA()[tagName + '|' + propName]; if (ctx) { return ctx; } - ctx = SECURITY_SCHEMA['*|' + propName]; + ctx = SECURITY_SCHEMA()['*|' + propName]; return ctx ? ctx : SecurityContext.NONE; } diff --git a/packages/compiler/src/schema/dom_security_schema.ts b/packages/compiler/src/schema/dom_security_schema.ts index 0843a95840..73bff2233e 100644 --- a/packages/compiler/src/schema/dom_security_schema.ts +++ b/packages/compiler/src/schema/dom_security_schema.ts @@ -20,39 +20,45 @@ import {SecurityContext} from '../core'; // ================================================================================================= /** Map from tagName|propertyName SecurityContext. Properties applying to all tags use '*'. */ -export const SECURITY_SCHEMA: {[k: string]: SecurityContext} = {}; +let _SECURITY_SCHEMA !: {[k: string]: SecurityContext}; -function registerContext(ctx: SecurityContext, specs: string[]) { - for (const spec of specs) SECURITY_SCHEMA[spec.toLowerCase()] = ctx; +export function SECURITY_SCHEMA(): {[k: string]: SecurityContext} { + if (!_SECURITY_SCHEMA) { + _SECURITY_SCHEMA = {}; + // Case is insignificant below, all element and attribute names are lower-cased for lookup. + + registerContext(SecurityContext.HTML, [ + 'iframe|srcdoc', + '*|innerHTML', + '*|outerHTML', + ]); + registerContext(SecurityContext.STYLE, ['*|style']); + // NB: no SCRIPT contexts here, they are never allowed due to the parser stripping them. + registerContext(SecurityContext.URL, [ + '*|formAction', 'area|href', 'area|ping', 'audio|src', 'a|href', + 'a|ping', 'blockquote|cite', 'body|background', 'del|cite', 'form|action', + 'img|src', 'img|srcset', 'input|src', 'ins|cite', 'q|cite', + 'source|src', 'source|srcset', 'track|src', 'video|poster', 'video|src', + ]); + registerContext(SecurityContext.RESOURCE_URL, [ + 'applet|code', + 'applet|codebase', + 'base|href', + 'embed|src', + 'frame|src', + 'head|profile', + 'html|manifest', + 'iframe|src', + 'link|href', + 'media|src', + 'object|codebase', + 'object|data', + 'script|src', + ]); + } + return _SECURITY_SCHEMA; } -// Case is insignificant below, all element and attribute names are lower-cased for lookup. - -registerContext(SecurityContext.HTML, [ - 'iframe|srcdoc', - '*|innerHTML', - '*|outerHTML', -]); -registerContext(SecurityContext.STYLE, ['*|style']); -// NB: no SCRIPT contexts here, they are never allowed due to the parser stripping them. -registerContext(SecurityContext.URL, [ - '*|formAction', 'area|href', 'area|ping', 'audio|src', 'a|href', - 'a|ping', 'blockquote|cite', 'body|background', 'del|cite', 'form|action', - 'img|src', 'img|srcset', 'input|src', 'ins|cite', 'q|cite', - 'source|src', 'source|srcset', 'track|src', 'video|poster', 'video|src', -]); -registerContext(SecurityContext.RESOURCE_URL, [ - 'applet|code', - 'applet|codebase', - 'base|href', - 'embed|src', - 'frame|src', - 'head|profile', - 'html|manifest', - 'iframe|src', - 'link|href', - 'media|src', - 'object|codebase', - 'object|data', - 'script|src', -]); +function registerContext(ctx: SecurityContext, specs: string[]) { + for (const spec of specs) _SECURITY_SCHEMA[spec.toLowerCase()] = ctx; +} diff --git a/packages/compiler/src/template_parser/template_parser.ts b/packages/compiler/src/template_parser/template_parser.ts index e7a26c865f..a646856cc8 100644 --- a/packages/compiler/src/template_parser/template_parser.ts +++ b/packages/compiler/src/template_parser/template_parser.ts @@ -57,7 +57,13 @@ const IDENT_EVENT_IDX = 10; const TEMPLATE_ATTR_PREFIX = '*'; const CLASS_ATTR = 'class'; -const TEXT_CSS_SELECTOR = CssSelector.parse('*')[0]; +let _TEXT_CSS_SELECTOR !: CssSelector; +function TEXT_CSS_SELECTOR(): CssSelector { + if (!_TEXT_CSS_SELECTOR) { + _TEXT_CSS_SELECTOR = CssSelector.parse('*')[0]; + } + return _TEXT_CSS_SELECTOR; +} export class TemplateParseError extends ParseError { constructor(message: string, span: ParseSourceSpan, level: ParseErrorLevel) { @@ -227,7 +233,7 @@ class TemplateParseVisitor implements html.Visitor { visitExpansionCase(expansionCase: html.ExpansionCase, context: any): any { return null; } visitText(text: html.Text, parent: ElementContext): any { - const ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR) !; + const ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR()) !; const valueNoNgsp = replaceNgsp(text.value); const expr = this._bindingParser.parseInterpolation(valueNoNgsp, text.sourceSpan !); return expr ? new t.BoundTextAst(expr, ngContentIndex, text.sourceSpan !) : @@ -775,7 +781,7 @@ class NonBindableVisitor implements html.Visitor { } visitText(text: html.Text, parent: ElementContext): t.TextAst { - const ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR) !; + const ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR()) !; return new t.TextAst(text.value, ngContentIndex, text.sourceSpan !); } diff --git a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json index 73854eced0..8fa753be8a 100644 --- a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json @@ -1439,9 +1439,6 @@ { "name": "SyncAsync" }, - { - "name": "TAG_DEFINITIONS" - }, { "name": "TAG_TO_PLACEHOLDER_NAMES" }, @@ -1730,9 +1727,6 @@ { "name": "_DEFAULT_SOURCE_LANG$1" }, - { - "name": "_DEFAULT_TAG_DEFINITION" - }, { "name": "_DOCTYPE" }, @@ -3509,6 +3503,9 @@ { "name": "refCount" }, + { + "name": "registerContext" + }, { "name": "registerModuleFactory" },