Rails 构建时下载 importmap 资源的方法

问题

Importmap-rails 默认的加载来源是 CDN,但是公共 CDN 在国内访问不稳定。另一个方法是将 JavaScript 下载到 vender/javascript 目录,但是这个方法需要将外部 JavaScript 签入源码管理工具,导致源码体积变大。

有没有方法可以将下载 JavaScript 延后到构建时执行呢?我经过实践找到了一个方法。

解决

第一步,修改 .gitignore

.gitignore 加入以下内容:

/vendor/javascript

这会使 git 忽略 importmap 下载的内容。

第二步,importmap pin {package} --download

importmap pin 的时候加上 --download 参数,例如:

bin/importmap pin sortablejs --download

这样会立即下载 sortablejs 的源码,存放到 vendor/javascript 中,同时在 config/importmap.rb 中添加以下内容:

pin "sortablejs" # @1.15.0

这是正常的 importmap 操作,只是不需要提交 js 到源码库,并且用 config/importmap.rb 里的内容作为配置。

第三步,添加 rake task

新建 lib/tasks/importmap.rake 文件,写入以下内容:

require "importmap/npm"
require "importmap/packager"

namespace :importmap do
  desc "Download"
  task :download do
    npm = Importmap::Npm.new
    packager = Importmap::Packager.new
    packages = npm.packages_with_versions.map { |item| item.join("@") }
    imports = packager.import(packages)
    imports.each do |package, url|
      puts %(Pinning "#{package}" to #{packager.vendor_path}/#{package}.js via download from #{url})
      packager.download(package, url)
    end
  end
end

Rake::Task["assets:precompile"].enhance(["importmap:download"])

这样在执行 bin/rails assets:precompile 的时候,会先执行 bin/rails importmap:download,该任务会根据 config/importmap.rb 的内容下载 JavaScript。

记得在 Dockerfile 中包含 bin/rails assets:precompile 的步骤,即使没有用到其他构建工具。

第四步,其他配置

在其他脚本里加上 bin/rails importmap:download,例如在 bin/setup 里面加上:

puts "\n== Importmap download =="
system! "bin/rails importmap:download"

方便搭建开发环境。

总结

本文通过添加一个 rake task 实现了将下载 importmap 资源延迟到构建阶段。这样既能自己掌控 JavaScript 文件的分发方式,又不需要将庞大的外部 JavaScript 文件提交到源码库。

这个脚本没有考虑混合使用 importmap 来源和 JavaScript 依赖非常复杂的情况。