mirror of
https://github.com/discourse/discourse.git
synced 2024-12-01 21:19:41 -06:00
565c753dd2
decorator-transforms (https://github.com/ef4/decorator-transforms) is a modern replacement for babel's plugin-proposal-decorators. It provides a decorator implementation using modern browser features, without needing to enable babel's full suite of class feature transformations. This improves the developer experience and performance.
In local testing with Google's 'tachometer' tool, this reduces Discourse's 'init-to-render' time by around 3-4% (230ms -> 222ms).
It reduces our initial gzip'd JS payloads by 3.2% (2.43MB -> 2.35MB), or 7.5% (14.5MB -> 13.4MB) uncompressed.
This was previously reverted in 97847f6
. This version includes a babel transformation which works around the bug in Safari <= 15.
For Cloudflare compatibility issues, check https://meta.discourse.org/t/311390
274 lines
10 KiB
Ruby
274 lines
10 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
RSpec.describe ThemeJavascriptCompiler do
|
|
let(:compiler) { ThemeJavascriptCompiler.new(1, "marks") }
|
|
|
|
describe "#append_raw_template" do
|
|
it "uses the correct template paths" do
|
|
template = "<h1>hello</h1>"
|
|
name = "/path/to/templates1"
|
|
compiler.append_raw_template("#{name}.raw", template)
|
|
expect(compiler.raw_content.to_s).to include("addRawTemplate(\"#{name}\"")
|
|
|
|
name = "/path/to/templates2"
|
|
compiler.append_raw_template("#{name}.hbr", template)
|
|
expect(compiler.raw_content.to_s).to include("addRawTemplate(\"#{name}\"")
|
|
|
|
name = "/path/to/templates3"
|
|
compiler.append_raw_template("#{name}.hbs", template)
|
|
expect(compiler.raw_content.to_s).to include("addRawTemplate(\"#{name}.hbs\"")
|
|
end
|
|
end
|
|
|
|
describe "#append_ember_template" do
|
|
it "maintains module names so that discourse-boot.js can correct them" do
|
|
compiler.append_ember_template("/connectors/blah-1", "{{var}}")
|
|
expect(compiler.raw_content.to_s).to include(
|
|
"define(\"discourse/theme-1/connectors/blah-1\", [\"exports\", ",
|
|
)
|
|
|
|
compiler.append_ember_template("connectors/blah-2", "{{var}}")
|
|
expect(compiler.raw_content.to_s).to include(
|
|
"define(\"discourse/theme-1/connectors/blah-2\", [\"exports\", ",
|
|
)
|
|
|
|
compiler.append_ember_template("javascripts/connectors/blah-3", "{{var}}")
|
|
expect(compiler.raw_content.to_s).to include(
|
|
"define(\"discourse/theme-1/javascripts/connectors/blah-3\", [\"exports\", ",
|
|
)
|
|
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")
|
|
compiler.append_tree(
|
|
{
|
|
"connectors/outlet/blah-1.hbs" => "{{var}}",
|
|
"connectors/outlet/blah-1.js" => "console.log('test')",
|
|
},
|
|
)
|
|
expect(compiler.raw_content.to_s).to include("discourse/theme-1/connectors/outlet/blah-1")
|
|
expect(compiler.raw_content.to_s).to include(
|
|
"discourse/theme-1/templates/connectors/outlet/blah-1",
|
|
)
|
|
expect(JSON.parse(compiler.source_map)["sources"]).to contain_exactly(
|
|
"connectors/outlet/blah-1.js",
|
|
"templates/connectors/outlet/blah-1.js",
|
|
)
|
|
|
|
# Colocated under `/templates/connectors`
|
|
compiler = ThemeJavascriptCompiler.new(1, "marks")
|
|
compiler.append_tree(
|
|
{
|
|
"templates/connectors/outlet/blah-1.hbs" => "{{var}}",
|
|
"templates/connectors/outlet/blah-1.js" => "console.log('test')",
|
|
},
|
|
)
|
|
expect(compiler.raw_content.to_s).to include("discourse/theme-1/connectors/outlet/blah-1")
|
|
expect(compiler.raw_content.to_s).to include(
|
|
"discourse/theme-1/templates/connectors/outlet/blah-1",
|
|
)
|
|
expect(JSON.parse(compiler.source_map)["sources"]).to contain_exactly(
|
|
"connectors/outlet/blah-1.js",
|
|
"templates/connectors/outlet/blah-1.js",
|
|
)
|
|
|
|
# Not colocated
|
|
compiler = ThemeJavascriptCompiler.new(1, "marks")
|
|
compiler.append_tree(
|
|
{
|
|
"templates/connectors/outlet/blah-1.hbs" => "{{var}}",
|
|
"connectors/outlet/blah-1.js" => "console.log('test')",
|
|
},
|
|
)
|
|
expect(compiler.raw_content.to_s).to include("discourse/theme-1/connectors/outlet/blah-1")
|
|
expect(compiler.raw_content.to_s).to include(
|
|
"discourse/theme-1/templates/connectors/outlet/blah-1",
|
|
)
|
|
expect(JSON.parse(compiler.source_map)["sources"]).to contain_exactly(
|
|
"connectors/outlet/blah-1.js",
|
|
"templates/connectors/outlet/blah-1.js",
|
|
)
|
|
end
|
|
end
|
|
|
|
describe "error handling" do
|
|
it "handles syntax errors in raw templates" do
|
|
expect do
|
|
compiler.append_raw_template("sometemplate.hbr", "{{invalidtemplate")
|
|
end.to raise_error(ThemeJavascriptCompiler::CompileError, /Parse error on line 1/)
|
|
end
|
|
|
|
it "handles syntax errors in ember templates" do
|
|
expect do
|
|
compiler.append_ember_template("sometemplate", "{{invalidtemplate")
|
|
end.to raise_error(ThemeJavascriptCompiler::CompileError, /Parse error on line 1/)
|
|
end
|
|
end
|
|
|
|
describe "#append_tree" do
|
|
it "can handle multiple modules" do
|
|
compiler.append_tree(
|
|
{
|
|
"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.raw_content).to include(
|
|
'define("discourse/theme-1/discourse/components/mycomponent"',
|
|
)
|
|
expect(compiler.raw_content).to include(
|
|
'define("discourse/theme-1/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.raw_content).to include("__COLOCATED_TEMPLATE__ =")
|
|
expect(compiler.raw_content).to include("setComponentTemplate")
|
|
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.raw_content).to include("__COLOCATED_TEMPLATE__ =")
|
|
expect(compiler.raw_content).to include("setComponentTemplate")
|
|
end
|
|
|
|
it "applies theme AST transforms to colocated components" do
|
|
compiler = ThemeJavascriptCompiler.new(12_345_678_910, "my theme name")
|
|
compiler.append_tree(
|
|
{ "discourse/components/mycomponent.hbs" => '{{theme-i18n "my_translation_key"}}' },
|
|
)
|
|
template_compiled_line = compiler.raw_content.lines.find { |l| l.include?('"block":') }
|
|
expect(template_compiled_line).to include("12345678910")
|
|
end
|
|
|
|
it "prints error when default export missing" do
|
|
compiler.append_tree(
|
|
{
|
|
"discourse/components/mycomponent.js" => <<~JS,
|
|
import Component from "@glimmer/component";
|
|
class MyComponent extends Component {}
|
|
JS
|
|
"discourse/components/mycomponent.hbs" => "{{my-component-template}}",
|
|
},
|
|
)
|
|
expect(compiler.raw_content).to include("__COLOCATED_TEMPLATE__ =")
|
|
expect(compiler.raw_content).to include("throw new Error")
|
|
end
|
|
|
|
it "handles template-only components" do
|
|
compiler.append_tree(
|
|
{ "discourse/components/mycomponent.hbs" => "{{my-component-template}}" },
|
|
)
|
|
expect(compiler.raw_content).to include("__COLOCATED_TEMPLATE__ =")
|
|
expect(compiler.raw_content).to include("setComponentTemplate")
|
|
expect(compiler.raw_content).to include("@ember/component/template-only")
|
|
end
|
|
end
|
|
|
|
describe "terser compilation" do
|
|
it "applies terser and provides sourcemaps" do
|
|
sources = {
|
|
"multiply.js" => "let multiply = (firstValue, secondValue) => firstValue * secondValue;",
|
|
"add.js" => "let add = (firstValue, secondValue) => firstValue + secondValue;",
|
|
}
|
|
|
|
compiler.append_tree(sources)
|
|
|
|
expect(compiler.content).to include("multiply")
|
|
expect(compiler.content).to include("add")
|
|
|
|
map = JSON.parse(compiler.source_map)
|
|
expect(map["sources"]).to contain_exactly(*sources.keys)
|
|
expect(map["sourcesContent"].to_s).to include("let multiply")
|
|
expect(map["sourcesContent"].to_s).to include("let add")
|
|
expect(map["sourceRoot"]).to eq("theme-1/")
|
|
end
|
|
|
|
it "handles invalid JS" do
|
|
compiler.append_raw_script("filename.js", "if(someCondition")
|
|
expect(compiler.content).to include('console.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.raw_content).to include("ember-this-fallback")
|
|
expect(compiler.raw_content).to include(
|
|
"The `value` property path was used in the `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.raw_content).to include(
|
|
"define(\"discourse/theme-1/discourse/components/my-component\", [\"exports\",",
|
|
)
|
|
expect(compiler.raw_content).to include('value = "foo";')
|
|
expect(compiler.raw_content).to include("setComponentTemplate")
|
|
expect(compiler.raw_content).to include("createTemplateFactory")
|
|
end
|
|
end
|
|
|
|
describe "safari <16 class field bugfix" do
|
|
it "is applied" do
|
|
compiler.append_tree({ "discourse/components/my-component.js" => <<~JS })
|
|
export default class MyComponent extends Component {
|
|
value = "foo";
|
|
complexValue = this.value + "bar";
|
|
}
|
|
JS
|
|
|
|
expect(compiler.raw_content).to include('value = "foo";')
|
|
expect(compiler.raw_content).to include('complexValue = (() => this.value + "bar")();')
|
|
end
|
|
end
|
|
end
|