Rails 7:编译具有不同文件夹结构的资产



我的rails应用程序有问题。今天我从 Rails 6 升级到 Rails 7。在 Rails 7 中,webpacker 被移除了,所以我现在使用 ESBuild。在我的项目中,我有TypeScript和SASS文件。为了编译这些资产,我正在运行以下脚本:

import esbuild from "esbuild";
import { sassPlugin } from "esbuild-sass-plugin";
import postcss from 'postcss';
import autoprefixer from 'autoprefixer';
// Generate CSS/JS Builds
esbuild
.build({
entryPoints: ['app/assets/scss/app.scss', 'app/assets/ts/app.ts'],
outdir: "dist",
bundle: true,
metafile: true,
plugins: [
sassPlugin({
async transform(source) {
const { css } = await postcss([autoprefixer]).process(source);
return css;
},
}),
],
})
.then(() => console.log("⚡ Build complete! ⚡"))
.catch(() => process.exit(1));

app/assets/config/manifest.js中,我有以下内容:

// = link_tree ../images
// = link_tree ../scss .css
// = link_tree ../ts .js
// = link_tree ../builds

但是我正在使用可能导致问题的其他文件夹结构。这是我.scss.ts文件的文件夹结构:

app/
└── assets/
├── scss/
│   ├── math/
│   │   └── calculate.scss
│   └── app.scss
└── ts/
├── math/
│   └── calculate.ts
└── app.ts

如您所见,在assets下,我创建了文件夹scssts,这对我来说比将打字稿放在 javascript 文件夹中更有意义。问题是当我使用包含标签时:

<%= javascript_include_tag 'math/calculate', 'data-turbolinks-track': 'reload' %>

找不到资产,我认为这是由于它仍在assets/javascript文件夹中查找而导致的。

我可以在public/assets中看到,我所有的ts文件现在都是.js文件,所有的.scss文件都是.css文件,所以ESBuild完成了他的工作,但我认为问题出在包含部分。我收到此错误:

The asset "math/calculate.js" is not present in the asset pipeline.

有人可以帮我解决这个问题吗,我希望我能像现在一样保持我的文件夹结构!?

首先,我必须提到rails附带了构建javascript和css的工具,我们将进行自己的设置,但这些仍然作为参考:
https://github.com/rails/jsbundling-rails
https://github.com/rails/cssbundling-rails


Rails 7中,js、css 和任何其他本地资产都由sprocket提供服务。为了让链轮找到您的资产,它们必须处于Rails.application.config.assets.paths;app/assets/下的任何目录都将自动成为资产路径的一部分:

