REFACTOR: Raw Handlebars ported to ES6
This commit is contained in:
parent
b8125b3512
commit
7ff5b228cd
|
@ -1,160 +0,0 @@
|
||||||
// keep IIF for simpler testing
|
|
||||||
|
|
||||||
// EmberCompatHandlebars is a mechanism for quickly rendering templates which is Ember aware
|
|
||||||
// templates are highly compatible with Ember so you don't need to worry about calling "get"
|
|
||||||
// and computed properties function, additionally it uses stringParams like Ember does
|
|
||||||
|
|
||||||
(function(){
|
|
||||||
|
|
||||||
// compat with ie8 in case this gets picked up elsewhere
|
|
||||||
var objectCreate = Object.create || function(parent) {
|
|
||||||
function F() {}
|
|
||||||
F.prototype = parent;
|
|
||||||
return new F();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
var RawHandlebars = Handlebars.create();
|
|
||||||
|
|
||||||
RawHandlebars.helper = function() {};
|
|
||||||
RawHandlebars.helpers = objectCreate(Handlebars.helpers);
|
|
||||||
|
|
||||||
RawHandlebars.helpers.get = function(context, options){
|
|
||||||
var firstContext = options.contexts[0];
|
|
||||||
var val = firstContext[context];
|
|
||||||
|
|
||||||
if (val && val.isDescriptor) { return Em.get(firstContext, context); }
|
|
||||||
val = val === undefined ? Em.get(firstContext, context): val;
|
|
||||||
return val;
|
|
||||||
};
|
|
||||||
|
|
||||||
// adds compatability so this works with stringParams
|
|
||||||
var stringCompatHelper = function(fn){
|
|
||||||
|
|
||||||
var old = RawHandlebars.helpers[fn];
|
|
||||||
RawHandlebars.helpers[fn] = function(context,options){
|
|
||||||
return old.apply(this, [
|
|
||||||
RawHandlebars.helpers.get(context,options),
|
|
||||||
options
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// #each .. in support (as format is transformed to this)
|
|
||||||
RawHandlebars.registerHelper('each', function(localName,inKeyword,contextName,options){
|
|
||||||
var list = Em.get(this, contextName);
|
|
||||||
var output = [];
|
|
||||||
var innerContext = Object.create(this);
|
|
||||||
for (var i=0; i<list.length; i++) {
|
|
||||||
innerContext[localName] = list[i];
|
|
||||||
output.push(options.fn(innerContext));
|
|
||||||
}
|
|
||||||
return output.join('');
|
|
||||||
});
|
|
||||||
|
|
||||||
stringCompatHelper("if");
|
|
||||||
stringCompatHelper("unless");
|
|
||||||
stringCompatHelper("with");
|
|
||||||
|
|
||||||
|
|
||||||
if (Handlebars.Compiler) {
|
|
||||||
RawHandlebars.Compiler = function() {};
|
|
||||||
RawHandlebars.Compiler.prototype = objectCreate(Handlebars.Compiler.prototype);
|
|
||||||
RawHandlebars.Compiler.prototype.compiler = RawHandlebars.Compiler;
|
|
||||||
|
|
||||||
RawHandlebars.JavaScriptCompiler = function() {};
|
|
||||||
|
|
||||||
RawHandlebars.JavaScriptCompiler.prototype = objectCreate(Handlebars.JavaScriptCompiler.prototype);
|
|
||||||
RawHandlebars.JavaScriptCompiler.prototype.compiler = RawHandlebars.JavaScriptCompiler;
|
|
||||||
RawHandlebars.JavaScriptCompiler.prototype.namespace = "Discourse.EmberCompatHandlebars";
|
|
||||||
|
|
||||||
function buildPath(blk, args) {
|
|
||||||
|
|
||||||
var result = { type: "PathExpression",
|
|
||||||
data: false,
|
|
||||||
depth: blk.path.depth,
|
|
||||||
loc: blk.path.loc };
|
|
||||||
|
|
||||||
// Server side precompile doesn't have jquery.extend
|
|
||||||
Object.keys(args).forEach(function (a) {
|
|
||||||
result[a] = args[a];
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function replaceGet(ast) {
|
|
||||||
var visitor = new Handlebars.Visitor();
|
|
||||||
visitor.mutating = true;
|
|
||||||
|
|
||||||
visitor.MustacheStatement = function(mustache) {
|
|
||||||
if (!(mustache.params.length || mustache.hash)) {
|
|
||||||
mustache.params[0] = mustache.path;
|
|
||||||
mustache.path = buildPath(mustache, { parts: ['get'], original: 'get', strict: true, falsy: true });
|
|
||||||
}
|
|
||||||
return Handlebars.Visitor.prototype.MustacheStatement.call(this, mustache);
|
|
||||||
};
|
|
||||||
|
|
||||||
// rewrite `each x as |y|` as each y in x`
|
|
||||||
// This allows us to use the same syntax in all templates
|
|
||||||
visitor.BlockStatement = function(block) {
|
|
||||||
if (block.path.original === 'each' && block.params.length === 1) {
|
|
||||||
var paramName = block.program.blockParams[0];
|
|
||||||
block.params = [ buildPath(block, { original: paramName }),
|
|
||||||
{ type: "CommentStatement", value: "in" },
|
|
||||||
block.params[0] ];
|
|
||||||
delete block.program.blockParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Handlebars.Visitor.prototype.BlockStatement.call(this, block);
|
|
||||||
};
|
|
||||||
|
|
||||||
visitor.accept(ast);
|
|
||||||
}
|
|
||||||
|
|
||||||
RawHandlebars.precompile = function(value, asObject) {
|
|
||||||
var ast = Handlebars.parse(value);
|
|
||||||
replaceGet(ast);
|
|
||||||
|
|
||||||
var options = {
|
|
||||||
knownHelpers: {
|
|
||||||
get: true
|
|
||||||
},
|
|
||||||
data: true,
|
|
||||||
stringParams: true
|
|
||||||
};
|
|
||||||
|
|
||||||
asObject = asObject === undefined ? true : asObject;
|
|
||||||
|
|
||||||
var environment = new RawHandlebars.Compiler().compile(ast, options);
|
|
||||||
return new RawHandlebars.JavaScriptCompiler().compile(environment, options, undefined, asObject);
|
|
||||||
};
|
|
||||||
|
|
||||||
RawHandlebars.compile = function(string) {
|
|
||||||
var ast = Handlebars.parse(string);
|
|
||||||
replaceGet(ast);
|
|
||||||
|
|
||||||
// this forces us to rewrite helpers
|
|
||||||
var options = { data: true, stringParams: true };
|
|
||||||
var environment = new RawHandlebars.Compiler().compile(ast, options);
|
|
||||||
var templateSpec = new RawHandlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
|
|
||||||
|
|
||||||
var template = RawHandlebars.template(templateSpec);
|
|
||||||
template.isMethod = false;
|
|
||||||
|
|
||||||
return template;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
RawHandlebars.get = function(ctx, property, options){
|
|
||||||
if (options.types && options.data.view) {
|
|
||||||
var view = options.data.view;
|
|
||||||
return view.getStream ? view.getStream(property).value() : view.getAttr(property);
|
|
||||||
} else {
|
|
||||||
return Ember.get(ctx, property);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Discourse.EmberCompatHandlebars = RawHandlebars;
|
|
||||||
|
|
||||||
})();
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { get } from 'discourse/lib/raw-handlebars';
|
||||||
|
|
||||||
// `Ember.Helper` is only available in versions after 1.12
|
// `Ember.Helper` is only available in versions after 1.12
|
||||||
export function htmlHelper(fn) {
|
export function htmlHelper(fn) {
|
||||||
if (Ember.Helper) {
|
if (Ember.Helper) {
|
||||||
|
@ -15,8 +17,6 @@ export function registerHelper(name, fn) {
|
||||||
Ember.HTMLBars._registerHelper(name, fn);
|
Ember.HTMLBars._registerHelper(name, fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
const get = Discourse.EmberCompatHandlebars.get;
|
|
||||||
|
|
||||||
function resolveParams(ctx, options) {
|
function resolveParams(ctx, options) {
|
||||||
let params = {};
|
let params = {};
|
||||||
const hash = options.hash;
|
const hash = options.hash;
|
||||||
|
|
|
@ -0,0 +1,164 @@
|
||||||
|
// This is a mechanism for quickly rendering templates which is Ember aware
|
||||||
|
// templates are highly compatible with Ember so you don't need to worry about calling "get"
|
||||||
|
// and computed properties function, additionally it uses stringParams like Ember does
|
||||||
|
|
||||||
|
// compat with ie8 in case this gets picked up elsewhere
|
||||||
|
const objectCreate = Object.create || function(parent) {
|
||||||
|
function F() {}
|
||||||
|
F.prototype = parent;
|
||||||
|
return new F();
|
||||||
|
};
|
||||||
|
|
||||||
|
const RawHandlebars = Handlebars.create();
|
||||||
|
|
||||||
|
RawHandlebars.helper = function() {};
|
||||||
|
RawHandlebars.helpers = objectCreate(Handlebars.helpers);
|
||||||
|
|
||||||
|
RawHandlebars.helpers.get = function(context, options){
|
||||||
|
var firstContext = options.contexts[0];
|
||||||
|
var val = firstContext[context];
|
||||||
|
|
||||||
|
if (val && val.isDescriptor) { return Em.get(firstContext, context); }
|
||||||
|
val = val === undefined ? Em.get(firstContext, context): val;
|
||||||
|
return val;
|
||||||
|
};
|
||||||
|
|
||||||
|
// adds compatability so this works with stringParams
|
||||||
|
function stringCompatHelper(fn) {
|
||||||
|
const old = RawHandlebars.helpers[fn];
|
||||||
|
RawHandlebars.helpers[fn] = function(context,options) {
|
||||||
|
return old.apply(this, [
|
||||||
|
RawHandlebars.helpers.get(context,options),
|
||||||
|
options
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// #each .. in support (as format is transformed to this)
|
||||||
|
RawHandlebars.registerHelper('each', function(localName,inKeyword,contextName,options){
|
||||||
|
var list = Em.get(this, contextName);
|
||||||
|
var output = [];
|
||||||
|
var innerContext = Object.create(this);
|
||||||
|
for (var i=0; i<list.length; i++) {
|
||||||
|
innerContext[localName] = list[i];
|
||||||
|
output.push(options.fn(innerContext));
|
||||||
|
}
|
||||||
|
return output.join('');
|
||||||
|
});
|
||||||
|
|
||||||
|
stringCompatHelper("if");
|
||||||
|
stringCompatHelper("unless");
|
||||||
|
stringCompatHelper("with");
|
||||||
|
|
||||||
|
|
||||||
|
if (Handlebars.Compiler) {
|
||||||
|
RawHandlebars.Compiler = function() {};
|
||||||
|
RawHandlebars.Compiler.prototype = objectCreate(Handlebars.Compiler.prototype);
|
||||||
|
RawHandlebars.Compiler.prototype.compiler = RawHandlebars.Compiler;
|
||||||
|
|
||||||
|
RawHandlebars.JavaScriptCompiler = function() {};
|
||||||
|
|
||||||
|
RawHandlebars.JavaScriptCompiler.prototype = objectCreate(Handlebars.JavaScriptCompiler.prototype);
|
||||||
|
RawHandlebars.JavaScriptCompiler.prototype.compiler = RawHandlebars.JavaScriptCompiler;
|
||||||
|
RawHandlebars.JavaScriptCompiler.prototype.namespace = "RawHandlebars";
|
||||||
|
|
||||||
|
function buildPath(blk, args) {
|
||||||
|
|
||||||
|
var result = { type: "PathExpression",
|
||||||
|
data: false,
|
||||||
|
depth: blk.path.depth,
|
||||||
|
loc: blk.path.loc };
|
||||||
|
|
||||||
|
// Server side precompile doesn't have jquery.extend
|
||||||
|
Object.keys(args).forEach(function (a) {
|
||||||
|
result[a] = args[a];
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceGet(ast) {
|
||||||
|
var visitor = new Handlebars.Visitor();
|
||||||
|
visitor.mutating = true;
|
||||||
|
|
||||||
|
visitor.MustacheStatement = function(mustache) {
|
||||||
|
if (!(mustache.params.length || mustache.hash)) {
|
||||||
|
mustache.params[0] = mustache.path;
|
||||||
|
mustache.path = buildPath(mustache, { parts: ['get'], original: 'get', strict: true, falsy: true });
|
||||||
|
}
|
||||||
|
return Handlebars.Visitor.prototype.MustacheStatement.call(this, mustache);
|
||||||
|
};
|
||||||
|
|
||||||
|
// rewrite `each x as |y|` as each y in x`
|
||||||
|
// This allows us to use the same syntax in all templates
|
||||||
|
visitor.BlockStatement = function(block) {
|
||||||
|
if (block.path.original === 'each' && block.params.length === 1) {
|
||||||
|
var paramName = block.program.blockParams[0];
|
||||||
|
block.params = [ buildPath(block, { original: paramName }),
|
||||||
|
{ type: "CommentStatement", value: "in" },
|
||||||
|
block.params[0] ];
|
||||||
|
delete block.program.blockParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Handlebars.Visitor.prototype.BlockStatement.call(this, block);
|
||||||
|
};
|
||||||
|
|
||||||
|
visitor.accept(ast);
|
||||||
|
}
|
||||||
|
|
||||||
|
RawHandlebars.precompile = function(value, asObject) {
|
||||||
|
var ast = Handlebars.parse(value);
|
||||||
|
replaceGet(ast);
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
knownHelpers: {
|
||||||
|
get: true
|
||||||
|
},
|
||||||
|
data: true,
|
||||||
|
stringParams: true
|
||||||
|
};
|
||||||
|
|
||||||
|
asObject = asObject === undefined ? true : asObject;
|
||||||
|
|
||||||
|
var environment = new RawHandlebars.Compiler().compile(ast, options);
|
||||||
|
return new RawHandlebars.JavaScriptCompiler().compile(environment, options, undefined, asObject);
|
||||||
|
};
|
||||||
|
|
||||||
|
RawHandlebars.compile = function(string) {
|
||||||
|
var ast = Handlebars.parse(string);
|
||||||
|
replaceGet(ast);
|
||||||
|
|
||||||
|
// this forces us to rewrite helpers
|
||||||
|
var options = { data: true, stringParams: true };
|
||||||
|
var environment = new RawHandlebars.Compiler().compile(ast, options);
|
||||||
|
var templateSpec = new RawHandlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
|
||||||
|
|
||||||
|
var template = RawHandlebars.template(templateSpec);
|
||||||
|
template.isMethod = false;
|
||||||
|
|
||||||
|
return template;
|
||||||
|
};
|
||||||
|
|
||||||
|
RawHandlebars.get = function(ctx, property, options) {
|
||||||
|
if (options.types && options.data.view) {
|
||||||
|
var view = options.data.view;
|
||||||
|
return view.getStream ? view.getStream(property).value() : view.getAttr(property);
|
||||||
|
} else {
|
||||||
|
return Ember.get(ctx, property);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function precompile(value, asObject) {
|
||||||
|
return RawHandlebars.precompile(value, asObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compile(string) {
|
||||||
|
return RawHandlebars.compile(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function get(ctx, property, options) {
|
||||||
|
return RawHandlebars.get(ctx, property, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RawHandlebars;
|
|
@ -21,7 +21,7 @@
|
||||||
//= require ./discourse/lib/helpers
|
//= require ./discourse/lib/helpers
|
||||||
//= require ./discourse/helpers/i18n
|
//= require ./discourse/helpers/i18n
|
||||||
//= require ./discourse/helpers/fa-icon
|
//= require ./discourse/helpers/fa-icon
|
||||||
//= require ./discourse/lib/ember_compat_handlebars
|
//= require ./discourse/lib/raw-handlebars
|
||||||
//= require ./discourse/lib/helpers
|
//= require ./discourse/lib/helpers
|
||||||
//= require ./discourse/lib/computed
|
//= require ./discourse/lib/computed
|
||||||
//= require ./discourse/lib/formatter
|
//= require ./discourse/lib/formatter
|
||||||
|
|
|
@ -44,7 +44,7 @@ PLUGIN_API_JS
|
||||||
name = node["name"] || node["data-template-name"] || "broken"
|
name = node["name"] || node["data-template-name"] || "broken"
|
||||||
precompiled =
|
precompiled =
|
||||||
if name =~ /\.raw$/
|
if name =~ /\.raw$/
|
||||||
"Discourse.EmberCompatHandlebars.template(#{Barber::Precompiler.compile(node.inner_html)})"
|
"RawHandlebars.template(#{Barber::Precompiler.compile(node.inner_html)})"
|
||||||
else
|
else
|
||||||
"Ember.HTMLBars.template(#{Barber::Ember::Precompiler.compile(node.inner_html)})"
|
"Ember.HTMLBars.template(#{Barber::Ember::Precompiler.compile(node.inner_html)})"
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,19 +2,36 @@
|
||||||
|
|
||||||
class Barber::Precompiler
|
class Barber::Precompiler
|
||||||
def sources
|
def sources
|
||||||
[File.open("#{Rails.root}/vendor/assets/javascripts/handlebars.js"), precompiler]
|
[File.open("#{Rails.root}/vendor/assets/javascripts/handlebars.js"),
|
||||||
|
precompiler]
|
||||||
end
|
end
|
||||||
|
|
||||||
def precompiler
|
def precompiler
|
||||||
@precompiler ||= StringIO.new <<END
|
if !@precompiler
|
||||||
var Discourse = {};
|
|
||||||
#{File.read(Rails.root + "app/assets/javascripts/discourse/lib/ember_compat_handlebars.js")}
|
source = File.read("#{Rails.root}/app/assets/javascripts/discourse/lib/raw-handlebars.js.es6")
|
||||||
var Barber = {
|
template = Tilt::ES6ModuleTranspilerTemplate.new {}
|
||||||
precompile: function(string) {
|
transpiled = template.babel_transpile(source)
|
||||||
return Discourse.EmberCompatHandlebars.precompile(string,false).toString();
|
|
||||||
}
|
# very hacky but lets us use ES6. I'm ashamed of this code -RW
|
||||||
};
|
transpiled.gsub!(/^export .*$/, '')
|
||||||
|
|
||||||
|
@precompiler = StringIO.new <<END
|
||||||
|
var __RawHandlebars;
|
||||||
|
(function() {
|
||||||
|
#{transpiled};
|
||||||
|
__RawHandlebars = RawHandlebars;
|
||||||
|
})();
|
||||||
|
|
||||||
|
Barber = {
|
||||||
|
precompile: function(string) {
|
||||||
|
return __RawHandlebars.precompile(string, false).toString();
|
||||||
|
}
|
||||||
|
};
|
||||||
END
|
END
|
||||||
|
end
|
||||||
|
|
||||||
|
@precompiler
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -27,7 +44,7 @@ module Discourse
|
||||||
end
|
end
|
||||||
|
|
||||||
def compile_handlebars(string)
|
def compile_handlebars(string)
|
||||||
"Discourse.EmberCompatHandlebars.compile(#{indent(string).inspect});"
|
"require('discourse/lib/raw-handlebars').compile(#{indent(string).inspect});"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
|
@ -109,8 +109,8 @@ HTML
|
||||||
c = SiteCustomization.create!(user_id: -1, name: "test", head_tag: with_template, body_tag: with_template)
|
c = SiteCustomization.create!(user_id: -1, name: "test", head_tag: with_template, body_tag: with_template)
|
||||||
expect(c.head_tag_baked).to match(/HTMLBars/)
|
expect(c.head_tag_baked).to match(/HTMLBars/)
|
||||||
expect(c.body_tag_baked).to match(/HTMLBars/)
|
expect(c.body_tag_baked).to match(/HTMLBars/)
|
||||||
expect(c.body_tag_baked).to match(/EmberCompatHandlebars/)
|
expect(c.body_tag_baked).to match(/RawHandlebars/)
|
||||||
expect(c.head_tag_baked).to match(/EmberCompatHandlebars/)
|
expect(c.head_tag_baked).to match(/RawHandlebars/)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should create body_tag_baked on demand if needed' do
|
it 'should create body_tag_baked on demand if needed' do
|
||||||
|
|
|
@ -1,18 +1,6 @@
|
||||||
import { acceptance } from "helpers/qunit-helpers";
|
import { acceptance } from "helpers/qunit-helpers";
|
||||||
|
|
||||||
acceptance("Category hashtag", {
|
acceptance("Category hashtag", { loggedIn: true });
|
||||||
loggedIn: true,
|
|
||||||
setup() {
|
|
||||||
const response = (object) => {
|
|
||||||
return [
|
|
||||||
200,
|
|
||||||
{"Content-Type": "application/json"},
|
|
||||||
object
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test("category hashtag is cooked properly", () => {
|
test("category hashtag is cooked properly", () => {
|
||||||
visit("/t/internationalization-localization/280");
|
visit("/t/internationalization-localization/280");
|
||||||
|
|
Loading…
Reference in New Issue