This example project demonstrates how to write a jQuery plugin using TypeScript. Experienced developers can get started quickly by following the brief instructions in the Getting Started section. For those who are new to TypeScript or those who want to learn more about the concepts behind the project structure there is a Step-by-Step Tutorial which explains the project setup in detail.
This is an example project that demonstrates what I currently find a best practice project setup for the development of JavaScript code using the TypeScript language. Even if it shows the basic ideas by writing a jQuery plugin those concepts are not limited to jQuery at all. You can use this setup for any kind of frontend JavaScript project. To keep the example simple and understandable it focuses only on the most essentials aspects of the development process:
-
All the source code should be written in TypeScript with all the benefits like type checking, code completion etc.
-
The TypeScript source code should be structured into various modules to allow separation of concerns and reuse of certain classes or functions.
-
The TypeScript source code should be tested automatically using Karma and Jasmine with a minimum of extra configuration.
-
For usage in modern browsers the TypeScript source code should be compiled into ES2015 modules.
-
For better compatibility with older browsers and to facilitate loading in the web page the ES2015 modules should be bundled into a single JavaScript file using rollup.js.
-
The JavaScript bundle should be minified using UglifyJS to reduce load time.
-
All compiled resources (ES2015 modules, JavaScript bundles) should provide sourcemaps for easier debugging in the browser.
See the section Getting Started for quick instructions how to run the build process and how to try the example jQuery plugin.
See the Step-by-Step Tutorial for detailed explanations of the basic ideas behind this project setup.
-
Clone the Git repository.
-
Open a terminal in the project root.
-
Make sure you have installed a recent version of Node.js and NPM.
-
Install the dependencies by running:
npm install
. -
Build the project by running:
npm run build
. -
Execute the tests by running:
npm test
. -
Open the file
dist/example.html
in your browser. See the jQuery plugin in action.
TypeScript code cannot be executed in the browser directly. Before it can be used in a web page it must be compiled into pure JavaScript code. So we need to set up some kind of build process for the TypeScript compilation.
The TypeScript compiler is available as a NPM module and therefore it is obvious the create a NPM project and install the compiler as a development dependency. All we need to do is:
-
Install a recent version of Node.js. I would recommend to use the LTS version because it is quite settled and should work with most of the libraries you may use in your project. The installation of Node.js also includes NPM, the package manager that we will be using for the build process and dependency management.
-
Create a new directory where we would like to store our project in.
-
Open a terminal in our project directory. Leave it open as we will have to execute various commands throughout this tutorial.
-
Run the command:
npm init
. This will prompt for some basic project information which can be changed at any time later on. Keep pressing ENTER if you are not sure what to do.
Finally, we will have a file package.json
in our project directory. This file is typical for all NPM projects and contains meta information about our project as well as the build scripts and a list of libraries that are required by the project.
Throughout this tutorial you will have to edit quite a lot of text files containing different kinds of source code (JavaScript, JSON). Even though it is possible to do all that with a simple text editor I would recommend to install a more sophisticated source code editor. You can start with the free and yet powerful Visual Studio Code if you are not sure what to choose. It has fantastic TypeScript support out-of-the-box.
Once we have initialized our NPM project we can install the TypeScript compiler.
-
Go to the terminal in our project directory.
-
Run the command:
npm install typescript --save-dev
.
You will notice a new directory node_modules
in our project. It is managed by NPM and contains all the libraries that our project depends on. Do not add or remove anything within this directory on your own because it is all handled by NPM.
Tip
|
You should exclude node_modules from your source code management. If you are using Git you should add a .gitignore file in your project directory which defines /node_modules as a resource to exclude.
|
Since the compiler is installed locally to our project we have to create a NPM script to be able to run the compilation from the command line.
-
Open the file
package.json
with the text editor. -
Add a new propery
compile
with the valuetsc
in thescripts
object:
{ ... "scripts": { ... "compile": "tsc" } ... }
This allows us to run the TypeScript compiler from the command line via npm run compile
. But do not execute it for now since we have not created a configuration yet.
We run the TypeScript compiler by simply executing the tsc
command in our project. Therefore the compiler needs some more information about where to look for source files and how the generated JavaScript code should look like. A very convenient way to configure the compiler is to put a tsconfig.json
file directly in our project directory.
-
Create a file
tsconfig.json
in our project directory. -
Open the file with the text editor and insert the following code:
{
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"target": "es2015",
"module": "es2015",
"moduleResolution": "node",
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"allowSyntheticDefaultImports": true,
"removeComments": true,
"sourceMap": true
}
}
"rootDir": "src"
-
Uses the
src
directory as the root directory for the resolution of source files. This ensures that our compiled JavaScript files in thedist
directory are organized in the same directory structure as in thesrc
directory. "outDir": "dist"
-
Writes the compiled JavaScript files to the
dist
directory. "target": "es2015"
-
Compiles the TypeScript code to JavaScript code that still utilizes ES2015 language features like lambda expressions. This language level is not supported by all browsers yet but we will convert it to a more compatible level later on.
"module": "es2015"
-
Compiles the TypeScript modules to ES2015 modules using the standardized
import/export
syntax. This module format is required because we want to use the compiled JavaScript files with rollup.js to create a single JavaScript file. "moduleResolution": "node"
-
Instructs the compiler to resolve non-relative module imports like
import $ from 'jquery'
with the Node.js mechanism thus looking for modules in thenode_modules
directory. This allows us to use modules installed with NPM in our TypeScript code. See the TypeScript handbook for further details. "noImplicitAny": true
-
Raises a compile error if any variable does not have an explicit type declaration and therefore the compiler would have to assume
any
for it. This helps to improve code stability because it enforces strict type checking. "noImplicitReturns": true
-
Raises a compile error if any function with a return type other than
void
orany
has branches that do not explicitly return any result. This may result in unexpected behavior and therefore it is reasonable to let the compiler check for such mistakes. "noImplicitThis": true
-
Raises a compile error if the keyword
this
is used in the code and the compiler does not know what kind of object it will be in that context. This can happen when you usethis
in functions which are not declared as methods of a class but by an interface. See the TypeScript handbook for an example and how to tell the compiler whatthis
means in a function. "allowSyntheticDefaultImports": true
-
Forces the compiler to accept default imports of modules which actually do not have any default export as it is the case with the jQuery type declaration. Without that option the statement
import $ from 'jquery'
would yield a compile error. "removeComments": true
-
Removes the source code comments from the compiled JavaScript files. This reduces their size and we still have sourcemaps for debugging, so comments are not needed in the output.
"sourceMap": true
-
Generates
.js.map
sourcemap files for all generated JavaScript files. This makes it easier to debug our source code when it runs in the browser.
Tip
|
If you want to dive into the TypeScript compiler options further please have a look at the documentation. |
Now that we have configured the TypeScript compiler we are ready to create a first source code file. Since we told the compiler to expect source files in the src
directory we should put all TypeScript files there (or in subdirectories beneath).
-
Create a directory
src
in our project directory. -
Create a new file
example-service.ts
in thesrc
directory. -
Open the new TypeScript file in the text editor and insert the following code:
export class ExampleService {
getExampleMessage(name: string): string {
return 'Hello, ' + name + '!';
}
}
This source code file defines a simple class which could be used in any project. Notice there is no dependency on jQuery yet. The keyword export
ensures that we can import
this class in other modules as we will do later. Also pay attention to the type declarations on the method parameter and the method itself. This allows for explicit type checking which can help identify errors in the code.
Now we can try a first build of our project.
-
Go to the terminal in our project directory.
-
Run the command:
npm run compile
. This will launch the TypeScript compiler since we provided thecompile
script in thepackage.json
.
After the command has been completed we should find a new directory dist
in our project directory. It contains the compiled JavaScript file example-service.js
and its corresponding sourcemap file example-service.js.map
.
Right from the beginning of our project we should write tests for all our source code. This will help us keeping the project stable even when it grows over time and makes it ready for continuous delivery.
We will use the JavaScript library Jasmine to define test cases and leverage the Karma runner to execute those tests. Explaining the two frameworks in detail is beyond the scope of this tutorial. We will focus on what is needed in our project to create a basic unit test.
-
Go to the terminal in our project directory.
-
Run the command:
npm install jasmine-core --save-dev
. This will install the Jasmine framework that we will use to define our test cases. -
Run the command:
npm install @types/jasmine --save-dev
. This will install the TypeScript declaration files for Jasmine. Those are required because Jasmine itself is not written in TypeScript but we want to write our unit tests in TypeScript as well. The declaration files provide hints for the TypeScript compiler so that it recogizes all the functions provided by Jasmine. -
Run the command:
npm install karma --save-dev
. This will install the Karma test runner. Think of it as a framework that creates a synthetic HTML page which loads our JavaScript files, starts an embedded web server to provide all those files, launches a browser to open the test page thus running the tests and collects results from test frameworks like Jasmine. -
Run the command:
npm install karma-jasmine --save-dev
. This will install the Jasmine framework support for Karma. -
Run the command:
npm install karma-phantomjs-launcher --save-dev
. This will install the PhantomJS browser support for Karma. PhantomJS is a headless browser which does not open any windows and works perfectly for the execution of JavaScript unit tests. No need to install that browser on our own. The launcher pulls it as a dependency. -
Run the command:
npm install karma-typescript --save-dev
. This will install a TypeScript preprocessor for Karma. So we can send our TypeScript source code directly to the test runner without having to compile them to JavaScript in advance. It also provides a reporter which generates a test coverage report to help us identify what source code is executed by our tests.
Once we have installed all the tools and frameworks required for running the tests we must create a configuration file karma.conf.js
to give the Karma runner some hints which files to include and how to run our tests.
-
Create a file
karma.conf.js
in our project directory. -
Open the configuration file with the text editor and insert the following code:
module.exports = function (config) {
config.set({
basePath: '',
browsers: ['PhantomJS'],
frameworks: ['phantomjs-shim', 'jasmine', 'karma-typescript'],
files: [
'src/**/*.ts'
],
preprocessors: {
'**/*.ts': ['karma-typescript']
},
karmaTypescriptConfig: {
compilerOptions: {
noImplicitAny: true,
noImplicitReturns: true,
noImplicitThis: true,
allowSyntheticDefaultImports: true,
lib: ['DOM', 'ES5', 'ScriptHost', 'ES2015.Core', 'ES2015.Iterable']
}
},
reporters: ['progress', 'karma-typescript'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: false,
singleRun: true,
concurrency: Infinity
})
}
The configuration is exported as a typical Node.js JavaScript module. Some noteworthy entries:
browsers: ['PhantomJS']
-
Uses the PhantomJS browser to execute our tests. This is especially useful for continous integration because we do not have to install more complex browsers like Chrome or Firefox in our CI server.
frameworks: ['phantomjs-shim', 'jasmine', 'karma-typescript']
-
Loads the framework plugins for ES2015 shims, Jasmine and TypeScript which we have installed as separate NPM modules.
files: ['src/**/*.ts']
-
Includes all TypeScript source files beneath the
src
directory in the test runner. preprocessors: {'**/*.ts': ['karma-typescript']}
-
Sends all TypeScript files to the preprocessor which automagically transforms them into JavaScript that can be loaded in the test page.
karmaTypescriptConfig: {…}
-
Configures the TypeScript preprocessor. We have to make sure that it uses the same compiler restrictions as our normal compilation process because it does not honor the
tsconfig.json
file in our project directory. CAUTION: Do not override thetarget
ormodule
options since the preprocessor must set its own values to work properly. Nevertheless, we have to tell the compiler that some newer ES2015 features are available in the browser because we use shims. reporters: ['progress', 'karma-typescript']
-
Processes the test results with the built-in
progress
reporter to display test progress in the console and with the TypeScript reporter to create a coverage report. port: 9876
-
Starts the embedded web server on port 9876. If this conflicts with your system for some reason you should change it to a different value.
Tip
|
If you want to learn more about the Karma configuration please have a look at the documentation. |
Since Karma is also installed locally in our project we will have to create NPM script to run the tests from the command line.
-
Open the file
package.json
with the text editor. -
There should already be a property
test
in thescripts
object (if not create a new property). Set the value of thetest
script tokarma start
:
{ ... "scripts": { ... "test": "karma start" } ... }
This allows us to run Karma from the command line via npm test
. Note that there is no run command here because test
is a default NPM script. Do not run the test now because we have not created a specification yet.
I would recommend to create a test specification file next to each TypeScript source file that contains testable code. The file should be named exactly like the source code file but with .spec.ts
extension.
-
Create the file
example-service.spec.ts
in thesrc
directory of our project. -
Open the specification file with the text editor and insert the following code:
import { ExampleService } from './example-service';
describe('ExampleService', () => {
it('should return a greeting for the given name', () => {
let exampleService = new ExampleService();
let messageText = exampleService.getExampleMessage('Frank');
expect(messageText).toBe('Hello, Frank!');
});
});
We imported the service class from the previously created module using an appropriate import
statement. Notice the ./
in the module name which is a relative reference and so the compiler knows that the module file can be found in the same directory as the specification file. Then we used the describe
function of the Jasmine framework to define a test suite. Typically it should be named like the class or function under test because that will produce nicely readable output if tests fail. Finally, we created a first test case by using Jasmine’s it
function. It should be given a brief but understandable description of the expected behavior that is verified by this test and a function to execute the test steps. Within a test case we can make assertions by using the expect
function.
Now the time has come to run the first test case.
-
Go to the terminal in our project directory.
-
Run the command:
npm test
. This will fire up the Karma test runner with the configuration we defined in thekarma.conf.js
.
Finally, we should see a message that one test has been executed successfully. Like this:
Executed 1 of 1 SUCCESS (0.004 secs / 0.001 secs)
You will notice a new directory coverage
in our project directory which contains the test coverage report. There we can see which functions have been executed during the tests and where we should better add some more test cases.
As the project grows larger it is desirable to split the source code across multiple files for several reasons:
-
Separation of concerns: As a best practice we should write classes and functions that are dedicated to a well-defined and manageable purpose. For example, why should some higher level busines logic care about how data is fetched from the server? There should be some other module which simply provides functions to retrieve business data and handles all the technical stuff under the hood.
-
Code reuse: We might write some more general classes or functions which can be reused across multiple parts of our project or even reused in other projects as well.
-
Better testability: Small and dedicated functions can be tested more easily.
-
Allow developers to work more independently on certain parts of the code. Having one large source code file would lead to merge conflicts all the time.
TypeScript and ES2015 support the concept of modules to allow source code to be split across multiple files. As we have done already in the first test case we can import classes, functions or even simple values from other source files by using respective import
statements. Note that we can only import resources that the modules export by using the export
keyword. See the TypeScript handbook for more insights on modules.
Let’s create another TypeScript file which makes use of the class we previously created.
-
Create a new file
example-plugin.ts
in thesrc
directory of our project. -
Open the new TypeScript file in the text editor and insert the following code:
import { ExampleService } from './example-service';
let example = function () {
let exampleService = new ExampleService();
let messageText = exampleService.getExampleMessage('Frank');
};
So we import the class ExampleService
from the module file ./example-service.ts
to use it in our plugin module. Our plugin only depends on the API that is exposed by the service class, so as long as we keep the API stable we ca change the internals of the service without having to touch the plugin.
Once we have created a separate module for our jQuery plugin we can use jQuery itself to register an extension.
-
Go to the terminal in our project directory.
-
Run the command
npm install jquery --save
. This will install jQuery as a normal dependency to our project. -
Run the command:
npm install @types/jquery --save-dev
. This will install the type declarations for jQuery to allow the TypeScript compiler to recogize all the functions provided by the framework. -
Open the file
example-plugin.ts
in the text editor and insert the following code:
import $ from 'jquery';
import { ExampleService } from './example-service';
$.fn.examplePlugin = function (this: JQuery): JQuery {
let exampleService = new ExampleService();
let messageText = exampleService.getExampleMessage(this.text());
this.text(messageText);
return this;
};
Important
|
Extending the $.fn object using TypeScript requires some additional declaration to be present in the project (see src/example-plugin-interface.d.ts ). This is required to tell the compiler that a new property/function will be available on the type JQuery and how it looks like.
|
The new thing here is the import
of the jquery
module which allows us to make use of the well-known dollar function. With the statement $.fn.examplePlugin = function …
we register an extension that can be used on any jQuery object like this:
$('.some-selector').examplePlugin(); // Executes our plugin function
Notice the this
parameter in our function declaration is not a real parameter but a TypeScript helper to define the type of this
within that function. This paramater is removed by the TypeScript compiler when it generates the JavaScript file.
A jQuery plugin function should typically return this
to allow chaining like that:
$('.some-selector').examplePlugin().css('color', 'red');
Important
|
TypeScript lambda expressions are not suitable for the declaration of jQuery plugin functions because they override the this context of the function so that we would not be able to access the jQuery object anymore.
|
So far we have created the two modules example-service.ts
and example-plugin.ts
which can be compiled to their respective ES2015 modules by running npm run compile
on the command line. But how can these modules be used in a normal web page? Up to now there is no native browser support for JavaScript modules (no matter which format), so we have to choose one of the following solutions:
-
Include a module loader into our web page (e.g. SystemJS) and configure it to load our ES2015 modules. This will typically generate one AJAX request per module because the loader requests each module as soon as it is imported.
-
Use a module bundler (e.g. rollup.js) during our build process to create a bundle JavaScript file which contains all of our modules and can be included into our web page with a simple
<script>
tag.
There are pros and cons for each of these approaches. While the first one seems promising to me once all major browsers natively support ES2015 modules and HTTP/2 has been rolled out, I would stick to the second solution for now because this makes it really easy to integrate our jQuery plugin into any web page without the hassle of a cumbersome module loader configuration.
We will be using rollup.js to package everything together. This requires some configuration in our project.
-
Go to the terminal in our project directory.
-
Run the command:
npm install rollup --save-dev
. This will install the rollup.js module bundler as a development dependency. -
Run the command:
npm install rollup-plugin-babel --save-dev
. This installs the Babel plugin for rollup.js that we will use to transpile our ES2015 modules to the more compatible ES5 language level. -
Run the command:
npm install rollup-plugin-sourcemaps --save-dev
. This installs the Sourcemap plugin for rollup.js so that we can still debug our TypeScript code in the browser even when we are using the JavaScript bundle. -
Run the commands:
npm install babel-preset-env --save-dev
andnpm install babel-plugin-external-helpers --save-dev
. This will install a preset and helper modules for Babel which be used by the Babel plugin.
As with the TypeScript compiler and Karma we need to configure rollup.js with a separate configuration file to tell the bundler which module to use as the entry point and what kind of bundle we would like to generate.
-
Create a new file
rollup.config.js
in our project directory. -
Open the new configuration file with the text editor and insert the following code:
import babel from 'rollup-plugin-babel';
import sourcemaps from 'rollup-plugin-sourcemaps';
export default {
input: 'dist/example-plugin.js',
output: {
file: 'dist/example-plugin-bundle.js',
format: 'iife',
sourcemap: true,
globals: {
jquery: 'jQuery'
}
},
external: [
'jquery'
],
plugins: [
babel({
exclude: 'node_modules/**',
}),
sourcemaps()
]
};
The configuration is a Node.js-style module which exports an object with the configuration values.
input: 'dist/example-plugin.js'
-
Uses the compiled ES2015 module of our jQuery plugin (compiler output of
example-plugin.ts
) as the entry point to start the bundling process. Starting from this module the bundler will work through the imports and package everything together. output.file: 'dist/example-plugin-bundle.js'
-
Writes the generated JavaScript bundle to the file
example-plugin-bundle.js
in thedist
directory of our project. output.format: 'iife'
-
Instructs rollup.js to generate the bundle in IIFE format (Immediately Invoked Function Expression) which is the right format to include the JavaScript file in a HTML page using a simple
<script>
tag. output.sourcemap: true
-
Produces a sourcemap file for the JavaScript bundle to allow easier debugging of our code even when only the single JavaScript file is loaded in the browser.
external: ['jquery'], output.globals: {jquery: 'jQuery'}
-
Tells the bundler that the module
jquery
is an external dependency which should not be included in the bundle and that this external module is defined in the global variablejQuery
. So we will have to include jQuery with a separate<script>
tag into our web page before we include our bundle. plugins: […]
-
Enables the Babel plugin to transpile our JavaScript bundle to the more compatible ES5 language level and to load the sourcemaps of the ES2015 modules we are using as input. This ensures that we can even debug our TypeScript code when we use the bundle in a web page.
Tip
|
For more details on the configuration options please refer to the documentation. |
Since we use the Babel plugin to scale the JavaScript language level down to ES5 we have to tell the transpiler what preset to use.
-
Create a new file
.babelrc
in our project directory. -
Open the new configuration file with the text editor and insert the following code:
{
"presets": [
["env", {
"modules": false
}]
],
"plugins": [
"external-helpers"
]
}
Since rollup.js is installed locally to our project we have to create a NPM script to allow bundling our modules from the command line.
-
Open the file
package.json
with the text editor. -
Add a new propery
bundle
with the valuerollup -c
in thescripts
object:
{ ... "scripts": { ... "bundle": "rollup -c" } ... }
Now we can start rollup.js from the command line via npm run bundle
. But be aware that this script requires our TypeScript sources to have been compiled. So we would always have to run npm run compile
first before we can create the bundle. Therefore it is convenient to create another script build
in our package.json
which executes both scripts as a sequence:
{ ... "scripts": { ... "build": "npm run compile && npm run bundle" } ... }
So let’s give it a try.
-
Go to the terminal in our project directory.
-
Run the command:
npm run build
. This should first execute the TypeScript compilation and once it has been completed successfully it will start the rollup.js bundling.
Finally, there should be the bundle JavaScript file example-plugin-bundle.js
in the dist
directory of our project. This file can be included right away in any web page that also includes jQuery. For example:
<!DOCTYPE html>
<html>
<body>
<p>Frank</p>
<script src="https://code.jquery.com/jquery-3.3.1.js"></script>
<script src="example-plugin-bundle.js"></script>
<script>
$('p').examplePlugin();
</script>
</body>
</html>
So far, so good. The JavaScript bundle we have created can be used as it is but it is still quite large because the JavaScript code is pretty formatted. This is not needed in a production environment. So we should further reduce the bundle size by creating a minified version with UglifyJS.
-
Go to the terminal in our project directory.
-
Run the command:
npm install uglify-js --save-dev
. This will install UglifyJS as a development dependency in our project. -
Open the file
package.json
with the text editor. -
Add a new propery
minify
in thescripts
object:
{ ... "scripts": { ... "minify": "uglifyjs dist/example-plugin-bundle.js --output dist/example-plugin-bundle.min.js --source-map \"filename='dist/example-plugin-bundle.min.js.map',url='example-plugin-bundle.min.js.map',content='dist/example-plugin-bundle.js.map'\"" } ... }
As you can see the script is a little bit more complicated because UglifyJS does not provide any built-in way to use a separate configuration file.
- First parameter
dist/example-plugin-bundle.js
-
Defines the input file that should be minified. We use our JavaScript bundle produced by rollup.js.
--output dist/example-plugin-bundle.min.js
-
Writes the minified version of the JavaScript bundle to the file
example-plugin-bundle.min.js
in thedist
directory. --source-map "filename=,url=,content="
-
Configures various options for the generation of sourcemaps which help debugging in the browser even when we use the minified JavaScript bundle in the web page. The
filename
option tells UglifyJS to generate a separate sourcemap fileexample-plugin-bundle.min.js.map
for the minified JavaScript. Theurl
option defines which URL should be written to the minified JavaScript to reference the sourcemap file. Thecontent
option tells UglifyJS that there is already a sourcemap for the input file generated by rollup.js which should be used to even map the minified code directly to the TypeScript sources.
Once the script has been added to the package.json
file we can run the minification from the command line via npm run minify
. We can also integrate it into the build
script sequence as an additional task:
{ ... "scripts": { ... "build": "npm run compile && npm run bundle && npm run minify" } ... }
Now see the whole build process in action.
-
Go to the terminal in our project directory.
-
Run the command:
npm run build
.
There should be an additional file example-plugin-bundle.min.js
in the dist
directory of our project. This minified version of the JavaScript bundle can be included in any web page like the normal one:
<!DOCTYPE html>
<html>
<body>
<p>Frank</p>
<script src="https://code.jquery.com/jquery-3.3.1.js"></script>
<script src="example-plugin-bundle.min.js"></script>
<script>
$('p').examplePlugin();
</script>
</body>
</html>
It is debatable if that workflow with three steps (TypeScript compilation, rollup.js bundling, UglifyJS minification) is really a good solution, especially if all you want is the minified bundle. There are plugins for rollup.js which could be used to reduce everything to a single step:
-
TypeScript plugin for rollup.js to allow for direct processing of TypeScript source files.
-
UglifyJS plugin for rollup.js to allow for direct minification of the produced bundle.
Moreover, there are additional development helpers which have not been demonstrated in this example project to keep it simple. It is always a good idea to have some kind of live reload server which really helps during development when you have to make frequent changes to your source code. See Lite-Server for an easy solution.