mirror of
https://github.com/glitch-soc/mastodon.git
synced 2025-12-13 15:58:50 +00:00
Convert from Webpack to Vite (#34450)
Co-authored-by: Renaud Chaput <renchap@gmail.com>
This commit is contained in:
@@ -2,21 +2,26 @@
|
||||
|
||||
module PremailerBundledAssetStrategy
|
||||
def load(url)
|
||||
asset_host = ENV['CDN_HOST'] || ENV['WEB_DOMAIN'] || ENV.fetch('LOCAL_DOMAIN', nil)
|
||||
if ViteRuby.instance.dev_server_running?
|
||||
# Request from the dev server
|
||||
return unless url.start_with?("/#{ViteRuby.config.public_output_dir}/")
|
||||
|
||||
if Webpacker.dev_server.running?
|
||||
asset_host = "#{Webpacker.dev_server.protocol}://#{Webpacker.dev_server.host_with_port}"
|
||||
url = File.join(asset_host, url)
|
||||
headers = {}
|
||||
# Vite dev server wants this header for CSS files, otherwise it will respond with a JS file that inserts the CSS (to support hot reloading)
|
||||
headers['Accept'] = 'text/css' if url.end_with?('.scss', '.css')
|
||||
|
||||
Net::HTTP.get(
|
||||
URI("#{ViteRuby.config.origin}#{url}"),
|
||||
headers
|
||||
).presence
|
||||
else
|
||||
path = Rails.public_path.join(url.delete_prefix('/'))
|
||||
return unless path.exist?
|
||||
|
||||
path.read
|
||||
end
|
||||
|
||||
css = if url.start_with?('http')
|
||||
HTTP.get(url).to_s
|
||||
else
|
||||
url = url[1..] if url.start_with?('/')
|
||||
Rails.public_path.join(url).read
|
||||
end
|
||||
|
||||
css.gsub(%r{url\(/}, "url(#{asset_host}/")
|
||||
rescue ViteRuby::MissingEntrypointError
|
||||
# If the path is not in the manifest, ignore it
|
||||
end
|
||||
|
||||
module_function :load
|
||||
|
||||
@@ -14,7 +14,9 @@ end
|
||||
|
||||
if Rake::Task.task_defined?('assets:precompile')
|
||||
Rake::Task['assets:precompile'].enhance do
|
||||
Webpacker.manifest.refresh
|
||||
Rake::Task['assets:generate_static_pages'].invoke
|
||||
end
|
||||
end
|
||||
|
||||
# We don't want vite_ruby to run yarn, we do that in a separate step
|
||||
Rake::Task['vite:install_dependencies'].clear
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Disable this task as we use pnpm
|
||||
|
||||
require 'semantic_range'
|
||||
|
||||
Rake::Task['webpacker:check_yarn'].clear
|
||||
|
||||
namespace :webpacker do
|
||||
desc 'Verifies if Yarn is installed'
|
||||
task check_yarn: :environment do
|
||||
begin
|
||||
yarn_version = `yarn --version`.strip
|
||||
raise Errno::ENOENT if yarn_version.blank?
|
||||
|
||||
yarn_range = '>=4 <5'
|
||||
is_valid = begin
|
||||
SemanticRange.satisfies?(yarn_version, yarn_range)
|
||||
rescue
|
||||
false
|
||||
end
|
||||
|
||||
unless is_valid
|
||||
warn "Mastodon and Webpacker requires Yarn \"#{yarn_range}\" and you are using #{yarn_version}"
|
||||
warn 'Exiting!'
|
||||
exit!
|
||||
end
|
||||
rescue Errno::ENOENT
|
||||
warn 'Yarn not installed. Please see the Mastodon documentation to install the correct version.'
|
||||
warn 'Exiting!'
|
||||
exit!
|
||||
end
|
||||
end
|
||||
end
|
||||
124
lib/vite_ruby/sri_extensions.rb
Normal file
124
lib/vite_ruby/sri_extensions.rb
Normal file
@@ -0,0 +1,124 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ViteRuby::ManifestIntegrityExtension
|
||||
def path_and_integrity_for(name, **)
|
||||
entry = lookup!(name, **)
|
||||
|
||||
{ path: entry.fetch('file'), integrity: entry.fetch('integrity', nil) }
|
||||
end
|
||||
|
||||
# Find a manifest entry by the *final* file name
|
||||
def integrity_hash_for_file(file_name)
|
||||
@integrity_cache ||= {}
|
||||
@integrity_cache[file_name] ||= begin
|
||||
entry = manifest.find { |_key, entry| entry['file'] == file_name }
|
||||
|
||||
entry[1].fetch('integrity', nil) if entry
|
||||
end
|
||||
end
|
||||
|
||||
def resolve_entries_with_integrity(*names, **options)
|
||||
entries = names.map { |name| lookup!(name, **options) }
|
||||
script_paths = entries.map do |entry|
|
||||
{
|
||||
file: entry.fetch('file'),
|
||||
# TODO: Secure this so we require the integrity hash outside of dev
|
||||
integrity: entry['integrity'],
|
||||
}
|
||||
end
|
||||
|
||||
imports = dev_server_running? ? [] : entries.flat_map { |entry| entry['imports'] }.compact
|
||||
|
||||
{
|
||||
scripts: script_paths,
|
||||
imports: imports.filter_map { |entry| { file: entry.fetch('file'), integrity: entry.fetch('integrity') } }.uniq,
|
||||
stylesheets: dev_server_running? ? [] : (entries + imports).flat_map { |entry| entry['css'] }.compact.uniq,
|
||||
}
|
||||
end
|
||||
|
||||
# We need to override this method to not include the manifest, as in our case it is too large and will cause a JSON max nesting error rather than raising the expected exception
|
||||
def missing_entry_error(name, **)
|
||||
raise ViteRuby::MissingEntrypointError.new(
|
||||
file_name: resolve_entry_name(name, **),
|
||||
last_build: builder.last_build_metadata,
|
||||
manifest: '',
|
||||
config: config
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
ViteRuby::Manifest.prepend ViteRuby::ManifestIntegrityExtension
|
||||
|
||||
module ViteRails::TagHelpers::IntegrityExtension
|
||||
def vite_javascript_tag(*names,
|
||||
type: 'module',
|
||||
asset_type: :javascript,
|
||||
skip_preload_tags: false,
|
||||
skip_style_tags: false,
|
||||
crossorigin: 'anonymous',
|
||||
media: 'screen',
|
||||
**options)
|
||||
entries = vite_manifest.resolve_entries_with_integrity(*names, type: asset_type)
|
||||
|
||||
''.html_safe.tap do |tags|
|
||||
entries.fetch(:scripts).each do |script|
|
||||
tags << javascript_include_tag(
|
||||
script[:file],
|
||||
integrity: script[:integrity],
|
||||
crossorigin: crossorigin,
|
||||
type: type,
|
||||
extname: false,
|
||||
**options
|
||||
)
|
||||
end
|
||||
|
||||
unless skip_preload_tags
|
||||
entries.fetch(:imports).each do |import|
|
||||
tags << vite_preload_tag(import[:file], integrity: import[:integrity], crossorigin: crossorigin, **options)
|
||||
end
|
||||
end
|
||||
|
||||
options[:extname] = false if Rails::VERSION::MAJOR >= 7
|
||||
|
||||
unless skip_style_tags
|
||||
entries.fetch(:stylesheets).each do |stylesheet|
|
||||
# This is for stylesheets imported from Javascript. The entry for the JS entrypoint only contains the final CSS file name, so we need to look it up in the manifest
|
||||
tags << stylesheet_link_tag(
|
||||
stylesheet,
|
||||
integrity: vite_manifest.integrity_hash_for_file(stylesheet),
|
||||
media: media,
|
||||
**options
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def vite_stylesheet_tag(*names, **options)
|
||||
''.html_safe.tap do |tags|
|
||||
names.each do |name|
|
||||
entry = vite_manifest.path_and_integrity_for(name, type: :stylesheet)
|
||||
|
||||
options[:extname] = false if Rails::VERSION::MAJOR >= 7
|
||||
|
||||
tags << stylesheet_link_tag(entry[:path], integrity: entry[:integrity], **options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def vite_preload_file_tag(name,
|
||||
asset_type: :javascript,
|
||||
crossorigin: 'anonymous', **options)
|
||||
''.html_safe.tap do |tags|
|
||||
entries = vite_manifest.resolve_entries_with_integrity(name, type: asset_type)
|
||||
|
||||
entries.fetch(:scripts).each do |script|
|
||||
tags << vite_preload_tag(script[:file], integrity: script[:integrity], crossorigin: crossorigin, **options)
|
||||
end
|
||||
end
|
||||
rescue ViteRuby::MissingEntrypointError
|
||||
# Ignore this error, it is not critical if the file is not preloaded
|
||||
end
|
||||
end
|
||||
|
||||
ViteRails::TagHelpers.prepend ViteRails::TagHelpers::IntegrityExtension
|
||||
@@ -1,27 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Webpacker::HelperExtensions
|
||||
def javascript_pack_tag(name, **options)
|
||||
src, integrity = current_webpacker_instance.manifest.lookup!(name, type: :javascript, with_integrity: true)
|
||||
javascript_include_tag(src, options.merge(integrity: integrity))
|
||||
end
|
||||
|
||||
def stylesheet_pack_tag(name, **options)
|
||||
src, integrity = current_webpacker_instance.manifest.lookup!(name, type: :stylesheet, with_integrity: true)
|
||||
stylesheet_link_tag(src, options.merge(integrity: integrity))
|
||||
end
|
||||
|
||||
def preload_pack_asset(name, **options)
|
||||
src, integrity = current_webpacker_instance.manifest.lookup!(name, with_integrity: true)
|
||||
|
||||
# This attribute will only work if the assets are on a different domain.
|
||||
# And Webpack will (correctly) only add it in this case, so we need to conditionally set it here
|
||||
# otherwise the preloaded request and the real request will have different crossorigin values
|
||||
# and the preloaded file wont be loaded
|
||||
crossorigin = 'anonymous' if Rails.configuration.action_controller.asset_host.present?
|
||||
|
||||
preload_link_tag(src, options.merge(integrity: integrity, crossorigin: crossorigin))
|
||||
end
|
||||
end
|
||||
|
||||
Webpacker::Helper.prepend(Webpacker::HelperExtensions)
|
||||
@@ -1,17 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Webpacker::ManifestExtensions
|
||||
def lookup(name, pack_type = {})
|
||||
asset = super
|
||||
|
||||
if pack_type[:with_integrity] && asset.respond_to?(:dig)
|
||||
[asset['src'], asset['integrity']]
|
||||
elsif asset.respond_to?(:dig)
|
||||
asset['src']
|
||||
else
|
||||
asset
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Webpacker::Manifest.prepend(Webpacker::ManifestExtensions)
|
||||
Reference in New Issue
Block a user