Alpine.js Bundle Size and CSP Tradeoffs Explained
AI generated
x-data
Alpine
Alpine.js · Bundle Size · CSP · Performance
Alpine.js Bundle Size and CSP Tradeoffs Explained
from the core package to a strict Content Security Policy

Alpine.js bundle size looks insignificantly small at first glance, but once plugins, the CSP build variant and several component libraries add up, the exact composition decides noticeable differences in load time and time to interactive. Knowing the kilobyte numbers and the CSP tradeoffs leads to deliberate rather than accidental decisions when assembling a bundle.

17 min read Bundle Size · CSP Build · Plugins · Code Splitting Alpine.js 3.x

1. Why bundle size matters for Alpine.js at all

Alpine.js is commonly considered a lightweight alternative to React or Vue, and at its core that is true. Still, a closer look at the actual Alpine.js bundle size is worthwhile, because the number on the documentation's landing page refers only to the minimal core package without plugins, without a CSP adjustment, and without the project's own components added on top. In practice, the shipped JavaScript quickly grows to a multiple of the advertised number.

This gap is especially relevant for projects with strict performance budgets, for example in e-commerce, where every additional second until interactivity has been shown to lower the conversion rate. Anyone who only looks at the marketing number and ignores plugins, the CSP build, and their own component libraries is planning on a false basis. Alpine.js bundle size is not a fixed value, but the result of several deliberate decisions that can each be optimized individually.

The second major factor besides raw size is the Content Security Policy. Alpine.js evaluates expressions by default using new Function(), which in many security-conscious environments like Magento with Hyvä themes requires a strict CSP without unsafe-eval. The CSP build variant needed for that in turn changes the bundle size and runtime behavior, leading to its own tradeoffs examined in detail below.

2. The Alpine.js core package: size and tree shaking limits

The minimal Alpine.js core package weighs roughly 7 to 8 kilobytes compressed and gzipped, which is indeed very small compared to React with ReactDOM or Vue with its full reactivity system. This size covers the basic directives like x-data, x-show, x-if, x-for, and the underlying reactivity system, but none of the optional plugins such as Mask, Intersect, or Collapse.

Unlike modern bundlers with strict ES module tree shaking, the Alpine.js core package can only be reduced so far, because the individual directives are tightly interwoven and are not exported as fully independent modules. A developer using only x-data and x-show, but never x-for, still receives the complete core package, because Alpine.js internally is not split as granularly as, for example, Lodash with its individually importable functions.

What matters more for the actual Alpine.js bundle size in a project is therefore not how many directives are actually used, but which version gets loaded and whether multiple copies of the library unnecessarily end up in the same bundle, for example through inconsistent version specifiers across different npm packages that bring Alpine.js along as a dependency themselves.


// package.json — pin a single Alpine.js version across the project
// to avoid duplicate copies inflating the real bundle size
{
  "dependencies": {
    "alpinejs": "3.14.1"
  },
  "overrides": {
    // Forces every transitive dependency that also requires
    // alpinejs to use the exact same version, no duplicate copy
    "alpinejs": "3.14.1"
  }
}

// Quick check for duplicate Alpine.js copies in a built bundle:
// grep -c "alpinejs@" dist/bundle.js.map
// A count greater than 1 usually means two versions ship together.

3. Official plugins and their impact on size

Every official Alpine.js plugin adds to the Alpine.js bundle size, to varying degrees. The Mask plugin for input formatting weighs around 2 kilobytes gzipped, the Collapse plugin for accordion animations sits at a similar order of magnitude, while the more extensive Anchor plugin for tooltip positioning brings a bit more weight due to the included positioning logic. In total, a project shipping five or six plugins can easily reach double or triple the size of the plain core package.

The crucial point for a realistic assessment of Alpine.js bundle size: plugins are typically loaded globally for the entire site, regardless of whether a specific subpage even needs that functionality. A checkout page might load the Mask plugin for credit card fields, while the product page that never uses it ships the exact same bundle with the exact same plugin included.

A deliberate alternative is to include plugins only where they are actually needed, instead of registering them globally in the main bundle. In server-rendered applications with clearly separated page types, such as Magento with Hyvä, this can be achieved through separate layout handles that only load the extra script for a plugin on the affected pages.


// Instead of registering every plugin globally in the main entry
// file, load plugins only where the specific page actually needs them.

// main.js — core bundle, loaded on every page
import Alpine from 'alpinejs';
window.Alpine = Alpine;
Alpine.start();

// checkout.js — separate entry, only loaded on checkout pages
import Alpine from 'alpinejs';
import mask from '@alpinejs/mask';

Alpine.plugin(mask); // ~2kb gzipped, only shipped where needed
Alpine.start();

// Result: the product listing bundle stays smaller because it
// never includes the mask plugin's ~2kb payload at all.

