mirror of
https://github.com/discourse/discourse.git
synced 2026-08-04 10:39:43 +08:00
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>
163 lines
5.1 KiB
JavaScript
Vendored
163 lines
5.1 KiB
JavaScript
Vendored
/* eslint-disable qunit/require-expect */
|
|
import { transformSync } from "@babel/core";
|
|
import { expect, test } from "vitest";
|
|
import BabelResolvePluginImports from "./babel-resolve-plugin-imports.js";
|
|
|
|
function compile(input) {
|
|
return transformSync(input, {
|
|
configFile: false,
|
|
plugins: [BabelResolvePluginImports],
|
|
}).code;
|
|
}
|
|
|
|
test("rewrites cross-plugin imports to the compatModules map", () => {
|
|
expect(
|
|
compile(`
|
|
import SharedThing, { somethingShared, other as renamed } from "discourse/plugins/other/lib/shared";
|
|
import * as Helpers from "discourse/plugins/other/lib/helpers";
|
|
|
|
SharedThing();
|
|
somethingShared(1);
|
|
renamed.property;
|
|
Helpers.doThing();
|
|
`)
|
|
).toMatchInlineSnapshot(`
|
|
"import _plugin_other from "discourse/plugins/other";
|
|
(0, (_plugin_other["lib/shared"] || _plugin_other["lib/shared/index"]).default)();
|
|
(0, (_plugin_other["lib/shared"] || _plugin_other["lib/shared/index"]).somethingShared)(1);
|
|
(_plugin_other["lib/shared"] || _plugin_other["lib/shared/index"]).other.property;
|
|
(_plugin_other["lib/helpers"] || _plugin_other["lib/helpers/index"]).doThing();"
|
|
`);
|
|
});
|
|
|
|
test("de-dupes the default import per plugin", () => {
|
|
expect(
|
|
compile(`
|
|
import { a } from "discourse/plugins/other/lib/one";
|
|
import { b } from "discourse/plugins/other/lib/two";
|
|
|
|
a();
|
|
b();
|
|
`)
|
|
).toMatchInlineSnapshot(`
|
|
"import _plugin_other from "discourse/plugins/other";
|
|
(0, (_plugin_other["lib/one"] || _plugin_other["lib/one/index"]).a)();
|
|
(0, (_plugin_other["lib/two"] || _plugin_other["lib/two/index"]).b)();"
|
|
`);
|
|
});
|
|
|
|
test("expands shorthand object properties inline", () => {
|
|
expect(
|
|
compile(`
|
|
import { foo, bar } from "discourse/plugins/other/lib/shared";
|
|
|
|
foo();
|
|
const obj = { bar };
|
|
`)
|
|
).toMatchInlineSnapshot(`
|
|
"import _plugin_other from "discourse/plugins/other";
|
|
(0, (_plugin_other["lib/shared"] || _plugin_other["lib/shared/index"]).foo)();
|
|
const obj = {
|
|
bar: (_plugin_other["lib/shared"] || _plugin_other["lib/shared/index"]).bar
|
|
};"
|
|
`);
|
|
});
|
|
|
|
test("throws when a cross-plugin import is re-exported", () => {
|
|
expect(() =>
|
|
compile(`
|
|
import { foo } from "discourse/plugins/other/lib/shared";
|
|
export { foo };
|
|
`)
|
|
).toThrow(/Re-exporting a cross-plugin import is not supported/);
|
|
});
|
|
|
|
test("imports are required by default, consolidating to the plain specifier", () => {
|
|
expect(
|
|
compile(`
|
|
import ChatChannel from "discourse/plugins/chat/models/chat-channel";
|
|
|
|
ChatChannel.create();
|
|
`)
|
|
).toMatchInlineSnapshot(`
|
|
"import _plugin_chat from "discourse/plugins/chat";
|
|
(_plugin_chat["models/chat-channel"] || _plugin_chat["models/chat-channel/index"]).default.create();"
|
|
`);
|
|
});
|
|
|
|
test('`discourseImport: "optional"` consolidates to the `?` specifier', () => {
|
|
expect(
|
|
compile(`
|
|
import ChatChannel from "discourse/plugins/chat/models/chat-channel" with { discourseImport: "optional" };
|
|
|
|
ChatChannel.create();
|
|
`)
|
|
).toMatchInlineSnapshot(`
|
|
"import _plugin_chat_optional from "discourse/plugins/chat?";
|
|
(_plugin_chat_optional["models/chat-channel"] || _plugin_chat_optional["models/chat-channel/index"]).default.create();"
|
|
`);
|
|
});
|
|
|
|
test("optional and required imports of the same plugin get separate specifiers", () => {
|
|
expect(
|
|
compile(`
|
|
import { a } from "discourse/plugins/chat/lib/one" with { discourseImport: "optional" };
|
|
import { b } from "discourse/plugins/chat/lib/two";
|
|
|
|
a();
|
|
b();
|
|
`)
|
|
).toMatchInlineSnapshot(`
|
|
"import _plugin_chat_optional from "discourse/plugins/chat?";
|
|
import _plugin_chat from "discourse/plugins/chat";
|
|
(0, (_plugin_chat_optional["lib/one"] || _plugin_chat_optional["lib/one/index"]).a)();
|
|
(0, (_plugin_chat["lib/two"] || _plugin_chat["lib/two/index"]).b)();"
|
|
`);
|
|
});
|
|
|
|
test('`discourseImport: "required"` consolidates to the plain specifier', () => {
|
|
expect(
|
|
compile(`
|
|
import ChatChannel from "discourse/plugins/chat/models/chat-channel" with { discourseImport: "required" };
|
|
|
|
ChatChannel.create();
|
|
`)
|
|
).toMatchInlineSnapshot(`
|
|
"import _plugin_chat from "discourse/plugins/chat";
|
|
(_plugin_chat["models/chat-channel"] || _plugin_chat["models/chat-channel/index"]).default.create();"
|
|
`);
|
|
});
|
|
|
|
test("recognises the quoted attribute key form", () => {
|
|
expect(
|
|
compile(`
|
|
import ChatChannel from "discourse/plugins/chat/models/chat-channel" with { "discourseImport": "required" };
|
|
|
|
ChatChannel.create();
|
|
`)
|
|
).toMatchInlineSnapshot(`
|
|
"import _plugin_chat from "discourse/plugins/chat";
|
|
(_plugin_chat["models/chat-channel"] || _plugin_chat["models/chat-channel/index"]).default.create();"
|
|
`);
|
|
});
|
|
|
|
test("throws on an unknown `discourseImport` value", () => {
|
|
expect(() =>
|
|
compile(`
|
|
import ChatChannel from "discourse/plugins/chat/models/chat-channel" with { discourseImport: "maybe" };
|
|
ChatChannel.create();
|
|
`)
|
|
).toThrow(/Invalid `discourseImport` import attribute "maybe"/);
|
|
});
|
|
|
|
test("leaves relative and core imports untouched", () => {
|
|
expect(
|
|
compile(`
|
|
import sibling from "./sibling";
|
|
import concatClass from "discourse/helpers/concat-class";
|
|
`)
|
|
).toMatchInlineSnapshot(`
|
|
"import sibling from "./sibling";
|
|
import concatClass from "discourse/helpers/concat-class";"
|
|
`);
|
|
});
|