klenod-build 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +29 -0
- data/lib/klenod/build/asset.rb +235 -0
- data/lib/klenod/build/asset_compression.rb +58 -0
- data/lib/klenod/build/asset_generation_queue.rb +50 -0
- data/lib/klenod/build/cli/application.rb +108 -0
- data/lib/klenod/build/cli.rb +3 -0
- data/lib/klenod/build/config.rb +142 -0
- data/lib/klenod/build/context.rb +370 -0
- data/lib/klenod/build/dependency.rb +21 -0
- data/lib/klenod/build/errors.rb +175 -0
- data/lib/klenod/build/filesystem_resolver.rb +147 -0
- data/lib/klenod/build/graph/invalidator.rb +246 -0
- data/lib/klenod/build/graph.rb +864 -0
- data/lib/klenod/build/graphviz.rb +282 -0
- data/lib/klenod/build/hashing.rb +21 -0
- data/lib/klenod/build/invalidation_result.rb +55 -0
- data/lib/klenod/build/load_result.rb +7 -0
- data/lib/klenod/build/loaded_module.rb +38 -0
- data/lib/klenod/build/module_id.rb +100 -0
- data/lib/klenod/build/module_record.rb +22 -0
- data/lib/klenod/build/plugin.rb +35 -0
- data/lib/klenod/build/plugins/class_names_runtime.rb +125 -0
- data/lib/klenod/build/plugins/component_defaults.rb +12 -0
- data/lib/klenod/build/plugins/data_plugin.rb +117 -0
- data/lib/klenod/build/plugins/gem_import_plugin.rb +125 -0
- data/lib/klenod/build/plugins/google_fonts_plugin/font_metrics.json +17687 -0
- data/lib/klenod/build/plugins/google_fonts_plugin/font_metrics.rb +105 -0
- data/lib/klenod/build/plugins/google_fonts_plugin/font_metrics.txt +31 -0
- data/lib/klenod/build/plugins/google_fonts_plugin.rb +387 -0
- data/lib/klenod/build/plugins/haml_plugin/companions.rb +40 -0
- data/lib/klenod/build/plugins/haml_plugin/errors.rb +114 -0
- data/lib/klenod/build/plugins/haml_plugin/helper_source.rb +123 -0
- data/lib/klenod/build/plugins/haml_plugin/parser.rb +85 -0
- data/lib/klenod/build/plugins/haml_plugin/transformer/ruby_builder.rb +1039 -0
- data/lib/klenod/build/plugins/haml_plugin/transformer.rb +766 -0
- data/lib/klenod/build/plugins/haml_plugin.rb +369 -0
- data/lib/klenod/build/plugins/image_plugin.rb +401 -0
- data/lib/klenod/build/plugins/intl_plugin.rb +34 -0
- data/lib/klenod/build/plugins/markdown_compiler.rb +180 -0
- data/lib/klenod/build/plugins/markdown_plugin.rb +161 -0
- data/lib/klenod/build/plugins/router_plugin.rb +942 -0
- data/lib/klenod/build/plugins/ruby_plugin.rb +34 -0
- data/lib/klenod/build/plugins/svg_plugin.rb +197 -0
- data/lib/klenod/build/plugins.rb +22 -0
- data/lib/klenod/build/profiler.rb +79 -0
- data/lib/klenod/build/resolution_error_formatter.rb +42 -0
- data/lib/klenod/build/resolver.rb +95 -0
- data/lib/klenod/build/ruby_import_rewriter.rb +436 -0
- data/lib/klenod/build/source_map/editor.rb +110 -0
- data/lib/klenod/build/source_map/map.rb +168 -0
- data/lib/klenod/build/source_map/vlq.rb +68 -0
- data/lib/klenod/build/source_map.rb +12 -0
- data/lib/klenod/build/transform_result.rb +12 -0
- data/lib/klenod/build/version.rb +7 -0
- data/lib/klenod/build/watched_pattern.rb +11 -0
- data/lib/klenod/build/watcher.rb +141 -0
- data/lib/klenod/build.rb +15 -0
- metadata +310 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../plugin"
|
|
4
|
+
require_relative "../ruby_import_rewriter"
|
|
5
|
+
require_relative "../transform_result"
|
|
6
|
+
|
|
7
|
+
module Klenod
|
|
8
|
+
module Build
|
|
9
|
+
module Plugins
|
|
10
|
+
module RubyPlugin
|
|
11
|
+
def self.new(...)
|
|
12
|
+
Plugin.new(...)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class Plugin < Klenod::Build::Plugin
|
|
16
|
+
def transform(module_id, code, context)
|
|
17
|
+
return TransformResult.identity(code) unless module_id.extname == ".rb"
|
|
18
|
+
|
|
19
|
+
result =
|
|
20
|
+
RubyImportRewriter
|
|
21
|
+
.new(
|
|
22
|
+
module_id: module_id,
|
|
23
|
+
kind: :ruby_import,
|
|
24
|
+
source_dir: context.source_dir,
|
|
25
|
+
profiler: context.profiler
|
|
26
|
+
)
|
|
27
|
+
.rewrite(code)
|
|
28
|
+
TransformResult.new(result.code, result.dependencies, nil, [], result.watched_patterns, {})
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
require_relative "../asset"
|
|
6
|
+
require_relative "../dependency"
|
|
7
|
+
require_relative "../errors"
|
|
8
|
+
require_relative "../hashing"
|
|
9
|
+
require_relative "../module_id"
|
|
10
|
+
require_relative "../plugin"
|
|
11
|
+
require_relative "../transform_result"
|
|
12
|
+
|
|
13
|
+
module Klenod
|
|
14
|
+
module Build
|
|
15
|
+
module Plugins
|
|
16
|
+
module SvgPlugin
|
|
17
|
+
def self.new(...)
|
|
18
|
+
Plugin.new(...)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class Plugin < Klenod::Build::Plugin
|
|
22
|
+
EXTENSIONS = [".svg"].freeze
|
|
23
|
+
SVG_RUNTIME_SPECIFIER = "virtual:klenod/svg"
|
|
24
|
+
SVG_RUNTIME_MODULE_ID = ModuleId.new("#{SVG_RUNTIME_SPECIFIER}.rb", nil)
|
|
25
|
+
|
|
26
|
+
def resolve(dependency, _context)
|
|
27
|
+
return nil unless dependency.specifier == SVG_RUNTIME_SPECIFIER
|
|
28
|
+
|
|
29
|
+
ResolvedDependency.new(dependency, SVG_RUNTIME_MODULE_ID, {virtual: true})
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def load(module_id, _context)
|
|
33
|
+
return nil unless module_id.scheme == :virtual && module_id == SVG_RUNTIME_MODULE_ID
|
|
34
|
+
|
|
35
|
+
svg_runtime_source
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def transform(module_id, code, _context)
|
|
39
|
+
return super unless EXTENSIONS.include?(module_id.extname)
|
|
40
|
+
|
|
41
|
+
raise UnsupportedFileError, "SVG imports do not support query options: #{module_id}" if module_id.query
|
|
42
|
+
|
|
43
|
+
dimensions = svg_dimensions(code)
|
|
44
|
+
hash = Hashing.short(code)
|
|
45
|
+
output_path = "/assets/#{asset_name(module_id)}.#{hash}.svg"
|
|
46
|
+
asset =
|
|
47
|
+
Asset.new(
|
|
48
|
+
module_id.path,
|
|
49
|
+
hash,
|
|
50
|
+
output_path,
|
|
51
|
+
nil,
|
|
52
|
+
code,
|
|
53
|
+
"image/svg+xml",
|
|
54
|
+
{
|
|
55
|
+
type: :svg,
|
|
56
|
+
width: dimensions.width,
|
|
57
|
+
height: dimensions.height
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
javascript_asset = javascript_svg_asset(module_id, asset)
|
|
61
|
+
svg_runtime_dependency =
|
|
62
|
+
Dependency
|
|
63
|
+
.create(
|
|
64
|
+
specifier: SVG_RUNTIME_SPECIFIER,
|
|
65
|
+
importer_id: module_id,
|
|
66
|
+
kind: :svg_runtime
|
|
67
|
+
)
|
|
68
|
+
.with(id: "#{module_id}:svg_runtime")
|
|
69
|
+
|
|
70
|
+
TransformResult.new(
|
|
71
|
+
svg_module_source(asset, svg_runtime_dependency),
|
|
72
|
+
[svg_runtime_dependency],
|
|
73
|
+
nil,
|
|
74
|
+
[asset, javascript_asset],
|
|
75
|
+
[],
|
|
76
|
+
{asset_bytes: code, svg_javascript_asset_path: javascript_asset.output_path}
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def import_value(_resolved_dependency, record, context)
|
|
81
|
+
return nil unless EXTENSIONS.include?(record.id.extname)
|
|
82
|
+
|
|
83
|
+
context.mods.fetch(record.id).const_get(:Exports)::Default
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def runtime_import_value(_resolved_dependency, record, _context)
|
|
87
|
+
return Runtime::DefaultImport.new(:Default) if EXTENSIONS.include?(record.id.extname)
|
|
88
|
+
|
|
89
|
+
super
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
Dimensions = Data.define(:width, :height)
|
|
95
|
+
|
|
96
|
+
def svg_runtime_source
|
|
97
|
+
<<~RUBY
|
|
98
|
+
Svg = Data.define(:src, :width, :height, :content_type)
|
|
99
|
+
RUBY
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def svg_module_source(asset, svg_runtime_dependency)
|
|
103
|
+
<<~RUBY
|
|
104
|
+
SvgRuntime = __klenod_import__(#{svg_runtime_dependency.id.inspect})
|
|
105
|
+
Default =
|
|
106
|
+
SvgRuntime::Svg.new(
|
|
107
|
+
src: #{asset.output_path.inspect},
|
|
108
|
+
width: #{asset.metadata[:width].inspect},
|
|
109
|
+
height: #{asset.metadata[:height].inspect},
|
|
110
|
+
content_type: #{asset.content_type.inspect}
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
RUBY
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def javascript_svg_asset(module_id, asset)
|
|
117
|
+
code = javascript_svg_module_source(asset)
|
|
118
|
+
hash = Hashing.short(code)
|
|
119
|
+
Asset.new(
|
|
120
|
+
module_id.path,
|
|
121
|
+
hash,
|
|
122
|
+
"#{asset.output_path}.js",
|
|
123
|
+
nil,
|
|
124
|
+
code,
|
|
125
|
+
"application/javascript",
|
|
126
|
+
{type: :svg_javascript_metadata, svg_metadata: true, svg_asset_path: asset.output_path}
|
|
127
|
+
)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def javascript_svg_module_source(asset)
|
|
131
|
+
metadata = {
|
|
132
|
+
src: asset.output_path,
|
|
133
|
+
width: asset.metadata[:width],
|
|
134
|
+
height: asset.metadata[:height],
|
|
135
|
+
contentType: asset.content_type
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
"export default #{JSON.generate(metadata)};\n"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def svg_dimensions(code)
|
|
142
|
+
attributes = svg_attributes(code)
|
|
143
|
+
return Dimensions.new(nil, nil) unless attributes
|
|
144
|
+
|
|
145
|
+
explicit_width = parse_length(attributes["width"])
|
|
146
|
+
explicit_height = parse_length(attributes["height"])
|
|
147
|
+
return Dimensions.new(explicit_width, explicit_height) if explicit_width && explicit_height
|
|
148
|
+
|
|
149
|
+
view_box_dimensions(attributes["viewBox"] || attributes["viewbox"])
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def svg_attributes(code)
|
|
153
|
+
match = code.match(/<svg(?=[\s>])(?<attributes>[^>]*)>/im)
|
|
154
|
+
return nil unless match
|
|
155
|
+
|
|
156
|
+
match[:attributes].scan(/([:\w.-]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/).to_h do |name, double_quoted, single_quoted, unquoted|
|
|
157
|
+
[name, double_quoted || single_quoted || unquoted]
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def view_box_dimensions(view_box)
|
|
162
|
+
values = view_box.to_s.split(/[,\s]+/).filter_map { |value| parse_number(value) }
|
|
163
|
+
return Dimensions.new(nil, nil) unless values.length == 4
|
|
164
|
+
|
|
165
|
+
width = positive_number(values.fetch(2))
|
|
166
|
+
height = positive_number(values.fetch(3))
|
|
167
|
+
Dimensions.new(width, height)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def parse_length(value)
|
|
171
|
+
match = value.to_s.strip.match(/\A(?<number>[+-]?(?:\d+(?:\.\d+)?|\.\d+))(?:px)?\z/i)
|
|
172
|
+
return nil unless match
|
|
173
|
+
|
|
174
|
+
positive_number(parse_number(match[:number]))
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def parse_number(value)
|
|
178
|
+
number = Float(value, exception: false)
|
|
179
|
+
return nil unless number&.finite?
|
|
180
|
+
|
|
181
|
+
number
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def positive_number(number)
|
|
185
|
+
return nil unless number&.positive?
|
|
186
|
+
|
|
187
|
+
(number == number.to_i) ? number.to_i : number
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def asset_name(module_id)
|
|
191
|
+
File.basename(module_id.path, module_id.extname).gsub(/[^A-Za-z0-9]+/, "_")
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Klenod
|
|
4
|
+
module Build
|
|
5
|
+
module Plugins
|
|
6
|
+
autoload :RubyPlugin, File.expand_path("plugins/ruby_plugin", __dir__)
|
|
7
|
+
autoload :IntlPlugin, File.expand_path("plugins/intl_plugin", __dir__)
|
|
8
|
+
autoload :HamlPlugin, File.expand_path("plugins/haml_plugin", __dir__)
|
|
9
|
+
autoload :MarkdownPlugin, File.expand_path("plugins/markdown_plugin", __dir__)
|
|
10
|
+
autoload :GemImportPlugin, File.expand_path("plugins/gem_import_plugin", __dir__)
|
|
11
|
+
autoload :GoogleFontsPlugin, File.expand_path("plugins/google_fonts_plugin", __dir__)
|
|
12
|
+
autoload :SvgPlugin, File.expand_path("plugins/svg_plugin", __dir__)
|
|
13
|
+
autoload :ImagePlugin, File.expand_path("plugins/image_plugin", __dir__)
|
|
14
|
+
autoload :DataPlugin, File.expand_path("plugins/data_plugin", __dir__)
|
|
15
|
+
autoload :JsonPlugin, File.expand_path("plugins/data_plugin", __dir__)
|
|
16
|
+
autoload :YamlPlugin, File.expand_path("plugins/data_plugin", __dir__)
|
|
17
|
+
autoload :TomlPlugin, File.expand_path("plugins/data_plugin", __dir__)
|
|
18
|
+
autoload :TextPlugin, File.expand_path("plugins/data_plugin", __dir__)
|
|
19
|
+
autoload :RouterPlugin, File.expand_path("plugins/router_plugin", __dir__)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Klenod
|
|
4
|
+
module Build
|
|
5
|
+
class Profiler
|
|
6
|
+
Event = Data.define(:name, :duration, :metadata)
|
|
7
|
+
|
|
8
|
+
def initialize(enabled: false, store_events: true, categories: [], progress: nil)
|
|
9
|
+
@enabled = enabled
|
|
10
|
+
@events = store_events ? [] : nil
|
|
11
|
+
@totals = Hash.new(0.0)
|
|
12
|
+
@totals_by_plugin = Hash.new(0.0)
|
|
13
|
+
@counts = Hash.new(0)
|
|
14
|
+
@categories = categories.map(&:to_sym).to_h { |category| [category, true] }
|
|
15
|
+
@progress = progress
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def enabled?
|
|
19
|
+
@enabled
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def events
|
|
23
|
+
@events || []
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def measure(name, metadata = nil)
|
|
27
|
+
return yield unless enabled?
|
|
28
|
+
|
|
29
|
+
started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
30
|
+
result = yield
|
|
31
|
+
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at
|
|
32
|
+
record(name, duration, metadata || {})
|
|
33
|
+
result
|
|
34
|
+
rescue
|
|
35
|
+
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at if started_at
|
|
36
|
+
record(name, duration, (metadata || {}).merge(error: true)) if duration
|
|
37
|
+
raise
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def category?(name)
|
|
41
|
+
@categories.key?(name.to_sym)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def count(name, value = 1)
|
|
45
|
+
return unless enabled?
|
|
46
|
+
|
|
47
|
+
@counts[name] += value
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def progress(name, metadata = {})
|
|
51
|
+
return unless enabled? && @progress
|
|
52
|
+
|
|
53
|
+
@progress.call(name, metadata)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def totals
|
|
57
|
+
@totals.dup
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def totals_by_plugin
|
|
61
|
+
@totals_by_plugin.dup
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def counts
|
|
65
|
+
@counts.dup
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def record(name, duration, metadata)
|
|
71
|
+
@events << Event.new(name, duration, metadata) if @events
|
|
72
|
+
@totals[name] += duration
|
|
73
|
+
|
|
74
|
+
plugin = metadata[:plugin]
|
|
75
|
+
@totals_by_plugin[[name, plugin]] += duration if plugin
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "errors"
|
|
4
|
+
|
|
5
|
+
module Klenod
|
|
6
|
+
module Build
|
|
7
|
+
module ResolutionErrorFormatter
|
|
8
|
+
ANSI_BACKGROUND = "\e[0;48;5;52m"
|
|
9
|
+
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def format(error, source_root: nil, source_context: nil, ansi: false)
|
|
13
|
+
return error.message unless error.resolution_failure?
|
|
14
|
+
|
|
15
|
+
lines = [error.title, "", " Import: #{error.requested_specifier}"]
|
|
16
|
+
lines << " Imported by: #{error.imported_by}" if error.imported_by
|
|
17
|
+
lines << " Source root: #{source_root}" if source_root
|
|
18
|
+
append_suggestions(lines, error)
|
|
19
|
+
lines.concat(["", source_context]) if source_context
|
|
20
|
+
text = lines.join("\n")
|
|
21
|
+
ansi ? format_ansi(text) : text
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def format_ansi(text)
|
|
25
|
+
title, separator, body = text.partition("\n")
|
|
26
|
+
body = body.gsub("\e[0m", ANSI_BACKGROUND)
|
|
27
|
+
body = "\n#{body}" unless separator.empty?
|
|
28
|
+
|
|
29
|
+
"\e[1;31;47m ERROR \e[1;37;41m #{title} #{ANSI_BACKGROUND}#{body}\e[0m"
|
|
30
|
+
end
|
|
31
|
+
private_class_method :format_ansi
|
|
32
|
+
|
|
33
|
+
def append_suggestions(lines, error)
|
|
34
|
+
return if error.suggestions.empty?
|
|
35
|
+
|
|
36
|
+
lines.concat(["", (error.reason == :incorrect_case) ? " Use:" : " Did you mean?"])
|
|
37
|
+
error.suggestions.each { |suggestion| lines << " - #{suggestion}" }
|
|
38
|
+
end
|
|
39
|
+
private_class_method :append_suggestions
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "errors"
|
|
4
|
+
require_relative "filesystem_resolver"
|
|
5
|
+
require_relative "module_id"
|
|
6
|
+
require_relative "dependency"
|
|
7
|
+
|
|
8
|
+
module Klenod
|
|
9
|
+
module Build
|
|
10
|
+
class Resolver
|
|
11
|
+
DEFAULT_EXTENSIONS = [".rb", ".haml"].freeze
|
|
12
|
+
|
|
13
|
+
def initialize(source_dir:, extensions: DEFAULT_EXTENSIONS, profiler: nil)
|
|
14
|
+
@source_dir_path = File.expand_path(source_dir)
|
|
15
|
+
@source_dir = Pathname.new(@source_dir_path)
|
|
16
|
+
@extensions = extensions
|
|
17
|
+
@profiler = profiler
|
|
18
|
+
@filesystem_resolver = FilesystemResolver.new(root: @source_dir, extensions: @extensions)
|
|
19
|
+
@resolved_module_ids = {}
|
|
20
|
+
@absolute_paths = {}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
attr_reader :source_dir
|
|
24
|
+
|
|
25
|
+
def resolve(dependency)
|
|
26
|
+
module_id = resolve_app_module_id(dependency)
|
|
27
|
+
raise ResolveError, "Unknown import scheme #{module_id.scheme.inspect} for #{dependency.specifier.inspect}" unless module_id.scheme == :app
|
|
28
|
+
|
|
29
|
+
base_path = File.expand_path(module_id.relative_path, @source_dir_path)
|
|
30
|
+
assert_inside_source_dir!(base_path)
|
|
31
|
+
key = [base_path, module_id.query]
|
|
32
|
+
resolved_module_id = @resolved_module_ids[key]
|
|
33
|
+
if resolved_module_id
|
|
34
|
+
@profiler&.count(:resolver_cache_hit)
|
|
35
|
+
else
|
|
36
|
+
@profiler&.count(:resolver_cache_miss)
|
|
37
|
+
resolved_path = @filesystem_resolver.resolve(module_id.relative_path)
|
|
38
|
+
relative = relative_source_path(resolved_path)
|
|
39
|
+
resolved_module_id = @resolved_module_ids[key] = ModuleId.new("app:/#{relative}", module_id.query)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
ResolvedDependency.new(dependency, resolved_module_id, {})
|
|
43
|
+
rescue ResolveError => error
|
|
44
|
+
raise error.with_path_prefix("app:/").with_resolution_context(dependency: dependency, importer_id: dependency.importer_id)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def absolute_path(module_id)
|
|
48
|
+
path = @absolute_paths[module_id.to_s]
|
|
49
|
+
if path
|
|
50
|
+
@profiler&.count(:resolver_absolute_path_cache_hit)
|
|
51
|
+
return path
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
@profiler&.count(:resolver_absolute_path_cache_miss)
|
|
55
|
+
@absolute_paths.fetch(module_id.to_s) do |path_key|
|
|
56
|
+
raise ResolveError, "Cannot map non-app module to source_dir: #{module_id}" unless module_id.scheme == :app
|
|
57
|
+
|
|
58
|
+
path = Pathname.new(File.expand_path(module_id.relative_path, @source_dir_path))
|
|
59
|
+
assert_inside_source_dir!(path)
|
|
60
|
+
@absolute_paths[path_key] = path
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def clear_cache
|
|
65
|
+
@resolved_module_ids.clear
|
|
66
|
+
@absolute_paths.clear
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def resolve_app_module_id(dependency)
|
|
72
|
+
specifier = dependency.specifier.to_s
|
|
73
|
+
return ModuleId.parse(specifier) if specifier.match?(ModuleId::SCHEME_PATTERN)
|
|
74
|
+
|
|
75
|
+
importer_id = dependency.importer_id
|
|
76
|
+
if importer_id
|
|
77
|
+
importer_id.merge(specifier)
|
|
78
|
+
else
|
|
79
|
+
ModuleId.new("app:/#{specifier.delete_prefix("/")}")
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def assert_inside_source_dir!(path)
|
|
84
|
+
expanded = File.expand_path(path.to_s)
|
|
85
|
+
return if expanded == @source_dir_path || expanded.start_with?("#{@source_dir_path}/")
|
|
86
|
+
|
|
87
|
+
raise ResolveError, "Resolved path escapes source_dir: #{path}"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def relative_source_path(path)
|
|
91
|
+
Pathname.new(path).relative_path_from(@source_dir).to_s
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|