I'm writing a JavaScript lib with TypeScript, using the excellent Vite for on-the-fly transpiling and packaging. This post is about the config I use and specifically covers removal of comments because I Googled for a solution but it wasn't obvious so I'm writing about it here.
Just to explain the components I'll be mentioning:
- Vite is a packager, doing the same job as WebPack. So you write some JS code in bite-sized modules and you need something to assemble it into a file you can distribute, take care of making it a good modular citizen for browser or node, minify it, uglify it, etc. That's a packager. Vite is more than this because it gives you very fast hot swapping of modules which makes dev a wait-less experience compared to some other options. But Vite can't do all of the things we want in a packager entirely on its own, so it relies on a couple of other apps in the form of....
- rollup is used by Vite for file bundling. It's a standalone lib that Vite employs for this part of the packaging process.
- terser is a "JavaScript mangler/compressor toolkit for ES6+". There's likely to be some overlap between rollup and terser, in my view. My assumption is that terser is better or faster at mangling. Of, mangling is making your code look unreadable and ugly whilst still having it run. It might even make it faster in some cases.
So, Vite has a build config and in it we specify config information that controls how these things talk to each other to get the job done.
Back to the mission - make the build work and remove comments on the way...
To configure Vite build to erase comments, you can use the terser plugin. Whoa - wait up! If you are a bit familiar with Vite build config then you might have seen that Vite lets us specify minify= 'terser'. So, yes, terser is already mentioned as being a part of the Vite build process. In addition, you might have seen that terser is included in the Vite build so when you "npm install vite" you get terser as part of the package. So is Vite able to tell terser to chop the comments? No.
The way I think of it, Vite is using rollup for the packaging of files, and the remove-comments capability is part of terser. What confused me initially was assuming (always bad!) that terser would know what to do since because it's already wired in to Vite.
Clearly Vite is making some use of terser if we set its config option minify= 'terser', but what we actually need to do is tell rollup that it should use terser too, and in particular to use the rollup-plugin-terser to chop out the comments.
Put another way, Vite asks rollup to do the packaging then asks terser to mangle it. But what we need to do is tell rollup that while its doing its thing for Vite that it should take care of the comments at the same time for us too.
So lets install the plugin
npm install rollup-plugin-terser
This is my package.json - notice the dependencies section showing the rollup-plugin-terser - the result of the install we just ran. The scripts section is inserted as part of creating a Vite project. Whilst in dev mode I use a plain Vite build process which is simple to set up because it's the default! I have a dev index.html file that imports an init.js file and that imports the dev version of my lib. Note that these are ES6 imports. There is no extra work, no extra config, Vite does all the work to transpile my code as it changes and do any module rewiring - all out of sight and without littering my project folder with build output. Nice.
{
"name": "toolbar",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"devDependencies": {
"typescript": "^5.0.2",
"vite": "^4.3.2"
},
"dependencies": {
"rollup-plugin-terser": "^7.0.2"
}
}
Here's my vite.config file for creating the lib - see the object at build.rollupOptions.plugins.terser() which is how to erase comments:
/** @type {import('vite').UserConfig} */
// vite.config.js
import { resolve } from 'path'
import { defineConfig } from 'vite'
import { terser } from 'rollup-plugin-terser';
export default defineConfig({
build: {
minify: 'terser',
lib: {
entry: resolve(__dirname, 'src/myFunkyLib.ts'),
name: 'myFunkyLib',
// the proper extensions will be added
fileName: 'myFunkyLib',
//formats: ['es','cjs', 'umd', 'iife'],
formats: ['es'],
},
rollupOptions: {
plugins: [terser({
format: {
comments: false,
},
mangle: {
keep_classnames: false,
reserved: [],
},
})],
// make sure to externalize deps that shouldn't be bundled
// into your library
external: ['some_amazing_package_I_installed'],
// external: ['konva'],
output: {
// Provide global variables to use in the UMD build
// for externalized deps
globals: {
},
},
},
},
})
When we run the build script [npm run build] the output we get excludes comments! To test I used a small standalone TS file.
/**
* Utility functions for the foo package.
* @module foo/util
* @license Apache-2.0
*/
/*! This is a comment
* that will not be erased in the build
*/
export class speak {
/* This is an inside comment
* that runs over
* multiple lines
*/
sayHello() { // this is an inline comment
console.log('hi')
}
// this is an own-line comment
sayGoodbye() {
console.log('goodbye')
}
}
The following results occur
- comments = false : No comments in the output file after build
- comments = "all" : All comments remain in the output file after build
- comments = true : All comments remain in the output file after build (same as "all")
- comments = "some" : Comments on lines 1 - 9 remain, all others are removed.
There are many other terser options that can be applied, for example retaining class names. I have not tested exhaustively, but it seems that these are handled by the rollupOptions.plugins.terser() config - certainly I found this to be the case with keep_classNames.
A note about the Vite public folder
One of the things you might want to do is manage fixed assets - that means files that don't need to be transpiled or in any way changed in the build process. Vite is generally very focussed on doing intelligent things with whatever files are in your project folder as long as those files are linked be being imported somewhere in your TypeScript files. But what about images and CSS files, for example?
In my project I'm not doing anything clever with CSS that would required CSS compiling such as would require tailwind - I'm writing a lib so I don't have a million different component styles, it's a real use-case. So that makes my CSS file fixed and unchanging, like an image.
But I want my CSS file to to get copied somewhere useful when I run the build. Ideally, I'd like the CSS to go into the /dist folder. How can I do that?
The simple answer is to make use of Vite's built-in <project root>/public folder. Anything you place in here gets copied unchanged into the <project root>/dist folder. That's what I'm doing.
The more complex answer, if you need subfolders etc, is to use a rollup plugin such as rollup-static-copy. See the link for an explanation.
Summary
We've seen the config I use to build a TypeScript based lib using Vite and how to remove comments with the extra rollup-plugin-terser plugin, how to configure for that in the vite.config and what the various options to handle comments do to your output.
I've used this approach for a couple of builds that went out into the wild and it seems to be consistent and workable.
Thanks for reading.
VW. May 2023
Photo by Belinda Fewings on Unsplash