如何从 Webpacker 切换到 CSS/JS bundling

最近 Rails 7 正式发布,其中一个引人注目的特性是 CSS/JS bundling,用于取代 Rails 6 的 Webpacker。我在之前的文章中介绍过新的方案带来什么变化。

现在 Rails 创建项目,将会增加两个参数:

-j, [--javascript=JAVASCRIPT]  # Choose JavaScript approach [options: importmap (default), webpack, esbuild, rollup]
-c, [--css=CSS]                # Choose CSS processor [options: tailwind, bootstrap, bulma, postcss, sass...]

例如要使用 esbuild 和 sass 可以用以下命令:

$ rails new myapp -j esbuild -c sass

但大多数人关心的可能是如何从已有项目上切换到新方案,下面进一步说明。

脚本切换

Rails 增加了两个 gem cssbundling-railsjsbundling-rails,用来支持 bundling 方案。他们依赖 Rails >= 6,所以不需要升级到 Rails 7 也可以使用。在 Gemfile 中加入以下内容:

gem 'cssbundling-rails'
gem 'jsbundling-rails'

执行 bundle install 之后,用以下命令安装需要的前端工具:

$ bin/rails css:install:sass
$ bin/rails javascript:install:esbuild

通过 git diff 可以看到有什么变动,要注意的地方有:

  1. 现在 CSS 和 JS 的构建源文件分别是 app/assets/stylesheets/application.sass.scssapp/javascript/application.js,需要手工迁移内容。
  2. app/assets/builds 是存放编译后静态文件的地方。
  3. 增加了一个 bin/dev 脚本,通过 foreman 一起启动 rails server 和 CSS/JS 构建进程,配置文件是 Procfile.dev。
  4. 检查 layout 里的标签,删除 webpacker 的引用。

如果项目的文件都按照 Rails 6 默认的目录结构摆放,那么用 bin/dev 启动开发进程就能看到 CSS 和 JS 构建进程在工作。

人工切换

如果你的项目比较复杂,脚本安装不能满足需要,那么可以用人工方式处理。实际上 cssbundling-railsjsbundling-rails 的内容只是一些安装脚本和 Rake task,没有运行时代码。理解如何手工切换到 CSS/JS bundling 可以让配置更符合项目的需求。下面介绍如何实现。

添加 builds 目录

假设项目的静态文件存放结构如下:

app/assets/
├── config
│   └── manifest.js
├── images
└── stylesheets
    └── application.css
app/javascript/
└── packs
    └── application.js

首先创建 builds 目录,用来存放编译后的文件:

$ mkdir app/assets/builds
$ touch app/assets/builds/.keep

.gitignore 中添加这个目录,避免 check in 里面的文件:

/app/assets/builds/*
!/app/assets/builds/.keep

修改 app/assets/config/manifest.js,删除以下配置:

//= link_directory ../stylesheets .css

添加以下配置:

//= link_tree ../builds

现在 Assets pipeline 就知道要从 builds 目录获取静态文件。

配置 sass

安装 sass

$ yarn add sass

package.json 中添加以下内容:

  "scripts": {
    "build:css": "sass app/assets/stylesheets/application.scss app/assets/builds/application.css --no-source-map --load-path=node_modules"
  }

创建文件 /app/assets/stylesheets/application.scss,将原先 app/assets/stylesheets/application.css 的内容迁移进去。

要注意 Assets Pipeline 提供的 require_tree require_self 等注释不再起作用,要用 sass@import 替代。

如果正常,使用 yarn build:css 可以看到 CSS 编译成功。

配置 esbuild

安装 esbuild

$ yarn add esbuild

package.json 中添加以下内容:

  "scripts": {
    "build:js": "esbuild app/javascript/application.js --bundle --outdir=app/assets/builds"
  }

创建文件 app/javascript/application.js,将原先 app/javascript/packs/application.js 的内容迁移进去。

要注意如果之前用的 webpacker,转到 esbuild 有可能需要修改 require 语法,例如 import "channels" 要改成 import "./channels"。另外依赖 webpack 的语句也需要修改,例如 Stimulus 的 Webpack Helpers 将不可用。

如果正常,使用 yarn build:js 可以看到 JS 编译成功。

添加 bin/dev 脚本

现在开发环境需要启动三个进程,一个 rails server,一个 sass,还有一个 esbuild。每次开发启动这三个进程会比较繁琐,所以可以借助一些工具管理,这里以 foreman 为例。

添加一个 bin/dev 文件,内容为:

#!/usr/bin/env bash

if ! command -v foreman &> /dev/null
then
  echo "Installing foreman..."
  gem install foreman
fi

foreman start -f Procfile.dev

bin/dev 添加可执行权限:

$ chmod +x bin/dev

添加文件 Procfile.dev,内容为:

web: bin/rails server -p 3000
css: yarn build:css --watch
js: yarn build:js --watch

这样开发的时候就可以用 bin/dev 一起启动三个进程。

添加 Rake task

最后是让 Assets Pipeline 编译静态文件的时候正确的调用外部编译。

添加文件 lib/tasks/build.rake,内容为:

namespace :build do
  desc "Run yarn install"
  task :install do
    system "yarn install"
  end

  desc "Build Javascript and CSS"
  task :all => [:javascript, :css]

  desc "Build JavaScript"
  task :javascript => :install do
    system "yarn build:js"
  end

  desc "Build CSS"
  task :css => :install do
    system "yarn build:css"
  end
end

Rake::Task["assets:precompile"].enhance(["build:all"])

如果正常,现在执行 bin/rails assets:precompile 时会自动执行 build:cssbuild:js

以上就是手工操作切换 CSS/JS bundling 的过程。根据你项目的复杂程度可能需要进行相应的更改。在切换完成后,就可以删掉 webpacker 相关的包和配置。

总结

看完如何手工切换 bundling,你应该会发现 bundling 的操作是在 Assets Pipeline 之前完成的。编译完成后把文件放到 builds 目录,然后告诉 Assets Pipeline 从这个目录读取文件就行了。

这是一次把 Rails 主体和前端工具链解藕的过程,我们可以用任意偏好的工具和方式处理前端文件,只要最后把它纳入 Assets Pipeline 管理就行了。这大大增加了灵活性,也不需要和框架的默认配置做斗争——这都取决于自己的选择。

希望这篇文章有助于理解如何按 Rails 7 的方式管理静态文件。

2
2