In this post I show how to combine lit-html with the Closure compiler to get smaller, more performant JavaScript bundles.
Templating
One of the challenges when creating web applications in vanilla JavaScript is a lack of support for html templating. All popular JavaScript frameworks (e.g. Angular, React) include this by default, but it can sometimes be too much overhead to bring in a big framework if all you need is simple templating. A potential alternative can be to bring in something more nimble like lit-html to take care of your templating needs.
The general idea behind lit-html is that you write your html templates as ES2015 string literals. Since this means your html is really JavaScript, it becomes much easier to integrate with advanced optimizers like the Closure compiler.
If you are new to the Closure compiler, I recommend reading one my previous articles first.
Demo
In my demo I have created a simple application that renders a list of friends using a rxjs timer. Let’s take a look at the code below.
main.js
import { html, render } from 'lit-html';
import { classMap } from 'lit-html/directives/class-map';
import { repeat } from 'lit-html/directives/repeat';
import { FriendsService } from './friends-service';
const friendsService = new FriendsService();
let friends = [];
friendsService.getFriends(4).subscribe(f => {
friends = [...friends, f];
render(friendsList(friends), document.body);
});
const listener = {
handleEvent(e) {
console.log('clicked');
},
capture: true,
};
const friendsList = (friendsList) => html`
<ul class="${classMap({ 'wrapper': true })}">
${repeat(friendsList, (friend) => friend.id, (friend) =>
html`<li>${friend.id} ${friend.name}</li>`)}
</ul>
<button @click=${listener}>Click Me</button>`
friends-service.js
import { timer } from 'rxjs';
import { map, take } from 'rxjs/operators';
export class FriendsService {
getFriends(numberOfFriends) {
return timer(10, 2000).pipe(
map(r => {
return { name: `Joe ${r}`, id: r };
}),
take(numberOfFriends)
);
}
}
I won’t discuss the details of lit-html since this is already explained well in the official documentation. Instead let’s take a look at the flag file used to optimize my demo application with the Closure compiler.
--compilation_level=ADVANCED_OPTIMIZATIONS
--language_out ECMASCRIPT_2015
--language_in ECMASCRIPT_2018
--js_output_file=public/build/bundle-closure.js
--output_manifest=public/build/manifest.MF
--variable_renaming_report=public/build/variable_renaming_report
--property_renaming_report=public/build/property_renaming_report
--create_source_map=%outname%.map
--rewrite_polyfills=false
--warning_level=QUIET
--rewrite_polyfills=false
--jscomp_off=checkVars
--package_json_entry_names es2015,module
--module_resolution=node
--js node_modules/lit-html/package.json
--js node_modules/lit-html/lit-html.js
--js node_modules/lit-html/lib/**.js
--js node_modules/lit-html/directives/**.js
--js node_modules/rxjs/package.json
--js node_modules/rxjs/_esm2015/index.js
--js node_modules/rxjs/_esm2015/internal/**.js
--js node_modules/rxjs/operators/package.json
--js node_modules/rxjs/_esm2015/operators/index.js
--js src/**.js
--entry_point=src/main.js
I haven’t created an extensive lit-html application, but my first impression is that lit-html seems very compatible with the Closure compiler by default. The only non-standard thing I had to do was surround the classMap class, wrapper, in single quotes to opt out of property name shortening.
Github
I have pushed my demo repo to Github in case you are interested in trying it out yourself.