Ini bisa menjadi kerangka kerja javascript lainnya.

Musim panas lalu, dalam proses mempersiapkan sebuah artikel untuk Habr, saya tidak terlalu malas untuk mengemas template saya untuk aplikasi backend di Node.js dalam paket npm, menjadikannya sebuah cli-utility untuk memulai cepat.


Tidak ada harapan bahwa orang lain selain saya akan menggunakan paket ini pada awalnya. Namun, ketika saya memutuskan untuk memperbarui templat dengan memperkenalkan fitur yang saya butuhkan, saya perhatikan bahwa paket npm memiliki beberapa lusinan unduhan per minggu, dan proyek di github memiliki 12 bintang. Disampaikan dalam kebaikan oleh orang-orang baik, tentunya untuk mendukung saya, bukan proyek. Hanya 12 bintang, tapi itu cukup bagiku untuk memutuskan bahwa aku akan mengembangkan bangkai seolah-olah itu diperlukan bukan hanya untukku.


Terlepas dari kenyataan bahwa pada awalnya saya memutuskan untuk membuat kerangka kerja ringan untuk aplikasi backend, selama proses pengembangan saya berhasil meyakinkan diri sendiri bahwa motor ini tidak diperlukan. Dan karcass itu seharusnya tidak menjadi kerangka kerja, tetapi alat universal untuk membuat aplikasi dari template.


gambar


Dalam versi pertama, logika naskah naskah kuno itu primitif.


  1. .
  2. karcass template .
  3. , ( - , ) ( ).
  4. , npm install.

:


gambar


. , .


, : Application , , . , :


Application.ts
import Express from 'express'
import { AbstractConsoleCommand } from './Base/Console/AbstractConsoleCommand'
import { DbService } from './Database/Service/DbService'
import { HelpCommand } from './Base/Console/HelpCommand'
import { LoggerService } from './Logger/Service/LoggerService'
import { IssueService } from './Project/Service/IssueService'
import { GitlabService } from './Gitlab/Service/GitlabService'
import { LocalCacheService } from './Base/Service/LocalCacheService'
import { ProjectService } from './Project/Service/ProjectService'
import { GroupService } from './Project/Service/GroupService'
import { UserService } from './User/Service/UserService'
import { UpdateProjectsCommand } from './Gitlab/Console/UpdateProjectsCommand'
import { CreateMigrationCommand } from './Database/Console/CreateMigrationCommand'
import { MigrateCommand } from './Database/Console/MigrateCommand'
import { MigrateUndoCommand } from './Database/Console/MigrateUndoCommand'
import IssueController from './Project/Controller/IssueController'
import fs from 'fs'

export class Application {
    public http!: Express.Express

    // Services
    public localCacheService!: LocalCacheService
    public loggerService!: LoggerService
    public dbService!: DbService
    public gitlabService!: GitlabService
    public issueService!: IssueService
    public projectService!: ProjectService
    public groupService!: GroupService
    public userService!: UserService

    // Commands
    public helpCommand!: HelpCommand
    public createMigrationCommand!: CreateMigrationCommand
    public migrateCommand!: MigrateCommand
    public migrateUndoCommand!: MigrateUndoCommand
    public updateProjectsCommand!: UpdateProjectsCommand

    // Controllers
    public issueController!: IssueController

    public constructor(public readonly config: IConfig) {
        if (config.columns.length < 2) {
            throw new Error('There are too few columns :-(')
        }
    }

    public async run() {
        this.initializeServices()
        if (process.argv[2]) {
            this.initializeCommands()
            for (const command of Object.values(this)
                .filter((c: any) => c instanceof AbstractConsoleCommand) as AbstractConsoleCommand[]
            ) {
                if (command.name === process.argv[2]) {
                    await command.execute()
                    process.exit()
                }
            }
            await this.helpCommand.execute()
            process.exit()
        } else {
            this.runWebServer()
        }
    }

    protected runWebServer() {
        this.initCron()
        this.http = Express()
        this.http.use('/', Express.static('vue/dist'))
        this.http.use((req, res, next) => {
            if (req.url.indexOf('/api') === -1) {
                res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate')
                res.header('Expires', '-1')
                res.header('Pragma', 'no-cache')
                return res.send(fs.readFileSync('vue/dist/index.html').toString())
            }
            next()
        })
        this.http.use(Express.urlencoded())
        this.http.use(Express.json())
        this.http.listen(this.config.listen, () => console.log(`Listening on port ${this.config.listen}`))

        this.initializeControllers()
    }

