2019-05-02 17:17:27 -05:00
|
|
|
# frozen_string_literal: true
|
2014-05-05 17:04:09 -05:00
|
|
|
require 'execjs'
|
2016-05-19 07:25:08 -05:00
|
|
|
require 'mini_racer'
|
2014-05-05 17:04:09 -05:00
|
|
|
|
2020-03-11 08:43:55 -05:00
|
|
|
class DiscourseJsProcessor
|
2014-05-05 17:04:09 -05:00
|
|
|
|
2020-03-11 08:43:55 -05:00
|
|
|
def self.call(input)
|
|
|
|
root_path = input[:load_path] || ''
|
|
|
|
logical_path = (input[:filename] || '').sub(root_path, '').gsub(/\.(js|es6).*$/, '').sub(/^\//, '')
|
|
|
|
data = input[:data]
|
2014-05-05 17:04:09 -05:00
|
|
|
|
2020-03-11 08:43:55 -05:00
|
|
|
if should_transpile?(input[:filename])
|
|
|
|
data = transpile(data, root_path, logical_path)
|
|
|
|
end
|
2017-04-17 09:11:51 -05:00
|
|
|
|
2020-03-11 08:43:55 -05:00
|
|
|
# add sourceURL until we can do proper source maps
|
|
|
|
unless Rails.env.production?
|
|
|
|
data = "eval(#{data.inspect} + \"\\n//# sourceURL=#{logical_path}\");\n"
|
2017-04-17 09:11:51 -05:00
|
|
|
end
|
|
|
|
|
2020-03-11 08:43:55 -05:00
|
|
|
{ data: data }
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.transpile(data, root_path, logical_path)
|
|
|
|
transpiler = Transpiler.new(skip_module: skip_module?(data))
|
|
|
|
transpiler.perform(data, root_path, logical_path)
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.should_transpile?(filename)
|
|
|
|
filename ||= ''
|
|
|
|
|
|
|
|
# es6 is always transpiled
|
|
|
|
return true if filename.end_with?(".es6") || filename.end_with?(".es6.erb")
|
|
|
|
|
|
|
|
# For .js check the path...
|
|
|
|
return false unless filename.end_with?(".js") || filename.end_with?(".js.erb")
|
|
|
|
|
|
|
|
relative_path = filename.sub(Rails.root.to_s, '').sub(/^\/*/, '')
|
2020-03-25 15:13:43 -05:00
|
|
|
relative_path.start_with?("app/assets/javascripts/discourse/") ||
|
|
|
|
relative_path.start_with?("app/assets/javascripts/admin/") ||
|
|
|
|
relative_path.start_with?("app/assets/javascripts/pretty-text/") ||
|
|
|
|
relative_path.start_with?("app/assets/javascripts/select-kit/") ||
|
|
|
|
relative_path.start_with?("app/assets/javascripts/wizard/") ||
|
|
|
|
relative_path.start_with?("app/assets/javascripts/discourse-common/") ||
|
|
|
|
relative_path.start_with?("app/assets/javascripts/ember-addons/") ||
|
|
|
|
relative_path.start_with?("app/assets/javascripts/confirm-new-email/") ||
|
|
|
|
relative_path == "app/assets/javascripts/preload-store.js" ||
|
|
|
|
relative_path == "app/assets/javascripts/preload-application-data.js" ||
|
|
|
|
relative_path == "app/assets/javascripts/wizard-start.js" ||
|
|
|
|
relative_path == "app/assets/javascripts/onpopstate-handler.js" ||
|
|
|
|
relative_path == "app/assets/javascripts/discourse.js" ||
|
|
|
|
relative_path == "app/assets/javascripts/google-tag-manager.js" ||
|
|
|
|
relative_path == "app/assets/javascripts/google-universal-analytics.js" ||
|
|
|
|
relative_path == "app/assets/javascripts/activate-account.js" ||
|
|
|
|
relative_path == "app/assets/javascripts/auto-redirect.js" ||
|
|
|
|
relative_path == "app/assets/javascripts/embed-application.js"
|
2020-03-11 08:43:55 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.skip_module?(data)
|
|
|
|
!!(data.present? && data =~ /^\/\/ discourse-skip-module$/)
|
|
|
|
end
|
|
|
|
|
|
|
|
class Transpiler
|
|
|
|
@mutex = Mutex.new
|
|
|
|
@ctx_init = Mutex.new
|
|
|
|
|
|
|
|
def self.mutex
|
|
|
|
@mutex
|
2014-05-05 17:04:09 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.create_new_context
|
2016-04-21 18:52:12 -05:00
|
|
|
# timeout any eval that takes longer than 15 seconds
|
2016-05-19 07:25:08 -05:00
|
|
|
ctx = MiniRacer::Context.new(timeout: 15000)
|
2017-07-04 15:34:55 -05:00
|
|
|
ctx.eval("var self = this; #{File.read("#{Rails.root}/vendor/assets/javascripts/babel.js")}")
|
2017-06-29 15:22:19 -05:00
|
|
|
ctx.eval(File.read(Ember::Source.bundled_path_for('ember-template-compiler.js')))
|
2018-12-03 21:48:13 -06:00
|
|
|
ctx.eval("module = {}; exports = {};")
|
2017-07-27 20:20:09 -05:00
|
|
|
ctx.attach("rails.logger.info", proc { |err| Rails.logger.info(err.to_s) })
|
|
|
|
ctx.attach("rails.logger.error", proc { |err| Rails.logger.error(err.to_s) })
|
2016-05-19 07:25:08 -05:00
|
|
|
ctx.eval <<JS
|
|
|
|
console = {
|
|
|
|
prefix: "",
|
|
|
|
log: function(msg){ rails.logger.info(console.prefix + msg); },
|
|
|
|
error: function(msg){ rails.logger.error(console.prefix + msg); }
|
|
|
|
}
|
2017-06-29 15:22:19 -05:00
|
|
|
|
2016-05-19 07:25:08 -05:00
|
|
|
JS
|
2020-03-25 15:13:26 -05:00
|
|
|
source = File.read("#{Rails.root}/lib/javascripts/widget-hbs-compiler.js.es6")
|
2017-06-29 15:22:19 -05:00
|
|
|
js_source = ::JSON.generate(source, quirks_mode: true)
|
|
|
|
js = ctx.eval("Babel.transform(#{js_source}, { ast: false, plugins: ['check-es2015-constants', 'transform-es2015-arrow-functions', 'transform-es2015-block-scoped-functions', 'transform-es2015-block-scoping', 'transform-es2015-classes', 'transform-es2015-computed-properties', 'transform-es2015-destructuring', 'transform-es2015-duplicate-keys', 'transform-es2015-for-of', 'transform-es2015-function-name', 'transform-es2015-literals', 'transform-es2015-object-super', 'transform-es2015-parameters', 'transform-es2015-shorthand-properties', 'transform-es2015-spread', 'transform-es2015-sticky-regex', 'transform-es2015-template-literals', 'transform-es2015-typeof-symbol', 'transform-es2015-unicode-regex'] }).code")
|
|
|
|
ctx.eval(js)
|
|
|
|
|
2014-05-05 17:04:09 -05:00
|
|
|
ctx
|
|
|
|
end
|
|
|
|
|
2016-11-01 21:34:20 -05:00
|
|
|
def self.reset_context
|
2017-07-19 23:17:45 -05:00
|
|
|
@ctx&.dispose
|
2016-11-01 21:34:20 -05:00
|
|
|
@ctx = nil
|
|
|
|
end
|
|
|
|
|
2014-05-05 17:04:09 -05:00
|
|
|
def self.v8
|
|
|
|
return @ctx if @ctx
|
|
|
|
|
|
|
|
# ensure we only init one of these
|
|
|
|
@ctx_init.synchronize do
|
|
|
|
return @ctx if @ctx
|
|
|
|
@ctx = create_new_context
|
|
|
|
end
|
|
|
|
|
|
|
|
@ctx
|
|
|
|
end
|
|
|
|
|
2020-03-11 08:43:55 -05:00
|
|
|
def initialize(skip_module: false)
|
|
|
|
@skip_module = skip_module
|
2016-03-18 13:41:27 -05:00
|
|
|
end
|
|
|
|
|
2020-03-11 08:43:55 -05:00
|
|
|
def perform(source, root_path = nil, logical_path = nil)
|
2016-06-14 13:31:51 -05:00
|
|
|
klass = self.class
|
2020-03-11 08:43:55 -05:00
|
|
|
klass.mutex.synchronize do
|
2016-06-14 13:31:51 -05:00
|
|
|
klass.v8.eval("console.prefix = 'BABEL: babel-eval: ';")
|
2017-06-29 15:22:19 -05:00
|
|
|
transpiled = babel_source(
|
|
|
|
source,
|
|
|
|
module_name: module_name(root_path, logical_path),
|
|
|
|
filename: logical_path
|
|
|
|
)
|
2017-07-05 13:14:30 -05:00
|
|
|
@output = klass.v8.eval(transpiled)
|
2016-06-14 13:31:51 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-07-27 20:20:09 -05:00
|
|
|
def babel_source(source, opts = nil)
|
2017-07-05 13:14:30 -05:00
|
|
|
opts ||= {}
|
|
|
|
|
2016-03-18 13:41:27 -05:00
|
|
|
js_source = ::JSON.generate(source, quirks_mode: true)
|
2017-07-05 13:14:30 -05:00
|
|
|
|
2020-03-11 08:43:55 -05:00
|
|
|
if opts[:module_name] && !@skip_module
|
2017-06-29 15:22:19 -05:00
|
|
|
filename = opts[:filename] || 'unknown'
|
|
|
|
"Babel.transform(#{js_source}, { moduleId: '#{opts[:module_name]}', filename: '#{filename}', ast: false, presets: ['es2015'], plugins: [['transform-es2015-modules-amd', {noInterop: true}], 'transform-decorators-legacy', exports.WidgetHbsCompiler] }).code"
|
2017-07-05 13:14:30 -05:00
|
|
|
else
|
2017-06-29 15:22:19 -05:00
|
|
|
"Babel.transform(#{js_source}, { ast: false, plugins: ['check-es2015-constants', 'transform-es2015-arrow-functions', 'transform-es2015-block-scoped-functions', 'transform-es2015-block-scoping', 'transform-es2015-classes', 'transform-es2015-computed-properties', 'transform-es2015-destructuring', 'transform-es2015-duplicate-keys', 'transform-es2015-for-of', 'transform-es2015-function-name', 'transform-es2015-literals', 'transform-es2015-object-super', 'transform-es2015-parameters', 'transform-es2015-shorthand-properties', 'transform-es2015-spread', 'transform-es2015-sticky-regex', 'transform-es2015-template-literals', 'transform-es2015-typeof-symbol', 'transform-es2015-unicode-regex', 'transform-regenerator', 'transform-decorators-legacy', exports.WidgetHbsCompiler] }).code"
|
2017-07-05 13:14:30 -05:00
|
|
|
end
|
2016-03-18 13:41:27 -05:00
|
|
|
end
|
|
|
|
|
2014-05-05 17:04:09 -05:00
|
|
|
def module_name(root_path, logical_path)
|
2014-05-15 15:31:45 -05:00
|
|
|
path = nil
|
|
|
|
|
2014-05-20 15:54:59 -05:00
|
|
|
root_base = File.basename(Rails.root)
|
2014-05-15 15:31:45 -05:00
|
|
|
# If the resource is a plugin, use the plugin name as a prefix
|
2014-05-20 15:54:59 -05:00
|
|
|
if root_path =~ /(.*\/#{root_base}\/plugins\/[^\/]+)\//
|
2014-05-15 15:31:45 -05:00
|
|
|
plugin_path = "#{Regexp.last_match[1]}/plugin.rb"
|
|
|
|
|
2017-07-27 20:20:09 -05:00
|
|
|
plugin = Discourse.plugins.find { |p| p.path == plugin_path }
|
2014-05-15 15:31:45 -05:00
|
|
|
path = "discourse/plugins/#{plugin.name}/#{logical_path.sub(/javascripts\//, '')}" if plugin
|
2014-05-05 17:04:09 -05:00
|
|
|
end
|
|
|
|
|
2020-03-11 08:43:55 -05:00
|
|
|
path || logical_path
|
2014-05-05 17:04:09 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
end
|