mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 10:57:04 +08:00
a169dc6832
This reverts commit 7217dcb67a
.
https://meta.discourse.org/t/failed-to-bootstrap-due-to-out-of-memory-killer/188141/18?u=osama
Precompiling test_helper.js is so expensive that it can make bootstrap
fail on servers with limited resources (2GB RAM). We will find another
way that doesn't require much resources.
254 lines
7.4 KiB
Ruby
254 lines
7.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class ThemeJavascriptCompiler
|
|
|
|
module PrecompilerExtension
|
|
def initialize(theme_id)
|
|
super()
|
|
@theme_id = theme_id
|
|
end
|
|
|
|
def discourse_node_manipulator
|
|
<<~JS
|
|
|
|
// Helper to replace old themeSetting syntax
|
|
function generateHelper(settingParts) {
|
|
const settingName = settingParts.join('.');
|
|
return {
|
|
"path": {
|
|
"type": "PathExpression",
|
|
"original": "theme-setting",
|
|
"this": false,
|
|
"data": false,
|
|
"parts": [
|
|
"theme-setting"
|
|
],
|
|
"depth":0
|
|
},
|
|
"params": [
|
|
{
|
|
type: "NumberLiteral",
|
|
value: #{@theme_id},
|
|
original: #{@theme_id}
|
|
},
|
|
{
|
|
"type": "StringLiteral",
|
|
"value": settingName,
|
|
"original": settingName
|
|
}
|
|
],
|
|
"hash": {
|
|
"type": "Hash",
|
|
"pairs": [
|
|
{
|
|
"type": "HashPair",
|
|
"key": "deprecated",
|
|
"value": {
|
|
"type": "BooleanLiteral",
|
|
"value": true,
|
|
"original": true
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
|
|
function manipulatePath(path) {
|
|
// Override old themeSetting syntax when it's a param inside another node
|
|
if(path.parts && path.parts[0] == "themeSettings"){
|
|
const settingParts = path.parts.slice(1);
|
|
path.type = "SubExpression";
|
|
Object.assign(path, generateHelper(settingParts))
|
|
}
|
|
}
|
|
|
|
function manipulateNode(node) {
|
|
// Magically add theme id as the first param for each of these helpers)
|
|
if (node.path.parts && ["theme-i18n", "theme-prefix", "theme-setting"].includes(node.path.parts[0])) {
|
|
if(node.params.length === 1){
|
|
node.params.unshift({
|
|
type: "NumberLiteral",
|
|
value: #{@theme_id},
|
|
original: #{@theme_id}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Override old themeSetting syntax when it's in its own node
|
|
if (node.path.parts && node.path.parts[0] == "themeSettings") {
|
|
Object.assign(node, generateHelper(node.path.parts.slice(1)))
|
|
}
|
|
}
|
|
JS
|
|
end
|
|
|
|
def source
|
|
[super, discourse_node_manipulator, discourse_extension].join("\n")
|
|
end
|
|
end
|
|
|
|
class RawTemplatePrecompiler < Barber::Precompiler
|
|
include PrecompilerExtension
|
|
|
|
def discourse_extension
|
|
<<~JS
|
|
let _superCompile = Handlebars.Compiler.prototype.compile;
|
|
Handlebars.Compiler.prototype.compile = function(program, options) {
|
|
|
|
// `replaceGet()` in raw-handlebars.js.es6 adds a `get` in front of things
|
|
// so undo this specific case for the old themeSettings.blah syntax
|
|
let visitor = new Handlebars.Visitor();
|
|
visitor.mutating = true;
|
|
visitor.MustacheStatement = (node) => {
|
|
if(node.path.original == 'get'
|
|
&& node.params
|
|
&& node.params[0]
|
|
&& node.params[0].parts
|
|
&& node.params[0].parts[0] == 'themeSettings'){
|
|
node.path.parts = node.params[0].parts
|
|
node.params = []
|
|
}
|
|
};
|
|
visitor.accept(program);
|
|
|
|
[
|
|
["SubExpression", manipulateNode],
|
|
["MustacheStatement", manipulateNode],
|
|
["PathExpression", manipulatePath]
|
|
].forEach((pass) => {
|
|
let visitor = new Handlebars.Visitor();
|
|
visitor.mutating = true;
|
|
visitor[pass[0]] = pass[1];
|
|
visitor.accept(program);
|
|
})
|
|
|
|
return _superCompile.apply(this, arguments);
|
|
};
|
|
JS
|
|
end
|
|
end
|
|
|
|
class EmberTemplatePrecompiler < Barber::Ember::Precompiler
|
|
include PrecompilerExtension
|
|
|
|
def discourse_extension
|
|
<<~JS
|
|
Ember.HTMLBars.registerPlugin('ast', function() {
|
|
return {
|
|
name: 'theme-template-manipulator',
|
|
visitor: {
|
|
SubExpression: manipulateNode,
|
|
MustacheStatement: manipulateNode,
|
|
PathExpression: manipulatePath
|
|
}
|
|
}
|
|
});
|
|
JS
|
|
end
|
|
end
|
|
|
|
class CompileError < StandardError
|
|
end
|
|
|
|
def self.force_default_settings(content, theme)
|
|
settings_hash = {}
|
|
theme.settings.each do |setting|
|
|
settings_hash[setting.name] = setting.default
|
|
end
|
|
content.prepend <<~JS
|
|
(function() {
|
|
require("discourse/lib/theme-settings-store").registerSettings(#{theme.id}, #{settings_hash.to_json}, { force: true });
|
|
})();
|
|
JS
|
|
end
|
|
|
|
attr_accessor :content
|
|
|
|
def initialize(theme_id, theme_name)
|
|
@theme_id = theme_id
|
|
@content = +""
|
|
@theme_name = theme_name
|
|
end
|
|
|
|
def prepend_settings(settings_hash)
|
|
@content.prepend <<~JS
|
|
(function() {
|
|
if ('require' in window) {
|
|
require("discourse/lib/theme-settings-store").registerSettings(#{@theme_id}, #{settings_hash.to_json});
|
|
}
|
|
})();
|
|
JS
|
|
end
|
|
|
|
# TODO Error handling for handlebars templates
|
|
def append_ember_template(name, hbs_template)
|
|
if !name.start_with?("javascripts/")
|
|
prefix = "javascripts"
|
|
prefix += "/" if !name.start_with?("/")
|
|
name = prefix + name
|
|
end
|
|
name = name.inspect
|
|
compiled = EmberTemplatePrecompiler.new(@theme_id).compile(hbs_template)
|
|
# the `'Ember' in window` check is needed for no_ember pages
|
|
content << <<~JS
|
|
(function() {
|
|
if ('Ember' in window) {
|
|
Ember.TEMPLATES[#{name}] = Ember.HTMLBars.template(#{compiled});
|
|
}
|
|
})();
|
|
JS
|
|
rescue Barber::PrecompilerError => e
|
|
raise CompileError.new e.instance_variable_get(:@error) # e.message contains the entire template, which could be very long
|
|
end
|
|
|
|
def raw_template_name(name)
|
|
name = name.sub(/\.(raw|hbr)$/, '')
|
|
name.inspect
|
|
end
|
|
|
|
def append_raw_template(name, hbs_template)
|
|
compiled = RawTemplatePrecompiler.new(@theme_id).compile(hbs_template)
|
|
@content << <<~JS
|
|
(function() {
|
|
const addRawTemplate = requirejs('discourse-common/lib/raw-templates').addRawTemplate;
|
|
const template = requirejs('discourse-common/lib/raw-handlebars').template(#{compiled});
|
|
addRawTemplate(#{raw_template_name(name)}, template);
|
|
})();
|
|
JS
|
|
rescue Barber::PrecompilerError => e
|
|
raise CompileError.new e.instance_variable_get(:@error) # e.message contains the entire template, which could be very long
|
|
end
|
|
|
|
def append_raw_script(script)
|
|
@content << script + "\n"
|
|
end
|
|
|
|
def append_module(script, name, include_variables: true)
|
|
name = "discourse/theme-#{@theme_id}/#{name.gsub(/^discourse\//, '')}"
|
|
script = "#{theme_settings}#{script}" if include_variables
|
|
transpiler = DiscourseJsProcessor::Transpiler.new
|
|
@content << <<~JS
|
|
if ('define' in window) {
|
|
#{transpiler.perform(script, "", name).strip}
|
|
}
|
|
JS
|
|
rescue MiniRacer::RuntimeError => ex
|
|
raise CompileError.new ex.message
|
|
end
|
|
|
|
def append_js_error(message)
|
|
@content << "console.error('Theme Transpilation Error:', #{message.inspect});"
|
|
end
|
|
|
|
private
|
|
|
|
def theme_settings
|
|
<<~JS
|
|
const settings = require("discourse/lib/theme-settings-store")
|
|
.getObjectForTheme(#{@theme_id});
|
|
const themePrefix = (key) => `theme_translations.#{@theme_id}.${key}`;
|
|
JS
|
|
end
|
|
end
|