mirror of
https://github.com/discourse/discourse.git
synced 2026-08-06 06:24:48 +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>
261 lines
10 KiB
Ruby
Vendored
261 lines
10 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
RSpec.describe ThemeJavascriptCompiler do
|
|
let(:compiler) { ThemeJavascriptCompiler.new(1, "marks", minify: false) }
|
|
|
|
describe "#append_ember_template" do
|
|
it "maintains module names" do
|
|
compiler.append_tree({ "connectors/blah-1.hbs" => "{{var}}" })
|
|
compiler.append_tree({ "connectors/blah-2.hbs" => "{{var}}" })
|
|
compiler.append_tree({ "javascripts/connectors/blah-3.hbs" => "{{var}}" })
|
|
|
|
expect(compiler.content.to_s).to include("\"templates/connectors/blah-1\":")
|
|
expect(compiler.content.to_s).to include("\"templates/connectors/blah-2\":")
|
|
expect(compiler.content.to_s).to include("\"javascripts/templates/connectors/blah-3\":")
|
|
end
|
|
end
|
|
|
|
describe "connector module name handling" do
|
|
it "separates colocated connectors to avoid module name clash" do
|
|
# Colocated under `/connectors`
|
|
compiler = ThemeJavascriptCompiler.new(1, "marks", minify: false)
|
|
compiler.append_tree(
|
|
{
|
|
"connectors/outlet/blah-1.hbs" => "{{var}}",
|
|
"connectors/outlet/blah-1.js" => "export default class MyComponent {};",
|
|
},
|
|
)
|
|
expect(compiler.content.to_s).to include('"connectors/outlet/blah-1":').once
|
|
expect(compiler.content.to_s).to include("templates/connectors/outlet/blah-1")
|
|
expect(compiler.content.to_s).not_to include("setComponentTemplate")
|
|
expect(compiler.content.to_s).to include("createTemplateFactory")
|
|
expect(JSON.parse(compiler.source_map)["sources"]).to include(
|
|
"theme-1/connectors/outlet/blah-1.js",
|
|
"theme-1/connectors/outlet/blah-1.hbs",
|
|
)
|
|
|
|
# Colocated under `/templates/connectors`
|
|
compiler = ThemeJavascriptCompiler.new(1, "marks", minify: false)
|
|
compiler.append_tree(
|
|
{
|
|
"templates/connectors/outlet/blah-1.hbs" => "{{var}}",
|
|
"templates/connectors/outlet/blah-1.js" => "export default {};",
|
|
},
|
|
)
|
|
expect(compiler.content.to_s).to include('"connectors/outlet/blah-1":').once
|
|
expect(compiler.content.to_s).to include("templates/connectors/outlet/blah-1")
|
|
expect(compiler.content.to_s).not_to include("setComponentTemplate")
|
|
expect(compiler.content.to_s).to include("createTemplateFactory")
|
|
expect(JSON.parse(compiler.source_map)["sources"]).to include(
|
|
"theme-1/templates/connectors/outlet/blah-1.js",
|
|
"theme-1/templates/connectors/outlet/blah-1.hbs",
|
|
)
|
|
|
|
# Not colocated
|
|
compiler = ThemeJavascriptCompiler.new(1, "marks", minify: false)
|
|
compiler.append_tree(
|
|
{
|
|
"templates/connectors/outlet/blah-1.hbs" => "{{var}}",
|
|
"connectors/outlet/blah-1.js" => "export default {};",
|
|
},
|
|
)
|
|
expect(compiler.content.to_s).to include('"connectors/outlet/blah-1":').once
|
|
expect(compiler.content.to_s).to include("templates/connectors/outlet/blah-1")
|
|
expect(compiler.content.to_s).not_to include("setComponentTemplate")
|
|
expect(compiler.content.to_s).to include("createTemplateFactory")
|
|
expect(JSON.parse(compiler.source_map)["sources"]).to include(
|
|
"theme-1/connectors/outlet/blah-1.js",
|
|
"theme-1/templates/connectors/outlet/blah-1.hbs",
|
|
)
|
|
|
|
# colocation in discourse directory
|
|
compiler = ThemeJavascriptCompiler.new(1, "marks", minify: false)
|
|
compiler.append_tree(
|
|
{
|
|
"discourse/connectors/outlet/blah-1.hbs" => "{{var}}",
|
|
"discourse/connectors/outlet/blah-1.js" => "export default {};",
|
|
},
|
|
)
|
|
expect(compiler.content.to_s).to include('"discourse/connectors/outlet/blah-1":').once
|
|
expect(compiler.content.to_s).to include("discourse/templates/connectors/outlet/blah-1")
|
|
expect(compiler.content.to_s).not_to include("setComponentTemplate")
|
|
expect(JSON.parse(compiler.source_map)["sources"]).to include(
|
|
"theme-1/discourse/connectors/outlet/blah-1.js",
|
|
)
|
|
end
|
|
end
|
|
|
|
describe "error handling" do
|
|
it "handles syntax errors in ember templates" do
|
|
compiler.append_tree({ "sometemplate.hbs" => "{{invalidtemplate" })
|
|
expect(compiler.content).to include("Parse error on line 1")
|
|
end
|
|
end
|
|
|
|
describe "#append_tree" do
|
|
it "can handle multiple modules" do
|
|
compiler.append_tree(
|
|
{
|
|
"discourse/initializers/my-initializer.js" => <<~JS,
|
|
import MyComponent from "../components/mycomponent";
|
|
|
|
export default {
|
|
name: "my-initializer",
|
|
initialize() {
|
|
console.log("my-initializer", MyComponent);
|
|
},
|
|
};
|
|
JS
|
|
"discourse/components/mycomponent.js" => <<~JS,
|
|
import Component from "@glimmer/component";
|
|
export default class MyComponent extends Component {}
|
|
JS
|
|
"discourse/templates/components/mycomponent.hbs" => "{{my-component-template}}",
|
|
},
|
|
)
|
|
expect(compiler.content).to include('"discourse/components/mycomponent":')
|
|
expect(compiler.content).to include('"discourse/templates/components/mycomponent":')
|
|
end
|
|
|
|
it "handles colocated components" do
|
|
compiler.append_tree(
|
|
{
|
|
"discourse/components/mycomponent.js" => <<~JS,
|
|
import Component from "@glimmer/component";
|
|
export default class MyComponent extends Component {}
|
|
JS
|
|
"discourse/components/mycomponent.hbs" => "{{my-component-template}}",
|
|
},
|
|
)
|
|
expect(compiler.content).to include("__COLOCATED_TEMPLATE__ =")
|
|
expect(compiler.content).to include("setComponentTemplate")
|
|
expect(compiler.content).to include("createTemplateFactory")
|
|
end
|
|
|
|
it "handles colocated admin components" do
|
|
compiler.append_tree(
|
|
{
|
|
"admin/components/mycomponent.js" => <<~JS,
|
|
import Component from "@glimmer/component";
|
|
export default class MyComponent extends Component {}
|
|
JS
|
|
"admin/components/mycomponent.hbs" => "{{my-component-template}}",
|
|
},
|
|
)
|
|
expect(compiler.content).to include("__COLOCATED_TEMPLATE__ =")
|
|
expect(compiler.content).to include("setComponentTemplate")
|
|
end
|
|
|
|
it "applies theme AST transforms to colocated components" do
|
|
compiler = ThemeJavascriptCompiler.new(12_345_678_910, "my theme name", minify: false)
|
|
compiler.append_tree(
|
|
{ "discourse/components/mycomponent.hbs" => '{{theme-i18n "my_translation_key"}}' },
|
|
)
|
|
template_compiled_line = compiler.content.lines.find { |l| l.include?('"block":') }
|
|
expect(template_compiled_line).to include("12345678910")
|
|
end
|
|
|
|
it "handles template-only components" do
|
|
compiler.append_tree(
|
|
{ "discourse/components/mycomponent.hbs" => "{{my-component-template}}" },
|
|
)
|
|
expect(compiler.content).to include("__COLOCATED_TEMPLATE__ =")
|
|
expect(compiler.content).to include("setComponentTemplate")
|
|
expect(compiler.content).to include("@ember/component/template-only")
|
|
end
|
|
end
|
|
|
|
describe "terser compilation" do
|
|
let(:compiler) { ThemeJavascriptCompiler.new(1, "marks", minify: true) }
|
|
|
|
it "applies terser and provides sourcemaps" do
|
|
sources = {
|
|
"multiply.js" =>
|
|
"export const multiply = (firstValue, secondValue) => firstValue * secondValue;",
|
|
"add.js" => "export const add = (firstValue, secondValue) => firstValue + secondValue;",
|
|
}
|
|
|
|
compiler.append_tree(sources)
|
|
|
|
expect(compiler.content).to include("multiply")
|
|
expect(compiler.content).to include("add")
|
|
expect(compiler.content).not_to include("firstValue")
|
|
expect(compiler.content).not_to include("secondValue")
|
|
|
|
map = JSON.parse(compiler.source_map)
|
|
expect(map["sources"]).to include("theme-1/multiply.js", "theme-1/add.js")
|
|
expect(map["sourcesContent"].to_s).to include("const multiply")
|
|
expect(map["sourcesContent"].to_s).to include("const add")
|
|
expect(map["sourcesContent"].to_s).to include("firstValue")
|
|
expect(map["sourcesContent"].to_s).to include("secondValue")
|
|
end
|
|
|
|
it "handles invalid JS" do
|
|
compiler.append_tree({ "filename.js" => "if(someCondition" })
|
|
expect(compiler.content).to include('throw new Error("[THEME 1')
|
|
expect(compiler.content).to include("Unexpected token")
|
|
end
|
|
end
|
|
|
|
describe "ember-this-fallback" do
|
|
it "applies its transforms" do
|
|
compiler.append_tree(
|
|
{
|
|
"discourse/components/my-component.js" => <<~JS,
|
|
import Component from "@glimmer/component";
|
|
export default class MyComponent extends Component {
|
|
value = "foo";
|
|
}
|
|
JS
|
|
"discourse/components/my-component.hbs" => "{{value}}",
|
|
},
|
|
)
|
|
expect(compiler.content).to include("ember-this-fallback")
|
|
expect(compiler.content).to include(
|
|
"The `value` property path was used in the `theme-1/discourse/components/my-component.hbs` template without using `this`. This fallback behavior has been deprecated, all properties must be looked up on `this` when used in the template: {{this.value}}",
|
|
)
|
|
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 {
|
|
<template>
|
|
{{this.value}}
|
|
</template>
|
|
|
|
value = "foo";
|
|
}
|
|
JS
|
|
|
|
expect(compiler.content).to include("\"discourse/components/my-component\":")
|
|
expect(compiler.content).to include('value = "foo";')
|
|
expect(compiler.content).to include("setComponentTemplate")
|
|
expect(compiler.content).to include("createTemplateFactory")
|
|
end
|
|
end
|
|
|
|
describe ".js.es6 extension deprecation" do
|
|
it "appends a deprecation warning for .js.es6 files" do
|
|
compiler.append_tree(
|
|
{ "discourse/components/my-component.js.es6" => "export default class MyComponent {}" },
|
|
)
|
|
|
|
expect(compiler.content).to include("discourse.es6-extension")
|
|
expect(compiler.content).to include(
|
|
"The file 'discourse/components/my-component.js.es6' uses the deprecated `.js.es6` extension. Use `.js` instead.",
|
|
)
|
|
end
|
|
|
|
it "does not add deprecation for regular .js files" do
|
|
compiler.append_tree(
|
|
{ "discourse/components/my-component.js" => "export default class MyComponent {}" },
|
|
)
|
|
|
|
expect(compiler.content).not_to include("discourse.es6-extension")
|
|
end
|
|
end
|
|
end
|