Parcel 27 ๐ฆ Migration from Parcel 1 โ Package rename, cache location & code changes
The migration from Parcel 1 to Parcel 2 is not a configuration update. It is a change in the package identity, the caching model, and the default transpilation behavior. The old package is parcel-bundler, and the new one is parcel. The old cache directory is .cache, and the new one is .parcel-cache. The old bundler transpiled JavaScript by default, and the new one does not. Each of these changes produces a failure that points somewhere other than the actual cause: the package rename produces a “command not found,” the cache rename produces a stale-build mystery, and the transpilation change produces a syntax error in an older browser that worked before the upgrade.
The migration guide lists the changes in three categories. The first is the mechanical update: the dependency, the scripts, and the .gitignore. The second is the code changes: the <script type="module"> requirement, the url: prefix for non-code assets, and the import.meta.url pattern for workers. The third is the configuration changes: the --target flag, the --out-dir rename, and the main field inference. The three categories are independent, and a project that skips one produces a different class of error.
Key point: The package name changed from parcel-bundler to parcel. The cache directory changed from .cache to .parcel-cache. Parcel 2 does not transpile JavaScript by default; the browserslist field in package.json enables it. The <script type="module"> tag is required for module scripts, and the main field in package.json is inferred as the library output path unless it is explicitly disabled.
Why the migration fails at the mechanical changes
The package rename problem. The dependency is parcel-bundler in Parcel 1 and parcel in Parcel 2. A project that updates the version without changing the name installs the old package, and the parcel command in the scripts section resolves to nothing. The error is a “command not found” or a “parcel is not recognized,” which points to the script rather than the dependency name. The fix is a two-step update: yarn remove parcel-bundler and yarn add parcel --dev.
The cache location problem. Parcel 1 writes its cache to .cache. Parcel 2 writes it to .parcel-cache. A project that upgrades without updating the .gitignore commits the new cache directory, and a project that deletes the old .cache directory does not delete the new one. The stale cache is the source of the “it works on my machine but not in CI” failures, because the CI environment starts with no cache and the developer’s environment starts with a cache that the new version may not invalidate correctly. The fix is to add .parcel-cache to .gitignore and to delete the old .cache directory.
The transpilation problem. Parcel 1 transpiled JavaScript to a default browser set automatically. Parcel 2 does not transpile by default. A project that used modern syntaxโoptional chaining, nullish coalescing, class fieldsโand supported older browsers in Parcel 1 will output the modern syntax in Parcel 2, and the older browsers will fail with a syntax error. The fix is to add a browserslist field to package.json that matches the targets the project supported before the upgrade.
The trade-off. The mechanical changes are small and fast. The code changes are larger and slower, because they touch the source files rather than the configuration. The <script type="module"> change is a one-line edit in the HTML, but the url: prefix and the import.meta.url pattern are code-level changes that may require a search-and-replace across the project. The migration is a sequence of small fixes, and each fix is independent.
a. The mechanical changes and the package setup
The first step is the dependency update. The old package is removed and the new one is installed. The scripts section is updated to use the parcel command.
# Remove the old package and install the new one.
yarn remove parcel-bundler
yarn add parcel --dev
// package.json โ the updated scripts
{
"scripts": {
"start": "parcel index.html",
"build": "parcel build index.html"
},
"devDependencies": {
"parcel": "^2.0.0"
}
}
The .gitignore update is the second mechanical step. The old .cache directory is replaced by the new .parcel-cache directory.
# .gitignore โ the Parcel 2 cache
.parcel-cache
dist
The dist directory is the output directory. Parcel 1 used dist as the default, and Parcel 2 uses dist as the default as well, but the --out-dir flag was renamed to --dist-dir. A project that passes --out-dir in a script will see an “unknown option” error.
b. The code changes and the configuration changes
The <script type="module"> change is the most common code-level failure. Parcel 1 treated a <script> tag as a module script, and imports and exports worked inside it. Parcel 2 matches the browser’s behavior: a classic <script> does not support imports or exports, and a module script requires the type="module" attribute.
<!-- Parcel 1: imports worked in a classic script -->
<script src="./index.js"></script>
<!-- Parcel 2: a module script requires type="module" -->
<script type="module" src="./index.js"></script>
The url: prefix is the second code-level change. Parcel 1 returned a URL for any imported non-JavaScript file. Parcel 2 returns a URL for known file typesโimages, fontsโbut an unknown file type requires the url: prefix or the new URL() constructor.
// Parcel 1: any import returned a URL
import fileUrl from './file.custom';
// Parcel 2: the url: prefix returns a URL
import fileUrl from 'url:./file.custom';
// Parcel 2: the URL constructor is the preferred approach
const fileUrl = new URL('./file.custom', import.meta.url);
The main field inference is the configuration change that produces the most surprising failure. Parcel 2 reads the main field in package.json and treats the project as a library if the field is present. A web application that has a main field from npm init will have its output written to the path in main, which is often index.js at the project root. The fix is to remove the main field or to set "targets": { "main": false }.
// package.json โ disabling the main field inference
{
"targets": {
"main": false
}
}
The --target flag is replaced by the engines field in package.json. A Node.js target is set with "engines": { "node": ">=18" }, and an Electron target is set with an electron key. The --experimental-scope-hoisting flag is replaced by the default behavior; scope hoisting is enabled by default, and the --no-scope-hoist flag disables it.
Complete Example Session
// ============================================
// PART 1: THE PACKAGE RENAME
// ============================================
// The old dependency:
// "parcel-bundler": "^1.0.0"
// The new dependency:
// "parcel": "^2.0.0"
// yarn remove parcel-bundler
// yarn add parcel --dev
// ============================================
// PART 2: THE CACHE LOCATION
// ============================================
// .gitignore
// .cache โ remove
// .parcel-cache โ add
// dist
// The old cache directory is .cache.
// The new cache directory is .parcel-cache.
// ============================================
// PART 3: THE BROWSERSLIST
// ============================================
// package.json
{
"browserslist": [
"> 0.5%",
"last 2 versions",
"not dead"
]
}
// Without the browserslist field, Parcel 2 outputs
// the modern syntax without transpilation.
// The field enables the transpilation.
// ============================================
// PART 4: THE SCRIPT MODULE
// ============================================
// Parcel 1: <script src="./index.js"></script>
// Parcel 2: <script type="module" src="./index.js"></script>
// The type="module" attribute is required for imports.
// ============================================
// PART 5: THE URL PREFIX
// ============================================
// Parcel 1: import fileUrl from './file.custom';
// Parcel 2: import fileUrl from 'url:./file.custom';
// The url: prefix returns a URL for a file type
// that Parcel does not handle by default.
// ============================================
// PART 6: THE MAIN FIELD
// ============================================
// package.json โ the main field inference
{
"main": "index.js",
"targets": {
"main": false
}
}
// The main field is inferred as the library output.
// The targets.main: false disables the inference.
// ============================================
// PART 7: THE TARGET FLAG
// ============================================
// Parcel 1: parcel build index.html --target node
// Parcel 2: the engines field in package.json
{
"engines": {
"node": ">=18"
}
}
// The --target flag is replaced by the engines field.
// ============================================
// PART 8: THE OUT DIR RENAME
// ============================================
// Parcel 1: parcel build index.html --out-dir dist
// Parcel 2: parcel build index.html --dist-dir dist
// The flag was renamed.
// ============================================
// PART 9: THE SCRIPT MODULE IN HTML
// ============================================
// The HTML file:
<!DOCTYPE html>
<html>
<body>
<script type="module" src="./index.js"></script>
</body>
</html>
// The type="module" attribute is the migration change.
// ============================================
// PART 10: THE CHECKLIST
// ============================================
// [ ] The dependency is parcel, not parcel-bundler.
// [ ] The .gitignore has .parcel-cache, not .cache.
// [ ] The browserslist field is set in package.json.
// [ ] The <script> tag has type="module".
// [ ] The non-code imports use url: or new URL().
// [ ] The main field is removed or targets.main is false.
// [ ] The --out-dir flag is renamed to --dist-dir.
// [ ] The --target flag is replaced by the engines field.
The ten parts covered the package rename, the cache location, the browserslist, the script module, the URL prefix, the main field, the target flag, the out-dir rename, the HTML change, and the checklist. The through-line is that each change is small, and the failures are specific to the change that was skipped.
Quick Reference
The mechanical changes
| Parcel 1 | Parcel 2 |
|---|---|
parcel-bundler | parcel |
.cache | .parcel-cache |
--out-dir | --dist-dir |
--out-file | Removed (use package.json) |
--target | engines field |
The code changes
| Parcel 1 | Parcel 2 |
|---|---|
<script> supports imports | <script type="module"> required |
| Any import returns URL | url: prefix or new URL() |
| Automatic transpilation | browserslist required |
main field ignored | main field inferred as output |
The configuration changes
| Option | Parcel 1 | Parcel 2 |
|---|---|---|
| Scope hoisting | --experimental-scope-hoisting | Enabled by default |
| Node modules bundling | --bundle-node-modules | includeNodeModules in targets |
| Log level | Numbers | Names (none, error, warn, info, verbose) |
Best Practices
โ Do This:
# Use the new package name
yarn add parcel --dev # โ
// Set the browserslist field
{ "browserslist": ["> 0.5%", "last 2 versions", "not dead"] } // โ
<!-- Use type="module" for the script -->
<script type="module" src="./index.js"></script> // โ
โ Don’t Do This:
# Use the old package name
yarn add parcel-bundler --dev // installs Parcel 1 // โ
// Import an unknown file type without the url: prefix
import fileUrl from './file.custom'; // may fail in Parcel 2 // โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
The parcel command is not found | The old package is installed | Install parcel, not parcel-bundler |
| The cache is stale after the upgrade | The old .cache is not deleted | Delete .cache and .parcel-cache |
| The build outputs to the wrong path | The main field is inferred | Remove main or set targets.main: false |
| Older browsers fail on modern syntax | No browserslist | Add the browserslist field |
| Imports fail in a classic script | The <script> has no type="module" | Add the attribute |
Real-World Examples
1. The package rename
yarn remove parcel-bundler && yarn add parcel --dev
2. The cache rename
.parcel-cache
3. The browserslist
{ "browserslist": ["> 0.5%", "last 2 versions"] }
4. The script module
<script type="module" src="./index.js"></script>
5. The url prefix
import fileUrl from 'url:./file.custom';
6. The URL constructor
const fileUrl = new URL('./file.custom', import.meta.url);
7. The main field disable
{ "targets": { "main": false } }
8. The engines field
{ "engines": { "node": ">=18" } }
9. The out-dir rename
parcel build index.html --dist-dir dist
10. The cache delete
rm -rf .cache .parcel-cache dist
Visual
The Migration Failures
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ EACH CHANGE PRODUCES A DIFFERENT FAILURE โ
โ โ
โ Package rename โโโถ "parcel: command not found" โ
โ Cache rename โโโโโถ stale build, "works on my machine" โ
โ Transpilation โโโโถ syntax error in older browsers โ
โ Script module โโโโถ "Cannot use import statement" โ
โ Main field โโโโโโโถ output written to the wrong path โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The Three Categories
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ MECHANICAL, CODE, AND CONFIGURATION โ
โ โ
โ Mechanical: โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Package name โ parcel-bundler โ parcel โ
โ โ Cache location โ .cache โ .parcel-cache โ
โ โ .gitignore โ add .parcel-cache โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Code: โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Script module โ type="module" โ
โ โ URL prefix โ url: or new URL() โ
โ โ Worker pattern โ import.meta.url โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Configuration: โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Main field โ targets.main: false โ
โ โ Target flag โ engines field โ
โ โ Out-dir flag โ --dist-dir โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The Transpilation Change
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ PARCEL 1 TRANSPILES, PARCEL 2 DOES NOT โ
โ โ
โ Parcel 1: โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Source: modern JS โ โ
โ โ Output: transpiled โ The default browser set โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Parcel 2 without browserslist: โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Source: modern JS โ โ
โ โ Output: modern JS โ No transpilation โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Parcel 2 with browserslist: โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Source: modern JS โ โ
โ โ Output: transpiled โ The browserslist targets โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Package rename | parcel-bundler โ parcel |
| Cache rename | .cache โ .parcel-cache |
| Transpilation | Disabled by default; browserslist enables it |
| Script module | <script type="module"> required |
| URL prefix | url: or new URL() for non-code assets |
main field | Inferred as library output; targets.main: false disables |
--out-dir | Renamed to --dist-dir |
--target | Replaced by engines field |
Key takeaways:
- The package rename is the first step. The dependency is
parcel, notparcel-bundler. Theparcelcommand resolves from the new package, and the old package is removed. The cache directory is.parcel-cache, not.cache. - Parcel 2 does not transpile by default. The
browserslistfield inpackage.jsonenables the transpilation. A project that supported older browsers in Parcel 1 must set the field to the same targets, or the output will contain modern syntax that the older browsers cannot parse. - The
<script type="module">tag is required for imports. Parcel 2 matches the browser’s behavior: a classic script does not support imports or exports. Thetype="module"attribute is the fix. - The
mainfield is inferred as the library output. A web application that has amainfield will have its output written to that path. The fix is to remove the field or to set"targets": { "main": false }. - The
--out-dirflag was renamed to--dist-dir. The--out-fileflag was removed, and the output path is specified inpackage.json. The--targetflag was replaced by theenginesfield.
Remember: The Parcel 1 to Parcel 2 migration is a sequence of small, independent fixes. The package rename, the cache rename, and the .gitignore update are the mechanical changes. The <script type="module"> tag, the url: prefix, and the import.meta.url pattern are the code changes. The browserslist field, the main field inference, and the --dist-dir rename are the configuration changes. Each change produces a different failure, and each failure points to the change that was skipped. The migration is the sequence, and the sequence is the discipline.
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!