1

I am trying to import an image from JS in production. I have rails 6 with esbuild and jsbundling, and it works fine in development environment, but in production, images being imported from JS has no fingerprint. I've seen a Stack Overflow post like this one (Specific Image Not Loading After Rails 7 ESBuilt Update), but their solution was to move those image files to public/images folder. It's a solution, but this requires changing all JS files to load from a different location....

esbuild.config.js

const path = require('path')

require("esbuild").build({
  entryPoints: ['app/javascript/application.js'],
  bundle: true,
  tsconfig: path.join(process.cwd(), "tsconfig.json"),
  outdir: path.join(process.cwd(), "app/assets/builds"),
  absWorkingDir: path.join(process.cwd(), "app/javascript"),
  watch: process.argv.includes("--watch"),
  incremental: process.argv.includes("--watch"),
  assetNames: "[name]-[hash].digested",
  publicPath: "/assets",
  plugins: [],
  loader: {
    ".js": "jsx",
    ".locale.json": "file",
    ".json": "json",
    ".png": "file",
    ".jpeg": "file",
    ".jpg": "file",
    ".svg": "file",
  }
}).catch(() => process.exit(1))

Importing Image, where JavaScript file is under app/javascript/, and image directory is app/javascript/images.

import Logo from './images/logo.svg';

Logo file in app/assets/builds in development

➜  work git:(master) ls -la app/assets/builds/logo*
-rw-r--r--  1 yang  staff  4710 Jun 26 16:30 app/assets/builds/logo-M5AMKDMO.digested.svg

Logo file referenced in HTML after the page was rendered in DEVELOPMENT

<img class="logo" src="/assets/logo-M5AMKDMO.svg">

Logo file in public/assets in production

-rw------- 1 u18958 dyno 4710 Jun 24 02:48 public/assets/logo-M5AMKDMO.digested-abddddc51310100c173ac88db35b27e1492e730c91cb447b346f772532ac85ba.svg

Logo file referenced in HTML after the page was rendered in PRODUCTION

<img class="logo" src="/assets/logo-M5AMKDMO.svg">

The img tag in production is totally missing the fingerprint by asset precompile. The server is deployed in Heroku, and they do precompile during deployment.

One more thing: I did one test by setting config.assets.compile to true, and that actually worked, but you shouldn't do that per this post (config.assets.compile=true in Rails production, why not?), so I feel quite stuck.

1 Answer 1

2

I'm guessing you have an old version of sprockets. v4.1.0 is when .digested thing works properly:

# Gemfile

gem "sprockets", ">= 4.1.0"

// esbuild.config.js

require('esbuild').build({
  watch: process.argv.includes('--watch'),
  entryPoints: ['app/javascript/application.js'],
  outdir: 'app/assets/builds',
  bundle: true,

  publicPath: '/assets',
  assetNames: '[name]-[hash].digested',

  loader: {
    '.svg': 'file'
  }
})
// package.json

"scripts": {
  "build": "node esbuild.config.js",
...

I'm not sure how you're getting /assets/logo-M5AMKDMO.svg path, with this config it should be /assets/logo-M5AMKDMO.digested.svg.

// app/javascript/application.js

import logo from './images/logo.svg'

console.log(logo) // => "/assets/logo-2RBQBF7Y.digested.svg"

That's the url generated by esbuild, it doesn't change in different rails environments. .digested is a recent addition, it's meant to skip the extra sprockets digest:

# gem "sprockets", "4.0.3"
>> helper.asset_path("logo-2RBQBF7Y.digested.svg")
=> "/assets/logo-2RBQBF7Y.digested-0c3d0155537d0e4f72356e8d9c7b41e8aa779b007b82f88d5af2766ba6166f45.svg"

# gem "sprockets", "4.1.0"
>> helper.asset_path("logo-2RBQBF7Y.digested.svg")
=> "/assets/logo-2RBQBF7Y.digested.svg"
$ RAILS_ENV=production bin/rails assets:precompile
$ ls public/assets/logo* 
public/assets/logo-2RBQBF7Y.digested.svg
public/assets/logo-2RBQBF7Y.digested.svg.gz

You can also just rename files after precompilation:

>> URI.open("http://localhost:3000/assets/logo-2RBQBF7Y.digested.svg").read
/home/alex/.rbenv/versions/3.1.2/lib/ruby/3.1.0/open-uri.rb:364:in 'open_http': 404 Not Found (OpenURI::HTTPError)

>> Rails.root.join("public/assets").glob("*.digested*").each do |f|
     f.rename(f.to_s.remove(/\.digested\K-\w+/))
   end

>> URI.open("http://localhost:3000/assets/logo-2RBQBF7Y.digested.svg").read
=> "<svg>i am image</svg>\n"
Sign up to request clarification or add additional context in comments.

1 Comment

Ding, ding, ding, ding! This is the answer. Thank you so much. I wouldn't have known old sprockets was the issue. I'd give 100 fist bumps if I can. Thank you, @Alex!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.