>> Rails.application.config.assets.paths
=> ["/home/alex/code/SO/ts/app/assets/builds",
"/home/alex/code/SO/ts/app/assets/config",
"/home/alex/code/SO/ts/app/assets/scss",
"/home/alex/code/SO/ts/app/assets/ts",
...

大多数情况下,应用程序将提供application.jsapplication.css。任何进入javascript_include_tag的资产,stylesheet_link_tag或任何其他需要在浏览器中提供的资产,也必须声明进行预编译。对于 js 和 css,这些是入口点,您可以在其中@import其他文件作为捆绑包的一部分:

# app/assets/config/manifest.js
//= link_tree ../builds

app/assets/builds目录位于资产路径中,该目录中的每个文件都将在生产中预编译。因为链轮可以处理jscss,所以必须有一个额外的构建步骤来编译ts=>jsscss=>css


资产源文件可以在任何地方,可以随心所欲地组织,只要编译后的资源最终app/assets/builds,rails 就不关心它们。

.CSS:

// esbuild.css.js
import esbuild from "esbuild";
import { sassPlugin } from "esbuild-sass-plugin";
import postcss from "postcss";
import autoprefixer from "autoprefixer";
const watch = process.argv.includes("--watch");
esbuild
.build({
// declare you entrypoint, where, I hope, you're importing all other styles.
entryPoints: ["app/assets/scss/app.scss"],
// spit out plain css into `builds`, so that sprockets can serve them.
outdir: "app/assets/builds",
// you'll want this running with --watch flag
watch: watch,
publicPath: "assets",
bundle: true,
metafile: true,
plugins: [
sassPlugin({
async transform(source) {
const { css } = await postcss([autoprefixer]).process(source);
return css;
},
}),
],
})
.then(() => console.log("⚡ CSS build complete! ⚡"))
.catch(() => process.exit(1));

.JS:

// esbuild.js
import esbuild from "esbuild";
import glob from "glob";
const watch = process.argv.includes("--watch");
esbuild
.build({
// you say you want every file compiled separately, best I could come up:
entryPoints: glob.sync("app/assets/ts/**/*.ts"),
// spit out plain js into `builds`
outdir: "app/assets/builds",
watch: watch,
publicPath: "assets",
bundle: true,
metafile: true,
})
.then(() => console.log("⚡ JS build complete! ⚡"))
.catch(() => process.exit(1));

Rails 分别编译 js和 css,我们也可以。这也避免了嵌套的build/ts/build/scss/目录。


如果你没有这个:

# bin/dev
#!/usr/bin/env sh
if ! gem list foreman -i --silent; then
echo "Installing foreman..."
gem install foreman
fi
exec foreman start -f Procfile.dev "$@"
# Procfile.dev
web: bin/rails server
_js: yarn build --watch
css: yarn build:css --watch

添加构建脚本:

// package.json
{
...
"scripts": {
"build": "node esbuild.js",
"build:css": "node esbuild.css.js"
}
}

我认为这应该是一切:

app/assets
├── builds               # <= this is the only directory "servable" by sprockets
├── config
│  └── manifest.js       # //= link_tree ../builds
├── scss
│  ├── app.scss          # @import "./math/calculate.scss"
│  └── math
│     └── calculate.scss
└── ts
├── app.ts            # no imports here? ¯_(ツ)_/¯
└── math
└── calculate.ts   # console.log("do the calc")

您可以运行bin/dev来启动服务器并开始编译scssts

$ bin/dev

或手动运行构建脚本:

$ yarn build && yarn build:css
⚡ JS build complete! ⚡
⚡ CSS build complete! ⚡

它编译成构建目录:

app/assets/builds
├── app.css
├── app.js
└── math
└── calculate.js

这些 ^ 是您可以在布局中使用的资源:

<%= stylesheet_link_tag "app", "data-turbo-track": "reload" %>
<%= javascript_include_tag "app", "math/calculate", "data-turbo-track": "reload", defer: true %>

现在,当你要求math/calculate时,链轮会在Rails.application.config.assets.paths中找到它,它会检查它是否被声明为预编译:

# NOTE: if it is, you get a digested url to that file
>> helper.asset_path("math/calculate.js")
=> "/assets/math/calculate-11273ac5ce5f76704d22644f4b03b94908a318451578f2d10a85847c0f7f2998.js"
# everything as expected here
>> puts URI.open(helper.asset_url("math/calculate.js", host: "http://localhost:5555")).read
(() => {
// app/assets/ts/math/calculate.ts
console.log("do the calc");
})();

将自定义构建脚本挂接到一些rails任务中,例如assets:precompile以便在部署时自动构建所有内容:
https://github.com/rails/jsbundling-rails/blob/v1.1.1/lib/tasks/jsbundling/build.rake

# lib/tasks/build.rake
namespace :ts_scss do
desc "Build your TS & SCSS bundle"
task :build do
# put your build commands here VVVVVVVVVVVVVVVVVVVVVVVVVVVV
unless system "yarn install && yarn build && yarn build:css"
raise "Command build failed, ensure yarn is installed"
end
end
end
if Rake::Task.task_defined?("assets:precompile")
Rake::Task["assets:precompile"].enhance(["ts_scss:build"])
end
if Rake::Task.task_defined?("test:prepare")
Rake::Task["test:prepare"].enhance(["ts_scss:build"])
elsif Rake::Task.task_defined?("spec:prepare")
Rake::Task["spec:prepare"].enhance(["ts_scss:build"])
elsif Rake::Task.task_defined?("db:test:prepare")
Rake::Task["db:test:prepare"].enhance(["ts_scss:build"])
end

天哪!不要在开发中预编译资产!我不知道人们从哪里得到这个想法。您将从public/assets提供资产,并且它们不会自动更新。如果您这样做了,只需撤消它:

bin/rails assets:clobber
<小时 />

长话短说:

app/assets/ts/app.ts    # compile to `js` with esbuild
V                     # output into builds
app/assets/builds       # is in `Rails.application.config.assets.paths`
V                     # is in `manifest.js` (//=link_tree ../builds)
javascript_include_tag("app")
V
asset_path("app.js")    # served by `sprockets`
V                     # or by something else in production (thats why we precompile)
Browser!

最新更新