Build for production
We recommend Rollup because it's designed to work with the standard ES module format.
For a set of sample build configurations using Rollup, see Building with Rollup.
If you're interested in building with a different tool, or integrating LitElement into an existing build system, see Build requirements.
Building with RollupPermalink to “Building with Rollup”
There are many ways to set up Rollup to bundle your project. This section describes a two basic builds:
- A modern build that runs on evergreen browsers.
- A universal build that runs on browsers back to Internet Explorer 11.
If you only need to support evergreen browsers, you can use the modern build by itself. If you want to support the widest range of browsers with a single build, you can use the backwards-compatible build.
The last part of this section discusses ways to use the two builds together to provide the faster, smaller modern build to browsers that support it while also supporting older browsers.
The example configurations here use the Shop demo app as an example. You can find all of the configurations described here in a branch of the Shop repo:
Modern browser buildPermalink to “Modern browser build”
The modern browser build uses the following npm packages:
rollup. The Rollup bundler.
@rollup/plugin-node-resolve. For resolving bare module specifiers. (See Bare module specifiers for more information.)
rollup-plugin-copy. For copying static assets to the build folder.
rollup-plugin-minify-html-literals. An optional optimization.
The Rollup configuration file for this build looks like this: \
Even simpler starterPermalink to “Even simpler starter”
The rollup-starter-app repo is a bare-bones starter project for building an app with Rollup. Although the repo doesn't include LitElement, it includes everything you need to bundle a LitElement app. If you want to use this project as a model for your own project, the most important files to look at are
Note that in addition to the required plugins, rollup-starter-app includes the CommonJS plugin, @rollup/plugin-commonjs. This plugin isn't required for LitElement, but can be useful if you want to import packages that are only distributed as CommonJS modules.
Universal buildPermalink to “Universal build”
The universal build requires all of the packages used by the modern build, plus the following:
Polyfills used by Babel:
SystemJS module loader:
If you just want to look at the code, here are the most important parts of the universal build:
- Rollup configuration: https://github.com/Polymer/shop/blob/rollup-examples-v2/rollup-universal.js
The following sections describe aspects of the build.
Babel build and polyfill bundlePermalink to “Babel build and polyfill bundle”
Unlike the modern build, this build produces two separate bundles: one for the application code, and one for the polyfills required by Babel. Many Babel configurations produce a single bundle, which includes the polyfills as well as the application code. However, this can cause problems with other polyfills, including the Web Components polyfills. Loading the Babel polyfills separately, prior to loading the Web Components polyfills, allows both sets of polyfills to work correctly. The Babel polyfills are supplied as separate Common JS modules.
Babel configurationPermalink to “Babel configuration”
The Babel configuration for this build is fairly small. It tells Babel to use the
@babel/preset-env plugin to compile to code compatible with Internet Explorer 11.
Application bundlePermalink to “Application bundle”
The Rollup configuration for the application bundle looks similar to the configuration for the model build:
There are three main differences from the modern configuration:
- It defines a
configsarray, instead of a single configuration object.
- The main app bundle uses a different format (SystemJS instead of ES modules).
- The main app bundle is compiled using the Babel plugin.
Polyfill bundlePermalink to “Polyfill bundle”
The Rollup config bundles all of these modules into a single file:
Loading it allPermalink to “Loading it all”
index.html file loads all of the bundles in the correct order:
- The Babel polyfills bundle.
- The Web Components polyfill loader, which performs feature detection and loads any required Web Components polyfills.
- The SystemJS loader.
- The application bundle.
index.html also includes a small application-specific polyfill for the
With extra material removed, the portion of
index.html that loads script looks like this:
Using multiple buildsPermalink to “Using multiple builds”
In theory, it's ideal to deliver a modern ES6 bundle to modern browsers and the larger ES5 bundle to older browsers. In practice, this can be challenging. Here are three possible techniques for delivering different builds to different browsers:
Client-side feature detection.
These approaches are discussed in the following sections.
Module/nomodule gambitPermalink to “Module/nomodule gambit”
Since browsers that don't support modules won't execute module scripts (
<script type="module">), and browsers that support modules won't load
nomodule scripts (
<script nomodule>), you can include the legacy bundles using
nomodule. In practice, the browsers that support modules also support the other ES6 language features used by LitElement, and don't require transpilation.
You can see this in practice in the Shop example app. See the
index-modnomod.html file. The advantage of this technique is that it doesn't require any logic on the server side.
Another drawback is that IE 11 (and possibly some other older browsers) may download (but not execute)
<script type="module"> bundles, and Edge 16-18 may download (but not execute)
<script nomodule> bundles. This represents a performance penalty on these older browsers.
Client-side feature detectionPermalink to “Client-side feature detection”
In this technique, the initial page load includes a small script that runs feature detection code and then imperatively loads one of the bundles.
Differential servingPermalink to “Differential serving”
In this technique, the server uses the User-Agent request header to determine which bundle to serve to the browser. This technique (also called "browser sniffing") has drawbacks. It's less correct than client-side feature detection, in that it relies on a static list of features supported by each browser. Also, some browsers may send an incorrect User-Agent header. However, it tends to have a performance advantage over the other approaches, since the browser only receives the bundles it needs.
The prpl-server project is a Node.js web server that supports differential serving.
Build requirementsPermalink to “Build requirements”
This section describes the requirements for building applications using LitElement. Use this section if you're creating your own build setup.
When building an app using LitElement, your build system will need to handle the following:
- Resolving bare (or Node-style) module identifiers. LitElement uses bare module specifiers.
- Transforming ES modules to another module system, if required, to support older browsers.
For older browsers, you'll also need to load certain polyfills:
- Web Components polyfills
- Dynamic imports polyfill. Required by browsers (in particular, Edge 16 through 18) that support static imports for ES modules, but not dynamic imports.
Bare module specifiersPermalink to “Bare module specifiers”
LitElement uses bare module specifiers to import modules from the lit-html library, like this:
Browsers currently only support loading modules from URLs or relative paths, not bare names referring to e.g. an npm package, so the build system needs to handle them: either by transforming the specifier to one that works for ES modules in the browser, or by producing a different type of module as output.
Webpack automatically handles bare module specifiers; for Rollup, you'll need a plugin (@rollup/plugin-node-resolve).
Why bare module specifiers? Bare module specifiers let you import modules without knowing exactly where the package manager has installed them. A standards proposal called Import maps would let browsers support bare module specifiers. In the meantime, bare import specifiers can easily be transformed as a build step. There are also some polyfills and module loaders that support import maps.
Supporting older browsersPermalink to “Supporting older browsers”
Supporting older browsers (specifically Internet Explorer 11), requires a number of extra steps:
- Transforming ES modules to another module system.
- Loading polyfills.
- Babel polyfills.
- Web Components polyfills.
You may need other polyfills depending on your application.
Transpiling to ES5Permalink to “Transpiling to ES5”
If you have a build already set up, it may be configured to ignore the
node_modules folder when transpiling. If this is the case, we recommend updating this to transpile LitElement and lit-html. For example, if you're using the Rollup Babel plugin, you might have a configuration like this to exclude the
node_modules folder from transpilation:
You can replace this with a rule to explicitly include folders to transpile:
index.html showing the load order.
If LitElement included multiple builds, individual elements could end up depending on different builds of LitElement—resulting in multiple versions of the library being shipped down to the browser.
Transforming modulesPermalink to “Transforming modules”
When producing output for IE11, there are three common output formats:
- No modules (IIFE). Code is bundled as a single file, wrapped in an immediately-invoked function expression (IIFE).
- AMD modules. Uses the Asynchronous Module Definition format; requires a module loader script, such as require.js.
The IIFE format works fine if all of your code can be bundled into a single file. To use code splitting with older browsers like IE11, you'll need to produce output in either the AMD or SystemJS module format.
The universal build in the Building with Rollup section uses SystemJS format. You can see an example of loading the main SystemJS module here:
PolyfillsPermalink to “Polyfills”
In addition to any application-specific polyfills, you'll need to load the Babel polyfills and the Web Components polyfills.
Note that the Babel polyfills should be bundled separately from the application bundle, and loaded before the Web Components polyfills. This is discussed in Babel build and polyfill bundle. To see an example of generating the Babel polyfill bundle in Rollup, see Polyfill bundle. For an example of loading the bundles, see Loading it all.
OptimizationsPermalink to “Optimizations”
LitElement projects benefit from the same optimizations as other web projects:
- Bundling (for example, using Rollup or webpack).
- Compression (such as gzip).
In addition, there are a few smaller optimizations that are more specific to LitElement:
- Minify template literals. Lit-html templates are defined using template literals, which don't get processed by standard HTML minifiers. Adding a plugin that minifies template literals can result in a modest decrease in code size. (Unless your templates are very large, this is a small optimization).
- Compile out shady-render. If you're only supporting modern browsers, you can compile out the shady-render module used to support older browsers.
The example builds presented in Building with Rollup include most of these optimizations.
Code minificationPermalink to “Code minification”
Minify template literalsPermalink to “Minify template literals”
Lit-html templates are defined using template literals, which don't get processed by standard HTML minifiers. Adding a plugin that minifies template literals can result in a modest decrease in code size. (Unless your templates are very large, this is a small optimization).
Several packages are available to perform this optimization:
Compile out the shady-render modulePermalink to “Compile out the shady-render module”
If you're building for modern browsers only, you can remove LitElement's built-in support for shady DOM, the shadow DOM polyfill, saving about 12KB of bundle size.
To do so, configure your build system to replace the
shady-render module with the base
lit-html module, which provides a generic version of
render. This saves about 12KB from your bundle.
For a Rollup build:
Configure the alias plugin to replace references to the
shady-rendermodule with references to the main
For a webpack build:
Add the following resolve.alias setting to your webpack configuration:
TypeScriptPermalink to “TypeScript”
For example, if you have a