actionherojs 的插件机制

actionherojs 的插件机制是比较强大的,基于插件我们可以直接实现npm包的安装与卸载,同时利用提供的reload api 实现
模块功能的生效(grouparoo 就利用了这些特性)

创建一个插件

  • 代码结构
    可以使用actionherojs 的cli 创建
 
├── README.md
├── package.json
├── public
└── dalong
└── index.html
├── src
├── actions
├── createChatRoom.ts
├── mydemo.ts
└── status.ts
├── bin
├── initializers
└── ah-dalong-ui.ts
├── server.ts
├── servers
└── tasks
├── tsconfig.json
└── yarn.lock
  • 代码说明
    plugin 实际上就是一个普通的actionherojs项目,只是有些功能我们不需要
    以上代码很简单,就是一个简单的ui 组件, 注意actionhero 为peer的
    package.json
 
{
  "author": "dalongrong",
  "name": "ah-dalong-plugin",
  "description": "my actionhero project",
  "version": "0.1.0",
  "engines": {
    "node": ">=8.0.0"
  },
  "dependencies": {
    "ioredis": "latest",
    "ioredis-mock": "latest",
    "winston": "latest",
    "ws": "latest"
  },
  "peerDependencies": {
    "actionhero": "28.1.7"
  },
  "devDependencies": {
    "@types/jest": "latest",
    "@types/node": "latest",
    "actionhero": "28.1.7",
    "jest": "latest",
    "prettier": "latest",
    "ts-jest": "latest",
    "ts-node-dev": "latest",
    "type-fest": "latest",
    "typescript": "latest"
  },
  "scripts": {
    "postinstall": "npm run build",
    "dev": "ts-node-dev --no-deps --transpile-only ./src/server",
    "debug": "tsc && ts-node-dev --transpile-only --no-deps --inspect -- ./src/server ",
    "start": "node ./dist/server.js",
    "actionhero": "actionhero",
    "test": "jest",
    "pretest": "npm run build && npm run lint",
    "build": "tsc --sourceMap false --declaration",
    "lint": "prettier --check src/*/** __tests__/*/**",
    "pretty": "prettier --write src/*/** __tests__/*/**"
  },
  "jest": {
    "testEnvironment": "node",
    "transform": {
      "^.+\\\\.ts?$": "ts-jest"
    }
  }
}

initializers/ah-dalong-ui.ts 主要实现了简单的路由注册

import { Initializer, route} from "actionhero";
 
export class AHResqueUIInitializer extends Initializer {
  constructor() {
    super();
    this.name = "ah-dalong-ui";
    this.loadPriority = 99999999;
  }
 
  async initialize() {
    /* ----- Route Injection ----- */
    route.registerRoute(
      "get",
      "/:apiVersion/dalong",
      "dalongrong"
    );
  }
}

actions/mydemo.ts

import {Action} from "actionhero"
 
module.exports = class MyDemoAction extends Action {
    async run() {
        return {name:"dalongrong"};
    }
    constructor(){
        super()
        this.name="dalongrong"
        this.description="demoapp",
        this.version=1
    }
}
  • 集成使用
    yarn link 模式使用
    actionhero 项目的config/plugins.ts
 
import { PluginConfig } from "actionhero";
 
const namespace = "plugins";
 
declare module "actionhero" {
  export interface ActionheroConfigInterface {
    [namespace]: ReturnType<typeof DEFAULT[typeof namespace]>;
  }
}
 
export const DEFAULT: { [namespace]: () => PluginConfig } = {
  [namespace]: () => {
    return {
      'dalong': { path: __dirname + '/../../node_modules/ah-dalong-plugin' }
    }
  }
};
  • 访问效果

 

 

  • 参考日志

 

 

  • 参考机制
    actionherojs 实际上 src/initializers/actions.ts 通过此加载的
 
for (const p of config.general.paths.action) {
      let files = glob.sync(path.join(p, "**", "**/*(*.js|*.ts)"));
      files = utils.ensureNoTsHeaderFiles(files);
      for (const j in files) {
        await api.actions.loadFile(files[j]);
      }
    }
    // 包含plugin的模式
    for (const plugin of Object.values(config.plugins)) {
      if (plugin.actions !== false) {
        const pluginPath: string = path.normalize(plugin.path);
 
        // old style at the root of the project
        let files = glob.sync(path.join(pluginPath, "actions", "**", "*.js"));
 
        files = files.concat(
          glob.sync(path.join(pluginPath, "dist", "actions", "**", "*.js"))
        );
 
        utils
          .ensureNoTsHeaderFiles(files)
          .forEach((f) => api.actions.loadFile(f)); // 基于import 动态加载插件里边的代码定义
      }
    }

对于静态内容,实际上是基于staticFile 的Initializer 解决的,使用了默认的public 路径
参考

 
  async initialize() {
    api.staticFile = {
      searchLocations: [],
      get: this.get,
      searchPath: this.searchPath,
      sendFile: this.sendFile,
      fileLogger: this.fileLogger,
      sendFileNotFound: this.sendFileNotFound,
      checkExistence: this.checkExistence,
      logRequest: this.logRequest,
    };
 
    // load in the explicit public paths first
    if (config.general.paths) {
      config.general.paths.public.forEach(function (p: string) {
        api.staticFile.searchLocations.push(path.normalize(p));
      });
    }
 
    // source the public directories from plugins
    for (const plugin of Object.values(config.plugins as PluginConfig)) {
      if (plugin.public !== false) {
        const pluginPublicPath: string = path.normalize(
          path.join(plugin.path, "public")
        );
       // 将public 放到searchLocations 中,当需要访问静态页面的时候通过searchLocations查找,然后sendFile
        if (
          fs.existsSync(pluginPublicPath) &&
          api.staticFile.searchLocations.indexOf(pluginPublicPath) < 0
        ) {
          api.staticFile.searchLocations.push(pluginPublicPath);
        }
      }
    }
 
    log(
      "static files will be served from these directories",
      "debug",
      api.staticFile.searchLocations
    );
  }

一些问题

  • 覆盖问题 an existing xxxx with the same name xxxx will be overridden by the file

如下

 

 


actionherojs 使用了插件优先的模式,本地程序的action 会被plugin 的覆盖,所以名称不重复很重要

说明

actionherojs 的插件机制,并不难,主要还是利用了import 动态加载的机制(ts 模式),对于commonjs 的模式为

 
let  collection = await Promise.resolve().then(() => require(fullFilePath));

建议大家阅读下actionherojs 的源码,源码并不多,但是设计还是比较巧妙的,后边会对与reload 介绍下grouparoo,很多地方就使用到了这个而且很巧妙
很多地方使用了类似java classpath 查找的模式,实现了js 模块的动态加载

参考资料

https://www.actionherojs.com/tutorials/plugins
https://github.com/actionhero/ah-resque-ui

posted on 2022-02-04 21:16  荣锋亮  阅读(78)  评论(0编辑  收藏  举报

导航