    protected initCron() {
        if (this.config.gitlab.updateInterval) {
            setInterval(async () => {
                if (!this.updateProjectsCommand) {
                    this.updateProjectsCommand = new UpdateProjectsCommand(this)
                }
                await this.updateProjectsCommand.execute()
            }, this.config.gitlab.updateInterval * 1000)
        }
    }

    protected initializeServices() {
        this.localCacheService = new LocalCacheService(this)
        this.gitlabService = new GitlabService(this)
        this.loggerService = new LoggerService(this)
        this.dbService = new DbService(this)
        this.issueService = new IssueService(this)
        this.projectService = new ProjectService(this)
        this.groupService = new GroupService(this)
        this.userService = new UserService(this)
    }

    protected initializeCommands() {
        this.helpCommand = new HelpCommand(this)
        this.createMigrationCommand = new CreateMigrationCommand(this)
        this.migrateCommand = new MigrateCommand(this)
        this.migrateUndoCommand = new MigrateUndoCommand(this)
        this.updateProjectsCommand = new UpdateProjectsCommand(this)
    }

    protected initializeControllers() {
        this.issueController = new IssueController(this)
    }

}

ProjectService.ts
import { AbstractService } from '../../Base/Service/AbstractService'
import { Project } from '../Entity/Project'

export class ProjectService extends AbstractService {

    public get projectRepository() {
        return this.app.dbService.connection.getRepository(Project)
    }

    public async updateProjects(allTime = false) {
        await this.app.groupService.updateGroups()
        for (const data of await this.app.gitlabService.getProjects()) {
            let project = await this.getProject(data.id)

            if (!project) {
                project = this.projectRepository.create({ id: data.id })
            }
            project.name = data.name
            project.url = data.web_url
            project.updatedTimestamp = Math.round(new Date(data.last_activity_at).getTime() / 1000)
            project.groupId = data.namespace && data.namespace.kind === 'group' ? data.namespace.id : null
            await this.projectRepository.save(project)
            await this.app.issueService.updateProjectIssues(project, allTime)
        }
    }

    public async getProject(id: number): Promise<Project|undefined> {
        return id ? this.app.localCacheService.get(`project.${id}`, () => this.projectRepository.findOne(id)) : undefined
    }

}

, , , . , DI-, cli.


Application.ts , ยซ ยป. .


Application.ts
import CreateExpress, { Express } from 'express';
import { TwingEnvironment, TwingLoaderFilesystem } from 'twing';
import { Container } from '@karcass/container';
import { Cli } from '@karcass/cli';
import { Connection, createConnection } from 'typeorm';
import { CreateMigrationCommand, MigrateCommand, MigrateUndoCommand } from '@karcass/migration-commands';
import { createLogger } from './routines/createLogger';
import { Logger } from 'winston';
import { FrontPageController } from './SampleBundle/Controller/FrontPageController';
import { Message } from './SampleBundle/Entity/Message';
import { MessagesService } from './SampleBundle/Service/MessagesService';

export class Application {
    private container = new Container();
    private console = new Cli();
    private controllers: object[] = [];
    private http!: Express;

    public constructor(public readonly config: IConfig) { }

    public async run() {
        await this.initializeServices();

        if (process.argv[2]) {
            this.initializeCommands();
            await this.console.run();
        } else {
            this.runWebServer();
        }
    }

    protected runWebServer() {
        this.http = CreateExpress();
        this.http.use('/public', CreateExpress.static('public'));
        this.http.use(CreateExpress.urlencoded());
        this.http.listen(this.config.listen, () => console.log(`Listening on port ${this.config.listen}`));

        this.container.add<Express>('express', () => this.http);
        this.container.add(TwingEnvironment, () => new TwingEnvironment(new TwingLoaderFilesystem('src')));

        this.initializeControllers();
    }

    protected async initializeServices() {
        await this.container.addInplace<Logger>('logger', () => createLogger(this.config.logdir));
        const typeorm = await this.container.addInplace(Connection, () => createConnection({
            type: 'sqlite',
            database: 'db/sample.sqlite',
            entities: ['build/**/Entity/*.js'],
            migrations: ['build/**/Migrations/*.js'],
            logging: ['error', 'warn', 'migration'],
        }));
        this.container.add('Repository<Message>', () => typeorm.getRepository(Message));
        this.container.add(MessagesService);
    }

