评论

小程序云开发 TypeScript 工程化实践

有关小程序云函数的工程化

使用 TypeScript

因为云函数是 node,所以一般是不支持 TypeScript 的。但是 TypeScript 的类型是真的不错,所以决定使用 TypeScript 进行云开发,而且使用 ts 之后可以直接跟前端使用 interface 进行接口定义,不需要另外写文档。

初期:每个函数有个 src 目录,里面存放 ts 文件,每次进行函数发布之前先使用 tsc 进行编译,然后发布到云环境。这个方法有个缺点就是多个云函数直接相同的代码不能通用,只能拷贝多份分布在每个云函数,这样的话每次这些通用函数的 bug 修复需要更新很多云函数。

后期:把所有的云函数提取到另外的目录,使用 Rollup 进行编译到云函数目录并根据依赖动态生成 package.json 文件。这样的话不同的云函数之间可以使用相同的代码。

编译云函数

首先根据云函数名称确定源代码的入口和输出的文件夹路径

module.exports = async function build(functionName, version) {
  // 输出路径
  const distPath = path.join(__dirname, `../cloudfunctions/${functionName}`);
 // 源文件入口文件
  const entryPath = path.join(__dirname, `../cloudfunctions-original/functions/${functionName}/index.ts`);
}


然后使用 rollup 的 typescript 和 commjs 插件把源文件编译成 node输出到指定的云函数目录。使用 alias 插件的原因是因为有部分数据同步的工作需要本地运行,然后我写了个 bridge,所以上传云函数的时候替换成 wx-server-sdk,通过编译的时候把 process.env.run 替换为 cloud 来对部分本地运行代码进行删除。

const bundle = await rollup.rollup({
    input: entryPath,
    plugins: [
      alias({
        entries: [
          { find: /^[\s|\S]*bridge\/index$/, replacement: 'wx-server-sdk' },
        ]
      }),
      replace({
        'process.env.run': JSON.stringify('cloud'),
      }),
      typescript({
        tsconfig: './tsconfig.json',
        sourceMap: false,
        outDir: distPath,
        include: [
          "../cloudfunctions-original/**/*.ts"
        ],
        tslib: require.resolve(path.join(__dirname, `../cloudfunctions-original/third-lib/tslib.es6.js`),),
      }),
      commonjs({
        extensions: ['.ts'],
        sourceMap: false,
      }),
    ],
    onwarn(warning) {
      if (warning.code !== 'PLUGIN_WARNING' && warning.code !== 'CANNOT_CALL_NAMESPACE') {
        console.warn(warning.message);
      }
    },
    external: [...Object.keys(packageJson.dependencies), 'lodash/fp', 'https', 'fs', 'path'],
    treeshake: {
      moduleSideEffects: false,
    }
  });

然后输出到硬盘

  const outputOptions = {
      format: 'cjs',
      interop: false,
      dir: distPath,
      sourcemap: false,
      preserveModules: true,
      preserveModulesRoot: entryPath.replace('/index.ts', ''),
      exports: 'auto',
      banner: `/* 此文件自动生成,请勿手动修改,源文件位于 cloudfunctions-original/functions/${functionName} */`,
    }
    // generate code and a sourcemap
    const result = await bundle.generate(outputOptions);
    // write the bundle to disk
    await bundle.write(outputOptions);

然后根据依赖输出 package.json

const functionDeps = flow(
      flatMap(prop('imports')),
      map(v => v === 'lodash/fp' ? 'lodash' : v),
      reject(v => includes(`/${functionName}/`)(v)),
    )(result.output);
    const functionPackageJson = {
      name: functionName,
      version: version || '1.0.0',
      ...pick([
        'description', 'main', 'author', 'license',
      ])(packageJson),
      dependencies: pick(functionDeps)(packageJson.dependencies),
    }
    // write the package.json to disk
    fs.writeFileSync(`${distPath}/package.json`, JSON.stringify(functionPackageJson, "", "\t"));


创建/更新云函数

上面完成了对单个云函数的打包,接下来我们需要进行批量的 build 和创建/更新云函数。

使用 miniprogram-ci 进行云函数的更新,因为 miniprogram-ci 不支持创建云函数,所以需要开通腾讯云开发,然后使用 @cloudbase/manager-node 进行云函数创建。

首先获取当前环境的所有云函数列表

const manager = new CloudBase({
  secretId: process.env.secretId,
  secretKey: process.env.secretKey,
  envId: process.env.cloudEnv
})
// 因为目前云函数最多 150 个,所以直接指定 150 个
manager.functions.getFunctionList(150)
      .then((res) => {
        functionList = res.Functions.map(v => v.FunctionName);
        console.log(JSON.stringify(functionList));
        resolve();
      })
      .catch((err) => {
        console.log('err');
        reject(err);
      });

然后判断云函数是否存在,如果不存在,则进行创建。创建的时候需要注意,如果有 trigger ,可以添加 trigger 字段

