Crowdin / Upload translations / upload-translations (push) Skipped
Historical data migration test / test (14-alpine) (push) Canceled after 0s
Historical data migration test / test (15-alpine) (push) Canceled after 0s
Historical data migration test / test (16-alpine) (push) Canceled after 0s
Historical data migration test / test (17-alpine) (push) Canceled after 0s
Bundler Audit / security (push) Canceled after 0s
Haml Linting / lint (push) Canceled after 0s
JavaScript Linting / lint (push) Canceled after 0s
Ruby Linting / lint (push) Canceled after 0s
JavaScript Testing / test (push) Canceled after 0s
Ruby Testing / build (production) (push) Canceled after 0s
Ruby Testing / test (.ruby-version) (push) Canceled after 0s
Ruby Testing / test (3.3) (push) Canceled after 0s
Ruby Testing / test (3.4) (push) Canceled after 0s
Ruby Testing / End to End testing (.ruby-version) (push) Canceled after 0s
Ruby Testing / End to End testing (3.3) (push) Canceled after 0s
Ruby Testing / End to End testing (3.4) (push) Canceled after 0s
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.29) (push) Canceled after 0s
Check i18n / check-i18n (push) Canceled after 0s
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (javascript) (push) Canceled after 0s
CodeQL / Analyze (ruby) (push) Canceled after 0s
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Canceled after 0s
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.29) (push) Canceled after 0s
Ruby Testing / Elastic Search integration testing (3.4, docker.elastic.co/elasticsearch/elasticsearch:7.17.29) (push) Canceled after 0s
Check formatting / lint (push) Canceled after 0s
CSS Linting / lint (push) Canceled after 0s
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.19.2) (push) Canceled after 0s
Chromatic / Check for relevant changes (push) Canceled after 0s
Chromatic / Run Chromatic (push) Canceled after 0s
Snapshot of upstream tag v4.7.3 (commit 8ff11c2bb7b9f0714e947b10fa3629c6542c7121) from https://github.com/mastodon/mastodon, unmodified.
193 lines
5.9 KiB
Ruby
193 lines
5.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module Vite
|
|
# We use two manifest files to list the assets loaded in our backend:
|
|
# - manifest.json: This is the main file that lists all the javascript and stylesheets our frontend app uses
|
|
# - manifest-assets.json: This only lists media assets linked directly from our backend, mainly images
|
|
#
|
|
# Not all the assets listed in the manifest.json file are linked or used by the app view, so we don't need to
|
|
# keep track of those. The idea behind this class is to keep track only of the main entrypoints and all their
|
|
# subsequent imports. Imports in this case include other javascript modules and stylesheets generated by Vite.
|
|
#
|
|
# This class focuses on reducing the amount of data we keep per entrypoint and making fast lookups. To do so
|
|
# it keeps two main datastructures:
|
|
# - entries: A Hash with references to each main entrypoint (including media assets)
|
|
# - pool: An Array with all the referenced imports and stylesheets
|
|
#
|
|
# Each entrypoint has a list of IDs for each import and stylesheet that it needs to load, this ID is
|
|
# an index to the main pool. With this the referenced asset is stored only once and referenced across
|
|
# entries with a unique ID. In the original manifest files references are also nested, if an entrypoint
|
|
# imports a javascript module, this first module can import other modules and stylesheet and so on. To keep
|
|
# fetching entrypoints fast, the final list of modules and stylesheets to load per entrypoint
|
|
# is expanded when loading the manifest data and not when rendering views. Each entrypoint has the final
|
|
# list of IDs of imports and stylesheets it will need to link in the backend, without further recursive
|
|
# checks.
|
|
class Manifest
|
|
class MissingManifestError < StandardError; end
|
|
|
|
class MissingEntryError < StandardError
|
|
def initialize(entry)
|
|
super("Failed to find entry '#{entry}' in manifest files")
|
|
end
|
|
end
|
|
|
|
Entrypoint = Struct.new(:file, :integrity, :import_ids, :stylesheet_ids)
|
|
Asset = Struct.new(:file, :integrity)
|
|
|
|
attr_reader :config, :entries, :pool, :logger
|
|
|
|
def initialize(config:, logger:)
|
|
@config = config
|
|
@virtual = {}
|
|
@entries = {}
|
|
@pool = []
|
|
@loaded = false
|
|
@logger = logger
|
|
end
|
|
|
|
def reload
|
|
@loaded = false
|
|
load
|
|
end
|
|
|
|
def load
|
|
return if @loaded
|
|
|
|
retries = 0
|
|
|
|
begin
|
|
build if retries.positive?
|
|
load_manifest
|
|
@loaded = true
|
|
rescue Errno::ENOENT => e
|
|
if retries.zero? && config.auto_build?
|
|
retries = 1
|
|
retry
|
|
else
|
|
raise MissingManifestError, e.message
|
|
end
|
|
end
|
|
end
|
|
|
|
def fetch(name, type: nil)
|
|
load
|
|
|
|
key = type == :virtual ? @virtual[name] : name
|
|
|
|
entries[key]
|
|
end
|
|
|
|
def fetch!(name, type: nil)
|
|
fetch(name, type:) || raise(MissingEntryError, name)
|
|
end
|
|
|
|
def imports_for(entry)
|
|
find_entries(entry[:import_ids] || [])
|
|
end
|
|
|
|
def stylesheets_for(entry)
|
|
find_entries(entry[:stylesheet_ids] || [])
|
|
end
|
|
|
|
private
|
|
|
|
def load_manifest
|
|
@virtual = {}
|
|
@entries = {}
|
|
@pool = []
|
|
# intermediate Hash for fast lookups while parsing the manifest
|
|
@lookup = {}
|
|
|
|
logger.debug { "loading manifest file: #{config.manifest_path}" }
|
|
json = JSON.load_file(config.manifest_path)
|
|
json.each do |key, raw|
|
|
# Only select main entrypoints and locales
|
|
next unless raw['isEntry'] || key.start_with?('mastodon/locales/')
|
|
|
|
import_ids, stylesheet_ids = resolve_relations(json, raw)
|
|
|
|
@virtual[raw['name']] = key
|
|
@entries[key] = Entrypoint.new(
|
|
raw['file'],
|
|
raw['integrity'],
|
|
import_ids,
|
|
stylesheet_ids
|
|
)
|
|
end
|
|
|
|
logger.debug { "loading manifest file: #{config.manifest_assets_path}" }
|
|
assets = JSON.load_file(config.manifest_assets_path)
|
|
assets.each do |key, raw|
|
|
@entries[key] = Asset.new(
|
|
raw['file'],
|
|
raw['integrity']
|
|
)
|
|
end
|
|
ensure
|
|
@lookup = {}
|
|
end
|
|
|
|
def build
|
|
logger.debug { 'AutoBuild enabled' }
|
|
builder.build
|
|
end
|
|
|
|
def builder
|
|
@builder ||= Vite::Builder.new(config:, logger:)
|
|
end
|
|
|
|
def resolve_relations(manifest, raw)
|
|
# NOTE: By using Set we make sure we add each asset only once
|
|
imports = Set.new
|
|
stylesheets = Set.new
|
|
|
|
raw['imports']&.each do |import|
|
|
raw_import = manifest[import]
|
|
next unless raw_import
|
|
|
|
id, deps = lookup(file: raw_import['file'], integrity: raw_import['integrity']) do
|
|
resolve_relations(manifest, raw_import)
|
|
end
|
|
|
|
imports.merge(deps[0])
|
|
stylesheets.merge(deps[1])
|
|
imports.add id
|
|
end
|
|
|
|
raw['css']&.each do |stylesheet|
|
|
# the css field includes the file names of the stylesheets and not
|
|
# the src names which are the ones used as keys in the manifest
|
|
raw_style = manifest.values.find { |value| value['file'] == stylesheet }
|
|
# some listed CSS files are not included as a entry in the manifest,
|
|
# but they are among the generated assets. We can still link to them
|
|
# but without the integrity field
|
|
raw_style = { 'file' => stylesheet } if raw_style.nil?
|
|
|
|
id, = lookup(file: raw_style['file'], integrity: raw_style['integrity'])
|
|
stylesheets.add id
|
|
end
|
|
|
|
[imports, stylesheets]
|
|
end
|
|
|
|
# Lookup in the internal cache if an entry has been processed
|
|
# If not, yield and save the result of the block as the entry deps
|
|
def lookup(file:, integrity:)
|
|
id, deps = @lookup[file] || []
|
|
|
|
if id.nil?
|
|
@pool << Asset.new(file, integrity)
|
|
id = @pool.size - 1 # last ID
|
|
deps = yield if block_given?
|
|
@lookup[file] = [id, deps]
|
|
end
|
|
|
|
[id, deps]
|
|
end
|
|
|
|
def find_entries(ids)
|
|
ids.filter_map { |id| pool[id] }
|
|
end
|
|
end
|
|
end
|