小程序云开发 TypeScript 工程化实践
发布于 4 年前 作者 jielu 2088 次浏览 来自 分享

使用 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 进行云函数的发布控制。根据参数进行环境配置和上传云函数进行规则过滤。

1 回复

社区大佬真多 我算是学到了

回到顶部