mirror of
https://github.com/discourse/discourse.git
synced 2026-08-09 21:45:25 +08:00
This means that we no longer need to recompile theme JS bundles when theme settings are changed. It has parity with site settings, which are already included in the preload data. Theme#cached_settings is already cached in-memory, so performance impact is minimal. Since theme JS bundles are now only dependent on `extra_js` ThemeFields, we can make their caching much more stricter, meaning that they don't need to be recompiled on every single `theme.save!`. Now they will only be invalidated when extra_js fields actually change, or when the theme compiler_version changes.
267 lines
11 KiB
Ruby
Vendored
267 lines
11 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("compatModules[\"templates/connectors/blah-1\"]")
|
|
expect(compiler.content.to_s).to include("compatModules[\"templates/connectors/blah-2\"]")
|
|
expect(compiler.content.to_s).to include(
|
|
"compatModules[\"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('compatModules["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('compatModules["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('compatModules["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(
|
|
'compatModules["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('compatModules["discourse/components/mycomponent"]')
|
|
expect(compiler.content).to include(
|
|
'compatModules["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("compatModules[\"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
|