Skip to content

Polymer Building — Bundling and Optimizing LitElement for Production

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Polymer Building. We cover key concepts, practical examples, and best practices to help you master this topic.

Building web components for production requires bundling, minification, code splitting, and browser compatibility optimizations.

What You'll Learn

  • Rollup configuration for LitElement
  • Code splitting and lazy loading
  • Build optimization techniques
  • Performance budgets
  • Deployment preparation

Why It Matters

A production build reduces bundle size by 60-80%, splits code for lazy loading, and ensures cross-browser compatibility for your component library.

Real-World Use

A component library deployed to CDN — users import only the components they need, tree-shaking eliminates unused code.

Build Architecture

flowchart TD
    A[Building] --> B[Bundling]
    A --> C[Optimization]
    A --> D[Deployment]
    B --> E[Rollup]
    B --> F[Code Splitting]
    C --> G[Minification]
    C --> H[Tree Shaking]
    D --> I[CDN]
    D --> J[NPM Package]
    style A fill:#e6f3ff,stroke:#4a90d9,stroke-width:2px

Rollup Configuration

// rollup.config.js
import resolve from '@rollup/plugin-node-resolve';
import terser from '@rollup/plugin-terser';
import summary from 'rollup-plugin-summary';
import litcss from 'rollup-plugin-lit-css';
import { importMetaAssets } from '@web/rollup-plugin-import-meta-assets';

export default {
  input: 'src/index.js',
  output: {
    dir: 'dist',
    format: 'esm',
    sourcemap: true,
    entryFileNames: '[name]-[hash].js'
  },
  plugins: [
    resolve(),
    litcss(),
    importMetaAssets(),
    terser({
      ecma: 2020,
      module: true,
      compress: { drop_console: true }
    }),
    summary()
  ],
  preserveEntrySignatures: 'strict'
};

Expected output: Rollup bundles entry points into hash-named ESM files. Terser minifies. Summary shows bundle size.

Development Server

// web-dev-server.config.mjs
import { esbuildPlugin } from '@web/dev-server-esbuild';

export default {
  nodeResolve: true,
  watch: true,
  open: true,
  appIndex: 'index.html',
  plugins: [esbuildPlugin({ ts: true, target: 'auto' })],
  middleware: [(ctx, next) => {
    if (ctx.url.startsWith('/api/')) {
      ctx.body = JSON.stringify({ mock: true });
      ctx.type = 'json';
    }
    return next();
  }]
};

Expected output: npm run start launches dev server with watch, auto-reload, and API middleware.

Code Splitting

// src/app.js
import { LitElement, html } from 'lit';

class AppShell extends LitElement {
  static properties = { currentRoute: { type: String } };

  constructor() { super(); this.currentRoute = 'home'; }

  async _navigate(route) {
    this.currentRoute = route;
    let module;
    switch (route) {
      case 'dashboard':
        module = await import('./pages/dashboard.js');
        break;
      case 'reports':
        module = await import('./pages/reports.js');
        break;
      case 'admin':
        module = await import('./pages/admin.js');
        break;
      default:
        module = await import('./pages/home.js');
    }
    await this.dispatchEvent(new CustomEvent('route-loaded', {
      detail: { component: module.default },
      bubbles: true, composed: true
    }));
  }

  render() {
    return html`
      <nav>
        <a @click=${() => this._navigate('dashboard')}>Dashboard</a>
        <a @click=${() => this._navigate('reports')}>Reports</a>
        <a @click=${() => this._navigate('admin')}>Admin</a>
      </nav>
      <div id="outlet"><slot></slot></div>
    `;
  }
}
customElements.define('app-shell', AppShell);

Expected output: Dynamic import() creates separate chunks. Rollup outputs chunk-[hash].js for each lazy route.

Tree Shaking and Bundle Analysis

// rollup.config.js
import bundleAnalyzer from 'rollup-plugin-bundle-analyzer';
import visualizer from 'rollup-plugin-visualizer';

