The Revite is the easy way to implement the application bootstrap through configurations. Approach borrowed from Laravel Framework.
- Installation
- Getting started
- Purpose of Revite development
- Basic Concepts
- Built-in contracts
- Dependencies
yarn add revite
tsconfig.json
{
"compilerOptions": {
"types": [
"revite/global"
]
}
}
- Create services and providers.
- Add these providers to the configuration.
- Load configuration via Revite
import {revite} from 'revite'
import config from '@revite/config'
revite.bootstrap(config)
See Demo Application
- Divide application to the separate isolated abstractions.
- Remove direct dependencies on external packages and environment variables to simplify further support, refactoring, and upgrade.
- Give the developer a tool to configure application parts.
- Simplify understanding of file locations in a project.
- Allow different versions of the application to run on the same codebase.
- Simplify testing of application parts through mocked services.
Contracts are the only one way to get the service instance. Abstract classes are used as contracts.
export abstract class NotesServiceContract {
notes: Service.Notes.Note[] = []
abstract get isEmpty(): boolean
abstract createNote(note: Service.Notes.Note): Promise<void>
abstract deleteNote(noteId: string): Promise<void>
}
import {revite} from 'revite'
import {NotesServiceContract} from "/~/services/notes";
async function getNotes() {
const notesService = await revite.resolve(NotesServiceContract)
return notesService.notes
}
- Each service can contain its configuration in a separate config file or
config
section inmain.ts
. - Configuration is a set of files:
config
- production
-- main.ts
-- auth.ts
- test
-- main.ts
-- auth.ts
main.ts
is a required file for the Revite bootstrap and should look like this:
import {defineConfig} from 'revite'
import {VueReactivityService} from 'revite/services/VueReactivityService'
export default defineConfig({
logger: {
level: 'debug',
},
reactivity: {
service: VueReactivityService,
},
packages: [
import('/~/packages/ViewsPackage'),
],
providers: [
import('/~/providers/EventServiceProvider'),
import('/~/services/render/RenderServiceProvider'),
import('/~/services/router/RouterServiceProvider'),
import('/~/services/auth/AuthServiceProvider'),
],
config: {
router: {
service: () => import('/~/services/router/versions/VueRouterService'),
} as Service.Router.Config,
},
})
config/prod/manifest.ts
export const config: Service.Manifest.Config = {
service: () => import('/~/services/manifest/versions/ManifestServiceV1'),
env: import.meta.env.VITE_ENV,
keys: {
recaptcha: '000',
},
}
There are two ways to load the configuration into Revite.
- Directly Load directory in source code.
import {revite} from 'revite'
revite.bootstrap(import.meta.globEager('./config/default/*.ts'))
- Apply Vite plugin
vite.config.ts
import {defineConfig} from 'vite'
import revite from 'revite/plugin'
export default defineConfig({
plugins: [
revite({
root: '/src/config',
use: process.env.TARGET_CONFIG as string,
})
],
})
main.ts
import {revite} from 'revite'
import config from '@revite/config'
revite.bootstrap(config)
Then start the application with:
env TARGET_CONFIG=prod yarn dev
config/prod
directory will be loaded
- Service providers are designed to link contracts to the services.
- Also, they can be used to bootstrap any code that you need.
import {BootContext, BeforeBootContext, RegisterContext, ServiceProvider} from 'revite'
import {AuthServiceContract} from '/~/services/auth'
import {UiServiceContract} from '/~/services/ui'
export class AuthServiceProvider extends ServiceProvider {
register(ctx: RegisterContext) {
const config: Service.Auth.Config = ctx.config('auth')
ctx.bind(AuthServiceContract).to({
service: config.service,
reactive: true,
singleton: true,
})
}
async beforeBoot(ctx: BeforeBootContext) {
const uiService = await ctx.resolve(UiServiceContract)
await uiService.setFetchStatus()
}
async boot(ctx: BootContext) {
const authService = await ctx.resolve(AuthServiceContract)
return authService.fetchUser()
}
}
ServiceProvider.register
Synchronous method to register services, bind events, and configuration to them.
export interface ResolveOptions {
loaded?: boolean
}
interface RegisterContext {
resolve<T extends AbstractConstructor>(contract: T, options?: ResolveOptions): Promise<InstanceType<T>>
resolveIfExist<T extends AbstractConstructor>(contract: T, options?: ResolveOptions): Promise<InstanceType<T> | undefined>
on(event: EventConstructor, listen: ListenerConstructor | ListenerConstructor[]): void
config<T>(name: string): T
bind<T extends AbstractConstructor>(contract: T): BindContext<T>
}
ctx.bind
usage
// The service itself is a contract
ctx.bind(HomeState).to({
reactive: true,
singleton: true,
// The factory is used when service contain links to other
async factory({Service}) {
const dashboardService = await ctx.resolve(DashboardService)
return () => new Service({
dashboardService,
})
},
})
ctx.bind(AuthServiceContract).to({
service: () => import('/~/services/auth/versions/AuthServiceV1')
reactive: true,
})
ServiceProvider.beforeBoot
Asynchronous method to inject any code to other services before providers start booting. For example, we want to inject routes to RouterService before it starts routing.
interface BeforeBootContext {
resolve<T extends AbstractConstructor>(contract: T, options?: ResolveOptions): Promise<InstanceType<T>>
resolveIfExist<T extends AbstractConstructor>(contract: T, options?: ResolveOptions): Promise<InstanceType<T> | undefined>
config<T>(name: string): T
}
ServiceProvider.boot
Asynchronous method to call any code after all providers will be registered and preloaded.
interface BootContext {
resolve<T extends AbstractConstructor>(contract: T, options?: ResolveOptions): Promise<InstanceType<T>>
resolveIfExist<T extends AbstractConstructor>(contract: T, options?: ResolveOptions): Promise<InstanceType<T> | undefined>
}
- Services should be extended from contracts.
- There are situations when the service itself does not contain any connections and can act as a contract.
import {NotesServiceContract} from '../'
export class MockNotesService extends NotesServiceContract {
...
}
import {Event} from 'revite'
export class NoteCreatedEvent extends Event {
constructor(public note: Service.Notes.Note) {
super()
}
}
import {Listener, revite} from 'revite'
import {NoteCreatedEvent} from '/~/events/NoteCreatedEvent'
import {NotifyServiceContract} from '/~/services/notify'
export class NoteCreatedNotify extends Listener {
async handle(event: NoteCreatedEvent) {
const notifyService = await revite.resolve(NotifyServiceContract)
notifyService.show({
title: 'Note created',
type: 'success',
text: 'ID ' + event.note.id,
})
}
}
import {RegisterContext, ServiceProvider} from 'revite'
import {NoteCreatedEvent} from '/~/events/NoteCreatedEvent'
import {NoteCreatedNotify} from '/~/listeners/NoteCreatedNotify'
export class EventServiceProvider extends ServiceProvider {
register(ctx: RegisterContext) {
ctx.on(NoteCreatedEvent, [
NoteCreatedNotify,
(event: NoteCreatedEvent) => {
// use event
}
])
/**
* Sequential execution of listeners
*/
ctx.on(NoteCreatedEvent, [
NoteCreatedNotify,
], {
sequential: true
})
/**
* Listeners will be executed one time in 500 ms
*/
ctx.on(NoteCreatedEvent, [
NoteCreatedNotify,
], {
wait: 500
})
}
}
import {Package} from 'revite'
export class ViewsPackage extends Package {
providers = [
import('/~/views/home/VueHomeViewProvider'),
import('/~/views/terms/vue/TermsViewProvider'),
import('/~/views/notes/VueNotesViewProvider'),
import('/~/views/report/VueReportViewProvider'),
]
}
These contracts are parts of Revite and can be configured in the main section.
- Default service uses console.log.
- The logger level can be configured.
- See LoggerServiceContract
import {defineConfig} from 'revite'
import {CustomLoggerService} from '/~/services/logger'
export default defineConfig({
logger: {
service: CustomLoggerService,
level: 'debug' // 'info' | 'warn' | 'error'
},
})
- Optional service.
- See ReactivityServiceContract
import {defineConfig} from 'revite'
import {VueReactivityService} from 'revite/services/VueReactivityService'
export default defineConfig({
reactivity: {
service: VueReactivityService,
},
})