From a12b5f930ad1a6c03839ab453918d7f386f89079 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Fri, 6 Dec 2019 08:41:44 +0000 Subject: [PATCH] fix(compiler): ensure localized strings are ES5 compatible for JIT mode (#34265) Previously the JIT evaluated code for ivy localized strings included backtick tagged template strings, which are not compatible with ES5 in legacy browsers such as IE 11. Now the generated code is ES5 compatible. Fixes #34246 PR Close #34265 --- .../src/output/abstract_js_emitter.ts | 38 ++++++++++++++++++- .../compiler/test/output/js_emitter_spec.ts | 9 +++++ .../compiler/test/output/ts_emitter_spec.ts | 7 ++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/output/abstract_js_emitter.ts b/packages/compiler/src/output/abstract_js_emitter.ts index 621a67af40..fde1fbe80b 100644 --- a/packages/compiler/src/output/abstract_js_emitter.ts +++ b/packages/compiler/src/output/abstract_js_emitter.ts @@ -7,7 +7,7 @@ */ -import {AbstractEmitterVisitor, CATCH_ERROR_VAR, CATCH_STACK_VAR, EmitterVisitorContext} from './abstract_emitter'; +import {AbstractEmitterVisitor, CATCH_ERROR_VAR, CATCH_STACK_VAR, EmitterVisitorContext, escapeIdentifier} from './abstract_emitter'; import * as o from './output_ast'; export abstract class AbstractJsEmitterVisitor extends AbstractEmitterVisitor { @@ -150,6 +150,42 @@ export abstract class AbstractJsEmitterVisitor extends AbstractEmitterVisitor { return null; } + visitLocalizedString(ast: o.LocalizedString, ctx: EmitterVisitorContext): any { + // The following convoluted piece of code is effectively the downlevelled equivalent of + // ``` + // $localize `...` + // ``` + // which is effectively like: + // ``` + // $localize(__makeTemplateObject(cooked, raw), expression1, expression2, ...); + // ``` + // + // The `$localize` function expects a "template object", which is an array of "cooked" strings + // plus a `raw` property that contains an array of "raw" strings. + // + // In some environments a helper function called `__makeTemplateObject(cooked, raw)` might be + // available, in which case we use that. Otherwise we must create our own helper function + // inline. + // + // In the inline function, if `Object.defineProperty` is available we use that to attach the + // `raw` array. + ctx.print( + ast, + '$localize((this&&this.__makeTemplateObject||function(e,t){return Object.defineProperty?Object.defineProperty(e,"raw",{value:t}):e.raw=t,e})('); + const parts = [ast.serializeI18nHead()]; + for (let i = 1; i < ast.messageParts.length; i++) { + parts.push(ast.serializeI18nTemplatePart(i)); + } + ctx.print(ast, `[${parts.map(part => escapeIdentifier(part.cooked, false)).join(', ')}], `); + ctx.print(ast, `[${parts.map(part => escapeIdentifier(part.raw, false)).join(', ')}])`); + ast.expressions.forEach(expression => { + ctx.print(ast, ', '); + expression.visitExpression(this, ctx); + }); + ctx.print(ast, ')'); + return null; + } + private _visitParams(params: o.FnParam[], ctx: EmitterVisitorContext): void { this.visitAllObjects(param => ctx.print(null, param.name), params, ctx, ','); } diff --git a/packages/compiler/test/output/js_emitter_spec.ts b/packages/compiler/test/output/js_emitter_spec.ts index e38e310ba5..a60c15c706 100644 --- a/packages/compiler/test/output/js_emitter_spec.ts +++ b/packages/compiler/test/output/js_emitter_spec.ts @@ -200,6 +200,15 @@ const externalModuleIdentifier = new o.ExternalReference(anotherModuleUrl, 'some ].join('\n')); }); + it('should support ES5 localized strings', () => { + expect(emitStmt(new o.ExpressionStatement(o.localizedString( + {}, ['ab\\:c', 'd"e\'f'], ['ph1'], + [o.literal(7, o.NUMBER_TYPE).plus(o.literal(8, o.NUMBER_TYPE))])))) + .toEqual( + String.raw + `$localize((this&&this.__makeTemplateObject||function(e,t){return Object.defineProperty?Object.defineProperty(e,"raw",{value:t}):e.raw=t,e})(['ab\\:c', ':ph1:d"e\'f'], ['ab\\\\:c', ':ph1:d"e\'f']), (7 + 8));`); + }); + it('should support try/catch', () => { const bodyStmt = o.variable('body').callFn([]).toStmt(); const catchStmt = diff --git a/packages/compiler/test/output/ts_emitter_spec.ts b/packages/compiler/test/output/ts_emitter_spec.ts index aa4b7ab649..cc287c22a8 100644 --- a/packages/compiler/test/output/ts_emitter_spec.ts +++ b/packages/compiler/test/output/ts_emitter_spec.ts @@ -249,6 +249,13 @@ const externalModuleIdentifier = new o.ExternalReference(anotherModuleUrl, 'some ].join('\n')); }); + it('should support localized strings', () => { + expect(emitStmt(new o.ExpressionStatement(o.localizedString( + {}, ['ab\\:c', 'd"e\'f'], ['ph1'], + [o.literal(7, o.NUMBER_TYPE).plus(o.literal(8, o.NUMBER_TYPE))])))) + .toEqual('$localize `ab\\\\:c${(7 + 8)}:ph1:d"e\'f`;'); + }); + it('should support try/catch', () => { const bodyStmt = o.variable('body').callFn([]).toStmt(); const catchStmt =