Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't load images in JavaScript files #76

Closed
cirdes opened this issue Jan 26, 2022 · 19 comments · Fixed by #158
Closed

Can't load images in JavaScript files #76

cirdes opened this issue Jan 26, 2022 · 19 comments · Fixed by #158

Comments

@cirdes
Copy link

cirdes commented Jan 26, 2022

Hi,

I'm trying to migrate my Rails + React application from Webpacker to Jsbundling + Esbuild following switch_from_webpacker guide to first replace webpacker for Jsbundling

Everything is bundled to "app/assets/builds" by webpack, images included.

application.js and application.css are working fine in application.html.erb but the images in jsx files aren't loading.

My React component are trying to load "0bdd8103a525a17c3528e4f40d701b33.svg" the same as output in assets/builds folder but public/assets has the digested version of my svg "0bdd8103a525a17c3528e4f40d701b33-b8fb0ca2943e7724f64b3717f8a3e2b3ecb321adac1053107f2c89142efffd17.svg"

import React from "react";
import Logo from "Assets/logo-brand.svg";

export default function Img() {
  return <img height="46px" src={Logo}  />;
}

I'm not sure if the correct approach is to move all assets from assets/build to public/asssets with a rake or if it is possible to skip sprockets-rails digest for images and rely on webpack digested file or if there is another way to load imagens inside js files

Thanks for jsbundling effort!

@richardkmiller
Copy link
Contributor

@cirdes PR #58 should be helpful to you. The short answer is to open package.json under "scripts" and edit the esbuild script to include --public-path=/assets. In the PR I suggested --public-path=assets but after further use, I found an issue that was resolved by using the leading slash: --public-path=/assets. Let me know if that doesn't work for you.

@richardkmiller
Copy link
Contributor

@cirdes Realizing too, you probably need step 2 from the PR, which is, in your case, to add --loader:.svg=file to your esbuild script. That should cause esbuild to copy logo-brand.svg to your build folder.

@cirdes
Copy link
Author

cirdes commented Jan 26, 2022

@richardkmiller , I'm planing to move for esbuild but as first step I just replace webpacker for jsbundling.
In my scripts I have webpack --config ./webpack.config.js

webpack.config.js

const path = require("path");

module.exports = {
  mode: "production",
  devtool: "source-map",
  entry: {
    application: "./app/javascript/application.js",
  },
  resolve: {
    extensions: [".css", ".yml", ".js", ".jsx"],
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: ["babel-loader"],
      },
      {
        test: /\.(png|jpe?g|gif|svg)$/i,
        use: [
          {
            loader: "file-loader",
          },
        ],
      },
    ],
  },
  output: {
    filename: "[name].js",
    sourceMapFilename: "[name].js.map",
    path: path.resolve(__dirname, "app/assets/builds"),
  },
};

Webpack moves everything to "app/assets/builds" as expected but sprockets moves my js, css and .svg files to public/assets and apply the digest. Because of the digested version in public/assets my JS components can't find images.

@cirdes
Copy link
Author

cirdes commented Jan 26, 2022

@richardkmiller,

This is what is happening with my file.svg:
file.svg -> webpack build -> app/assets/builds/[webpack-hash].svg
app/assets/builds/[webpack-hash].svg -> sprockets -> public/assets/[webpack-hash]-[sprockets-hash].svg

my JS component is resolving the image as assets/[webpack-hash].svg and can't find it because only assets/[webpack-hash]-[sprockets-hash].svg is accessible.

@richardkmiller
Copy link
Contributor

Ah I see what you mean. I'm not sure about this, but you may be able to make it work by removing the keyword hash from your webpack configuration so webpack won't rename the svg file. Then you can use the asset pipeline helpers to refer to it by name in your JavaScript. Even though the asset pipeline will add a hash/digest, that's ok because the helpers still work.

These might help:

https://stackoverflow.com/questions/45809887/webpack-disable-hashing-of-image-name-on-output

https://guides.rubyonrails.org/asset_pipeline.html#javascript-coffeescript-and-erb (section 2.3.3)

@yfxie
Copy link

yfxie commented Feb 18, 2022

Same problem here. app/asset/builds is an intermediate space for the output from webpack. Sprocket moves js and images to public/assets and add hash to the tail of filename. So even we remove hash added by webpack can't solve the problem.

For example, app/javascript/application.js import app/javascript/logo.svg(or other places) in JS way. After webpack building, we have app/assets/builds/application-abc.js and app/assets/builds/logo-abc.svg. Then sprocket produces public/assets/application-abc-123.js and public/assets/logo-abc-123.svg. We can resolve the application.js inside the public folder by javascript_include_tag "application". But the image lost due to the image filename inside the JS is logo-abc.svg rather than logo-abc-123.svg.

@ioev
Copy link

ioev commented Feb 25, 2022

Same general problem, but with rails 7.0 + propshaft + jsbundling + esbuild, specifically with react-leaflet. Using --loader:.png=file --public-path=/assets and am trying to override the paths as mentioned in PaulLeCam/react-leaflet#453 (comment) and the file ends up as assets/builds/marker-icon-2V3QKKVC.png, but rails can't find this file without it's own digest. I've also tried using --entry-names=[name]-[hash].digested on esbuild, but this doesn't appear to apply to the file loader.

Edit: Digging a little deeper into the esbuild config, I got this to work by using the asset-names parameter:
--loader:.png=file --public-path=/assets --asset-names=[name]-[hash].digested

@giedomak
Copy link

Propshaft looks for the specific pattern of -[digest].digested.js as the postfix to any asset file as an indication that the file has already been digested.

