webpacker: Webpacker fails to find image asset in a subdirectory

Using webpacker 6.0.0.pre.2 and webpack 5.26.1, rails view cannot find paths for image asset.

Here is the haml line that fails in my view:

= image_pack_tag('flags/fr.svg', class: 'flag rounded-circle me-1')

I’m getting the Webpacker::Manifest::MissingEntryError exception. The image is located in app/webpack/images/flags/fr.svg. The directory structure looks like:

app/webpack/
├── images
│   ├── favicon.ico
│   └── flags
│       ├── en.svg
│       ├── fr.svg
│       └── zh.svg
├── packs
│   ├── application.js
│   └── application.scss
├── src/...
└── stylesheets/...

webpacker.yml contains:

default: &default
  source_path: app/webpack
  source_entry_path: packs
  public_root_path: public
  public_output_path: packs
  cache_path: tmp/cache/webpacker
  check_yarn_integrity: false
  webpack_compile_output: true

  resolved_paths: ["app/webpack/images"]

It seems that the problem lies with the manifest generation. The asset path should be flags/fr.svg relative to app/webpack/images but the flags/ path prefix seems to be missing. The manifest contains:

{
  "application.css": "/packs/css/application.css",
  "application.js": "/packs/js/application-725aea22742bad3243cc.js",
  "css/application.css.map": "/packs/css/application.css.map",
  "entrypoints": {
    "application": {
      "assets": {
        "js": [
          "/packs/js/runtime-089dda7adff9a5688a94.js",
          "/packs/js/vendors-node_modules_hotwired_turbo-rails_app_javascript_turbo_index_js-node_modules_rails_ac-f25bc2-cef2fc19854103ad4fbc.js",
          "/packs/js/application-725aea22742bad3243cc.js"
        ],
        "css": [
          "/packs/css/application.css"
        ]
      }
    }
  },
  "js/application-725aea22742bad3243cc.js.map": "/packs/js/application-725aea22742bad3243cc.js.map",
  "js/runtime-089dda7adff9a5688a94.js.map": "/packs/js/runtime-089dda7adff9a5688a94.js.map",
  "js/vendors-node_modules_hotwired_turbo-rails_app_javascript_turbo_index_js-node_modules_rails_ac-f25bc2-cef2fc19854103ad4fbc.js": "/packs/js/vendors-node_modules_hotwired_turbo-rails_app_javascript_turbo_index_js-node_modules_rails_ac-f25bc2-cef2fc19854103ad4fbc.js",
  "js/vendors-node_modules_hotwired_turbo-rails_app_javascript_turbo_index_js-node_modules_rails_ac-f25bc2-cef2fc19854103ad4fbc.js.map": "/packs/js/vendors-node_modules_hotwired_turbo-rails_app_javascript_turbo_index_js-node_modules_rails_ac-f25bc2-cef2fc19854103ad4fbc.js.map",
  "js/vendors-node_modules_rails_actioncable_src_index_js-145d9f25b4424ce0cea9.chunk.js": "/packs/js/vendors-node_modules_rails_actioncable_src_index_js-145d9f25b4424ce0cea9.chunk.js",
  "js/vendors-node_modules_rails_actioncable_src_index_js-145d9f25b4424ce0cea9.chunk.js.map": "/packs/js/vendors-node_modules_rails_actioncable_src_index_js-145d9f25b4424ce0cea9.chunk.js.map",
  "media/images/en.svg": "/packs/media/images/601fdab623a59f2a2c9a.svg",
  "media/images/favicon.ico": "/packs/media/images/5c5c455f9d579ead5efe.ico",
  "media/images/fr.svg": "/packs/media/images/c88df3297cffe49852ae.svg",
  "media/images/zh.svg": "/packs/media/images/f5a6f048eb8367343bd4.svg",
  "runtime.js": "/packs/js/runtime-089dda7adff9a5688a94.js"
}

Perhaps this is a misconfiguration or a bug of webpack-assets-manifest in https://github.com/rails/webpacker/blob/45cc2a2690964a916a4a64ddf626b70525c91e6f/package/environments/base.js#L57-L63 but I’m not sure

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 9
  • Comments: 22 (2 by maintainers)

Most upvoted comments

I’m not much of a javascript programmer so there is probably a better way to do this, but this is my current work around in case it helps anyone. Basically just restoring the file-loader rule to that of version 5.2.1.

//config/webpack/base.js

const { webpackConfig, rules } = require('@rails/webpacker')

