Rocket.Apps-engine 是 Rocket.Chat 的 app 引擎,使用 TypeScript 实现。下面将逐步分析其项目的源码。

package.json

对于任何一个陌生的前端项目,首先看其根目录下的 package.json 是一种良好的习惯。

{
  "name": "@rocket.chat/apps-engine",
  ...
  "main": "index",
  "typings": "index",
  "scripts": {
    "start": "gulp"
    ...
  },
  ...
  "files": [
    "client/**",
    "server/**",
    "definition/**"
  ],

首先我们可以从上述信息中得知,Rocket.Chat.Apps-engine 在 npm 发布的正式包名是 @rocket.chat/apps-engine,整个包包含的内容有client/**server/**definition/** 这三个目录。再根据 .gitignore 文件中的内容:

## dev environment stuff
/examples/
/dev-dist/
/data/
/tests/test-data/dbs
/client
/definition
/server

可知这三个文件夹是根据 src 目录下源代码自动编译生成出来的。

gulpfile.js

package.json 文件中我们知道了,npm start 实际上调用的就是 gulp 命令。实际上,对于大多数 TypeScript 项目来说,使用 Gulp 作为其项目自动构建工具都是一个不错的选择。

const gulp = require('gulp'); // gulp
const del = require('del'); // Delete files and directories using globs. https://www.npmjs.com/package/del
const sourcemaps = require('gulp-sourcemaps');
const tsc = require('gulp-typescript'); // tsc
const tslint = require('gulp-tslint'); // tslint
const shell = require('gulp-shell'); // shell
const bump = require('gulp-bump'); // Bump any Semver version in any file with gulp https://www.npmjs.com/package/gulp-bump
const fs = require('fs'); // node fs

const tsp = tsc.createProject('tsconfig.json'); // TypeScript project

// Remove the tests include from the project
const testIndex = tsp.config.include.indexOf('tests');
if (testIndex > -1) {
    tsp.config.include.splice(testIndex, 1);
}

function clean_generated() {
    return del(['./server', './client', './definition']);
}

function lint_ts() {
    return tsp.src().pipe(tslint({ formatter: 'verbose' })).pipe(tslint.report());
}

function compile_ts() {
    return tsp.src().pipe(sourcemaps.init())
            .pipe(tsp())
            .pipe(sourcemaps.write('.'))
            .pipe(gulp.dest('.'));
}

function update_ts_definition_version() {
    const { version } = JSON.parse(fs.readFileSync('./package.json'));

    return gulp.src('src/definition/package.json')
            .pipe(bump({ version }))
            .pipe(gulp.dest('src/definition/'));
}

//Tasks for getting it ready and publishing
function ts_definition_module_files() {
    return gulp.src(['LICENSE', 'src/definition/package.json'])
            .pipe(gulp.dest('definition/'));
}

function watch() {
    gulp.watch('src/**/*.ts', gulp.series(lint_ts, compile_ts));
    gulp.watch('package.json', gulp.series(update_ts_definition_version, ts_definition_module_files));
}

const compile = gulp.series(clean_generated, lint_ts, compile_ts, update_ts_definition_version, ts_definition_module_files);

gulp.task('clean', clean_generated);

gulp.task('compile', compile);

gulp.task('default', gulp.series(compile, watch));

gulp.task('pack', gulp.series(clean_generated, lint_ts, compile_ts, shell.task([
    'npm pack'
])));

gulp.task('publish', gulp.series(clean_generated, lint_ts, compile_ts, shell.task([
    'npm publish --access public && npm pack'
], [
    'cd definition && npm publish --access public && npm pack'
])));

这里作者一共定义了 4 个 Gulp task 分别是 cleancompliepackpubish。默认会依次执行 compliewatch。当源码发生变动时,会立即触发 lint_tscomplie_ts 进行 linting 和重新编译。

src/definition

项目的 README 文件对这一部分有详细介绍,Rocket.Chat Apps TypeScript Definitions。引擎为开发者提供了 4 种 Handlers,分别用于处理以下 4 种事件:

  1. PreEventPrevent 在事件发生前会执行,用来判断这个事件是否会被阻止
  2. PreEventExtend 在这个事件发生时,可以在不破坏原数据的情况下拓展数据
  3. PreEventModify 在这个事件发生时,可以修改任何数据(破坏性的)
  4. PostEvent 仅仅用于监听这个事件发生了(此时事件已经发生了)