See: https://github.com/rails/propshaft#bypassing-the-digest-step

Looks like there is bug in the above 'bypassing the digest step' feature for both propshaft and sprockets.

This is the issue for sprockets:

This will fix it for sprockets:

It has already been fixed for propshaft:

@cirdes
Copy link
Author

cirdes commented Mar 13, 2022

@giedomak , thanks for pointing out this PRs. Hope rails/sprockets#726 could be merged soon.

@jon-sully
Copy link

jon-sully commented Mar 24, 2022

Wanted to comment back a solution to folks that are using JSB-Rails + Webpack 5 not ESBuild — specifically in my case how to setup Webpack to output the asset/resource files with the right name such that Propshaft doesn't re-stamp them with a new digest. This should work for folks using Sprockets instead of Propshaft even without rails/sprockets#726 merging (if I'm reading the diff correctly).

      {
        test: /\.(png|jpe?g|gif|eot|woff2|woff|ttf|svg)$/i,
        type: 'asset/resource',
        generator: {
          filename: '[name]-[hash].digested[ext][query]' // Setup asset filename so Propshaft doesn't re-digest
        }
      },

That helped fix things for us.

@cirdes
Copy link
Author

cirdes commented Mar 25, 2022

@jon-sully , It's working with JSB-Rails + Webpack 5!

I'm using webpack with this config and now sprockets are not re-stamping the assets.

Also you need sprockets >4.0.3 -> https://github.com/rails/sprockets/blob/master/CHANGELOG.md

 {
        test: /\.(png|jpe?g|gif|svg)$/i,
        use: [
          {
            loader: "file-loader",
            options: {
              name: "[name]-[hash].digested.[ext]",
            },
          },
        ],
      },

@jon-sully
Copy link

jon-sully commented Mar 25, 2022

Nice 👍 yeah same premise using file-loader instead of asset/resource (though I'd encourage the upgrade!!). Good stuff! 🤓

@cirdes
Copy link
Author

cirdes commented Mar 30, 2022

@richardkmiller, moving forward to JSB-Rails with Esbuild I had to point sprockets to rails/sprockets#726 and use --public-path=/assets

Images assets were working but not using my CDN to serve the assets. So I came with this solution: "--public-path=$CDN_URL_FULL/assets"

esbuild app/javascript/*.* --bundle --outdir=app/assets/builds --loader:.png=file --public-path=$CDN_URL_FULL/assets --asset-names=[name]-[hash].digested

I'm not sure if esbuild should generate assets string with cdn domain or if sprockets should somehow be responsible to rewrite urls with CND.

any thoughts about that?

@richardkmiller
Copy link
Contributor

@cirdes If it were my decision, I guess I might leave this as a post-install configuration step. For example, the README explains how to use --loader:.png=file but you have to add it manually. Likewise, the use of --public-path could be explained in the README for those who need it. If you agree, I'll create a PR for it, and I'll also add the leading slash as the default which I did not include in #58.

@andrewhavens
Copy link

I'm having a similar, but maybe slightly different problem. After upgrading from Rails 6 and switching from Webpack to esbuild, I can load images in development, but not after deploying to production (Heroku). In production, it looks like an additional fingerprint is added to the asset path.

remote:        $ node esbuild.config.js
remote:        Done in 2.79s.
remote:        I, [2022-06-15T20:31:18.764370 #688]  INFO -- : Writing /tmp/build_ec2e3794/public/assets/logo-Y5WNYHYB.digested-6f90874fa6e725ac79c88da3fc17b3dd7739c0a66c44586d8d247951fe8c06d6.png

My esbuild.config.js looks like this:

const path = require("path")
const vuePlugin = require("esbuild-vue")

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

@brenogazzola
Copy link
Contributor

@andrewhavens Are you on Sprockets? The digest skip feature os only available on master. There are no released versions with it yet.

@gsouf
Copy link

gsouf commented Jun 17, 2022

Also had a bunch of issue in rails 6 + sprocket + webpack with external files and code splitting due to the re-stamping . So we decided to bypass all the asset management of rails and relying only on webpack by outputting webpack directly to public:

output: {
        ....
        path: 'public/js'
    },

and stamping by ourselves the application.js. That might not be the rail's flavor, it gives us the control and flexibility we needed on webpack and it works very well for us and saves us a lot of headaches until a better solution arises

@tgaff
Copy link
Contributor

tgaff commented May 24, 2023

Edit: Digging a little deeper into the esbuild config, I got this to work by using the asset-names parameter: --loader:.png=file --public-path=/assets --asset-names=[name]-[hash].digested

Confirming that @ioev 's suggestion of --asset-names=[name]-[hash].digested works in production for a Sprockets + jsbundling + esbuild install.

@shepmaster
Copy link

A note for people hitting the same problem as me: Your asset name must have a dash before the digest.

The default value for file-loader's name option is [contenthash].[ext], so I changed it to [contenthash].digested.[ext]. However, sprockets uses a regex that looks for the hyphen.

After changing the option to [name]-[contenthash].digested.[ext] or asset-[contenthash].digested.[ext], the extra digest is no longer appended.

@dhh dhh closed this as completed in #158 Jun 18, 2023
MartinSugasti added a commit to MartinSugasti/simplest-resumes that referenced this issue Sep 6, 2023
Better explained in a later comment rails/jsbundling-rails#76

And I'm trying this solution rails/jsbundling-rails#76

I would have said the solution makes not only images, but also .js and .css lose the fingerprint functionality, but for whatever reason it's still fingerptinting everything except images..
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.