网上有不少此类文章,这里就是总结下自己是实践结果。其中遇到不少问题,并解决。
下面先上个目录结构图
构建工具用的是rollup,构建完成是esm模块在Cocos中可以调用 单元测试用的是jest+babel,支持esm。
这里说一下重点,cocos中引用npm库,可能需要设置package.json
"main": "dist/lib/index.js", "types": "dist/types/index.d.ts", "type": "module",
但是有了这个"type": "module",后jest test测试就一直报错,下面会解决这个问题,
下面就把各文件统一贴出来,大家自己理解吧。这里面稍微复杂一点的就是jest 支持esm,使用了各种方法都不得果,最后总算是解决了。
package.json
{ "name": "@momo/gaea-core", "version": "1.0.0", "description": "gaea framework", "scripts": { "build": "npx tsc --declaration --emitDeclarationOnly --declarationDir ./tmp && npx rollup -c", "test-single": "npx jest --silent --verbose", "test-all": "npx jest --silent --coverage", "test": "NODE_OPTIONS=--experimental-vm-modules npx jest", "prettier": "npx prettier --write ." }, "author": "momo", "license": "ISC", "main": "dist/lib/index.js", "types": "dist/types/index.d.ts", "type": "module", "devDependencies": { "@babel/core": "^7.17.9", "@babel/preset-env": "^7.16.11", "@rollup/plugin-json": "^4.1.0", "@rollup/plugin-typescript": "^8.3.2", "@types/jest": "^27.4.1", "@typescript-eslint/eslint-plugin": "^5.19.0", "@typescript-eslint/parser": "^5.19.0", "babel-jest": "^27.5.1", "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2", "eslint": "^8.13.0", "jest": "^27.5.1", "jest-canvas-mock": "^2.3.1", "prettier": "^2.6.2", "rollup": "^2.70.2", "rollup-plugin-dts": "^4.2.1", "rollup-plugin-terser": "^7.0.2", "ts-jest": "^27.1.4", "ts-node": "^10.7.0", "typescript": "^4.6.3" } }
tsconfig.json 这个文件着重标记下
"target": "ES2015", "module": "ES2015",
{ "compilerOptions": { "target": "ES2015", "module": "ES2015", "moduleResolution": "Node", "experimentalDecorators": true, "skipLibCheck": true, "downlevelIteration": true, "esModuleInterop": true, "resolveJsonModule": true, // 用于限制在function中使用this关键字 "noImplicitThis": true, "types": [ "./lib/cc.env", "./lib/cc", "jest", ] }, "include": [ "src/**/*" ], "exclude": [ "./dist/", "./example/" ] }
rollup.config.js
//@ts-check import json from "@rollup/plugin-json"; import typescript from "@rollup/plugin-typescript"; import dts from "rollup-plugin-dts"; import { terser } from "rollup-plugin-terser"; import { version } from "./package.json"; /** * 打包为2个js文件的配置 * @param {boolean} is_terser 是否压缩代码 * @returns {import("rollup").RollupOptions} */ function create_config(is_terser) { return { input: "./src/index.ts", external: ['cc', 'cc/env'], output: { // 输出文件 //file: `./dist/lib/index${is_terser ? ".min" : ""}.js`, file: `./dist/lib/index.js`, // 需要在creator中作为插件脚本使用,因此必须要生成为umd模块 format: 'esm', // umd包名 name: "gaeacore", // 文件标题 banner: `/*! gaeacore-${version} */`, }, plugins: [ // 从package.json中获取项目信息 json(), // 用于打包typescript typescript(), // 用于压缩js代码 is_terser ? terser() : undefined, ], }; } /** * 打包声明文件dts的配置 * - 之前需要先生成声明文件,参考命令:npx tsc --declaration --emitDeclarationOnly --declarationDir ./dist * @returns {import("rollup").RollupOptions} */ function create_config_dts_bundle() { return { input: "./tmp/index.d.ts", output: { file: `./dist/types/index.d.ts`, banner: `/*! gaeacore-${version} */`, //footer: "export as namespace lwj;", }, plugins: [dts()], }; } // export default [create_config_dts_bundle(), create_config(false), create_config(true)]; export default [create_config_dts_bundle(), create_config(false)];
jest.config.js
// jest.config.js export default { preset: "ts-jest", globals: { // 全局属性。如果你的被测试的代码中有使用、定义全局变量,那你应该在这里定义全局属性 window: {}, cc: {} }, testMatch: ["<rootDir>/test/**/*.(spec|test).ts?(x)"], transform: { // 将.js后缀的文件使用babel-jest处理 "^.+\\.js$": "babel-jest", "^.+\\.(ts|tsx)$": "ts-jest" }, moduleDirectories: [ "node_modules", "lib", "src" ], coverageDirectory: './docs/testReport/', // 下面非要从重要, 将不忽略 lodash-es, other-es-lib 这些es库, 从而使babel-jest去处理它们 transformIgnorePatterns: [ "<rootDir>/node_modules/(?!(lodash-es|other-es-lib))" ] }
.prettierrc
{ "printWidth": 100, "arrowParens": "avoid", "trailingComma": "all" }
.prettierignore
/test/*.js /dist/**/* !/dist/fy*.d.ts /types/creator.d.ts /coverage/ /tmp/
.eslintrc.json
{ "extends": ["plugin:@typescript-eslint/recommended"], "parser": "@typescript-eslint/parser", "parserOptions": { "sourceType": "module" }, "plugins": ["@typescript-eslint"], // 只检测src和test文件夹下的ts文件 "ignorePatterns": ["**/*.js", "**/*.ts", "!src/*.ts", "!test/*.ts"], "rules": { // 命名规范 "@typescript-eslint/naming-convention": [ "warn", // 默认使用snake_case,特殊使用UPPER_CASE,_前缀表示需要被导出但不希望被使用 { "selector": "default", "format": ["snake_case", "UPPER_CASE"], "leadingUnderscore": "allow" }, // 类型使用PascalCase { "selector": "typeLike", "format": ["PascalCase"] }, // 枚举成员使用PascalCase { "selector": "enumMember", "format": ["PascalCase"] } ], // 不允许未被使用的变量,忽略以_作为前缀的变量 "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }], // 允许使用any但尽量不要使用 "@typescript-eslint/no-explicit-any": ["off"], // 允许空方法 "@typescript-eslint/no-empty-function": ["off"], // 不允许使用var定义变量 "no-var": "warn", // 除Top-Level-Function外,均使用箭头函数 "prefer-arrow-callback": "warn", // 常量定义使用const,非常量但内容不被改变的量,允许使用let "prefer-const": "off" } }
test/GaeaCore.spec.ts
import { GaeaCore } from "../src/Core/GaeaCore"; const gaeaCore = GaeaCore.new(); describe("222", () => { test('#getIndex', () => { let ret = gaeaCore.getIndex(1); expect(ret).toBe(2); }); });
src/Core/GaeaCore.ts
export class GaeaCore { private static _instance: GaeaCore; static new(): GaeaCore { if (!GaeaCore._instance) { GaeaCore._instance = new GaeaCore() } return GaeaCore._instance } getIndex(n: number): number { return 1 + n; } }
src/UIKite/UIBase.ts
import { Component } from "cc"; export class UIBase extends Component{ }
src/index.ts
export * from "./Core/GaeaCore"; export * from "./UIKite/UIBase";
lib/cc.d.ts 是cocos 中的文件直接复制过来即可
/// <reference path="/Applications/CocosCreator/Creator/3.4.2/CocosCreator.app/Contents/Resources/resources/3d/engine/bin/.declarations/cc.d.ts"/> /** * @deprecated Global variable `cc` was dropped since 3.0. Use ES6 module syntax to import Cocos Creator APIs. */ declare const cc: never;
lib/cc.env.d.ts 是cocos 中的文件直接复制过来即可
declare module "cc/env" { export const EXPORT_TO_GLOBAL: boolean; export const BUILD: boolean; export const TEST: boolean; export const EDITOR: boolean; export const PREVIEW: boolean; export const DEV: boolean; export const DEBUG: boolean; export const JSB: boolean; export const NATIVE: boolean; export const HTML5: boolean; export const WECHAT: boolean; export const MINIGAME: boolean; export const RUNTIME_BASED: boolean; export const ALIPAY: boolean; export const XIAOMI: boolean; export const BYTEDANCE: boolean; export const BAIDU: boolean; export const COCOSPLAY: boolean; export const HUAWEI: boolean; export const OPPO: boolean; export const VIVO: boolean; export const SUPPORT_JIT: boolean; export const SERVER_MODE: boolean; }