    protected initializeCommands() {
        this.console.add(CreateMigrationCommand, () => new CreateMigrationCommand());
        this.console.add(MigrateCommand, async () => new MigrateCommand(await this.container.get(Connection)));
        this.console.add(MigrateUndoCommand, async () => new MigrateUndoCommand(await this.container.get(Connection)));
    }

    protected async initializeControllers() {
        this.controllers.push(
            await this.container.inject(FrontPageController),
        );
    }

}

TypeScript :


FrontPageController.ts
import { Express } from 'express';
import { Dependency } from '@karcass/container';
import { TwingEnvironment } from 'twing';
import { AbstractController, QueryData } from './AbstractController';
import { MessagesService } from '../Service/MessagesService';

export class FrontPageController extends AbstractController {

    public constructor(
        @Dependency('express') protected express: Express,
        @Dependency(TwingEnvironment) protected twing: TwingEnvironment,
        @Dependency(MessagesService) protected messagesService: MessagesService,
    ) {
        super(express);

        this.onQuery('/', 'get', this.frontPageAction);
        this.onQuery('/', 'post', this.sendMessageAction);
    }

    public async sendMessageAction(data: QueryData) {
        await this.messagesService.addMessage(data.params.text);
        data.res.redirect('/');
    }

    public async frontPageAction() {
        if (await this.messagesService.isEmpty()) {
            await this.messagesService.createSampleMessages();
        }
        return this.twing.render('SampleBundle/Views/front.twig', {
            messages: await this.messagesService.getMessages(),
        });
    }

}

JavaScript, ยซยป:


protected async initializeControllers() {
    this.controllers.push(
        new FrontPageController(
            await this.container.get('express'),
            await this.container.get(TwingEnvironment),
            await this.container.get(MessagesService),
        ),
    );
}

template karcass, : . , .


: TemplateReducer.ts TemplateReducer.js, TemplateReducer, :


interface TemplateReducerInterface {
    getConfigParameters(): Promise<ConfigParametersResult>
    getConfig(): Record<string, any>
    setConfig(config: Record<string, any>): void
    getDirectoriesForRemove(): Promise<string[]>
    getFilesForRemove(): Promise<string[]>
    getDependenciesForRemove(): Promise<string[]>
    getFilesContentReplacers(): Promise<ReplaceFileContentItem[]>
    finish(): Promise<void>
    getTestConfigSet(): Promise<Record<string, any>[]>
}

, , , - , karcass , JavaScript/TypeScript. -. - webpack. , - create-react-app ... , vue create.


TemplateReducerInterface, , . karcass:


hello index.js :


console.log('Hello, [replacethisname]!')

TemplateReducer.js, karcass' :


const reducer = require('@karcass/template-reducer')
const Type = reducer.ConfigParameterType

class TemplateReducer extends reducer.AbstractTemplateReducer {
    getConfigParameters() {
        return [
            { name: 'name', description: 'Please enter your name', type: Type.string },
        ]
    }
    async getFilesContentReplacers() {
        return [
            { filename: 'index.js', replacer: (content) => {
                return content.replace('[replacethisname]', this.config.name)
            } },
        ]
    }
    async finish() {
        console.log(`Application installed, to launch it execute\n  cd ${this.directoryName} && node index.js`)it.`)
    }
}
module.exports = { TemplateReducer }

, , , โ€” @karcass/template-reducer, , package.json:


npm init && npm install @karcass/template-reducer

, getDependenciesForRemove, karcass .


karcass . , .


gambar


, karcass . github.com, :


npx karcass create helloworld https://github.com/karcass-ts/hello-world

, -, :


npx karcass create ooohhhh-ok-show-it

? , TemplateReducer helloworld- :


    getTestConfigSet() {
        return [
            { name: 'testname1' },
            { name: 'testname2' },
        ]
    }

:


npx karcass test www/karcass/hello

โ€” . , , , :


gambar


Saya memutuskan untuk tidak melakukan longride dengan deskripsi semua fitur karcass, tetapi untuk membuat catatan pencarian fakta kecil untuk tujuan presentasi dan mengumpulkan umpan balik: umpan balik apa pun akan berguna bagi saya untuk memahami ke mana harus pergi berikutnya dan apakah itu layak. Sementara itu, menulis dokumentasi adalah prioritas.


Repositori karcass di github ;

Source: https://habr.com/ru/post/undefined/


All Articles