阿里云函数计算typescript脚手架

2020-02-11

背景

最近一直在折腾阿里云函数计算,发现这东西确实方便啊,最理想的情况下确实可以达到noOps。但问题是,函数计算需要改写程序入口,普通的nodejs服务器如果要运行到函数计算平台上,需要做许多改写的处理,导致没办法用许多成熟的nodejs服务器脚手架,比如eggJS。于是基于官方骨架教程做了一套我自己熟悉技术栈的骨架模板。用于以后的开发。

  • 语言: TypeScript
  • 运行时: nodejs8
  • 开发框架: express
  • 代码打包: webpack
  • 代码规范: eslint(airbnb规则)+prettier

代码地址

Github仓库

使用方法

使用方法主要用函数计算提供的cli工具fun

Github Readme

实现思路

实现主要问题是将函数计算的HTTP触发器绑定到Express服务器实例上。通常的函数计算HTTP触发器是:

module.exports.handler = (req, res, context) => {
  // ...
};

一个express服务器则是:

const express = require('express')
const app = express()

app.get('/', (req, res) => res.send('Hello World!'))

app.listen(3000, () => console.log('Example app listening on port 3000!'))

我们可以看出最大的区别,如果我们的代码要运行在函数计算平台,我们不需要app.listen来监听运行环境的某个端口,而是由函数计算平台来主动调用handler。

而函数计算官方提供了一个将函数计算的HTTP触发器事件代理到nodejs服务器的库@webserverless/fc-express,我们删除掉主动listen的代码,用这个库做一层http转发。

import { Server } from '@webserverless/fc-express';
import express from 'express';

const app = express();
app.all('*', (req, res) => {
  res.send('Hello serverless with TypeScript & Express5!');
});

const server = new Server(app);

// 删除主动listen的代码
// app.listen(3000, () => console.log('Example app listening on port 3000!'))

// 代理触发器事件
module.exports.handler = (req, res, context) => {
  server.httpProxy(req, res, context);
};

于是我们就可以使用我们熟悉的express来做开发了。

Webpack和TypeScript配置

// webpack.config.common.js
const path = require('path');

module.exports = {
  entry: './src/index.ts',
  target: 'node',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'index.js',
    libraryTarget: 'umd',
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
      },
      {
        test: /\.ts$/,
        exclude: /node_modules/,
        loader: 'awesome-typescript-loader',
      },
    ],
    exprContextCritical: false, // 去除express的模块规范警告
  },
};
// tsconfig.json
{
  "include": ["src/*"],
  "exclude": ["node_modules", "**/*.test.ts", "dist", ".fun"],
  "compilerOptions": {
    "esModuleInterop": true,
    "inlineSourceMap": false,
    "sourceMap": true,
    "moduleResolution": "node",
    "allowJs": true,
    "lib": ["dom", "es5", "scripthost", "es2015.promise"]
  }
}
# template.yml
ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
  demo-service: # service name
    Type: 'Aliyun::Serverless::Service'
    Properties:
      Description: ''
    demo: # function name
      Type: 'Aliyun::Serverless::Function'
      Properties:
        Handler: index.handler
        Runtime: nodejs8
        CodeUri: './dist'
        Timeout: 60
      Events:
        httpTrigger:
          Type: HTTP
          Properties:
            AuthType: ANONYMOUS
            Methods: ['POST', 'GET']

主要就几个要点:

  • 入口改写为index.ts
  • 目标模块标准为函数计算支持的umd标准
  • 使用awesome-typescript-loader去加载ts模块
  • webpack打包输出到dist文件夹
  • yaml模板中配置触发器类型为HTTP,并指定触发器代码在dist文件夹

参考