下面是 definition 目录下文件作用的分析。首先是 accessors 目录,

definition
├── accessors
│   ├── IApiExtend.ts
│   ├── IAppAccessors.ts
│   ├── IConfigurationExtend.ts
│   ├── IConfigurationModify.ts
│   ├── IEnvironmentalVariableRead.ts
│   ├── IEnvironmentRead.ts
│   ├── IExternalComponents.ts
│   ├── IExternalComponent.ts
│   ├── IHttp.ts
│   ├── ILogEntry.ts
│   ├── ILogger.ts
│   ├── IMessageRead.ts
│   ├── IModify.ts
│   ├── index.ts <------------------------- accessors 的入口。每次新增一个
│   ├── IPersistenceRead.ts
│   ├── IPersistence.ts
│   ├── IRead.ts
│   ├── IRoomRead.ts
│   ├── IServerSettingRead.ts
│   ├── IServerSettingsModify.ts
│   ├── ISettingRead.ts
│   ├── ISettingsExtend.ts
│   ├── ISlashCommandsExtend.ts
│   ├── ISlashCommandsModify.ts
│   └── IUserRead.ts
├── api
│   ├── ApiEndpoint.ts
│   ├── IApiEndpointInfo.ts
│   ├── IApiEndpointMetadata.ts
│   ├── IApiEndpoint.ts
│   ├── IApiExample.ts
│   ├── IApi.ts
│   ├── index.ts
│   ├── IRequest.ts
│   └── IResponse.ts
├── app-schema.json
├── AppStatus.ts
├── App.ts
├── assets
│   ├── IAssetProvider.ts
│   ├── IAsset.ts
│   └── index.ts
├── client
│   ├── AppClientManager.ts
│   ├── AppServerCommunicator.ts
│   └── index.ts
├── example-app.json
├── IApp.ts
├── LICENSE
├── messages
│   ├── IMessageAction.ts
│   ├── IMessageAttachmentAuthor.ts
│   ├── IMessageAttachmentField.ts
│   ├── IMessageAttachmentTitle.ts
│   ├── IMessageAttachment.ts
│   ├── IMessageReaction.ts
│   ├── IMessage.ts
│   ├── index.ts
│   ├── IPostMessageDeleted.ts
│   ├── IPostMessageSent.ts
│   ├── IPostMessageUpdated.ts
│   ├── IPreMessageDeletePrevent.ts
│   ├── IPreMessageSentExtend.ts
│   ├── IPreMessageSentModify.ts
│   ├── IPreMessageSentPrevent.ts
│   ├── IPreMessageUpdatedExtend.ts
│   ├── IPreMessageUpdatedModify.ts
│   ├── IPreMessageUpdatedPrevent.ts
│   ├── MessageActionButtonsAlignment.ts
│   ├── MessageActionType.ts
│   └── MessageProcessingType.ts
├── metadata
│   ├── AppMethod.ts
│   ├── IAppAuthorInfo.ts
│   ├── IAppInfo.ts
│   ├── index.ts
│   └── RocketChatAssociations.ts
├── package.json
├── rooms
│   ├── index.ts
│   ├── IPostRoomCreate.ts
│   ├── IPostRoomDeleted.ts
│   ├── IPreRoomCreateExtend.ts
│   ├── IPreRoomCreateModify.ts
│   ├── IPreRoomCreatePrevent.ts
│   ├── IPreRoomDeletePrevent.ts
│   ├── IRoom.ts
│   └── RoomType.ts
├── settings
│   ├── index.ts
│   ├── ISetting.ts
│   └── SettingType.ts
├── slashcommands
│   ├── index.ts
│   ├── ISlashCommandPreview.ts
│   ├── ISlashCommand.ts
│   └── SlashCommandContext.ts
└── users
    ├── index.ts
    ├── IUserEmail.ts
    ├── IUser.ts
    ├── UserStatusConnection.ts
    └── UserType.ts