Why must images be imported for Webpack?

Understanding your build system

As you migrate from legacy, vanilla code systems towards advanced build systems and single-page applications, you may notice some changes that don’t make immediate sense. There are additional steps that are a bit unintuitive, and it’s not clear what problem is being solved. One such difference is that <img src="./file.gif" /> no longer works. You now have to use import image from './file.gif';. Why?

With a build system like Webpack, your application is no longer running in the same directory as your HTML, e.g. your src directory. It goes through a "build" process that first optimizes the files before outputting the final result into a different directory. Webpack typically starts with your index.js or index.ts file. It concatenates and minifies every JavaScript file it finds to create a single, gigantic, optimized JavaScript file that contains all of your code. That single JS file is inserted into your index.html, and that index.html file is what you see in your browser. This finalized application runs from your build directory in a production build or from memory in a development build, not from your src directory. For a React application, your build directory, aside from the CSS and JavaScript files that Webpack generates, will look about the same as your public directory.

If Webpack never sees your image file, it won’t put it into the build directory. For all it knows, it’s just an unused file, so it shouldn’t be put into the final output. Similarly, if you create a my-file.js in your src directory but you never use it anywhere, Webpack won't include it in the final bundle. This is a good thing! It would be a waste of space!

Webpack does not scan the src directory for files. Webpack starts at an entry point (index.js) and follows import and require statements to find files to bundle. When you import the image, Webpack now knows that you are using it. Webpack now “sees” that image.

A custom image loader is used to import images. This is different than when you import a JavaScript file. When you import a JavaScript file, the JavaScript loader says to include all that JavaScript right here, right now. When you import an image file, the image loader says to add the image to the final build (what you wouldn’t be doing with <img src="..." />) and return a string with the URL of the image. You just tell it “I want blank.gif” and it says “Okay, I’ll move this blank.gif file to the build directory, and here is the path in the build directory: /blank.some-unique-hashed-filename.gif”. You use that file path as your image’s src attribute, and the browser will be able to access that file — because your browser is looking for files in the build directory, not in src!

The unique hash added to the image URL solves two problems. First, it prevents image.gif from src/a/image.gif from being overwritten by src/b/image.gif. Second, it prevents the customer from seeing an outdated cache when you update the image. If the customer views and permanently caches image.gif that was bundled from src/a/image.gif, then you as a developer update that image file, the customer still sees their permanently cached version. Permanent cache is a great tool to improve the loading speed and customer experience of your application. Rather than disabling permanent cache, the better alternative is to provide and change a unique hash whenever the source image file changes. Each time you build your image.gif file, you will generate image.unique-hash-1.gif. Once you change that source image.gif file, it will become image.unique-hash-2.gif, bypassing the customer’s cache of image.unique-hash-1.gif and providing them the updated image.

You can likely get similar behavior by putting your static (unchanging, cache-friendly) files in the public directory. The public directory gets copied over to the build directory, almost verbatim. The only exception is that index.html gets overridden to include the aforementioned JavaScript bundle. If you put blank.gif in your public directory, you should be able to reference it as just blank.gif, because — as it is copied to the build directory — you will be able to find it in the build directory. This is in contrast to files in src which are only copied to the build directory if Webpack finds them through the entry point file (index.js). All other things in src are ignored.

Conclusion 🔚

If you have any questions or great insight into build processes, please leave them in the comments below.

To read more of my columns, you may follow me on LinkedIn and Twitter, or check out my portfolio on CharlesStover.com.

Senior front end engineer / charlesstover.com

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store