TypeScript build process
One of the framework's goals is to provide first-class support for TypeScript. This goes beyond the static types and IntelliSense you can enjoy while writing the code.
We also ensure that you never have to install any additional build tools to compile your code during development or for production.
This guide assumes that you have some knowledge about TypeScript and the build tools ecosystem.
Common bundling approaches
Following are some of the common approaches for developing a Node.js application written in TypeScript.
Using tsc
The simplest way to compile your TypeScript code to JavaScript is using the official tsc
command line.
- During development, you can compile your code in the watch mode using the
tsc --watch
command. - Next, you may grab
nodemon
to watch the compiled output (JavaScript code) and restart the HTTP server on every change. By this time, you have two watchers running. - Also, you may have to write custom scripts to copy static files like templates to the build folder so that your runtime JavaScript code can find and reference it.
Using ts-node
ts-node does improve the development experience, as it compiles code in memory and does not output it on the disk. Thus, you can combine ts-node
and nodemon
and run your TypeScript code as a first-class citizen.
However, for larger applications, ts-node
may get slow as it has to recompile the entire project on every file change. In contrast, tsc
was re-building only the changed file.
Do note, ts-node
is a development-only tool. So you still have to compile your code to JavaScript using tsc
and write custom scripts to copy static files for production.
Using Webpack
After trying the above approaches, you may decide to give Webpack a try. Webpack is a build tool and has a lot to offer. But, it comes with its own set of downsides.
- Very first, using Webpack to bundle the backend code is an overkill. You may not even need 90% of the Webpack features created to serve the frontend ecosystem.
- You may have to repeat some of the configurations in the
webpack.config.js
config andtsconfig.json
file mainly, which files to watch and ignore. - Also, we are not even sure if you can instruct Webpack NOT TO bundle the entire backend into a single file.
AdonisJS approach
We are not a big fan of over-complicated build tools and bleeding-edge compilers. Having a calm development experience is way more valuable than exposing config to tune every single knob.
We started with the following set of goals.
- Stick to the official compiler of TypeScript and do not use any other tools like
esbuild
orswc
. They are great alternatives, but they don't support some of the TypeScript features (ex. the Transformers API ). - The existing
tsconfig.json
file should manage all the configurations. - If the code runs in development, then it should run in production too. Meaning, do not use two completely different development and production tools and then teach people how to adjust their code.
- Add lightweight support for copying static files to the final build folder. Usually, these will be the Edge templates.
- Make sure the REPL can also run the TypeScript code as a first-class citizen. All of the above approaches, except
ts-node
, cannot compile and evaluate the TypeScript code directly.
In-memory development compiler
Similar to ts-node, we created the @adonisjs/require-ts
module. It uses the TypeScript compiler API, meaning all the TypeScript features work, and your tsconfig.json
file is the single source of truth.
However, @adonisjs/require-ts
is slightly different from ts-node
in the following ways.
- We do not perform any type-checking during development and expect you to rely on your code editor for the same.
- We store the compiled output in a cache folder. So the next time when your server restarts, we do not recompile the unchanged files. This does improve the speed dramatically.
- The cached files have to be deleted at some point. The
@adonisjs/require-ts
module exposes the helper methods that AdonisJS file watcher uses to clear the cache for the recently changed file. - Clearing cache is only essential for claiming the disk space. It does not impact the behavior of the program.
Every time you run node ace serve --watch
, we start the HTTP server along with the in-memory compiler and watch the filesystem for file changes.
Standalone production builds
You build your code for production by running the node ace build --production
command. It performs the following operations.
- Clean the existing
build
directory (if any). - Build your frontend assets using Webpack Encore or Vite (only if it is installed).
- Use the TypeScript compiler API to compile the TypeScript code to JavaScript and write it inside the
build
folder. This time, we do perform type checking and report the TypeScript errors. - Copy all the static files to the
build
folder. The static files are registered inside the.adonisrc.json
file under themetaFiles
array. - Copy the
package.json
andpackage-lock.json
/yarn.lock
to thebuild
folder. - Generate the
ace-manifest.json
file. It contains an index of all the commands your project is using. - That is all.
Why do we call it a standalone build?
After running the build
command, the output folder has everything you need to deploy your application in production.
You can copy the build
folder without your TypeScript source code, and your application will work just fine.
Creating a standalone build
folder does help in reducing the size of code that you deploy on your production server. This is usually helpful when you package your app as a Docker image. However, there is no need to have both the source and build output in your Docker image and keep it lightweight.
Points to keep in mind
- Post-build, the output folder becomes the root of your JavaScript application.
- You must always
cd
into thebuild
folder and then run your app.cd buildnode server.js - You must install production-only dependencies inside the
build
folder.cd buildnpm ci --production - We do not copy the
.env
file to the output folder. Because the environment variables are not transferable, you must define environment variables for production separately.