const fileIndex = rules.findIndex(r => r.type === 'asset/resource')
if (fileIndex !== -1) {
  rules[fileIndex] = {
    test: [
      /\.bmp$/,
      /\.gif$/,
      /\.jpe?g$/,
      /\.png$/,
      /\.tiff$/,
      /\.ico$/,
      /\.avif$/,
      /\.webp$/,
      /\.eot$/,
      /\.otf$/,
      /\.ttf$/,
      /\.woff$/,
      /\.woff2$/,
      /\.svg$/
    ],
    exclude: [/\.(js|mjs|jsx|ts|tsx)$/],
    use: [
      {
        loader: 'file-loader',
        options: {
          name(file) {
            if (file.includes('app/packs')) {
              return 'media/[path][name]-[hash].[ext]'
            }
            return 'media/[folder]/[name]-[hash:8].[ext]'
          },
          context: 'app/packs'
        },
      },
    ],
  }
}
module.exports = webpackConfig

I faced the same issue and got it working with webpack’s built-in asset handling (v5), the solution is the contextRelativeKeys flag, which must be set to true and also setting the context path.

Here’s my setup:

Versions:

webpack-assets-manifest@5.0.6
@rails/webpacker@6.0.0-beta.7
webpack@5.39.0

Folder structure:

app/
  webpacker/
     fonts/
     images/
       icons/
     packs/
     styles/
     scripts/

app/config/webpack/base.js:

const path = require('path')
const { webpackConfig, merge } = require('@rails/webpacker')
const WebpackAssetsManifest = require('webpack-assets-manifest')

const manifestPlugin = webpackConfig.plugins.find(r => r instanceof WebpackAssetsManifest)
if (manifestPlugin) {
  manifestPlugin.options.contextRelativeKeys = true // <-- important!
}

const customConfig = {
  context: path.resolve(__dirname, '../../app/webpacker'), // <-- Adjust to your folder structure!
  // ... omitted rest of config
}

module.exports = merge(webpackConfig, customConfig)

With this I get the correct subdirectories in my manifest.json:

"application.js": "/packs/js/application-6f017e22569258b8eb93.js",
"fonts/ObjektivMk2_W_Bd.eot": "/packs/media/images/bd920ee6b47f6c02808a.eot",
"images/icons/notice-alert.svg": "/packs/media/images/782e9f7b586c7677dd9f.svg"
...

Thanks @sled! For anyone else following this and wants to revert to the webpacker-5 mainfest structure you will also need to also use WebpackAssetManifest’s customize option to change the keys

const path = require('path')
const { webpackConfig, merge } = require('@rails/webpacker')
const WebpackAssetsManifest = require('webpack-assets-manifest')

const manifestPlugin =
  webpackConfig.plugins.find(r => r instanceof WebpackAssetsManifest)
if (manifestPlugin) {
  manifestPlugin.options.contextRelativeKeys = true;
  manifestPlugin.options.customize = function(entry) {
    if (entry.key.startsWith('images/')) {
      return {
        key: entry.key.replace(/^images/, 'media/images'),
        value: entry.value
      };
    }
    if (entry.key.startsWith('fonts/')) {
      return {
        key: entry.key.replace(/^fonts/, 'media/fonts'),
        value: entry.value
      };
    }

    return entry;
  };
}

const customConfig = {
  context: path.resolve(__dirname, '../../app/packs'), // Default webpacker setup, old setup was app/javascripts, this is the value from webpacker.yml "source_path"
  // ...rest of your customConfig goes here
}

module.exports = merge(webpackConfig, customConfig)

Still seeing nested directories get dropped.

I don’t think this change has been proposed as PR yet, I also ran into a different problem, if you have an asset file twice with identical content hash, only one will land in the output, i.e:

app/
  packs/
    images/
       one/a.jpg
       two/a.jpg

manifest.json

  "media/images/one/a.jpg": "/packs/media/images/c88df3297cffe49852ae.jpg",

which means if you have something like image_pack_tag('two/a.jpg') in your views it will crash because it’s missing in the manifest.

Ran into the same problem.

Still seeing nested directories get dropped.

I don’t think this change has been proposed as PR yet, I also ran into a different problem, if you have an asset file twice with identical content hash, only one will land in the output, i.e:

app/
  packs/
    images/
       one/a.jpg
       two/a.jpg

manifest.json

  "media/images/one/a.jpg": "/packs/media/images/c88df3297cffe49852ae.jpg",

which means if you have something like image_pack_tag('two/a.jpg') in your views it will crash because it’s missing in the manifest.

Still seeing nested directories get dropped. For example:

app
  /packs
    /images
      /some_dir
        something.png

Ends up as static/something.png in the manifest. This worked but would love to remove 😃

I would expect static/some_dir/something.png. The spelunking above seems correct to me after having a similar look. Seem unclear to me if change was intentional?

webpacker: 6.0.0-rc.5 webpack: 5.51.1

I’m having the same issue. Looks like this was an issue before, and it was fixed in https://github.com/rails/webpacker/pull/1964 (and https://github.com/rails/webpacker/pull/1973 I guess). The way the rules are set up seems to have significantly changed in 6.x for webpack 5 compatbility, so https://github.com/rails/webpacker/pull/2802 has I think “broken” these original fixes. I believe the change that causes this problem is here.