|
class ImportMap |
|
module HTMLHelpers |
|
private def map : ImportMap |
|
ImportMap.instance |
|
end |
|
|
|
def render_script_tags : String |
|
tags = [] of String |
|
asset_renderer = AssetRenderer.instance |
|
|
|
# constant imports, array of [name, path] |
|
rendered_imports = map.imports |
|
.map { |import| [import.name, import.path] } |
|
|
|
# render the dynamic imports |
|
# todo move this logic into ImportGlob |
|
map.paths.each do |path| |
|
puts "Globbing #{path.glob_path}" |
|
Dir.glob(path.glob_path).each do |file| |
|
path_ = Path[file].relative_to(path.root) |
|
file_ = asset_renderer.hash_and_copy_file( |
|
Path[file].relative_to(asset_renderer.source_path) |
|
).to_s |
|
rendered_imports << [path_.parent.join(path_.stem).to_s, file_] |
|
end |
|
end |
|
|
|
# render the import map script tag |
|
tags << String.build do |s| |
|
s << %|<script type="importmap">| |
|
s << { "imports": rendered_imports.to_h }.to_json |
|
s << %|</script>| |
|
end |
|
|
|
# render the preload tags |
|
map.imports.select { |import| import.preload }.each do |import| |
|
tags << %|<script type="modulepreload" src="#{import.path}"></script>| |
|
end |
|
|
|
tags.join("\n") |
|
end |
|
end |
|
|
|
struct Import |
|
getter name : String |
|
getter preload : Bool |
|
|
|
getter? local : Bool |
|
|
|
def initialize(@name : String, @path : String, @preload : Bool, @local : Bool = false) |
|
end |
|
|
|
def path : String |
|
if local? |
|
AssetRenderer.instance.hash_and_copy_file(@path).to_s |
|
else |
|
@path |
|
end |
|
end |
|
end |
|
|
|
struct ImportGlob |
|
getter root : String |
|
getter glob : String |
|
|
|
def initialize(@root : String, @glob : String) |
|
end |
|
|
|
def glob_path : Path |
|
Path[@root] / @glob |
|
end |
|
end |
|
|
|
def self.instance : self |
|
@@instance ||= new |
|
end |
|
|
|
def self.config : Nil |
|
with instance yield |
|
end |
|
|
|
def self.render_script_tags : String |
|
instance.render_script_tags |
|
end |
|
|
|
getter imports = [] of Import |
|
getter paths = [] of ImportGlob |
|
|
|
def initialize |
|
end |
|
|
|
# Adds a remote file to the import map. Useful for packages from jsdeliver.net, unpkg, etc. |
|
# |
|
# Eg. |
|
# ```crystal |
|
# ImportMap.config do |
|
# import_remote("stimulus", path: "https://cdn.jsdelivr.net/npm/[email protected]/+esm", preload: true) |
|
# end |
|
# ``` |
|
# |
|
def import_remote(name : String, path : String, preload : Bool = false) |
|
@imports << Import.new name, path, preload |
|
end |
|
|
|
# Adds a static local file to the import map. |
|
# |
|
# Eg: |
|
# |
|
# ```crystal |
|
# ImportMap.config do |
|
# import_local "application", "assets/application.js" |
|
# end |
|
# ``` |
|
def import_local(name : String, path : String, preload : Bool = false) |
|
@imports << Import.new name, path, preload, local: true |
|
end |
|
|
|
# Adds a dynamic file glob to the import map. |
|
# Each time the import map is rendered, the files matching the pattern |
|
# will be added to the map. Files are hashed and the stem is used as the |
|
# import name. |
|
# |
|
# Eg: |
|
# |
|
# ```crystal |
|
# ImportMap.config do |
|
# import_glob(root: "src/javascript", glob: "controllers/**/*.js") |
|
# end |
|
# ``` |
|
def import_glob(root : String, glob : String) : Nil |
|
@paths << ImportGlob.new(root, glob) |
|
end |
|
end |