Parcel 15 ๐ฆ Static Assets โ Images, fonts & URL imports
Parcel handles static assets through the same dependency graph that it uses for JavaScript and CSS. When a file references an image, a font, or another binary asset, Parcel adds that asset to the graph, processes it through the appropriate transformer, and emits the result to the output directory with a content hash. The reference in the source file is rewritten to point at the emitted file. The alternative to the graph-managed asset is the public directory, which copies files as-is without processing or hashing.
This chapter covers the two mechanisms: the imported asset that enters the build pipeline and receives a hashed filename, and the public directory that bypasses the pipeline entirely. It also covers the image optimization query parameters that Parcel supports, the URL constructor pattern for dynamic references, and the url: prefix that forces URL treatment for non-standard file types.
Key point: An imported asset is processed and hashed. A public asset is copied as-is. The import is the default because it gives the bundler control over the output path and enables optimization. The public directory is for files that must keep their exact name or are never referenced from source code.
Why imported assets are the default
The dependency graph problem. Parcel builds an asset graph from every reference it finds: an import statement in JavaScript, a url() in CSS, and a src attribute in HTML . When the reference points to an image or a font, Parcel adds the file to the graph and processes it. The output file receives a content hash in its name, and the reference in the source is rewritten to the new path. This is the mechanism that makes the asset cacheable indefinitely while still updating when the file changes.
The URL import problem. In Parcel 2, importing a known file type such as an image from JavaScript returns a URL string. The Chinese documentation confirms this: “Other asset types will be exported as URLs to the output file in the JavaScript bundle so that you can reference them in your code” . The import statement import imageURL from './test.png' assigns the hashed output URL to imageURL, which can then be used as the src of an <img> element .
The public directory problem. A file in the public directory is copied to the output directory without processing, without hashing, and without being added to the dependency graph. It is referenced by an absolute path from the root. This is the mechanism for robots.txt, favicon.ico, and files that are referenced dynamically by a string that Parcel cannot statically analyze.
a. The image and font imports
An image or font is imported from JavaScript or referenced from CSS, and Parcel emits the file with a hashed name.
// JavaScript import
import logo from './images/logo.png'
const img = document.createElement('img')
img.src = logo
/* CSS reference */
.hero {
background-image: url('./images/hero.jpg');
}
The output directory contains logo.a1b2c3.png and hero.d4e5f6.jpg, and the references are rewritten to the hashed paths. The url: prefix is the explicit form for a file type that Parcel does not recognize by default, as the migration guide describes: “you may also choose to prefix the dependency specifier in an import statement with url:” .
Parcel also supports image optimization through query parameters. The documentation states that Parcel “supports resizing, converting, and optimizing images! Just pass query parameters for the format and size you need when referencing the image file” . A reference like ./image.jpg?as=webp&width=400 generates a WebP variant at 400 pixels wide, and the output is a new file that the browser can request.
b. The URL constructor and the dynamic reference
The new URL() constructor is the preferred pattern for referencing an asset with a path that is computed at runtime but still statically analyzable. The migration guide states that “the preferred approach for referencing URLs in JavaScript is to use the URL constructor” .
const url = new URL('./images/' + name + '.png', import.meta.url)
The constructor resolves the path relative to the current module, and Parcel treats the result as a URL dependency. The pattern is the recommended way to construct a URL for an asset whose filename is not a literal string, as long as the base path and the extension are statically analyzable.
The fs.readFileSync pattern is the alternative when the file contents must be inlined rather than referenced. The Chinese documentation shows the pattern: fs.readFileSync(__dirname + '/test.png') returns a Buffer, which can be converted to a base64 data URL . The path must be static; the documentation notes that the URL must be statically analyzable except for __dirname and __filename .
c. The public directory and the unreferenced asset
A file that is never referenced from source code is not discovered by the asset graph. The public directory is the mechanism for including it in the output. The file is copied to the root of the output directory, and it keeps its original name.
The Stack Overflow answers describe the community workarounds for Parcel 1 and 2. The parcel-plugin-static-files-copy plugin copies a static folder to the output . A package script that runs cp after the build achieves the same result . Neither is the built-in public directory mechanism, which is the supported path in Parcel 2.
The public directory is appropriate for a file that is referenced by a URL that Parcel cannot see: a path constructed in a template string that is not statically analyzable, a file that a third-party script requests by a fixed name, or a robots.txt that must be served at the root. The trade-off is the loss of content hashing and optimization.
Complete Example Session
// ============================================
// PART 1: THE IMAGE IMPORT
// ============================================
import logo from './images/logo.png'
const img = document.createElement('img')
img.src = logo
// logo is the hashed output URL.
/* ============================================
PART 2: THE CSS URL REFERENCE
============================================ */
.hero {
background-image: url('./images/hero.jpg');
}
/* The URL is rewritten to the hashed output. */
<!-- ============================================
PART 3: THE HTML IMAGE REFERENCE
============================================ -->
<img src="./images/banner.png" alt="Banner">
<!-- Parcel discovers the reference and processes the image. -->
// ============================================
// PART 4: THE IMAGE OPTIMIZATION
// ============================================
import heroWebp from './images/hero.jpg?as=webp&width=800'
// A WebP variant at 800px is generated.
// ============================================
// PART 5: THE URL CONSTRUCTOR
// ============================================
const url = new URL('./images/' + name + '.png', import.meta.url)
// The path is resolved relative to the module.
// ============================================
// PART 6: THE URL PREFIX
// ============================================
import fileUrl from 'url:./data.custom'
// Forces URL treatment for a non-standard file type.
// ============================================
// PART 7: THE INLINE READ
// ============================================
import fs from 'fs'
const buffer = fs.readFileSync(__dirname + '/test.png')
const dataUrl = `data:image/png;base64,${buffer.toString('base64')}`
// The file is inlined as a data URL.
// ============================================
// PART 8: THE PUBLIC DIRECTORY
// ============================================
public/robots.txt โ dist/robots.txt
public/favicon.ico โ dist/favicon.ico
// Copied as-is, no hash.
// ============================================
// PART 9: THE OUTPUT
// ============================================
dist/logo.a1b2c3.png
dist/hero.d4e5f6.jpg
dist/robots.txt
// Imported assets are hashed.
// Public assets keep their names.
// ============================================
// PART 10: THE DECISION
// ============================================
// Referenced from source? โ import
// Must keep exact name? โ public
// Dynamic path? โ URL constructor
The ten parts covered the image import, the CSS reference, the HTML reference, the optimization query, the URL constructor, the url: prefix, the inline read, the public directory, the output, and the decision.
Quick Reference
Asset mechanisms
| Mechanism | Processing | Filename | Use case |
|---|---|---|---|
| Import | Yes (hash, optimize) | Hashed | Referenced from source |
url: prefix | Yes | Hashed | Non-standard file type |
new URL() | Yes | Hashed | Dynamic but analyzable path |
public/ | No | Original | Fixed name, unreferenced |
Image optimization query
| Query | Effect |
|---|---|
?as=webp | Convert to WebP |
?width=400 | Resize to 400px wide |
?as=webp&width=800 | Convert and resize |
The import patterns
| Pattern | Returns |
|---|---|
import img from './x.png' | Hashed URL string |
new URL('./x.png', import.meta.url) | URL object |
import x from 'url:./file.ext' | Hashed URL string |
fs.readFileSync(__dirname + '/x.png') | Buffer |
The public directory
| Item | Behavior |
|---|---|
| Location | public/ at project root |
| Output | Copied to dist/ root |
| Hashing | None |
| Processing | None |
Best Practices
โ Do This:
// Import images from JavaScript
import logo from './images/logo.png' // โ
// Use the URL constructor for dynamic paths
new URL('./images/' + name + '.png', import.meta.url) // โ
/* Reference images from CSS */
background: url('./images/hero.jpg'); // โ
// Use public/ for fixed-name files
public/robots.txt // โ
โ Don’t Do This:
// Use a string path without the URL constructor
const url = './images/' + name + '.png' // โ (not processed)
// Import an image without the url: prefix for unknown types
import file from './data.custom' // โ (no loader)
// Put a component image in public/ without hashing
public/logo.png // โ (no cache busting)
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Dynamic image path is not processed | String concatenation is not analyzable | Use new URL() |
| Asset is not in the output | Not referenced from source | Put it in public/ |
| Image is not optimized | No query parameters | Add ?as=webp&width=... |
url: import fails | The file type has no default transformer | Check the file type |
| Public asset is stale | No content hash | Rename or import it |
Real-World Examples
1. Image import
import logo from './images/logo.png'
2. CSS reference
background: url('./images/hero.jpg');
3. HTML reference
<img src="./images/banner.png" alt="Banner">
4. Optimization query
import hero from './images/hero.jpg?as=webp&width=800'
5. URL constructor
const url = new URL('./images/' + name + '.png', import.meta.url)
6. URL prefix
import file from 'url:./data.custom'
7. Inline read
const buffer = fs.readFileSync(__dirname + '/test.png')
8. Public directory
public/robots.txt โ dist/robots.txt
9. The output
dist/logo.a1b2c3.png
10. The decision
Referenced? โ import; Fixed name? โ public.
Visual
The asset graph
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ app.js โ
โ import logo from './logo.png' โ
โ โ โ
โ โผ โ
โ Asset graph โ
โ โโโ logo.png โโโบ hashed output โ
โ โโโ app.js โโโบ bundle โ
โ โ
โ The reference is rewritten to the hashed URL. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The public directory
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ public/robots.txt โ
โ โ โ
โ โผ โ
โ Copied as-is to dist/robots.txt โ
โ โ
โ No hash, no processing, no graph entry. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The URL constructor
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ new URL('./images/' + name + '.png', import.meta.url) โ
โ โ โ
โ โผ โ
โ Parcel resolves the path relative to the module โ
โ โ โ
โ โผ โ
โ The asset is added to the graph and hashed โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The image optimization
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ './hero.jpg?as=webp&width=800' โ
โ โ โ
โ โผ โ
โ Parcel generates a WebP variant at 800px โ
โ โ โ
โ โผ โ
โ dist/hero.a1b2c3.webp โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Imported asset | Processed, hashed, added to graph |
| Public asset | Copied as-is, no hash |
| Image optimization | Query parameters (?as=, ?width=) |
| Dynamic path | new URL() constructor |
| Non-standard type | url: prefix |
| Inline read | fs.readFileSync |
| Default output | dist/ |
Key takeaways:
- An imported asset is processed and hashed, and the reference is rewritten. Parcel adds the file to the dependency graph, emits it with a content hash, and updates the reference in the source. The hashed filename allows the browser to cache the asset indefinitely .
- The
new URL()constructor is the preferred pattern for dynamic references. It resolves the path relative to the current module and keeps the reference statically analyzable, which allows Parcel to process the asset . - The
url:prefix forces URL treatment for non-standard file types. A file type that Parcel does not recognize by default can be imported with the prefix, and the import returns the hashed output URL . - Image optimization is available through query parameters. A reference like
?as=webp&width=400generates a converted and resized variant, and the output is a new file that the browser can request . - The
publicdirectory bypasses the pipeline. A file inpublic/is copied to the output root without hashing or processing. It is the mechanism for files that must keep their exact name or are never referenced from source code. - The
fs.readFileSyncpattern inlines the file contents. It returns aBufferthat can be converted to a base64 data URL. The path must be statically analyzable except for__dirnameand__filename.
Remember: Parcel’s static asset handling is a choice between the graph and the public directory. The import, the CSS url(), the HTML src, the new URL() constructor, and the url: prefix all add the file to the graph, which means it is processed, hashed, and referenced by the output path. The public directory copies the file as-is, which is the right choice only when the file must keep its exact name or is referenced by a string that Parcel cannot see. The image optimization query parameters are the bonus of the graph: a single source image can produce multiple optimized variants for different browsers and devices.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!