Skip to content

Commit

Permalink
Merge pull request #700 from rwjblue/fix-canary-template-compiler-issues
Browse files Browse the repository at this point in the history
TemplateCompiler fixes / improvements (avoid monkey patch for Ember 3.27+)
  • Loading branch information
ef4 authored Feb 28, 2021
2 parents 1f7452a + e6d4d81 commit ed5e9bc
Show file tree
Hide file tree
Showing 2 changed files with 249 additions and 138 deletions.
247 changes: 152 additions & 95 deletions packages/core/src/patch-template-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,124 +15,181 @@ import {
import { NodePath } from '@babel/traverse';
import { transform } from '@babel/core';

export function patch(source: string, templateCompilerPath: string) {
function emberVersionGte(templateCompilerPath: string, source: string, major: number, minor: number): boolean {
// ember-template-compiler.js contains a comment that indicates what version it is for
// that looks like:

/*!
* @overview Ember - JavaScript Application Framework
* @copyright Copyright 2011-2020 Tilde Inc. and contributors
* Portions Copyright 2006-2011 Strobe Inc.
* Portions Copyright 2008-2011 Apple Inc. All rights reserved.
* @license Licensed under MIT license
* See https://raw.github.com/emberjs/ember.js/master/LICENSE
* @version 3.25.1
*/

let version = source.match(/@version\s+([\d\.]+)/);
if (!version || !version[1]) {
throw new Error(
`Could not find version string in \`${templateCompilerPath}\`. Maybe we don't support your ember-source version?`
);
}

let numbers = version[1].split('.');
let actualMajor = parseInt(numbers[0], 10);
let actualMinor = parseInt(numbers[1], 10);

return actualMajor > major || (actualMajor === major && actualMinor >= minor);
}

export function patch(source: string, templateCompilerPath: string): string {
if (emberVersionGte(templateCompilerPath, source, 3, 27)) {
// no modifications are needed after https://github.com/emberjs/ember.js/pull/19426
return source;
}

let replacedVar = false;
let patchedSource;

// patch applies to ember 3.12 through 3.16. The template compiler contains a
// comment with the version.
let needsPatch = /@version\s+3\.1[23456][^\d]/.test(source);
let needsAngleBracketPrinterFix =
emberVersionGte(templateCompilerPath, source, 3, 12) && !emberVersionGte(templateCompilerPath, source, 3, 17);

// here we are stripping off the first `var Ember;`. That one small change
// lets us crack open the file and get access to its internal loader, because
// we can give it our own predefined `Ember` variable instead, which it will
// use and put `Ember.__loader` onto.
//
// on ember 3.12 through 3.16 (which use variants of glimmer-vm 0.38.5) we
// also apply a patch to the printer in @glimmer/syntax to fix
// https://github.com/glimmerjs/glimmer-vm/pull/941/files because it can
// really bork apps under embroider, and we'd like to support at least all
// active LTS versions of ember.
let patchedSource = transform(source, {
plugins: [
function () {
return {
visitor: {
VariableDeclarator(path: NodePath<VariableDeclarator>) {
let id = path.node.id;
if (id.type === 'Identifier' && id.name === 'Ember' && !replacedVar) {
replacedVar = true;
path.remove();
}
},
CallExpression: {
enter(path: NodePath<CallExpression>, state: BabelState) {
if (!needsPatch) {
return;
}
let callee = path.get('callee');
if (!callee.isIdentifier() || callee.node.name !== 'define') {
return;
}
let firstArg = path.get('arguments')[0];
if (!firstArg.isStringLiteral() || firstArg.node.value !== '@glimmer/syntax') {
return;
if (needsAngleBracketPrinterFix) {
// here we are stripping off the first `var Ember;`. That one small change
// lets us crack open the file and get access to its internal loader, because
// we can give it our own predefined `Ember` variable instead, which it will
// use and put `Ember.__loader` onto.
//
// on ember 3.12 through 3.16 (which use variants of glimmer-vm 0.38.5) we
// also apply a patch to the printer in @glimmer/syntax to fix
// https://github.com/glimmerjs/glimmer-vm/pull/941/files because it can
// really bork apps under embroider, and we'd like to support at least all
// active LTS versions of ember.
patchedSource = transform(source, {
plugins: [
function () {
return {
visitor: {
VariableDeclarator(path: NodePath<VariableDeclarator>) {
let id = path.node.id;
if (id.type === 'Identifier' && id.name === 'Ember' && !replacedVar) {
replacedVar = true;
path.remove();
}
state.definingGlimmerSyntax = path;
},
exit(path: NodePath<CallExpression>, state: BabelState) {
if (state.definingGlimmerSyntax === path) {
state.definingGlimmerSyntax = false;
}
CallExpression: {
enter(path: NodePath<CallExpression>, state: BabelState) {
let callee = path.get('callee');
if (!callee.isIdentifier() || callee.node.name !== 'define') {
return;
}
let firstArg = path.get('arguments')[0];
if (!firstArg.isStringLiteral() || firstArg.node.value !== '@glimmer/syntax') {
return;
}
state.definingGlimmerSyntax = path;
},
exit(path: NodePath<CallExpression>, state: BabelState) {
if (state.definingGlimmerSyntax === path) {
state.definingGlimmerSyntax = false;
}
},
},
},
FunctionDeclaration: {
enter(path: NodePath<FunctionDeclaration>, state: BabelState) {
if (!state.definingGlimmerSyntax) {
return;
}
let id = path.get('id');
if (id.isIdentifier() && id.node.name === 'build') {
state.declaringBuildFunction = path;
}
FunctionDeclaration: {
enter(path: NodePath<FunctionDeclaration>, state: BabelState) {
if (!state.definingGlimmerSyntax) {
return;
}
let id = path.get('id');
if (id.isIdentifier() && id.node.name === 'build') {
state.declaringBuildFunction = path;
}
},
exit(path: NodePath<FunctionDeclaration>, state: BabelState) {
if (state.declaringBuildFunction === path) {
state.declaringBuildFunction = false;
}
},
},
exit(path: NodePath<FunctionDeclaration>, state: BabelState) {
if (state.declaringBuildFunction === path) {
state.declaringBuildFunction = false;
}
SwitchCase: {
enter(path: NodePath<SwitchCase>, state: BabelState) {
if (!state.definingGlimmerSyntax) {
return;
}
let test = path.get('test');
if (test.isStringLiteral() && test.node.value === 'ElementNode') {
state.caseElementNode = path;
}
},
exit(path: NodePath<SwitchCase>, state: BabelState) {
if (state.caseElementNode === path) {
state.caseElementNode = false;
}
},
},
},
SwitchCase: {
enter(path: NodePath<SwitchCase>, state: BabelState) {
if (!state.definingGlimmerSyntax) {
IfStatement(path: NodePath<IfStatement>, state: BabelState) {
if (!state.caseElementNode) {
return;
}
let test = path.get('test');
if (test.isStringLiteral() && test.node.value === 'ElementNode') {
state.caseElementNode = path;
// the place we want is the only if with a computed member
// expression predicate.
if (test.isMemberExpression() && test.node.computed) {
path.node.alternate = ifStatement(
memberExpression(identifier('ast'), identifier('selfClosing')),
blockStatement([
expressionStatement(
callExpression(memberExpression(identifier('output'), identifier('push')), [
stringLiteral(' />'),
])
),
]),
path.node.alternate
);
}
},
exit(path: NodePath<SwitchCase>, state: BabelState) {
if (state.caseElementNode === path) {
state.caseElementNode = false;
},
};
},
],
})!.code!;
} else {
// applies to < 3.12 and >= 3.17
//
// here we are stripping off the first `var Ember;`. That one small change
// lets us crack open the file and get access to its internal loader, because
// we can give it our own predefined `Ember` variable instead, which it will
// use and put `Ember.__loader` onto.
patchedSource = transform(source, {
plugins: [
function () {
return {
visitor: {
VariableDeclarator(path: NodePath<VariableDeclarator>) {
let id = path.node.id;
if (id.type === 'Identifier' && id.name === 'Ember' && !replacedVar) {
replacedVar = true;
path.remove();
}
},
},
IfStatement(path: NodePath<IfStatement>, state: BabelState) {
if (!state.caseElementNode) {
return;
}
let test = path.get('test');
// the place we want is the only if with a computed member
// expression predicate.
if (test.isMemberExpression() && test.node.computed) {
path.node.alternate = ifStatement(
memberExpression(identifier('ast'), identifier('selfClosing')),
blockStatement([
expressionStatement(
callExpression(memberExpression(identifier('output'), identifier('push')), [stringLiteral(' />')])
),
]),
path.node.alternate
);
}
},
},
};
},
],
})!.code!;
};
},
],
})!.code!;
}

if (!replacedVar) {
throw new Error(
`didn't find expected source in ${templateCompilerPath}. Maybe we don't support your ember-source version?`
);
}

return `
let module = { exports: {} };
let Ember = {};
${patchedSource};
module.exports.Ember = Ember;
return module.exports
let Ember = {};
${patchedSource};
module.exports.Ember = Ember;
`;
}

Expand Down
Loading

0 comments on commit ed5e9bc

Please sign in to comment.