discourse/lib/tasks/javascript.rake
Martin Brennan d8a0d2262c
DEV: Update pretender and fake-xml-http-request (#13937)
We are still on a version of pretender since 2017
https://github.com/pretenderjs/pretender/releases/tag/v1.6.1

Since then many changes have been made, including adding support
for xhr.upload. Upgrading will let us write proper acceptance
tests for uppy, which uses XmlHTTPRequest internally including
xhr.upload.

Updates pretender to 3.4.7 and fake-xml-http-request to 2.1.2.

Note: There have been no breaking changes in the releases that would
affect us, mainly dropping support for old node versions.
2021-08-05 08:23:01 +10:00

410 lines
12 KiB
Ruby

# frozen_string_literal: true
def public_root
"#{Rails.root}/public"
end
def public_js
"#{public_root}/javascripts"
end
def vendor_js
"#{Rails.root}/vendor/assets/javascripts"
end
def library_src
"#{Rails.root}/node_modules"
end
def html_for_section(group)
icons = group["icons"].map do |icon|
class_attr = icon["diversity"] ? " class=\"diversity\"" : ""
" {{replace-emoji \":#{icon['name']}:\" (hash lazy=true#{class_attr})}}"
end
<<~SECTION
<div class="section" data-section="#{group["name"]}">
<div class="section-header">
<span class="title">{{i18n "emoji_picker.#{group["name"]}"}}</span>
</div>
<div class="section-group">
#{icons.join("\n").strip}
</div>
</div>
SECTION
end
def write_template(path, task_name, template)
header = <<~HEADER
// DO NOT EDIT THIS FILE!!!
// Update it by running `rake javascript:#{task_name}`
HEADER
basename = File.basename(path)
output_path = "#{Rails.root}/app/assets/javascripts/#{path}"
File.write(output_path, "#{header}\n\n#{template}")
puts "#{basename} created"
%x{yarn run prettier --write #{output_path}}
puts "#{basename} prettified"
end
def write_hbs_template(path, task_name, template)
header = <<~HEADER
{{!-- DO NOT EDIT THIS FILE!!! --}}
{{!-- Update it by running `rake javascript:#{task_name}` --}}
HEADER
basename = File.basename(path)
output_path = "#{Rails.root}/app/assets/javascripts/#{path}"
File.write(output_path, "#{header}\n#{template}")
puts "#{basename} created"
end
def dependencies
[
{
source: 'bootstrap/js/modal.js',
destination: 'bootstrap-modal.js'
}, {
source: 'ace-builds/src-min-noconflict/ace.js',
destination: 'ace.js',
public: true
}, {
source: '@json-editor/json-editor/dist/jsoneditor.js',
package_name: '@json-editor/json-editor',
public: true
}, {
source: 'chart.js/dist/Chart.min.js',
public: true
}, {
source: 'chartjs-plugin-datalabels/dist/chartjs-plugin-datalabels.min.js',
public: true
}, {
source: 'diffhtml/dist/diffhtml.min.js',
public: true
}, {
source: 'magnific-popup/dist/jquery.magnific-popup.min.js',
public: true
}, {
source: 'pikaday/pikaday.js',
public: true
}, {
source: 'spectrum-colorpicker/spectrum.js',
uglify: true,
public: true
}, {
source: 'spectrum-colorpicker/spectrum.css',
public: true
}, {
source: 'handlebars/dist/handlebars.js'
}, {
source: 'handlebars/dist/handlebars.runtime.js'
}, {
source: '@highlightjs/cdn-assets/.',
destination: 'highlightjs'
}, {
source: 'jquery.autoellipsis/src/jquery.autoellipsis.js',
destination: 'jquery.autoellipsis-1.0.10.js'
}, {
source: 'jquery-color/dist/jquery.color.js'
}, {
source: 'blueimp-file-upload/js/jquery.fileupload.js',
}, {
source: 'blueimp-file-upload/js/jquery.iframe-transport.js',
}, {
source: 'blueimp-file-upload/js/jquery.fileupload-process.js',
}, {
source: 'blueimp-file-upload/js/vendor/jquery.ui.widget.js',
}, {
source: 'jquery/dist/jquery.js'
}, {
source: 'jquery-tags-input/src/jquery.tagsinput.js'
}, {
source: 'markdown-it/dist/markdown-it.js'
}, {
source: 'mousetrap/mousetrap.js'
}, {
source: 'moment/moment.js'
}, {
source: 'moment/locale/.',
destination: 'moment-locale',
}, {
source: 'moment-timezone/builds/moment-timezone-with-data-10-year-range.js',
destination: 'moment-timezone-with-data.js'
}, {
source: 'lodash.js',
destination: 'lodash.js'
}, {
source: 'moment-timezone-names-translations/locales/.',
destination: 'moment-timezone-names-locale'
}, {
source: 'mousetrap/plugins/global-bind/mousetrap-global-bind.js'
}, {
source: 'resumablejs/resumable.js'
}, {
source: 'workbox-sw/build/.',
destination: 'workbox',
public: true,
skip_versioning: true
}, {
source: 'workbox-routing/build/.',
destination: 'workbox',
public: true,
skip_versioning: true
}, {
source: 'workbox-core/build/.',
destination: 'workbox',
public: true,
skip_versioning: true
}, {
source: 'workbox-strategies/build/.',
destination: 'workbox',
public: true,
skip_versioning: true
}, {
source: 'workbox-expiration/build/.',
destination: 'workbox',
public: true,
skip_versioning: true
}, {
source: 'workbox-cacheable-response/build/.',
destination: 'workbox',
skip_versioning: true,
public: true
}, {
source: '@popperjs/core/dist/umd/popper.js'
}, {
source: '@popperjs/core/dist/umd/popper.js.map',
public_root: true
},
{
source: 'route-recognizer/dist/route-recognizer.js'
}, {
source: 'route-recognizer/dist/route-recognizer.js.map',
public_root: true
},
{
source: 'qunit/qunit/qunit.js'
},
{
source: 'pretender/dist/pretender.js'
},
{
source: 'fake-xml-http-request/fake_xml_http_request.js'
},
{
source: 'sinon/pkg/sinon.js'
},
{
source: 'squoosh/codecs/mozjpeg/enc/mozjpeg_enc.js',
destination: 'squoosh',
public: true,
skip_versioning: true
},
{
source: 'squoosh/codecs/mozjpeg/enc/mozjpeg_enc.wasm',
destination: 'squoosh',
public: true,
skip_versioning: true
},
{
source: 'squoosh/codecs/resize/pkg/squoosh_resize.js',
destination: 'squoosh',
public: true,
skip_versioning: true
},
{
source: 'squoosh/codecs/resize/pkg/squoosh_resize_bg.wasm',
destination: 'squoosh',
public: true,
skip_versioning: true
},
{
source: 'custom-uppy-build.js',
destination: 'uppy.js'
}
]
end
def node_package_name(f)
f[:package_name] || f[:source].split('/').first
end
def public_path_name(f)
f[:destination] || node_package_name(f)
end
task 'javascript:update_constants' => :environment do
task_name = 'update_constants'
write_template("discourse/app/lib/constants.js", task_name, <<~JS)
export const SEARCH_PRIORITIES = #{Searchable::PRIORITIES.to_json};
export const SEARCH_PHRASE_REGEXP = '#{Search::PHRASE_MATCH_REGEXP_PATTERN}';
JS
pretty_notifications = Notification.types.map do |n|
" #{n[0]}: #{n[1]},"
end.join("\n")
write_template("discourse/tests/fixtures/concerns/notification-types.js", task_name, <<~JS)
export const NOTIFICATION_TYPES = {
#{pretty_notifications}
};
JS
write_template("pretty-text/addon/emoji/data.js", task_name, <<~JS)
export const emojis = #{Emoji.standard.map(&:name).flatten.inspect};
export const tonableEmojis = #{Emoji.tonable_emojis.flatten.inspect};
export const aliases = #{Emoji.aliases.inspect.gsub("=>", ":")};
export const searchAliases = #{Emoji.search_aliases.inspect.gsub("=>", ":")};
export const translations = #{Emoji.translations.inspect.gsub("=>", ":")};
export const replacements = #{Emoji.unicode_replacements_json};
JS
write_template("pretty-text/addon/emoji/version.js", task_name, <<~JS)
export const IMAGE_VERSION = "#{Emoji::EMOJI_VERSION}";
JS
groups_json = JSON.parse(File.read("lib/emoji/groups.json"))
emoji_buttons = groups_json.map do |group|
<<~BUTTON
<button type="button" data-section="#{group["name"]}" {{action onCategorySelection "#{group["name"]}"}} class="btn btn-default category-button emoji">
{{replace-emoji ":#{group["tabicon"]}:"}}
</button>
BUTTON
end
emoji_sections = groups_json.map { |group| html_for_section(group) }
components_dir = "discourse/app/templates/components"
write_hbs_template("#{components_dir}/emoji-group-buttons.hbs", task_name, emoji_buttons.join)
write_hbs_template("#{components_dir}/emoji-group-sections.hbs", task_name, emoji_sections.join)
end
task 'javascript:update' => 'clean_up' do
require 'uglifier'
yarn = system("yarn install")
abort('Unable to run "yarn install"') unless yarn
versions = {}
start = Time.now
dependencies.each do |f|
src = "#{library_src}/#{f[:source]}"
unless f[:destination]
filename = f[:source].split("/").last
else
filename = f[:destination]
end
if src.include? "highlightjs"
puts "Cleanup highlightjs styles and install smaller test bundle"
system("rm -rf node_modules/@highlightjs/cdn-assets/styles")
# We don't need every language for tests
langs = ['javascript', 'sql', 'ruby']
test_bundle_dest = 'vendor/assets/javascripts/highlightjs/highlight-test-bundle.min.js'
File.write(test_bundle_dest, HighlightJs.bundle(langs))
end
if f[:public_root]
dest = "#{public_root}/#{filename}"
elsif f[:public]
if f[:skip_versioning]
dest = "#{public_js}/#{filename}"
else
package_dir_name = public_path_name(f)
package_version = JSON.parse(File.read("#{library_src}/#{node_package_name(f)}/package.json"))["version"]
versions[filename] = "#{package_dir_name}/#{package_version}/#{filename}"
path = "#{public_js}/#{package_dir_name}/#{package_version}"
dest = "#{path}/#{filename}"
FileUtils.mkdir_p(path) unless File.exists?(path)
end
else
dest = "#{vendor_js}/#{filename}"
end
if src.include? "ace.js"
versions["ace/ace.js"] = versions.delete("ace.js")
ace_root = "#{library_src}/ace-builds/src-min-noconflict/"
addtl_files = [ "ext-searchbox", "mode-html", "mode-scss", "mode-sql", "theme-chrome", "worker-html"]
dest_path = dest.split('/')[0..-2].join('/')
addtl_files.each do |file|
FileUtils.cp_r("#{ace_root}#{file}.js", dest_path)
end
end
# lodash.js needs building
if src.include? "lodash.js"
puts "Building custom lodash.js build"
system('yarn run lodash include="escapeRegExp,each,filter,map,range,first,isEmpty,chain,extend,every,omit,merge,union,sortBy,uniq,intersection,reject,compact,reduce,debounce,throttle,values,pick,keys,flatten,min,max,isArray,delay,isString,isEqual,without,invoke,clone,findIndex,find,groupBy" minus="template" -d -o "node_modules/lodash.js"')
end
# we need a custom build of uppy because we cannot import
# their modules easily, using browserify to do so
if src.include? "custom-uppy-build"
puts "Building custom uppy using browserify"
system("yarn run browserify #{vendor_js}/custom-uppy.js -o node_modules/custom-uppy-build.js")
end
unless File.exists?(dest)
STDERR.puts "New dependency added: #{dest}"
end
if f[:uglify]
File.write(dest, Uglifier.new.compile(File.read(src)))
else
FileUtils.cp_r(src, dest)
end
# use absolute path for popper.js's sourcemap
# avoids noisy console warnings in dev environment for non-homepage paths
if dest.end_with? "popper.js"
File.open(dest) do |file|
contents = file.read
contents.gsub!("sourceMappingURL=popper", "sourceMappingURL=/popper")
File.open(dest, "w+") { |d| d.write(contents) }
end
end
end
write_template("discourse/app/lib/public-js-versions.js", "update", <<~JS)
export const PUBLIC_JS_VERSIONS = #{versions.to_json};
JS
STDERR.puts "Completed copying dependencies: #{(Time.now - start).round(2)} secs"
end
task 'javascript:clean_up' do
processed = []
dependencies.each do |f|
next unless f[:public] && !f[:skip_versioning]
package_dir_name = public_path_name(f)
next if processed.include?(package_dir_name)
versions = Dir["#{File.join(public_js, package_dir_name)}/*"].collect { |p| p.split('/').last }
next unless versions.present?
versions = versions.sort { |a, b| Gem::Version.new(a) <=> Gem::Version.new(b) }
puts "Keeping #{package_dir_name} version: #{versions[-1]}"
# Keep the most recent version
versions[0..-2].each do |version|
remove_path = File.join(public_js, package_dir_name, version)
puts "Removing: #{remove_path}"
FileUtils.remove_dir(remove_path)
end
processed << package_dir_name
end
end