diff --git a/app/assets/javascripts/discourse/package.json b/app/assets/javascripts/discourse/package.json index 23db96c43b2..8678b086dad 100644 --- a/app/assets/javascripts/discourse/package.json +++ b/app/assets/javascripts/discourse/package.json @@ -36,6 +36,7 @@ "a11y-dialog": "7.5.0", "admin": "^1.0.0", "discourse-plugins": "^1.0.0", + "babel-plugin-ember-template-compilation": "^1.0.2", "bootstrap": "3.4.1", "broccoli-asset-rev": "^3.0.0", "deepmerge": "^4.2.2", diff --git a/app/assets/javascripts/mini-loader.js b/app/assets/javascripts/mini-loader.js index 24882b5226d..cd544945b54 100644 --- a/app/assets/javascripts/mini-loader.js +++ b/app/assets/javascripts/mini-loader.js @@ -40,12 +40,17 @@ let define, requirejs; } Module.prototype.makeRequire = function () { - return ( - this._require || - (this._require = function (dep) { - return requirejs(resolve(dep, this.name)); - }) - ); + if (this._require) { + return this._require; + } + this._require = (dep) => { + return requirejs(resolve(dep, this.name)); + }; + this._require.has = (dep) => { + const moduleName = resolve(dep, this.name); + return require.has(moduleName); + }; + return this._require; }; define = function (name, deps, callback) { @@ -105,6 +110,12 @@ let define, requirejs; function requireFrom(name, origin) { let mod = JS_MODULES[name] || registry[name]; + + if (!mod) { + name = name + "/index"; + mod = registry[name]; + } + if (!mod) { throw new Error( "Could not find module `" + name + "` imported from `" + origin + "`" @@ -128,6 +139,11 @@ let define, requirejs; mod = registry[mod.callback.name]; } + if (!mod) { + name = name + "/index"; + mod = registry[name]; + } + if (!mod) { missingModule(name); } @@ -206,4 +222,12 @@ let define, requirejs; requirejs.entries = requirejs._eak_seen = registry = {}; seen = {}; }; + require.has = function (moduleName) { + return ( + Boolean(registry[moduleName]) || Boolean(registry[moduleName + "/index"]) + ); + }; + + globalThis.define = define; + globalThis.require = require; })(); diff --git a/app/assets/javascripts/yarn.lock b/app/assets/javascripts/yarn.lock index e1a36bdf6cf..d5403568e05 100644 --- a/app/assets/javascripts/yarn.lock +++ b/app/assets/javascripts/yarn.lock @@ -2378,7 +2378,7 @@ babel-plugin-ember-modules-api-polyfill@^3.5.0: dependencies: ember-rfc176-data "^0.3.17" -babel-plugin-ember-template-compilation@^1.0.0: +babel-plugin-ember-template-compilation@^1.0.0, babel-plugin-ember-template-compilation@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/babel-plugin-ember-template-compilation/-/babel-plugin-ember-template-compilation-1.0.2.tgz#e0695b8ad5a8fe6b2cbdff1eadb01cf402731ad6" integrity sha512-4HBMksmlYsWEf/C/n3uW5rkBRbUp4FNaspzdQTAHgLbfCJnkLze8R6i6sUSge48y/Wne7mx+vcImI1o6rlUwXQ== diff --git a/app/models/theme.rb b/app/models/theme.rb index 9bda971d549..b89c7ca71a3 100644 --- a/app/models/theme.rb +++ b/app/models/theme.rb @@ -6,7 +6,7 @@ require 'json_schemer' class Theme < ActiveRecord::Base include GlobalPath - BASE_COMPILER_VERSION = 59 + BASE_COMPILER_VERSION = 60 attr_accessor :child_components diff --git a/lib/discourse_js_processor.rb b/lib/discourse_js_processor.rb index 4d7cb1ddbd2..36cc74323e2 100644 --- a/lib/discourse_js_processor.rb +++ b/lib/discourse_js_processor.rb @@ -110,27 +110,79 @@ class DiscourseJsProcessor @mutex end + def self.load_file_in_context(ctx, path, wrap_in_module: nil) + contents = File.read("#{Rails.root}/app/assets/javascripts/#{path}") + if wrap_in_module + contents = <<~JS + define(#{wrap_in_module.to_json}, ["exports", "require"], function(exports, require){ + #{contents} + }); + JS + end + ctx.eval(contents, filename: path) + end + def self.create_new_context # timeout any eval that takes longer than 15 seconds ctx = MiniRacer::Context.new(timeout: 15000, ensure_gc_after_idle: 2000) - ctx.eval("#{File.read("#{Rails.root}/app/assets/javascripts/node_modules/@babel/standalone/babel.js")}") - ctx.eval(File.read(Ember::Source.bundled_path_for('ember-template-compiler.js'))) - ctx.eval("module = {}; exports = {};") - ctx.eval("const DISCOURSE_COMMON_BABEL_PLUGINS = #{DISCOURSE_COMMON_BABEL_PLUGINS.to_json};") - 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) }) - ctx.eval < precompile), { enableLegacyModules: ["ember-cli-htmlbars"] }], + ] + JS ctx end @@ -184,7 +236,7 @@ JS filename: '#{filename}', ast: false, plugins: [ - exports.WidgetHbsCompiler, + ...DISCOURSE_TEMPLATE_COMPILER_PLUGINS, ['transform-modules-amd', {noInterop: true}], ...DISCOURSE_COMMON_BABEL_PLUGINS ] @@ -198,7 +250,7 @@ JS { ast: false, plugins: [ - exports.WidgetHbsCompiler, + ...DISCOURSE_TEMPLATE_COMPILER_PLUGINS, ...DISCOURSE_COMMON_BABEL_PLUGINS ] } diff --git a/spec/lib/discourse_js_processor_spec.rb b/spec/lib/discourse_js_processor_spec.rb index 4d48dc7a1c7..51f8008b8f9 100644 --- a/spec/lib/discourse_js_processor_spec.rb +++ b/spec/lib/discourse_js_processor_spec.rb @@ -35,4 +35,47 @@ RSpec.describe DiscourseJsProcessor do expect(DiscourseJsProcessor.skip_module?("// just some JS\nconsole.log()")).to eq(false) end end + + it "correctly transpiles widget hbs" do + result = DiscourseJsProcessor.transpile(<<~JS, "blah", "blah/mymodule") + import hbs from "discourse/widgets/hbs-compiler"; + const template = hbs`{{somevalue}}`; + JS + expect(result).to eq <<~JS.strip + define("blah/mymodule", [], function () { + "use strict"; + + const template = function (attrs, state) { + var _r = []; + + _r.push(somevalue); + + return _r; + }; + }); + JS + end + + it "correctly transpiles ember hbs" do + result = DiscourseJsProcessor.transpile(<<~JS, "blah", "blah/mymodule") + import { hbs } from 'ember-cli-htmlbars'; + const template = hbs`{{somevalue}}`; + JS + expect(result).to eq <<~JS.strip + define("blah/mymodule", ["@ember/template-factory"], function (_templateFactory) { + "use strict"; + + const template = (0, _templateFactory.createTemplateFactory)( + /* + {{somevalue}} + */ + { + "id": null, + "block": "[[[1,[34,0]]],[],false,[\\"somevalue\\"]]", + "moduleName": "(unknown template module)", + "isStrictMode": false + }); + }); + JS + end end