From 5a904949b277f5cb85be4b4a2d40b34fec8ee3da Mon Sep 17 00:00:00 2001 From: Jarek Radosz Date: Mon, 2 Oct 2023 12:36:06 +0200 Subject: [PATCH] DEV: Add gjs support for themes (#23473) --- .gitignore | 1 - .../discourse-common/addon/helpers/i18n.js | 6 +- app/assets/javascripts/discourse/package.json | 2 +- app/assets/javascripts/package.json | 3 +- .../patches/content-tag+1.1.0.patch | 29 ++++++++ .../javascripts/theme-transpiler/build.js | 68 +++++++++++++++++++ .../javascripts/theme-transpiler/package.json | 29 ++++++++ .../transpiler.js} | 23 +++++-- app/assets/javascripts/yarn.lock | 64 ++++++++--------- app/models/theme.rb | 2 +- app/models/theme_field.rb | 2 + lib/discourse_js_processor.rb | 37 +++++----- lib/tasks/assets.rake | 8 +-- lib/tasks/db.rake | 2 +- lib/theme_javascript_compiler.rb | 10 +-- spec/lib/theme_javascript_compiler_spec.rb | 23 +++++++ spec/tasks/assets_precompile_spec.rb | 6 +- 17 files changed, 242 insertions(+), 73 deletions(-) create mode 100644 app/assets/javascripts/patches/content-tag+1.1.0.patch create mode 100644 app/assets/javascripts/theme-transpiler/build.js create mode 100644 app/assets/javascripts/theme-transpiler/package.json rename app/assets/javascripts/{js-processor.js => theme-transpiler/transpiler.js} (87%) diff --git a/.gitignore b/.gitignore index 4a1b8e1fb68..3c7b1482942 100644 --- a/.gitignore +++ b/.gitignore @@ -75,4 +75,3 @@ openapi/* # Cached License Data Files /.licenses -/app/assets/javascripts/compiled-js-processor.js diff --git a/app/assets/javascripts/discourse-common/addon/helpers/i18n.js b/app/assets/javascripts/discourse-common/addon/helpers/i18n.js index f9fc65aa8da..995df7dfd2e 100644 --- a/app/assets/javascripts/discourse-common/addon/helpers/i18n.js +++ b/app/assets/javascripts/discourse-common/addon/helpers/i18n.js @@ -1,7 +1,11 @@ import I18n from "I18n"; import { registerUnbound } from "discourse-common/lib/helpers"; -registerUnbound("i18n", (key, params) => I18n.t(key, params)); +export default function i18n(key, params) { + return I18n.t(key, params); +} +registerUnbound("i18n", i18n); + registerUnbound("i18n-yes-no", (value, params) => I18n.t(value ? "yes_value" : "no_value", params) ); diff --git a/app/assets/javascripts/discourse/package.json b/app/assets/javascripts/discourse/package.json index 8eb19728a50..e3ec8bd730b 100644 --- a/app/assets/javascripts/discourse/package.json +++ b/app/assets/javascripts/discourse/package.json @@ -88,6 +88,7 @@ "ember-test-selectors": "^6.0.0", "eslint": "^8.50.0", "eslint-plugin-qunit": "^8.0.0", + "float-kit": "1.0.0", "html-entities": "^2.4.0", "imports-loader": "^4.0.1", "js-yaml": "^4.1.0", @@ -101,7 +102,6 @@ "sass": "^1.68.0", "select-kit": "1.0.0", "sinon": "^16.0.0", - "float-kit": "1.0.0", "source-map": "^0.7.4", "terser": "^5.20.0", "truth-helpers": "1.0.0", diff --git a/app/assets/javascripts/package.json b/app/assets/javascripts/package.json index ec190bd9a9a..02b12679805 100644 --- a/app/assets/javascripts/package.json +++ b/app/assets/javascripts/package.json @@ -15,9 +15,10 @@ "discourse-widget-hbs", "ember-cli-progress-ci", "ember-production-deprecations", + "float-kit", "pretty-text", "select-kit", - "float-kit", + "theme-transpiler", "truth-helpers", "wizard" ], diff --git a/app/assets/javascripts/patches/content-tag+1.1.0.patch b/app/assets/javascripts/patches/content-tag+1.1.0.patch new file mode 100644 index 00000000000..416fc8055a8 --- /dev/null +++ b/app/assets/javascripts/patches/content-tag+1.1.0.patch @@ -0,0 +1,29 @@ +diff --git a/node_modules/content-tag/content_tag.js b/node_modules/content-tag/content_tag.js +index 6ff5969..38915da 100644 +--- a/node_modules/content-tag/content_tag.js ++++ b/node_modules/content-tag/content_tag.js +@@ -448,11 +448,17 @@ module.exports.__wbindgen_memory = function() { + return addHeapObject(ret); + }; + +-const path = require('path').join(__dirname, 'content_tag_bg.wasm'); +-const bytes = require('fs').readFileSync(path); +- +-const wasmModule = new WebAssembly.Module(bytes); +-const wasmInstance = new WebAssembly.Instance(wasmModule, imports); +-wasm = wasmInstance.exports; +-module.exports.__wasm = wasm; ++// Check for nodejs environment ++if (process.version) { ++ const path = require('path').join(__dirname, 'content_tag_bg.wasm'); ++ const bytes = require('fs').readFileSync(path); ++ ++ const wasmModule = new WebAssembly.Module(bytes); ++ const wasmInstance = new WebAssembly.Instance(wasmModule, imports); ++ wasm = wasmInstance.exports; ++} else { ++ const load = require('./content_tag_bg.wasm').default; ++ wasm = load(imports); ++ module.exports.__wasm = wasm; ++} + diff --git a/app/assets/javascripts/theme-transpiler/build.js b/app/assets/javascripts/theme-transpiler/build.js new file mode 100644 index 00000000000..57f649b5ac3 --- /dev/null +++ b/app/assets/javascripts/theme-transpiler/build.js @@ -0,0 +1,68 @@ +// See: https://esbuild.github.io/plugins/#webassembly-plugin + +const esbuild = require("esbuild"); +const path = require("node:path"); +const fs = require("node:fs"); +const { argv } = require("node:process"); + +let wasmPlugin = { + name: "wasm", + + setup(build) { + build.onResolve({ filter: /\.wasm$/ }, (args) => { + if (args.namespace === "wasm-stub") { + return { + path: args.path, + namespace: "wasm-binary", + }; + } + + if (args.resolveDir === "") { + return; + } + + return { + path: path.isAbsolute(args.path) + ? args.path + : path.join(args.resolveDir, args.path), + namespace: "wasm-stub", + }; + }); + + build.onLoad({ filter: /.*/, namespace: "wasm-stub" }, async (args) => { + return { + contents: `import wasm from ${JSON.stringify(args.path)}; + export default (imports) => { + const wasmModule = new WebAssembly.Module(wasm); + const wasmInstance = new WebAssembly.Instance(wasmModule, imports); + return wasmInstance.exports; + };`, + }; + }); + + build.onLoad({ filter: /.*/, namespace: "wasm-binary" }, async (args) => { + return { + contents: await fs.promises.readFile(args.path), + loader: "binary", + }; + }); + }, +}; + +esbuild + .build({ + logLevel: "warning", + bundle: true, + minify: true, + alias: { + util: "./app/assets/javascripts/node_modules/@zxing/text-encoding", + }, + define: { + process: `{ "env": {} }`, + }, + external: ["fs", "path"], + entryPoints: ["./app/assets/javascripts/theme-transpiler/transpiler.js"], + outfile: argv[2], + plugins: [wasmPlugin], + }) + .then(() => {}); diff --git a/app/assets/javascripts/theme-transpiler/package.json b/app/assets/javascripts/theme-transpiler/package.json new file mode 100644 index 00000000000..84fc0432fd7 --- /dev/null +++ b/app/assets/javascripts/theme-transpiler/package.json @@ -0,0 +1,29 @@ +{ + "name": "theme-transpiler", + "version": "1.0.0", + "private": true, + "description": "Uses esbuild to create a 'theme transpiler' bundle for loading into mini-racer", + "author": "Discourse", + "license": "GPL-2.0-only", + "keywords": [], + "dependencies": { + "@babel/standalone": "^7.23.1", + "@zxing/text-encoding": "^0.9.0", + "babel-plugin-ember-template-compilation": "^2.2.0", + "content-tag": "^1.1.0", + "discourse-common": "1.0.0", + "discourse-widget-hbs": "1.0.0", + "ember-cli-htmlbars": "^6.3.0", + "ember-source": "~3.28.12", + "ember-this-fallback": "^0.3.1", + "handlebars": "^4.7.8", + "path-browserify": "^1.0.1", + "polyfill-crypto.getrandomvalues": "^1.0.0", + "terser": "^5.20.0" + }, + "engines": { + "node": "16.* || >= 18", + "npm": "please-use-yarn", + "yarn": ">= 1.21.1" + } +} diff --git a/app/assets/javascripts/js-processor.js b/app/assets/javascripts/theme-transpiler/transpiler.js similarity index 87% rename from app/assets/javascripts/js-processor.js rename to app/assets/javascripts/theme-transpiler/transpiler.js index 3beeb806b1b..c7030ce4ded 100644 --- a/app/assets/javascripts/js-processor.js +++ b/app/assets/javascripts/theme-transpiler/transpiler.js @@ -26,6 +26,13 @@ import RawHandlebars from "discourse-common/addon/lib/raw-handlebars"; import { WidgetHbsCompiler } from "discourse-widget-hbs/lib/widget-hbs-compiler"; import EmberThisFallback from "ember-this-fallback"; +// A sub-dependency of content-tag (getrandom) needs `getRandomValues` +// so we polyfill it +import getRandomValues from "polyfill-crypto.getrandomvalues"; +globalThis.crypto = { getRandomValues }; + +import { Preprocessor } from "content-tag"; + const thisFallbackPlugin = EmberThisFallback._buildPlugin({ enableLogging: false, isTheme: true, @@ -60,10 +67,10 @@ function buildEmberTemplateManipulatorPlugin(themeId) { }; } -function buildTemplateCompilerBabelPlugins({ themeId }) { +function buildTemplateCompilerBabelPlugins({ extension, themeId }) { const compiler = { precompile }; - if (themeId) { + if (themeId && extension !== "gjs") { compiler.precompile = (src, opts) => { return precompile(src, { ...opts, @@ -116,10 +123,16 @@ globalThis.compileRawTemplate = function (source, themeId) { }; globalThis.transpile = function (source, options = {}) { - const { moduleId, filename, skipModule, themeId, commonPlugins } = options; - const plugins = []; + const { moduleId, filename, extension, skipModule, themeId, commonPlugins } = + options; - plugins.push(...buildTemplateCompilerBabelPlugins({ themeId })); + if (extension === "gjs") { + const preprocessor = new Preprocessor(); + source = preprocessor.process(source); + } + + const plugins = []; + plugins.push(...buildTemplateCompilerBabelPlugins({ extension, themeId })); if (moduleId && !skipModule) { plugins.push(["transform-modules-amd", { noInterop: true }]); } diff --git a/app/assets/javascripts/yarn.lock b/app/assets/javascripts/yarn.lock index ee545ffb6c8..bc5323b812f 100644 --- a/app/assets/javascripts/yarn.lock +++ b/app/assets/javascripts/yarn.lock @@ -119,25 +119,12 @@ lodash.debounce "^4.0.8" resolve "^1.14.2" -"@babel/helper-environment-visitor@^7.22.20": +"@babel/helper-environment-visitor@^7.22.20", "@babel/helper-environment-visitor@^7.22.5": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== -"@babel/helper-environment-visitor@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98" - integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q== - -"@babel/helper-function-name@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be" - integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ== - dependencies: - "@babel/template" "^7.22.5" - "@babel/types" "^7.22.5" - -"@babel/helper-function-name@^7.23.0": +"@babel/helper-function-name@^7.22.5", "@babel/helper-function-name@^7.23.0": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== @@ -233,16 +220,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== -"@babel/helper-validator-identifier@^7.22.20": +"@babel/helper-validator-identifier@^7.22.20", "@babel/helper-validator-identifier@^7.22.5": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== -"@babel/helper-validator-identifier@^7.22.5": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz#601fa28e4cc06786c18912dca138cec73b882044" - integrity sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ== - "@babel/helper-validator-option@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040" @@ -1225,7 +1207,7 @@ ember-cli-version-checker "^5.1.2" semver "^7.3.5" -"@embroider/addon-shim@^1.0.0": +"@embroider/addon-shim@^1.0.0", "@embroider/addon-shim@^1.8.3", "@embroider/addon-shim@^1.8.4": version "1.8.6" resolved "https://registry.yarnpkg.com/@embroider/addon-shim/-/addon-shim-1.8.6.tgz#b676991b4fa32c3a98dc7db7dc6cd655029c3f09" integrity sha512-siC9kP78uucEbpDcVyxjkwa76pcs5rVzDVpWO4PDc9EAXRX+pzmUuSTLAK3GztUwx7/PWhz1BenAivqdSvSgfg== @@ -1234,15 +1216,6 @@ broccoli-funnel "^3.0.8" semver "^7.3.8" -"@embroider/addon-shim@^1.8.3", "@embroider/addon-shim@^1.8.4": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@embroider/addon-shim/-/addon-shim-1.8.5.tgz#c0aae417f9583058f40550f206fc53444e325f11" - integrity sha512-pDgpdTsC9i/+5hHziygK5VIZc64OG8bupiqL0OxJp+bnINURalHMbu5B3Gikq/a0QIvMPzDFWzKxIZCBpeiHkg== - dependencies: - "@embroider/shared-internals" "^2.1.0" - broccoli-funnel "^3.0.8" - semver "^7.3.8" - "@embroider/babel-loader-9@3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@embroider/babel-loader-9/-/babel-loader-9-3.1.0.tgz#eae859b82215fc7ee0e69ec867fda7b4eb4de2c0" @@ -1347,7 +1320,7 @@ resolve "^1.20.0" semver "^7.3.2" -"@embroider/shared-internals@2.5.0", "@embroider/shared-internals@^2.0.0", "@embroider/shared-internals@^2.1.0", "@embroider/shared-internals@^2.2.3": +"@embroider/shared-internals@2.5.0", "@embroider/shared-internals@^2.0.0", "@embroider/shared-internals@^2.2.3": version "2.5.0" resolved "https://registry.yarnpkg.com/@embroider/shared-internals/-/shared-internals-2.5.0.tgz#4a0b5127c589718fae60fc22f81374ed558b944a" integrity sha512-7qzrb7GVIyNqeY0umxoeIvjDC+ay1b+wb2yCVuYTUYrFfLAkLEy9FNI3iWCi3RdQ9OFjgcAxAnwsAiPIMZZ3pQ== @@ -2126,6 +2099,11 @@ resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== +"@zxing/text-encoding@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@zxing/text-encoding/-/text-encoding-0.9.0.tgz#fb50ffabc6c7c66a0c96b4c03e3d9be74864b70b" + integrity sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA== + a11y-dialog@8.0.2: version "8.0.2" resolved "https://registry.yarnpkg.com/a11y-dialog/-/a11y-dialog-8.0.2.tgz#08b8315d500b7f8c4200ec37d3a0fd15ccd54738" @@ -4174,6 +4152,11 @@ content-disposition@0.5.4: dependencies: safe-buffer "5.2.1" +content-tag@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/content-tag/-/content-tag-1.1.0.tgz#fcef4bdcf1850f9b67bf0b6f7aee217c6d7ea9fa" + integrity sha512-bktivDORs9M890KwVKrIONYvHhwshfgF4b1G/TFPrjH12Ag2GDiSdxVHqIzMxWZ297VgIRPSImURlpcOzJP/LQ== + content-type@~1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" @@ -8115,6 +8098,11 @@ merge2@^1.2.3, merge2@^1.3.0: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== +mersenne-twister@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mersenne-twister/-/mersenne-twister-1.1.0.tgz#f916618ee43d7179efcf641bec4531eb9670978a" + integrity sha512-mUYWsMKNrm4lfygPkL3OfGzOPTR2DBlTkBNHM//F6hGp8cLThY897crAlk3/Jo17LEOOjQUrNAx6DvgO77QJkA== + message-bus-client@^4.3.8: version "4.3.8" resolved "https://registry.yarnpkg.com/message-bus-client/-/message-bus-client-4.3.8.tgz#5ee23c03236b250b13613034764a87881c350d4e" @@ -8832,6 +8820,11 @@ patch-package@^8.0.0: tmp "^0.0.33" yaml "^2.2.2" +path-browserify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" + integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -8939,6 +8932,13 @@ pkg-up@^3.1.0: dependencies: find-up "^3.0.0" +polyfill-crypto.getrandomvalues@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/polyfill-crypto.getrandomvalues/-/polyfill-crypto.getrandomvalues-1.0.0.tgz#5c95602976ebb6155b163cb65d77b9eede3b61a4" + integrity sha512-GIkU6bg4auRnDFOqUNit7eLn9hzznrJU1CGFuivQzDeVp4Ys8cY4OY6GhAdndJwo4jryz5cJyjg9ELhvQjdrtw== + dependencies: + mersenne-twister "^1.0.1" + portfinder@^1.0.32: version "1.0.32" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.32.tgz#2fe1b9e58389712429dc2bea5beb2146146c7f81" diff --git a/app/models/theme.rb b/app/models/theme.rb index b96e72454e4..71395e67d6b 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 = 75 + BASE_COMPILER_VERSION = 76 attr_accessor :child_components diff --git a/app/models/theme_field.rb b/app/models/theme_field.rb index 5ed57393014..36bd6948316 100644 --- a/app/models/theme_field.rb +++ b/app/models/theme_field.rb @@ -149,6 +149,7 @@ class ThemeField < ActiveRecord::Base js_compiler.append_module( js, "discourse/initializers/#{initializer_name}", + "js", include_variables: true, ) rescue ThemeJavascriptCompiler::CompileError => ex @@ -276,6 +277,7 @@ class ThemeField < ActiveRecord::Base js_compiler.append_module( js, "discourse/pre-initializers/theme-#{theme_id}-translations", + "js", include_variables: false, ) rescue ThemeTranslationParser::InvalidYaml => e diff --git a/lib/discourse_js_processor.rb b/lib/discourse_js_processor.rb index 3fee4259af9..a70361325a4 100644 --- a/lib/discourse_js_processor.rb +++ b/lib/discourse_js_processor.rb @@ -49,9 +49,9 @@ class DiscourseJsProcessor { data: data } end - def self.transpile(data, root_path, logical_path, theme_id: nil) + def self.transpile(data, root_path, logical_path, theme_id: nil, extension: nil) transpiler = Transpiler.new(skip_module: skip_module?(data)) - transpiler.perform(data, root_path, logical_path, theme_id: theme_id) + transpiler.perform(data, root_path, logical_path, theme_id: theme_id, extension: extension) end def self.should_transpile?(filename) @@ -98,8 +98,14 @@ class DiscourseJsProcessor end class Transpiler - JS_PROCESSOR_PATH = - Rails.env.production? ? "tmp/js-processor.js" : "tmp/js-processor/#{Process.pid}.js" + TRANSPILER_PATH = + ( + if Rails.env.production? + "tmp/theme-transpiler.js" + else + "tmp/theme-transpiler/#{Process.pid}.js" + end + ) @mutex = Mutex.new @ctx_init = Mutex.new @@ -109,19 +115,13 @@ class DiscourseJsProcessor @mutex end - def self.generate_js_processor + def self.build_theme_transpiler Discourse::Utils.execute_command( - "yarn", - "--silent", - "esbuild", - "--log-level=warning", - "--bundle", - "--external:fs", - "--define:process='{\"env\":{}}'", - "app/assets/javascripts/js-processor.js", - "--outfile=#{JS_PROCESSOR_PATH}", + "node", + "app/assets/javascripts/theme-transpiler/build.js", + TRANSPILER_PATH, ) - JS_PROCESSOR_PATH + TRANSPILER_PATH end def self.create_new_context @@ -135,10 +135,10 @@ class DiscourseJsProcessor # Theme template AST transformation plugins if Rails.env.development? || Rails.env.test? - @processor_mutex.synchronize { generate_js_processor } + @processor_mutex.synchronize { build_theme_transpiler } end - ctx.eval(File.read(JS_PROCESSOR_PATH), filename: "js-processor.js") + ctx.eval(File.read(TRANSPILER_PATH), filename: "theme-transpiler.js") ctx end @@ -190,7 +190,7 @@ class DiscourseJsProcessor @skip_module = skip_module end - def perform(source, root_path = nil, logical_path = nil, theme_id: nil) + def perform(source, root_path = nil, logical_path = nil, theme_id: nil, extension: nil) self.class.v8_call( "transpile", source, @@ -198,6 +198,7 @@ class DiscourseJsProcessor skipModule: @skip_module, moduleId: module_name(root_path, logical_path), filename: logical_path || "unknown", + extension: extension, themeId: theme_id, commonPlugins: DISCOURSE_COMMON_BABEL_PLUGINS, }, diff --git a/lib/tasks/assets.rake b/lib/tasks/assets.rake index 6a03db57f06..881ddf9efc8 100644 --- a/lib/tasks/assets.rake +++ b/lib/tasks/assets.rake @@ -309,16 +309,16 @@ task "assets:precompile:compress_js": "environment" do end end -task "assets:precompile:js_processor": "environment" do - path = DiscourseJsProcessor::Transpiler.generate_js_processor - puts "Compiled js-processor: #{path}" +task "assets:precompile:theme_transpiler": "environment" do + path = DiscourseJsProcessor::Transpiler.build_theme_transpiler + puts "Compiled theme-transpiler: #{path}" end # Run these tasks **before** Rails' "assets:precompile" task task "assets:precompile": %w[ assets:precompile:before maxminddb:refresh - assets:precompile:js_processor + assets:precompile:theme_transpiler ] # Run these tasks **after** Rails' "assets:precompile" task diff --git a/lib/tasks/db.rake b/lib/tasks/db.rake index bf08148679b..0be0c5305f2 100644 --- a/lib/tasks/db.rake +++ b/lib/tasks/db.rake @@ -220,7 +220,7 @@ task "db:migrate" => %w[ load_config environment set_locale - assets:precompile:js_processor + assets:precompile:theme_transpiler ] do |_, args| DistributedMutex.synchronize( "db_migration", diff --git a/lib/theme_javascript_compiler.rb b/lib/theme_javascript_compiler.rb index e9581c2c443..334a638fa7d 100644 --- a/lib/theme_javascript_compiler.rb +++ b/lib/theme_javascript_compiler.rb @@ -169,8 +169,8 @@ class ThemeJavascriptCompiler 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) + if extension == "js" || extension == "gjs" + append_module(content, module_name, extension) elsif extension == "hbs" append_ember_template(module_name, content) elsif extension == "hbr" @@ -232,15 +232,15 @@ class ThemeJavascriptCompiler @output_tree << [filename, script + "\n"] end - def append_module(script, name, include_variables: true) + def append_module(script, name, extension, include_variables: true) original_filename = name name = "discourse/theme-#{@theme_id}/#{name}" script = "#{theme_settings}#{script}" if include_variables transpiler = DiscourseJsProcessor::Transpiler.new - @output_tree << ["#{original_filename}.js", <<~JS] + @output_tree << ["#{original_filename}.#{extension}", <<~JS] if ('define' in window) { - #{transpiler.perform(script, "", name, theme_id: @theme_id).strip} + #{transpiler.perform(script, "", name, theme_id: @theme_id, extension: extension).strip} } JS rescue MiniRacer::RuntimeError, DiscourseJsProcessor::TranspileError => ex diff --git a/spec/lib/theme_javascript_compiler_spec.rb b/spec/lib/theme_javascript_compiler_spec.rb index 85aeab5ffe5..6ad14c5f2fa 100644 --- a/spec/lib/theme_javascript_compiler_spec.rb +++ b/spec/lib/theme_javascript_compiler_spec.rb @@ -233,4 +233,27 @@ RSpec.describe ThemeJavascriptCompiler do ) end end + + describe "ember-template-imports" do + it "applies its transforms" do + compiler.append_tree({ "discourse/components/my-component.gjs" => <<~JS }) + import Component from "@glimmer/component"; + + export default class MyComponent extends Component { + + + value = "foo"; + } + JS + + expect(compiler.raw_content).to include( + "define(\"discourse/theme-1/discourse/components/my-component\", [\"exports\",", + ) + expect(compiler.raw_content).to include("_defineProperty(this, \"value\", \"foo\");") + expect(compiler.raw_content).to include("setComponentTemplate") + expect(compiler.raw_content).to include("createTemplateFactory") + end + end end diff --git a/spec/tasks/assets_precompile_spec.rb b/spec/tasks/assets_precompile_spec.rb index e91a7b7d3c1..51a8f1ab4d8 100644 --- a/spec/tasks/assets_precompile_spec.rb +++ b/spec/tasks/assets_precompile_spec.rb @@ -6,11 +6,11 @@ RSpec.describe "assets:precompile" do Discourse::Application.load_tasks end - describe "assets:precompile:js_processor" do + describe "assets:precompile:theme_transpiler" do it "compiles the js processor" do - out = capture_stdout { Rake::Task["assets:precompile:js_processor"].invoke } + out = capture_stdout { Rake::Task["assets:precompile:theme_transpiler"].invoke } - expect(out).to match(%r{Compiled js-processor: tmp/js-processor}) + expect(out).to match(%r{Compiled theme-transpiler: tmp/theme-transpiler}) path = out.match(/: (.+)/)[1] expect(File.exist?(path)).to eq(true) end