October release - 2020

Alright! Here we go with another release of AdonisJS. This is a big one in terms of the development workflow you will be using moving forward.

Highlights

Steps to upgrade

Before we dive into the specifics and the motivation behind the changes. Let's quickly talk about the steps you will have to take to upgrade your application.

  1. We have recently encapsulated a lot of eco-system dependencies within the @adonisjs/core package and hence those dependencies can be removed from your project.

    Run the following command to remove @adonisjs/fold and @adonisjs/ace.

    npm uninstall @adonisjs/fold @adonisjs/ace
  2. Next, upgrade all dependencies to their latest alpha version. Do remember to install with the @alpha tag.

  3. Now since your application is not using @adonisjs/ace as a direct dependency, you must update your local commands inside the commands directory to use @adonisjs/core for importing the BaseCommand.

    - import { BaseCommand } from '@adonisjs/ace'
    + import { BaseCommand } from '@adonisjs/core/build/standalone'
  4. The commands handle method has been deprecated in favor of the the run method. It feels natural to say run the command vs saying handle the command.

  5. The process started by the command will close itself after the run method has finished executing. If you want your commands to stay alive, then you need to use the stayAlive flag on the settings object.

    class MyCommand extends BaseCommand {
    public static settings = {
    stayAlive: true,
    }
    }
  6. Also update the ./commands/index.ts file to use the following code snippet.

    import { listDirectoryFiles } from '@adonisjs/core/build/standalone'
    import Application from '@ioc:Adonis/Core/Application'
    export default listDirectoryFiles(__dirname, Application.appRoot, ['./commands/index'])
  7. If you are using the @adonisjs/auth package. You need to update the config/auth.ts file to lazy import the models used for finding the users.

    So begin by removing the top level import statement

    import User from 'App/Models/User'

    And move it inline next to the model property as follows:

    provider: {
    driver: 'lucid',
    model: () => import('App/Models/User')
    }
  8. Finally, we have deprecated the Env.getOrFail method in favor of the Env validations. You just need to find its usages and replace it with Env.get to avoid getting deprecation warnings.

  9. Using Japa as your test runner? Here are the instructions to upgrade the test runner to run tests using the TypeScript source directly.

In-memory TypeScript compilation

I am not a big fan of build tools or adding an additional step to prepare my code for getting executed. However, when using TypeScript there is no way to escape the process of compiling TypeScript to JavaScript since v8 is meant to run JavaScript only.

Initially, we did add a build step in which we compile TypeScript to JavaScript before starting the development server.

In the following screenshot, the first five steps involve compiling the code to JavaScript and copying some files to the build folder to start the HTTP server.

Even though the process seems logical, it has a lot of rough edges that will bite you once in a while. Many users created GitHub discussion threads lately expressing:

All this confusion is a result of a stale build folder and you have to make sure that one process is always running to keep the build output up to date.

Let's do something better

I have been banging my head lately to find some alternative which feels more natural and intuitive over this additional build step, and voila there is ts-node .

Ts-Node is a library to run typescript code directly without transpiling it first. But, I decided to not use it and instead write my version of it for the following reasons.

How in-memory compilation works?

Now that you are aware of the reasons for not using the ts-node, let's expand upon how in-memory compilation works and also the entire cache mechanism built to make subsequent runs faster.

So, if you combine the above two you can compile the typescript code on the fly. However, typescript does take some time to compile the code and this can make restarts slow. Let's visualize a standard development workflow.

Using cache

On-disk caching is the only solution to avoid re-compiling the entire project after a single file change. The following are the steps we perform for caching.

The term ignore the cache is important here. Since we don't remove the old cache there is going to be a time when the cache will end taking a lot of disk space.

To counter that, we expose an API from the @adonisjs/require-ts module that the file watchers can use to clear the entire cache or remove a single file from it.

Result

Finally, we end up with a development workflow that can run TypeScript source code directly and uses cache for faster restarts.

[video url="https://res.cloudinary.com/adonis-js/video/upload/v1603462870/adonisjs.com/quick-restart_cgjdfa.mp4 ", controls]

Validating environment variables

Environment variables play a very important role in our applications. Moreover, environment variables are not in control of our source code and we heavily rely on the outside factors to provide the correct values. For example:

We believe that validating the environment variable early in the lifecycle of running the application is a better approach instead of running an unstable system with in-correct or missing values.

To get started, create an env.ts file in the root of your application and paste the following code snippet inside it.

import Env from '@ioc:Adonis/Core/Env'
export default Env.rules({
HOST: Env.schema.string({ format: 'host' }),
PORT: Env.schema.number(),
APP_KEY: Env.schema.string(),
})

With the above file in place, AdonisJS will automatically load this file to perform the validations.

Getting intellisense

Since we are already performing the runtime validations, wouldn't it be great if we can also get IntelliSense for the validated environment variables? Well, we can:

Begin by creating a new file contracts/env.ts and paste the following code snippet inside it.

declare module '@ioc:Adonis/Core/Env' {
type CustomTypes = typeof import('../env').default
interface EnvTypes extends CustomTypes {}
}

Now, you will get proper IntelliSense when using the Env module.

Introducing AdonisJS REPL

REPL stands read–eval–print loop, a way to quickly execute single-line inputs and return the result. Node.js also has its REPL and to give it try, you can open up your terminal, type node, and press enter.

[video url="https://res.cloudinary.com/adonis-js/video/upload/v1603467681/adonisjs.com/node-repl_s6hsuz.mp4 ", controls]

Similar to the Node.js REPL, AdonisJS now also has its REPL with first-class primitives to let you interact with your application. To begin, install the @adonisjs/repl package from the registry.

[codegroup]

npm i @adonisjs/repl
yarn add @adonisjs/repl

[/codegroup]

Next, run the following command to set up the package.

node ace invoke @adonisjs/repl

That's all! Now you can run the node ace repl command to start the REPL session.

[video url="https://res.cloudinary.com/adonis-js/video/upload/v1603469977/adonisjs.com/adonis-repl_yiqo3z.mp4 ", controls]

Using Japa?

Projects setup to use Japa test runner also have to update the japaFile.ts file.