Search code examples
javascriptvue.jswebpackvue-routerwebpack-4

Workaround for webpack require.context() using dynamic directories in Vue App


I have a Vue app, and it contains a route with multiple image galleries. A user can click on one of these galleries to be taken to a dynamic component that will display the images that belong to the specific gallery they're looking at.

For example, the user sees 2 cards, foo and bar. Clicking on them will take the user to /gallery/foo or /gallery/bar. My assets folder is organized like this

src/assets/images
├── foo
│   └── 1.jpg
└── bar
    └── 2.jpg

So every gallery has a corresponding folder with all the images in it. I have no access to any kind of DB or APIs here, this project is basically a static website and calling any kind of external resource is outside the scope of this project.

I tried using Webpack's require.context() here, but it doesn't work with a dynamic path like I'm trying to do, as can be seen in Webpack Issue #4772.

The relevant code that I tried

data() {
  return {
    images: null
  }
),
mounted() {
    this.populatePage();
  }
},
methods: {
  populatePage() {
    const imagePath = `@/assets/images/${this.$route.params.id}/`;
    this.images = this.importAll(require.context(imagePath, true, /\.(jpe?g)$/));
  },
  importAll(r) {
     return r.keys().map(r);
  }
}

This obviously doesn't work as demonstrated in the Github issue due to me using the imagePath in my require.context(). If I just do @/assets/images/, it works fine, but the problem is that it will import every single jpg in the images directory, which is obviously not what I want.

What's the best way to get the desired effect needed here? Preferably I'd want to avoid loading any images that don't belong to the page the user is on, as there can potentially be hundreds of them, and even though they're optimized as much as possible, they can still be quite large (~150kb each as these have to be a very high resolution)

Initially I tried doing some variation of just importing everything and then maybe regexing the folder name from the chunk that webpack generates, but webpack will ommit the directory and add a different hash to the end of the filename instead. Is there perhaps a way to make webpack append a directory to a filename in Vue, or really in general?

/img/1.2f159e72.jpg
/img/2.2c1da0da.jpg

Solution

  • Webpack is highly configurable. You can set it up to retain directory structure - see file-loader documentation

    webpack.config.js

    module.exports = {
      module: {
        rules: [
          {
            test: /\.(png|jpe?g|gif)$/i,
            loader: 'file-loader',
            options: {
              name: '[path][name].[ext]',
            },
          },
        ],
      },
    };
    

    Then you can easily use your original approach importing whole root dir (with gallery directories) and filter only files from the gallery you want. Don't be afraid of importing everything - file-loader doesn't "load" the file over the wire, it just returns URL of the file (during the build). In order to load the file to the browser, you need to use it inside the img tag (for example)