4. The CSP build variant: cost and benefit

Alpine.js offers a dedicated CSP build variant that avoids new Function() and eval entirely, so the library also works under a strict Content Security Policy without unsafe-eval. This variant is especially relevant for Magento with Hyvä themes, because Magento admin areas and many security-critical projects mandate a CSP without unsafe-eval.

The tradeoff: the CSP build variant's Alpine.js bundle size comes out slightly larger than the standard variant, because an interpreter for expressions has to be shipped that works without dynamic function creation. Additionally, every expression in the HTML needs to be pre-compiled into a special format at build time, which requires an extra build step in the pipeline that the standard variant does not need.

This added complexity almost always pays off once a project has a strict CSP requirement, because the alternative would be allowing unsafe-eval in the Content Security Policy, which represents a significant security risk and completely undermines many of a CSP's protections against cross-site scripting. The slightly larger Alpine.js bundle size of the CSP variant is a small price compared to that security gain.


// Standard build: evaluates expressions with new Function(),
// requires 'unsafe-eval' in the Content Security Policy
import Alpine from 'alpinejs';

// CSP build: no new Function(), no eval, works under a strict CSP
// Requires expressions to be pre-compiled at build time
import Alpine from '@alpinejs/csp';

document.addEventListener('alpine:init', () => {
  // Component logic works identically in both builds,
  // only the expression evaluation mechanism differs internally
  Alpine.data('counter', () => ({
    count: 0,
    increment() { this.count++; }
  }));
});

Alpine.start();

// CSP-safe: no inline expressions with complex logic in HTML,
// keep expressions to simple property/method references
// GOOD:  <button @click="increment()">
// AVOID: <button @click="count = count + (isPremium ? 2 : 1)">

5. CDN inclusion versus bundler integration

The delivery path also plays a role for the Alpine.js bundle size that actually reaches the user. A CDN inclusion through a single script tag loads the complete library including every feature contained in the CDN build, often without a way to filter out unused parts. The advantage: with popular CDN providers, there is a good chance the file is already sitting in the user's browser cache, because another visited site loaded the same version from the same CDN.

Bundler integration through npm and a build step like Vite or Webpack, on the other hand, allows more precise control over the Alpine.js bundle size, because only plugins that are actually imported end up in the final bundle, and the result gets shipped together with the rest of the application code in a single compressed file. That reduces the number of HTTP requests but forgoes the potential cache advantage of a popular CDN URL.

For most production projects, especially in the Magento and Hyvä space, the advantage of bundler integration outweighs the alternative, because precise control over plugins and integration into the existing asset pipeline matters more than the theoretical cache advantage of a CDN, which in practice loses effect anyway due to increasingly strict browser cache partitioning.

6. Lazy loading and code splitting for components

For larger applications with many different Alpine.js components, code splitting pays off to keep the initially loaded Alpine.js bundle size small. Instead of loading every component definition immediately on page load, Alpine.data() can also be registered lazily, once the associated element actually appears in the viewport or a user interaction requires it.

This technique is especially effective for components that themselves bring heavyweight dependencies along, for example a rich text editor or a chart library. Instead of bundling that dependency into the main bundle, you load it via a dynamic import() only once the user actually interacts with the corresponding component. For most page views where the user never touches that component, the extra size stays entirely unloaded.


document.addEventListener('alpine:init', () => {
  // Lightweight component, always part of the main bundle
  Alpine.data('accordion', () => ({
    open: false,
    toggle() { this.open = !this.open; }
  }));

  // Heavyweight component, only loaded when actually rendered
  Alpine.data('richTextEditor', () => ({
    editorInstance: null,

    async init() {
      // Dynamic import: the editor library's code is only
      // downloaded once this specific component mounts
      const { default: EditorLibrary } = await import('./heavy-editor.js');
      this.editorInstance = new EditorLibrary(this.$refs.editorRoot);
    },

    destroy() {
      this.editorInstance?.destroy();
    }
  }));
});

7. Measuring bundle size with analysis tools

Without concrete numbers, any discussion of Alpine.js bundle size remains pure speculation. Tools like source-map-explorer or the Webpack Bundle Analyzer visualize what share of the final bundle belongs to Alpine.js itself, to individual plugins, and to your own application code. This visualization immediately reveals whether multiple versions of Alpine.js unexpectedly end up in the same bundle, a common but easily overlooked problem in monorepos with several sub-applications.

For quick individual measurements, a look at the Network tab of the browser DevTools, filtered to JavaScript files with the compressed transfer size column enabled, is enough. The difference between uncompressed and gzipped size is particularly large for Alpine.js, because the code contains many recurring patterns that compress extremely well, which is why the uncompressed number alone can easily be misleading.


