Parcel 2 Documentation

repository·v2·Indexed 18 days ago

https://github.com/parcel-bundler/website

Documentation for the Parcel 2 bundler, covering its pipeline-based architecture, .parcelrc configuration, and plugin system. Includes guides on using SWC for transpilation, tree shaking for dynamic imports and CSS Modules, differential bundling via ES modules, and the Parcel CSS transformer and optimizer.

Tokens
82.3K
Snippets
329
Records
413
Agent score
62%

What's inside Parcel 2

  1. Key features of Parcel 2

    v2

    Parcel 2 is a ground-up rewrite designed for scalability and extensibility. Key features include:

    • Plugin System: A fully extensible architecture for all aspects of the build process.
    • Tree Shaking: Enabled by default for ES modules, CommonJS, dynamic imports, and CSS modules.
    • Performance: Includes a Rust-based JavaScript compiler and parallelized architecture.
    • Differential Bundling: Automatic bundling via native ES modules with fallback for older browsers.
    • Code Splitting: Automatic splitting and deduplication of common modules.
    • Image Optimization: Automatic resizing, conversion (AVIF, WebP), and lossless optimization (JPEG, PNG).
    • Caching: Improved, portable cache that tracks configs, plugins, and dev dependencies automatically.
    • Hot Reloading: Improved support, including React Fast Refresh.
    • Library Support: Ability to output to ES modules, CommonJS, and bundle TypeScript definitions.
    • Lazy Development Mode: Builds only files requested by the browser to improve startup times.
  2. What is a Validator plugin?

    v2

    A Validator is a plugin type used to analyze assets and emit warnings or errors (e.g., linting or type errors).

    Execution Lifecycle

    • Timing: Validators run after a build has fully completed to avoid impacting compilation performance.
    • Watch Mode (parcel watch or parcel serve): Errors are merely logged; Parcel continues to serve/save updated bundles.
    • Build Mode (parcel build): Parcel exits with a failure status code if a validator throws an error, preventing the deployment of invalid code.

    Note: The Validator API is experimental and subject to change.

  3. Optimize CommonJS Modules for Tree Shaking

    v2

    Parcel can analyze CommonJS modules if exports are assigned statically.

    In the module (Exports):

    • Do: Use static assignments to exports, module.exports, or this (e.g., exports.foo = 2;).
    • Avoid: Dynamic assignments (e.g., exports[someVar] = 2;), re-assigning the exports or module variables, or passing exports/this/module to unknown functions.

    In the importer (Requires):

    • Do: Use static property access (require('./m').foo) or destructuring (const { foo } = require('./m');).
    • Avoid: Dynamic property access (require('./m')[op]) or inline requires passed to functions (doSomething(require('./m'));).
    // ✅ Static exports assignments
    exports.foo = 2;
    module.exports.foo = 2;
    this.foo = 2;
    
    // ✅ module.exports assignment
    module.exports = 2;
    
    // 🚫 Dynamic exports assignments
    exports[someVar] = 2;
    module.exports[someVar] = 2;
    this[someVar] = 2;
    
    // 🚫 Exports re-assignment
    let e = exports;
    e.foo = 2;
    
    // 🚫 Module re-assignment
    let m = module;
    m.exports.foo = 2;
    
    // 🚫 Unknown exports usage
    doSomething(exports);
    doSomething(this);
    
    // 🚫 Unknown module usage
    doSomething(module);
    
    // ✅ Static property access
    const math = require('./math');
    console.log(math.add(2, 3));
    
    // ✅ Static destructuring
    const {add} = require('./math');
    
    // ✅ Static property assignment
    const add = require('./math').add;
    
    // 🚫 Non-static property access
    const math = require('./math');
    console.log(math[op](2, 3));
    
    // 🚫 Inline require
    doSomething(require('./math'));
    console.log(require('./math').add(2, 3));
  4. Understand Scope Hoisting and Tree Shaking

    v2

    In production builds, Parcel uses scope hoisting to concatenate modules into a single scope instead of wrapping each module in a separate function. This improves runtime performance and makes minification more effective.

    Parcel also performs tree shaking (dead code elimination) by statically analyzing imports and exports to remove unused code. This works for ES modules, CommonJS, dynamic imports, and CSS modules.

  5. Locally scope CSS variables in CSS Modules

    v2

    Parcel allows you to locally scope CSS variables and dashed identifiers (e.g., --foo or @font-palette-values) within CSS Modules to prevent naming collisions between files. When enabled, Parcel renames these variables to include a hash of the filename.

    To reference a variable defined in a different CSS module, use the following syntax extension:

    .button {
      background: var(--accent-color from "./vars.module.css");
    }

    Tree Shaking: By explicitly declaring these file dependencies, Parcel can also tree shake unused CSS variables, automatically removing declarations that are not referenced within the module graph.

  6. Optimize Dynamic Imports for Tree Shaking

    v2

    Parcel supports tree shaking for dynamic import() calls, provided you use static property access or destructuring on the resulting Promise or await value.

    Do:

    • const { add } = await import('./math');
    • import('./math').then(({ add }) => ...);
    • const math = await import('./math'); console.log(math.add(2, 3));

    Avoid:

    • Dynamic property access: math[op]
    • Passing the returned Promise to an unknown function: doSomething(import('./math'));
    • Passing an unknown argument to .then(): import('./math').then(doSomething);

    Note: For await cases, unused exports can only be removed if await is not transpiled away (requires a modern browserslist config).

    // ✅ Destructuring await
    let {add} = await import('./math');
    
    // ✅ Static member access of await
    let math = await import('./math');
    console.log(math.add(2, 3));
    
    // ✅ Destructuring Promise#then
    import('./math').then(({add}) => console.log(add(2, 3)));
    
    // ✅ Static member access of Promise#then
    import('./math').then(math => console.log(math.add(2, 3)));
    
    // 🚫 Dynamic property access of await
    let math = await import('./math');
    console.log(math[op](2, 3));
    
    // 🚫 Dynamic property access of Promise#then
    import('./math').then(math => console.log(math[op](2, 3)));
    
    // 🚫 Unknown use of returned Promise
    doSomething(import('./math'));
    
    // 🚫 Unknown argument passed to Promise#then
    import('./math').then(doSomething);
  7. Understand Auto Install behavior

    v2

    Parcel automatically installs missing dependencies (like transformers) when it encounters a language or plugin it doesn't have by default (e.g., adding a .sass file triggers the installation of @parcel/transformer-sass).

    • Package Manager: Parcel detects your package manager via lock files (yarn.lock $\rightarrow$ Yarn, pnpm-lock.yaml $\rightarrow$ Pnpm, package-lock.json $\rightarrow$ Npm). If no lock file exists, it uses what is installed on your system in this order: Yarn, Pnpm, Npm.
    • Scope: Auto install only occurs during development by default. During production builds, missing dependencies cause a build failure.
    • Disable: Use the --no-autoinstall CLI flag to disable this feature.
    # Disable automatic dependency installation
    parcel src/index.html --no-autoinstall
  8. How Parcel 2 plugin pipelines work

    v2

    Parcel 2 uses a pipeline-based architecture where assets move through stages based on their file type.

    1. Initial Match: An asset matches an initial pipeline via a glob pattern.
    2. Transformation: The asset runs through plugins in that pipeline until its file type changes (e.g., .ts $\rightarrow$ .js).
    3. Pipeline Handoff: Once the type changes, the asset enters the next appropriate pipeline.
    4. Multi-asset Output: Transformations can output multiple assets (like inline <script> or <style> tags from an HTML file). These sub-assets are processed through their own pipelines and then re-inserted into the original file after processing.
  9. Reference assets with url()

    v2

    The url() function can be used to reference files like images or fonts. Parcel processes these files and rewrites the URL to point to the correct output filename. You can also use the data-url: scheme to inline a file as a data URL.

    Important: When using url() inside CSS custom properties (variables), you must use absolute paths. Relative paths in custom properties resolve from the location where the variable is used, not where it is defined, which leads to broken links.

    /* Standard usage */
    body {
      background: url(images/background.png);
    }
    
    /* Inlining via data-url: */
    .logo {
      background: url('data-url:./logo.png');
    }
    
    /* ✅ Correct: Use absolute paths in custom properties */
    :root {
      --logo: url(/src/images/logo.png);
    }
    
    /* ❌ Incorrect: Relative paths in custom properties */
    :root {
      --logo: url(images/logo.png);
    }
  10. How Parcel CSS architecture works

    v2

    Unlike many CSS processors that treat property values as strings or untyped tokens, Parcel CSS parses all values using the grammar from the CSS specification and exposes a specific, typed value for each property.

    This approach, similar to how browsers parse CSS, provides several benefits:

    • Performance: Transformers do not need to re-parse, transform, or interpret property values; they can access the structured data directly.
    • Reliability: It prevents inconsistencies caused by different tools using different regexes or string manipulation logic.
    • Better Minification: Because implicit default values are filled in during parsing, the optimizer can automatically remove them, merge longhand properties into shorthands, and optimize whitespace more effectively.
  11. Implement code splitting with dynamic import()

    v2

    While standard import and require statements load dependencies synchronously, the dynamic import() function loads dependencies asynchronously. This returns a Promise and is a key technique for code splitting, allowing you to load code lazily on demand to reduce the initial bundle size.

    import('./pages/about').then(function(page) {
      page.render();
    });