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.