adonisjs / rfcs

💬 Sharing big changes with community to add them to the AdonisJs eco-system
52 stars 6 forks source link

Enhancing Ignitor API #13

Closed thetutlage closed 3 years ago

thetutlage commented 5 years ago

Ignitor API

Ignitor has been bootstrapping AdonisJs applications for a while now. With the release of Adonis5, we want to improve on this module and make its API even more powerful.

First, we are moving the package to the core, and also an instance of ignitor will be passed to the Service providers, so that they can also use it's API transparently, without dropping boilerplate code to the application code.

Access points

Following are the ways in which providers and application code can access the ignitor instance.

Access ignitor inside providers

class ValidationProvider {
  constructor (ioc, ignitor) {
    this.ioc = ioc
    this.ignitor = ignitor
  }

  register () {
    this
      .ignitor
      .addDefaultDirectory('validators', 'Validators')

      this.ioc.bind('App/Validator', function () {
      })
  }
}

Access ignitor inside your app

The apps have to make use of the hooks file to access the ignitor instance.

Note: This file is loaded when your app is not ready or booted. So you have to wait for specific events before using providers in this file.

Following will fail

const Route = use('Route')
module.exports = function (ignitor) {
}

This is how you should write it

module.exports = function ({ hooks }) {

  hooks.after.providersBooted(() => {
    const Route = use('Route')
  })

}

API

Now let's move to the API of the ignitor. The goal of the module is to expose an extensible API, that your app and modules can use to hook into the lifecycle of the app and perform specific actions.

Hooks

Following is the list of hooks exposed by ignitor. Also, Hooks are synchronous, and you cannot run async code inside them.

ignitor.hooks[lifecycle][hookName](function () {
  // your code
})
Hook Description
before.ready Executed as soon as ignitor is ready to boot your app
after.ready The last hook executed by ignitor when everything has been done. In case of ace command, it's executed before the command is executed
before.providersRegistered Before providers have been registered
after.providersRegistered After providers have been registered
before.providersBooted Before provider have been booted.
after.providersBooted After providers have been booted.
before.httpServer Before starting the HTTP server
after.httpServer After starting the HTTP server
before.aceCommand Before the ace command
after.aceCommand After the ace command has been completed
after.preloaded After a file has been preloaded. You can define the file relative path from the app root.

Knowing the environment

The following properties can be used on the ignitor to know about the environment in which the process has been started.

Property Description
isAceCommand Process started as an ace command
isHttpServer Process started as the HTTP server
usingTypescript The project is using Typescript or not

Conventional Directories

AdonisJs uses conventional directories to store code for different parts of your application. For example:

Going forward, you will be able to configure them inside package.json file(See FAQ's). However, as a package author, you can also define conventional directories for your package.

For example AdonisJs validator uses app/Validators directory, so it will be able to tell ignitor about it as follows.

class ValidationProvider {
  register () {
    this
      .ignitor
      .addDefaultDirectory('validators', 'Validators')
  }
}

Then inside the codebase, Validator can use the Helpers module to make instance of Validators as follows:

Helpers.makeNamespace('validators', 'StoreUser')
// app/Validators/StoreUser.(js|ts)

Why define directory with ignitor and then use Helpers to make the namespace?

This is covered in the FAQ's below.

Preloading files

During the boot process, you can tell ignitor to preload some files. For example, The core of the framework tells it to preload start/routes.js file.

In the same way, package authors can define their files to be preloaded.

class RedisProvider {
  register () {
    this.ignitor.preload('start/redis.js')

    // do not raise error if file is missing (it's optional)
    this.ignitor.preload('start/redis.js', true)
  }
}

FAQ's

Why define directory with ignitor and then use Helpers to make the namespace?

The job of the ignitor is to collect your application details and then boot it. During the boot process, it will pass data to different core providers like Helpers and IoC container.

After that, the ignitor will destroy itself, and no other part of your application should hold access to it. This will help the Garbage collector to claim the memory used by ignitor and keeps the overall memory usage of your apps slow.

How to customize the conventional directories for my app?

You can customize the conventional set of directories inside package.json file.

This is all you can define inside the package.json.

{
  "adonisjs": {
    "directories": {
      "httpControllers": "Controllers/Http",
      "wsControllers": "Controllers/Ws",
      "models": "Models"
    },
    "autoload": {
      "App": "./app"
    },
    "extensions": {
      "config": ["ts"],
      "migrations": ["ts"]
    }
  }
}
PazzaVlad commented 5 years ago

Thanks for explaining all details! Customization of the conventional set of directories is sooo anticipated feature