0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-06 01:10:43 +08:00
discourse/frontend/asset-processor/babel-resolve-core-imports.test.mjs
David Taylor e3054ef590
DEV: Improve cross-plugin/theme import handling (#40939)
1. Remove 'federated exports' system, which was named entrypoint exports
for every module inside the plugin. Replace it with a single import of
the target plugin's 'compatModules', and then update call sites to do a
'just in time' lookup of the module and export. This is implemented in a
new `babel-resolve-plugin-imports` plugin

2. Update theme & plugin build systems to produce a list of external
plugins which are imported. For plugins, it's stored in the manifest.
For themes, it's stored in a new column of the javascript_caches table.

3. Refactor theme extra_js loading to use a more structured data model,
and move the HTML generation to the erb template

4. Update core importmap to identify any missing plugin dependencies and
add a fake placeholder module for them. This allows optional imports to
exist without causing a boot error. The imported values will resolve to
'null'.

5. Update core plugins to remove use of `optionalRequire`, and replace
it with regular imports, and a `with { discourseImport: "optional" }`
suffix. This is functionally equivalent to optionalRequire, but without
leaning on the legacy `loader.js` system of core

6. Add support for `with { discourseImport: "optional" }` for
plugins/themes importing core modules. This is useful when a
theme/plugin needs to target multiple versions of Discourse core.

Co-authored-by: Jarek Radosz <jarek@cvx.dev>
2026-06-30 16:11:38 +01:00

92 lines
2.9 KiB
JavaScript
Vendored

/* eslint-disable qunit/require-expect */
import { transformSync } from "@babel/core";
import { expect, test } from "vitest";
import BabelResolveCoreImports from "./babel-resolve-core-imports.js";
function compile(input) {
return transformSync(input, {
configFile: false,
plugins: [BabelResolveCoreImports],
}).code;
}
test("destructures core imports from moduleBroker", () => {
expect(
compile(`
import concatClass from "discourse/helpers/concat-class";
import { default as renamedDefaultImport, namedImport, otherNamedImport as renamedImport } from "discourse/module-1";
`)
).toMatchInlineSnapshot(`
"const {
default: concatClass
} = window.moduleBroker.lookup("discourse/helpers/concat-class");
const {
default: renamedDefaultImport,
namedImport: namedImport,
otherNamedImport: renamedImport
} = window.moduleBroker.lookup("discourse/module-1");"
`);
});
test("handles core namespace imports", () => {
expect(
compile(`
import * as MyModule from "discourse/module-1";
import defaultExport, * as MyModule2 from "discourse/module-2";
`)
).toMatchInlineSnapshot(`
"const MyModule = window.moduleBroker.lookup("discourse/module-1");
const {
default: defaultExport
} = window.moduleBroker.lookup("discourse/module-2");
const MyModule2 = window.moduleBroker.lookup("discourse/module-2");"
`);
});
test("marks optional core imports with a second lookup argument", () => {
expect(
compile(`
import concatClass from "discourse/helpers/concat-class" with { discourseImport: "optional" };
import * as MyModule from "discourse/module-1" with { discourseImport: "optional" };
`)
).toMatchInlineSnapshot(`
"const {
default: concatClass
} = window.moduleBroker.lookup("discourse/helpers/concat-class", true);
const MyModule = window.moduleBroker.lookup("discourse/module-1", true);"
`);
});
test('`discourseImport: "required"` keeps the default required lookup', () => {
expect(
compile(`
import concatClass from "discourse/helpers/concat-class" with { discourseImport: "required" };
`)
).toMatchInlineSnapshot(`
"const {
default: concatClass
} = window.moduleBroker.lookup("discourse/helpers/concat-class");"
`);
});
test("throws on an unknown `discourseImport` value", () => {
expect(() =>
compile(`
import concatClass from "discourse/helpers/concat-class" with { discourseImport: "maybe" };
`)
).toThrow(/Invalid `discourseImport` import attribute "maybe"/);
});
test("leaves relative, virtual, theme and plugin imports untouched", () => {
expect(
compile(`
import sibling from "./sibling";
import { settings } from "discourse/theme-12/settings";
import Thing from "discourse/plugins/other/lib/thing";
`)
).toMatchInlineSnapshot(`
"import sibling from "./sibling";
import { settings } from "discourse/theme-12/settings";
import Thing from "discourse/plugins/other/lib/thing";"
`);
});