我正在将一个大型商业(专有)Rails 6 应用程序升级到 Rails 7。我们从未使用过 Webpacker,而是直接从 Bootstrap 之类的捆绑 gem 转向“Rails 7 方式”。
事实证明,Rails 7 的“无节点”工作流程对于同时包含 CSS 和 JS 组件的组件没有好的答案。在我们的例子中,最明显的罪犯是 Bootstrap。面对通过导入映射维护 Bootstrap 的 JS“一半”和通过旧的 Bootstrap gem 或手动供应商之类的 CSS“一半”(是的,这里真的没有其他解决方案没有 Node 这里),我们最终回到了完整的 Node 工作流程。
这是在一起的。所有提供 CSS 和/或 JS 的前端组件都已经在 NPM 中可用,所以现在所有这些都通过
package.json
和 Yarn 进行管理,其中 bin/dev
驱动 Sass 和 esbuild
编译从中提取的 SCSS 和 JS 组件app/assets
、app/javascript
或 node_modules/...
;因此,资产管道 manifest.js
仅包含对 build
内的 images
和 app/assets
文件夹的引用。
感觉有点倒退,所有重量级的文件名列表手动维护(不再支持通配符导入)以及现在在 Foreman 下运行的多个进程的复杂性与仅在 Sprockets 中同步处理的内容相比请求基础,但随着所有这些东西被弃用/废弃软件,显然是时候更新了。
这一切在开发和生产模式下都运行良好,但是测试呢?我们使用RSpec;在 CI 中,没有内置资产,开发人员不想每次运行
esbuild
时都必须记住运行 assets:precompile
或 rspec
或其他任何内容。别的不说,就是很慢。
当您想要使用最新资产运行测试时,基于 Yarn/Node 的工作流程中专门使用
cssbundling-rails
和 jsbundling-rails
的官方惯用 Rails 7 解决方案是什么?
jsbundling-rails
和
cssbundling-rails
都将自己附加到名为
test:prepare
的rake任务中。有几种方法可以使
test:prepare
运行,具体取决于您的整体构建过程。
bundle exec rails test:prepare test
rails
命令之外运行 rspec:
bundle exec rails test:prepare && bundle exec rspec
test:prepare
的测试任务。奇怪的是,只有一些测试任务调用(依赖于)
test:prepare
,而其他任务(包括默认的
test
任务)则不会。示例:
bundle exec rails test:all
test:prepare
成为您首选测试任务的依赖项。例如,如果您通常通过运行
spec
来使用
bundle exec rails spec
任务,请将其添加到新的或现有的任务文件(例如
lib/tasks/tests.rake
)中:
task spec: ['css:build', 'javascript:build']
test:prepare
是 Rails 定义的空任务。
cssbundling-rails
和
jsbundling-rails
都将自身添加为该任务的依赖项。一般来说,
test:prepare
是一个有用的地方,可以添加运行测试所需的任何类型的依赖项,但需要注意的是,只有一些 Rails 的默认测试任务依赖于它。但如上所述,您始终可以直接调用它或添加自己的依赖项。在大多数情况下,调用
test:prepare
相当于同时调用
css:build
和
javascript:build
,这就是我在上述大多数示例中显示
test:prepare
的原因。有时,其他 gem 或您的应用程序也可能会扩展
test:prepare
以及其他命令,在这种情况下,这些命令也将运行(并且可能需要)。另请注意,
assets:precompile
还取决于
css:build
和
javascript:build
。根据我的经验,
test:prepare
(或单独的
css:build
和
javascript:build
)比
assets:precompile
运行得更快,可能是因为我们正在运行
sprockets-rails
(而不是
propshaft
)和
assets:precompile
的轻量级配置运行整个链轮编译过程。
bin/dev
未运行。
# Under Rails 7 with 'cssbundling-rails' and/or the 'jsbundling-rails' gems,
# entirely external systems are used for asset management. With Sprockets no
# longer synchronously building assets on-demand and only when the source files
# changed, compiled assets might be (during local development) or will almost
# always be (CI systems) either out of date or missing when tests are run.
#
# People are used to "bundle exec rspec" and things working. The out-of-box gem
# 'cssbundling-rails' hooks into a vanilla Rails "prepare" task, running a full
# "css:build" task in response. This is quite slow and generates console spam
# on every test run, but points to a slightly better solution for RSpec.
#
# This class is a way of packaging that solution. The class wrapper is really
# just a namespace / container for the code.
#
# First, if you aren't already doing this, add the folllowing lines to
# "spec_helper.rb" somewhere *after* the "require 'rspec/rails'" line:
#
# require 'rake'
# YourAppName::Application.load_tasks
#
# ...and call MaintainTestAssets::maintain! (see that method's documentation
# for details). See also constants MaintainTestAssets::ASSET_SOURCE_FOLDERS and
# MaintainTestAssets::EXPECTED_ASSETS for things you may want to customise.
#
class MaintainTestAssets
# All the places where you have asset files of any kind that you expect to be
# dynamically compiled/transpiled/etc. via external tooling. The given arrays
# are passed to "Rails.root.join..." to generate full pathnames.
#
# Folders are checked recursively. If any file timestamp therein is greater
# than (newer than) any of EXPECTED_ASSETS, a rebuild is triggered.
#
ASSET_SOURCE_FOLDERS = [
['app', 'assets', 'stylesheets'],
['app', 'javascript'],
['vendor']
]
# The leaf files that ASSET_SOURCE_FOLDERS will build. These are all checked
# for in "File.join(Rails.root, 'app', 'assets', 'builds')". Where files are
# written together - e.g. a ".js" and ".js.map" file - you only need to list
# any one of the group of concurrently generated files.
#
# In a standard JS / CSS combination this would just be 'application.css' and
# 'application.js', but more complex applications might have added or changed
# entries in the "scripts" section of 'package.json'.
#
EXPECTED_ASSETS = %w{
application.js
application.css
}
# Call this method somewhere at test startup, e.g. in "spec_helper.rb" before
# tests are actually run (just above "RSpec.configure..." works reasonably).
#
def self.maintain!
run_build = false
newest_mtime = Time.now - 100.years
# Find the newest modificaftion time across all source files of any type -
# for simplicity, timestamps of JS vs CSS aren't considered
#
ASSET_SOURCE_FOLDERS.each do | relative_array |
glob_path = Rails.root.join(*relative_array, '**', '*')
Dir[glob_path].each do | filename |
next if File.directory?(filename) # NOTE EARLY LOOP RESTART
source_mtime = File.mtime(filename)
newest_mtime = source_mtime if source_mtime > newest_mtime
end
end
# Compile the built asset leaf names into full file names for convenience.
#
built_assets = EXPECTED_ASSETS.map do | leaf |
Rails.root.join('app', 'assets', 'builds', leaf)
end
# If any of the source files are newer than expected built assets, or if
# any of those assets are missing, trigger a rebuild task *and* force a new
# timestamp on all output assets (just in case build script optimisations
# result in a file being skipped as "already up to date", which would cause
# the code here to otherwise keep trying to rebuild it on every run).
#
run_build = built_assets.any? do | filename |
File.exist?(filename) == false || File.mtime(filename) < newest_mtime
end
if run_build
Rake::Task['javascript:build'].invoke()
Rake::Task[ 'css:build'].invoke()
built_assets.each { | filename | FileUtils.touch(filename, nocreate: true) }
end
end
end
(编辑)正如下面的评论者指出的,您需要确保 Rake 任务已加载到您的 spec_helper.rb
中,例如:
require 'rake'
Rails.application.load_tasks
在阅读了 @tm 上面写的精彩答案后,我运行
rails -T
寻找 RSpec 的类似任务并“发现”了这个:
bin/rails spec # Run all specs in spec directory (excluding plugin specs)
它还调用@tm提到的test:prepare
。