| | |

Parcel 15 ๐Ÿ“ฆ Static Assets โ€” Images, fonts & URL imports

โฑ๏ธ Estimated read: 11 min | |

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

MechanismProcessingFilenameUse case
ImportYes (hash, optimize)HashedReferenced from source
url: prefixYesHashedNon-standard file type
new URL()YesHashedDynamic but analyzable path
public/NoOriginalFixed name, unreferenced

Image optimization query

QueryEffect
?as=webpConvert to WebP
?width=400Resize to 400px wide
?as=webp&width=800Convert and resize

The import patterns

PatternReturns
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

ItemBehavior
Locationpublic/ at project root
OutputCopied to dist/ root
HashingNone
ProcessingNone

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

PitfallWhy It HappensFix
Dynamic image path is not processedString concatenation is not analyzableUse new URL()
Asset is not in the outputNot referenced from sourcePut it in public/
Image is not optimizedNo query parametersAdd ?as=webp&width=...
url: import failsThe file type has no default transformerCheck the file type
Public asset is staleNo content hashRename 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

ItemValue
Imported assetProcessed, hashed, added to graph
Public assetCopied as-is, no hash
Image optimizationQuery parameters (?as=, ?width=)
Dynamic pathnew URL() constructor
Non-standard typeurl: prefix
Inline readfs.readFileSync
Default outputdist/

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=400 generates a converted and resized variant, and the output is a new file that the browser can request .
  • The public directory bypasses the pipeline. A file in public/ 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.readFileSync pattern inlines the file contents. It returns a Buffer that can be converted to a base64 data URL. The path must be statically analyzable except for __dirname and __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.

Was this helpful?


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!