# Analyze what makes up the final bundle, broken down by module
npx source-map-explorer dist/bundle.js dist/bundle.js.map

# Quick check: how large is the gzipped Alpine.js core alone?
curl -s https://unpkg.com/alpinejs@3.14.1/dist/cdn.min.js | gzip -9 | wc -c

# Compare with the CSP build variant
curl -s https://unpkg.com/@alpinejs/csp@3.14.1/dist/cdn.min.js | gzip -9 | wc -c

8. Practical tradeoffs: when the CSP build pays off

The decision between the standard build and the CSP build is rarely a pure question of Alpine.js bundle size, it depends on the project's security requirements. For an internal tool without third-party user input, unsafe-eval may be an acceptable risk. For a public e-commerce application with user accounts, payment data, and forms, a strict CSP without unsafe-eval is practically mandatory, and the slightly larger CSP build variant is the right price to pay for it.

A second practical tradeoff concerns the extra build step the CSP variant requires. Teams that already run an established build pipeline with Vite or Webpack usually integrate the additional pre-compilation step without much friction. Teams without existing build infrastructure that have so far only included Alpine.js via a CDN script tag need to accept extra tooling complexity for the CSP variant, which can delay the switch, but is rarely avoidable given the security benefits.

9. Bundle strategies compared

The table below contrasts the most important strategies around Alpine.js bundle size and CSP, with typical size ranges and the best fit for each.

Strategy Typical size (gzip) CSP compatible Best for
Core package alone 7 to 8 KB No (needs unsafe-eval) Internal tools without a strict CSP
Core package + 2 to 3 plugins 12 to 15 KB No Content pages with moderate functionality
CSP build without plugins 9 to 10 KB Yes Magento with Hyvä, security-critical areas
Lazily loaded component Variable, often 0 KB initial Yes, if CSP build is used Rare, heavy components like editors
CDN inclusion Full bundle size, no splitting Depends on build used Prototypes, very small projects

In most production Magento and Hyvä projects, the path through the CSP build variant combined with deliberately loaded plugins and code splitting for rare, heavy components leads to the best balance between security and the actual Alpine.js bundle size in the user's browser.

Mironsoft

Alpine.js and Hyvä development for Magento 2

Bundles too large or a CSP blocking your frontend rebuild?

We build Alpine.js bundle strategies with CSP build, deliberate plugin loading, and code splitting, tailored to your security level and performance goals.

Bundle audit

Analysis of your current Alpine.js bundle size with a bundle analyzer

CSP migration

Switching to the CSP build variant without unsafe-eval

Code splitting

Lazy loading for heavy components like editors and charts

10. Summary

Alpine.js bundle size is not a fixed value, it is the result of several decisions: which core package, which plugins, CSP build or standard build, CDN or bundler integration. The plain core package stays small at 7 to 8 kilobytes, but plugins, multiple copies of the library, and globally loaded, rarely used components can quickly double or triple the actually shipped size.

The CSP build variant costs a bit of extra size and an extra build step, but for any project with a strict Content Security Policy it is practically the only option, because unsafe-eval represents too large a security risk. Code splitting for heavy, rarely used components, and restricting plugins to the pages that actually need them, provide the biggest lever for keeping the Alpine.js bundle size small in the user's browser.

Alpine.js Bundle Size and CSP: Key Takeaways

Core package

7 to 8 KB gzipped, covers basic directives, no granular tree shaking of the directives themselves.

CSP build

Slightly larger, but usable without unsafe-eval. Practically mandatory for public, security-critical applications.

Load plugins deliberately

Register only on the pages that actually need them, instead of globally in the main bundle.

Code splitting

Load heavy components like editors via dynamic import() only when actually used.

11. FAQ: Alpine.js Bundle Size and CSP

1How big is the core package?
Around 7 to 8 KB gzipped, without plugins and without the CSP build variant.
2Why is the CSP build larger?
Contains an interpreter for expressions without new Function() or eval, adding extra code.
3When do I need the CSP build?
When the project enforces a CSP without unsafe-eval, such as Magento admin areas or security-critical public applications.
4Load plugins on specific pages only?
Yes, via separate entry points in the bundler included only on the affected pages.
5CDN or bundler better?
Bundler gives more precise control, CDN a theoretical cache advantage for popular versions.
6How do I measure bundle size?
With source-map-explorer, the Webpack Bundle Analyzer, or the Network tab of the browser DevTools.
7What is code splitting here?
Delaying the load of heavy dependencies via dynamic import() inside init().
8Multiple copies in the bundle possible?
Yes, from inconsistent versions. An overrides field in package.json enforces one consistent version.
9Does CSP build change behavior?
Component logic stays the same, but complex inline expressions in attributes should be avoided.
10Worth it for small projects?
Often not strictly necessary for internal tools, almost always worth it for public applications with user data.