discourse/app/controllers/theme_javascripts_controller.rb

122 lines
3.3 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
class ThemeJavascriptsController < ApplicationController
DISK_CACHE_PATH = "#{Rails.root}/tmp/javascript-cache"
TESTS_DISK_CACHE_PATH = "#{Rails.root}/tmp/javascript-cache/tests"
skip_before_action(
:check_xhr,
:handle_theme,
:preload_json,
:redirect_to_login_if_required,
:verify_authenticity_token,
only: %i[show show_map show_tests],
)
before_action :is_asset_path, :no_cookies, :apply_cdn_headers, only: %i[show show_map show_tests]
def show
raise Discourse::NotFound unless last_modified.present?
return render body: nil, status: 304 if not_modified?
# Security: safe due to route constraint
cache_file = "#{DISK_CACHE_PATH}/#{params[:digest]}.js"
write_if_not_cached(cache_file) do
content, has_source_map = query.pick(:content, "source_map IS NOT NULL")
if has_source_map
content +=
"\n//# sourceMappingURL=#{params[:digest]}.map?__ws=#{Discourse.current_hostname}\n"
end
content
end
serve_file(cache_file)
end
def show_map
raise Discourse::NotFound unless last_modified.present?
return render body: nil, status: 304 if not_modified?
# Security: safe due to route constraint
cache_file = "#{DISK_CACHE_PATH}/#{params[:digest]}.map"
write_if_not_cached(cache_file) { query.pick(:source_map) }
serve_file(cache_file)
end
FEATURE: Introduce theme/component QUnit tests (take 2) (#12661) This commit allows themes and theme components to have QUnit tests. To add tests to your theme/component, create a top-level directory in your theme and name it `test`, and Discourse will save all the files in that directory (and its sub-directories) as "tests files" in the database. While tests files/directories are not required to be organized in a specific way, we recommend that you follow Discourse core's tests [structure](https://github.com/discourse/discourse/tree/master/app/assets/javascripts/discourse/tests). Writing theme tests should be identical to writing plugins or core tests; all the `import` statements and APIs that you see in core (or plugins) to define/setup tests should just work in themes. You do need a working Discourse install to run theme tests, and you have 2 ways to run theme tests: * In the browser at the `/qunit` route. `/qunit` will run tests of all active themes/components as well as core and plugins. The `/qunit` now accepts a `theme_name` or `theme_url` params that you can use to run tests of a specific theme/component like so: `/qunit?theme_name=<your_theme_name>`. * In the command line using the `themes:qunit` rake task. This take is meant to run tests of a single theme/component so you need to provide it with a theme name or URL like so: `bundle exec rake themes:qunit[name=<theme_name>]` or `bundle exec rake themes:qunit[url=<theme_url>]`. There are some refactors to how Discourse processes JavaScript that comes with themes/components, and these refactors may break your JS customizations; see https://meta.discourse.org/t/upcoming-core-changes-that-may-break-some-themes-components-april-12/186252?u=osama for details on how you can check if your themes/components are affected and what you need to do to fix them. This commit also improves theme error handling in Discourse. We will now be able to catch errors that occur when theme initializers are run and prevent them from breaking the site and other themes/components.
2021-04-12 07:02:58 -05:00
def show_tests
digest = params[:digest]
raise Discourse::NotFound if !digest.match?(/\A\h{40}\z/)
theme = Theme.find_by(id: params[:theme_id])
raise Discourse::NotFound if theme.blank?
content, content_digest = theme.baked_js_tests_with_digest
raise Discourse::NotFound if content.blank? || content_digest != digest
@cache_file = "#{TESTS_DISK_CACHE_PATH}/#{digest}.js"
return render body: nil, status: 304 if not_modified?
write_if_not_cached(@cache_file) { content }
serve_file @cache_file
FEATURE: Introduce theme/component QUnit tests (take 2) (#12661) This commit allows themes and theme components to have QUnit tests. To add tests to your theme/component, create a top-level directory in your theme and name it `test`, and Discourse will save all the files in that directory (and its sub-directories) as "tests files" in the database. While tests files/directories are not required to be organized in a specific way, we recommend that you follow Discourse core's tests [structure](https://github.com/discourse/discourse/tree/master/app/assets/javascripts/discourse/tests). Writing theme tests should be identical to writing plugins or core tests; all the `import` statements and APIs that you see in core (or plugins) to define/setup tests should just work in themes. You do need a working Discourse install to run theme tests, and you have 2 ways to run theme tests: * In the browser at the `/qunit` route. `/qunit` will run tests of all active themes/components as well as core and plugins. The `/qunit` now accepts a `theme_name` or `theme_url` params that you can use to run tests of a specific theme/component like so: `/qunit?theme_name=<your_theme_name>`. * In the command line using the `themes:qunit` rake task. This take is meant to run tests of a single theme/component so you need to provide it with a theme name or URL like so: `bundle exec rake themes:qunit[name=<theme_name>]` or `bundle exec rake themes:qunit[url=<theme_url>]`. There are some refactors to how Discourse processes JavaScript that comes with themes/components, and these refactors may break your JS customizations; see https://meta.discourse.org/t/upcoming-core-changes-that-may-break-some-themes-components-april-12/186252?u=osama for details on how you can check if your themes/components are affected and what you need to do to fix them. This commit also improves theme error handling in Discourse. We will now be able to catch errors that occur when theme initializers are run and prevent them from breaking the site and other themes/components.
2021-04-12 07:02:58 -05:00
end
private
def query
@query ||= JavascriptCache.where(digest: params[:digest]).limit(1)
end
def last_modified
@last_modified ||=
begin
if params[:action].to_s == "show_tests"
File.exist?(@cache_file) ? File.ctime(@cache_file) : nil
else
query.pick(:updated_at)
end
end
end
def not_modified?
cache_time =
begin
Time.rfc2822(request.env["HTTP_IF_MODIFIED_SINCE"])
rescue ArgumentError
nil
end
cache_time && last_modified && last_modified <= cache_time
end
def set_cache_control_headers
if Rails.env.development?
response.headers["Last-Modified"] = Time.zone.now.httpdate
immutable_for(1.second)
else
response.headers["Last-Modified"] = last_modified.httpdate if last_modified
immutable_for(1.year)
end
end
def write_if_not_cached(cache_file)
unless File.exist?(cache_file)
content = yield
raise Discourse::NotFound if content.nil?
FileUtils.mkdir_p(File.dirname(cache_file))
File.write(cache_file, content)
end
end
def serve_file(cache_file)
# this is only required for NGINX X-SendFile it seems
response.headers["Content-Length"] = File.size(cache_file).to_s
set_cache_control_headers
type = cache_file.end_with?(".map") ? "application/json" : "text/javascript"
send_file(cache_file, type: type, disposition: :inline)
end
end