export default {
  plugins: [
    visualizer({ filename: 'dist/stats.html', open: true }),
    bundleAnalyzer()
  ]
};

Expected output: visualizer generates interactive treemap of bundle contents. bundleAnalyzer identifies large dependencies.

Performance Budget

// bundlesize.config.json
{
  "files": [
    { "path": "dist/index-*.js", "maxSize": "20 KB" },
    { "path": "dist/chunk-*.js", "maxSize": "10 KB" },
    { "path": "dist/**/*.css", "maxSize": "5 KB" }
  ]
}
// package.json scripts
{
  "build": "rollup -c",
  "analyze": "rollup -c && npx bundlesize",
  "size": "npx size-limit"
}

Expected output: Bundle size checks fail if chunks exceed budget. CI pipeline blocks oversized bundles.

LitElement Build Optimizations

import { LitElement, html, css } from 'lit';
import { ifDefined } from 'lit/directives/if-defined.js';

// Use static styles for shared, parse-once CSS
class OptimizedComponent extends LitElement {
  static styles = css`
    :host { display: block; }
    .optimized { display: flex; }
  `;

  static properties = {
    items: { type: Array },
    active: { type: String }
  };

  render() {
    return html`
      <div class="optimized">
        ${this.items.map(item => html`
          <div class="${ifDefined(item === this.active ? 'active' : undefined)}">
            ${item}
          </div>
        `)}
      </div>
    `;
  }
}
customElements.define('optimized-component', OptimizedComponent);

Expected output: ifDefined avoids rendering undefined attributes. Static styles are parsed once per class, not per instance.

NPM Package Build

// rollup.config.js (library mode)
import resolve from '@rollup/plugin-node-resolve';
import terser from '@rollup/plugin-terser';

export default {
  input: 'src/index.js',
  output: [
    { file: 'dist/my-components.js', format: 'esm' },
    { file: 'dist/my-components.min.js', format: 'esm', plugins: [terser()] }
  ],
  external: ['lit'],
  plugins: [resolve()]
};
{
  "name": "@myorg/my-components",
  "version": "1.0.0",
  "module": "dist/my-components.js",
  "exports": {
    ".": "./dist/my-components.js",
    "./button": "./src/button.js"
  }
}

Expected output: Library outputs ESM and minified variants. lit is external (not bundled). Consumers tree-shake unused components.

Common Mistakes

  1. Not generating source maps - Essential for debugging production issues.

  2. Missing browser targets - Use @babel/preset-env with targets for transpilation.

  3. Bundling lit into library - lit should be external for library packages.

  4. Forgetting hash for cache busting - [hash] in filenames enables long-term Caching.

  5. Not analyzing bundle before release - Use visualizer to catch large dependencies.

Practice Questions

  1. How does Rollup differ from Webpack for LitElement builds?
  2. How does dynamic import enable code splitting?
  3. What is tree shaking and how does it benefit LitElement bundles?
  4. How do you set up a performance budget for component builds?
  5. How do you package a LitElement library for npm?

Challenge: Set up a complete build pipeline for a component library with: Rollup bundling, code splitting per component group, TypeScript compilation, lit-css processing, terser minification, bundle analysis, performance budgets, and npm package output.

FAQ

Can I use Vite instead of Rollup?

Yes. Vite uses Rollup for production builds and provides faster dev server with native ESM.

How do I handle CSS in LitElement builds?

Use rollup-plugin-lit-css to inline CSS imports. Or use static styles.

How do I support older browsers?

Use @web/rollup-plugin-polyfills-loader and @babel/preset-env for transpilation.

How do I measure bundle sizes in CI?

Use bundlesize or size-limit packages with CI integration.

Mini Project

Build a complete build pipeline for a dashboard application: Rollup config with code splitting, lazy loading for 4 route pages, lit-css processing, terser minification, bundle analysis visualization, performance budgets, and npm library output for shared components.

What's Next

Build pipelines prepare components for production. Learn how Polymer Project brings everything together in a complete application.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro