mirror of
https://github.com/discourse/discourse.git
synced 2024-11-25 09:42:07 +08:00
65a5c84a92
Previously, compiling theme 'extra_js' was done with a number of steps. Each theme_field would be compiled into its own value_baked column, and then the JavascriptCache content would be built by concatenating all of those compiled values. This commit streamlines things by removing the value_baked step. The raw value of all extra_js theme_fields are passed directly to the ThemeJavascriptCompiler, and then the result is stored in the JavascriptCache. In itself, this commit should not cause any behavior change. It is designed to open the door to more advanced compilation features which have interdependencies between different source files (e.g. template colocation, sourcemaps).
142 lines
4.6 KiB
Ruby
142 lines
4.6 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class ThemeJavascriptCompiler
|
|
|
|
COLOCATED_CONNECTOR_REGEX = /\A(?<prefix>.*)\/connectors\/(?<outlet>[^\/]+)\/(?<name>[^\/\.]+)\z/
|
|
|
|
class CompileError < StandardError
|
|
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
|
|
|
|
def append_tree(tree, for_tests: false)
|
|
root_name = "discourse"
|
|
|
|
# Replace legacy extensions
|
|
tree.transform_keys! do |filename|
|
|
if filename.ends_with? ".js.es6"
|
|
filename.sub(/\.js\.es6\z/, ".js")
|
|
elsif filename.ends_with? ".raw.hbs"
|
|
filename.sub(/\.raw\.hbs\z/, ".hbr")
|
|
else
|
|
filename
|
|
end
|
|
end
|
|
|
|
# Transpile and write to output
|
|
tree.each_pair do |filename, content|
|
|
module_name, extension = filename.split(".", 2)
|
|
module_name = "test/#{module_name}" if for_tests
|
|
if extension == "js"
|
|
append_module(content, module_name)
|
|
elsif extension == "hbs"
|
|
append_ember_template(module_name, content)
|
|
elsif extension == "hbr"
|
|
append_raw_template(module_name.sub("discourse/templates/", ""), content)
|
|
else
|
|
append_js_error("unknown file extension '#{extension}' (#{filename})")
|
|
end
|
|
rescue CompileError => e
|
|
append_js_error "#{e.message} (#{filename})"
|
|
end
|
|
end
|
|
|
|
def append_ember_template(name, hbs_template)
|
|
name = "/#{name}" if !name.start_with?("/")
|
|
module_name = "discourse/theme-#{@theme_id}#{name}"
|
|
|
|
# Some themes are colocating connector JS under `/connectors`. Move template to /templates to avoid module name clash
|
|
if (match = COLOCATED_CONNECTOR_REGEX.match(module_name)) && !match[:prefix].end_with?("/templates")
|
|
module_name = "#{match[:prefix]}/templates/connectors/#{match[:outlet]}/#{match[:name]}"
|
|
end
|
|
|
|
# Mimics the ember-cli implementation
|
|
# https://github.com/ember-cli/ember-cli-htmlbars/blob/d5aa14b3/lib/template-compiler-plugin.js#L18-L26
|
|
script = <<~JS
|
|
import { hbs } from 'ember-cli-htmlbars';
|
|
export default hbs(#{hbs_template.to_json}, { moduleName: #{module_name.to_json} });
|
|
JS
|
|
|
|
template_module = DiscourseJsProcessor.transpile(script, "", module_name, theme_id: @theme_id)
|
|
content << <<~JS
|
|
if ('define' in window) {
|
|
#{template_module}
|
|
}
|
|
JS
|
|
rescue MiniRacer::RuntimeError, DiscourseJsProcessor::TranspileError => ex
|
|
raise CompileError.new ex.message
|
|
end
|
|
|
|
def raw_template_name(name)
|
|
name = name.sub(/\.(raw|hbr)$/, '')
|
|
name.inspect
|
|
end
|
|
|
|
def append_raw_template(name, hbs_template)
|
|
compiled = DiscourseJsProcessor::Transpiler.new.compile_raw_template(hbs_template, theme_id: @theme_id)
|
|
@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 MiniRacer::RuntimeError, DiscourseJsProcessor::TranspileError => ex
|
|
raise CompileError.new ex.message
|
|
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\//, '')}"
|
|
|
|
# Some themes are colocating connector JS under `/templates/connectors`. Move out of templates to avoid module name clash
|
|
if (match = COLOCATED_CONNECTOR_REGEX.match(name)) && match[:prefix].end_with?("/templates")
|
|
name = "#{match[:prefix].delete_suffix("/templates")}/connectors/#{match[:outlet]}/#{match[:name]}"
|
|
end
|
|
|
|
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, DiscourseJsProcessor::TranspileError => ex
|
|
raise CompileError.new ex.message
|
|
end
|
|
|
|
def append_js_error(message)
|
|
message = "[THEME #{@theme_id} '#{@theme_name}'] Compile error: #{message}"
|
|
append_raw_script "console.error(#{message.to_json});"
|
|
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
|