Class: RSX::Loader
- Inherits:
-
Object
- Object
- RSX::Loader
- Defined in:
- lib/rsx/loader.rb
Overview
Finds, compiles, evaluates and reloads .rsx files.
Defined Under Namespace
Classes: Entry
Constant Summary collapse
- EXTENSIONS =
[".rsx", ".html.rsx"].freeze
Instance Method Summary collapse
- #clear ⇒ Object
- #compile_cache ⇒ Object
-
#current_entry ⇒ Object
The file currently being evaluated, used to attribute components to it.
- #default_export(path) ⇒ Object
- #entries ⇒ Object
- #files(paths = @config.paths) ⇒ Object
- #import(spec, as: nil, from: nil) ⇒ Object
-
#initialize(config) ⇒ Loader
constructor
A new instance of Loader.
-
#load(path, force: false) ⇒ Object
Compiles and evaluates a .rsx file exactly once per digest.
-
#load_all(paths = @config.paths) ⇒ Object
Loads every .rsx file under the configured paths.
- #loaded?(path) ⇒ Boolean
-
#reload! ⇒ Object
Reloads only the files whose contents changed.
-
#resolve(spec, from: nil) ⇒ Object
Resolves an import specifier or template path to a file on disk.
- #resolve!(spec, from: nil) ⇒ Object
-
#track(component) ⇒ Object
Records a component defined by the file currently being loaded.
Constructor Details
#initialize(config) ⇒ Loader
Returns a new instance of Loader.
18 19 20 21 22 23 |
# File 'lib/rsx/loader.rb', line 18 def initialize(config) @config = config @entries = {} @stack = [] @monitor = Monitor.new end |
Instance Method Details
#clear ⇒ Object
110 111 112 113 114 115 |
# File 'lib/rsx/loader.rb', line 110 def clear @monitor.synchronize do @entries.each_value { |entry| unload(entry) } @entries.clear end end |
#compile_cache ⇒ Object
38 39 40 |
# File 'lib/rsx/loader.rb', line 38 def compile_cache @config.compile_cache end |
#current_entry ⇒ Object
The file currently being evaluated, used to attribute components to it.
34 35 36 |
# File 'lib/rsx/loader.rb', line 34 def current_entry @stack.last end |
#default_export(path) ⇒ Object
166 167 168 169 |
# File 'lib/rsx/loader.rb', line 166 def default_export(path) entry = load(path) entry.default || entry.components.first end |
#entries ⇒ Object
25 26 27 |
# File 'lib/rsx/loader.rb', line 25 def entries @entries.values end |
#files(paths = @config.paths) ⇒ Object
88 89 90 91 92 93 94 |
# File 'lib/rsx/loader.rb', line 88 def files(paths = @config.paths) Array(paths).flat_map do |root| next [] unless File.directory?(root) Dir.glob(File.join(root, "**", "*.rsx")).sort end end |
#import(spec, as: nil, from: nil) ⇒ Object
148 149 150 151 152 |
# File 'lib/rsx/loader.rb', line 148 def import(spec, as: nil, from: nil) entry = load(resolve!(spec, from: from)) Array(as).each { |name| alias_constant(name, entry) } entry.default || entry.components.first end |
#load(path, force: false) ⇒ Object
Compiles and evaluates a .rsx file exactly once per digest.
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/rsx/loader.rb', line 43 def load(path, force: false) absolute = File.(path) raise FileNotFoundError, "no such RSX file: #{absolute}" unless File.file?(absolute) @monitor.synchronize do source = File.read(absolute) digest = compile_cache.digest(source) existing = @entries[absolute] return existing if existing && existing.digest == digest && !force unload(existing) if existing entry = Entry.new(path: absolute, digest: digest, mtime: mtime(absolute), components: [], default: nil) @entries[absolute] = entry @stack.push(entry) begin ruby = compile_cache.fetch(absolute, source) do Transformer.transform(source, path: absolute) end if Transformer.defines_components?(ruby) # Component files are evaluated at the top level so that `class`, # `def` and constants behave exactly as they do in a .rb file. eval(ruby, TOPLEVEL_BINDING, absolute, 1) # rubocop:disable Security/Eval else entry.template = Template.from_ruby(ruby, path: absolute, digest: digest) end rescue Exception # rubocop:disable Lint/RescueException @entries.delete(absolute) raise ensure @stack.pop end entry end end |
#load_all(paths = @config.paths) ⇒ Object
Loads every .rsx file under the configured paths. Called at boot in production so no request pays for compilation.
84 85 86 |
# File 'lib/rsx/loader.rb', line 84 def load_all(paths = @config.paths) files(paths).each { |file| load(file) } end |
#loaded?(path) ⇒ Boolean
29 30 31 |
# File 'lib/rsx/loader.rb', line 29 def loaded?(path) @entries.key?(File.(path)) end |
#reload! ⇒ Object
Reloads only the files whose contents changed. Used by the Rails reloader.
97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/rsx/loader.rb', line 97 def reload! @monitor.synchronize do @entries.values.each do |entry| if !File.file?(entry.path) unload(entry) @entries.delete(entry.path) elsif mtime(entry.path) != entry.mtime load(entry.path, force: true) end end end end |
#resolve(spec, from: nil) ⇒ Object
Resolves an import specifier or template path to a file on disk.
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/rsx/loader.rb', line 118 def resolve(spec, from: nil) spec = spec.to_s candidates = [] if spec.start_with?("/") candidates << spec else candidates << File.(spec, File.dirname(from)) if from && !from.empty? Array(@config.paths).each { |root| candidates << File.join(root, spec) } candidates << File.(spec, Dir.pwd) end candidates.each do |candidate| return candidate if File.file?(candidate) EXTENSIONS.each do |extension| with_extension = "#{candidate}#{extension}" return with_extension if File.file?(with_extension) end end nil end |
#resolve!(spec, from: nil) ⇒ Object
142 143 144 145 146 |
# File 'lib/rsx/loader.rb', line 142 def resolve!(spec, from: nil) resolve(spec, from: from) || raise(FileNotFoundError, "could not find `#{spec}`#{" imported from #{from}" if from}. " \ "Looked in: #{Array(@config.paths).join(", ")}") end |
#track(component) ⇒ Object
Records a component defined by the file currently being loaded.
155 156 157 158 159 160 161 162 163 164 |
# File 'lib/rsx/loader.rb', line 155 def track(component) entry = current_entry return component unless entry entry.components << component entry.default ||= component component.rsx_source_path = entry.path component.rsx_source_digest = entry.digest component end |