discourse/app/assets/javascripts/theme-transpiler/transpiler.js
David Taylor f4b70c746d
DEV: Extract theme script tags into their own JS files (#33647)
Previously, all script tags in a theme field would be combined into a
single `.js` bundle along with the `text/discourse-plugin` modules. This
led to some unexpected run order, and also makes it hard to implement
the `type=module` change being developed in #33103.

This commit refactors things so that each raw script gets extracted into
its own `.js` bundle, which is then placed in exactly the same place in
the HTML.
2025-07-16 17:17:36 +01:00

130 lines
3.5 KiB
JavaScript
Vendored

// This is executed in mini_racer to provide the JS logic for lib/discourse_js_processor.rb
import "./shims";
import "./postcss";
import { transform as babelTransform } from "@babel/standalone";
import HTMLBarsInlinePrecompile from "babel-plugin-ember-template-compilation";
import DecoratorTransforms from "decorator-transforms";
import colocatedBabelPlugin from "ember-cli-htmlbars/lib/colocated-babel-plugin";
import { precompile } from "ember-source/dist/ember-template-compiler";
import EmberThisFallback from "ember-this-fallback";
import { minify as terserMinify } from "terser";
import { WidgetHbsCompiler } from "discourse-widget-hbs/lib/widget-hbs-compiler";
import { browsers } from "../discourse/config/targets";
import { Preprocessor } from "./content-tag";
import buildEmberTemplateManipulatorPlugin from "./theme-hbs-ast-transforms";
const thisFallbackPlugin = EmberThisFallback._buildPlugin({
enableLogging: false,
isTheme: true,
}).plugin;
function buildTemplateCompilerBabelPlugins({ extension, themeId }) {
const compiler = { precompile };
if (themeId && extension !== "gjs") {
compiler.precompile = (src, opts) => {
return precompile(src, {
...opts,
plugins: {
ast: [
buildEmberTemplateManipulatorPlugin(themeId),
thisFallbackPlugin,
],
},
});
};
}
return [
colocatedBabelPlugin,
WidgetHbsCompiler,
[
HTMLBarsInlinePrecompile,
{
compiler,
enableLegacyModules: [
"ember-cli-htmlbars",
"ember-cli-htmlbars-inline-precompile",
"htmlbars-inline-precompile",
],
},
],
];
}
globalThis.transpile = function (source, options = {}) {
const { moduleId, filename, extension, skipModule, themeId, generateMap } =
options;
if (extension === "gjs") {
const preprocessor = new Preprocessor();
source = preprocessor.process(source).code;
}
const plugins = [];
plugins.push(...buildTemplateCompilerBabelPlugins({ extension, themeId }));
if (moduleId && !skipModule) {
plugins.push(["transform-modules-amd", { noInterop: true }]);
}
plugins.push([DecoratorTransforms, { runEarly: true }]);
try {
const result = babelTransform(source, {
moduleId,
filename,
ast: false,
plugins,
presets: [
[
"env",
{
modules: false,
targets: {
browsers,
},
},
],
],
sourceMaps: generateMap,
});
if (generateMap) {
return {
code: result.code,
map: JSON.stringify(result.map),
};
} else {
return result.code;
}
} catch (error) {
// Workaround for https://github.com/rubyjs/mini_racer/issues/262
error.message = JSON.stringify(error.message);
throw error;
}
};
// mini_racer doesn't have native support for getting the result of an async operation.
// To work around that, we provide a getMinifyResult which can be used to fetch the result
// in a followup method call.
let lastMinifyError, lastMinifyResult;
globalThis.minify = async function (sources, options) {
lastMinifyError = lastMinifyResult = null;
try {
lastMinifyResult = await terserMinify(sources, options);
} catch (e) {
lastMinifyError = e;
}
};
globalThis.getMinifyResult = function () {
const error = lastMinifyError;
const result = lastMinifyResult;
lastMinifyError = lastMinifyResult = null;
if (error) {
throw error.toString();
}
return result;
};