try {
      config = require(`${functionDirName}/${filePath}/config.json`);
    } catch (error) {}
    console.log(`开始创建云函数: ${filePath}`)
    await manager.functions.createFunction({
      func: {
        timeout: 3,
        name: filePath,
        installDependency: true,
        ignore: ['node_modules/'],
        triggers: config ? config.triggers : [],
        runtime: 'Nodejs10.15',
      },
      functionPath: `${functionDirName}/${filePath}`,
    })

否则,更新云函数

await ci.cloud.uploadFunction({
    project: new ci.Project({
      appid,
      type: 'miniProgram',
      projectPath: functionDirName,
      privateKeyPath,
      ignores: ['node_modules/**/*'],
    }),
    env: process.env.cloudEnv,
    name: filePath,
    path: `${functionDirName}/${filePath}`,
    remoteNpmInstall: true,
  });


批量编译+创建/更新云函数

前面完成了命令式的编译和上传云函数,接下来我们把他们组合起来进行批量处理。

获取所有的云函数名称,因为我们把所有的云函数放到了一个目录,直接读取当前的所有文件夹就可以,然后可以自定义过滤规则。

const dirs = fs.readdirSync(dirName, { withFileTypes: true })

然后对每个函数调用编译+上传。如果函数比较多,可以考虑使用 node 的 cluster 模块进行多进程处理。

使用 jenkins 进行自动上传云函数

前面已经把批量上传云函数写成了 node 可执行文件,接下来就可以使用 jenkins 进行云函数的发布控制。根据参数进行环境配置和上传云函数进行规则过滤。

最后一次编辑于  2021-06-02  
点赞 3
收藏
评论

3 个评论

  • Zhao ZW
    Zhao ZW
    2022-04-02

    请教下,如果把cloudfunctions做为包 在其中安装rollup 是不是也可以呢?这样做有什么问题么??配置文件如下:

    ```import typescript from `rollup-plugin-typescript2`

    export default [

        {

            input: ./add_user/index.ts;,

            output: { file: ./add_user/index.js, format:cjs}, 

            plugins: [typescript()],

        },

        {

            input: ./get_user/index.ts,

            output: { file: ./get_user/index.js, format: cjs },

            plugins: [typescript()],

        },

    ];```

    // package.json 监控各个云函数ts文件变化 并打包

       scripts: {

        build:rollup -c -w;

      },


    2022-04-02
    赞同
    回复
  • 小白17709205217
    小白17709205217
    2021-11-12

    您也可以从配置文件中导出一个数组,以一次从几个不相关的输入构建包。要使用相同的输入构建不同的包,您需要为每个输入提供一组输出选项:

    // rollup.config.ts (building more than one bundle)
    
    export default [
      {
        input: 'main-a.js',
        output: {
          file: 'dist/bundle-a.js',
          format: 'cjs'
        }
      },
      {
        input: 'main-b.js',
        output: [
          {
            file: 'dist/bundle-b1.js',
            format: 'cjs'
          },
        ]
      }
    ];
    


    然后将cloudfunctionsTS/test目录 对应到 云函数的 cloudfunctions/test 目录

    export default [
      {
        external: ['wx-server-sdk'],
        input"cloudfunctionsTS/test1/index.ts",
        output: [
          {
            dir: "cloudfunctions/test1",
            format"cjs",
            name: "test",
          },
        ],
        plugins: [typescript()],
      },
      {
        external: ['wx-server-sdk'],
        input"cloudfunctionsTS/test1/index.ts",
        output: [
          {
            dir: "cloudfunctions/test10",
            format"cjs",
            name: "test1",
          },
        ],
        plugins: [typescript()],
      },
    ];
    


    现在就可以在 cloudfunctionsTS/test1/index.ts 写ts代码

    运行rollup -c 将ts代码输出到 对应的云函数目录下



    最后 external: ['wx-server-sdk'], rollup 会将 wx-server-sdk 视为运行时导入。


    就可以愉快地用ts 写 云函数了。


    2021-11-12
    赞同
    回复 2
    • Zhao ZW
      Zhao ZW
      2022-04-02
      请教下,如果把cloudfunctions做为包 在其中安装rollup 是不是也可以呢?这样做有什么问题么??配置文件如下:
      ```import typescript from 'rollup-plugin-typescript2'
      export default [
          {
              input: ./add_user/index.ts;,
              output: { file: ./add_user/index.js, format:cjs}, 
              plugins: [typescript()],
          },
          {
              input: ./get_user/index.ts,
              output: { file: ./get_user/index.js, format: cjs },
              plugins: [typescript()],
          },
      ];```
      // package.json 监控各个云函数ts文件变化 并打包
         scripts: {
          build:rollup -c -w;
        },
      2022-04-02
      回复
    • 小白17709205217
      小白17709205217
      2022-04-08
      cloudfunctions 作为编译目标
      2022-04-08
      回复
  • 林三
    林三
    发表于移动端
    2021-10-13
    社区大佬666
    2021-10-13
    赞同
    回复
登录 后发表内容