feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,63 @@
# @coze-arch/report-tti
A architecture package for the Coze Studio monorepo
## Overview
This package is part of the Coze Studio monorepo and provides architecture functionality. It serves as a core component in the Coze ecosystem.
## Getting Started
### Installation
Add this package to your `package.json`:
```json
{
"dependencies": {
"@coze-arch/report-tti": "workspace:*"
}
}
```
Then run:
```bash
rush update
```
### Usage
```typescript
import { /* exported functions/components */ } from '@coze-arch/report-tti';
// Example usage
// TODO: Add specific usage examples
```
## Features
- Core functionality for Coze Studio
- TypeScript support
- Modern ES modules
## API Reference
Please refer to the TypeScript definitions for detailed API documentation.
## Development
This package is built with:
- TypeScript
- React
- Vitest for testing
- ESLint for code quality
## Contributing
This package is part of the Coze Studio monorepo. Please follow the monorepo contribution guidelines.
## License
Apache-2.0

View File

@@ -0,0 +1,125 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook } from '@testing-library/react';
import { useReportTti } from '../src/index';
// 模拟 custom-perf-metric 模块
vi.mock('../src/utils/custom-perf-metric', () => ({
reportTti: vi.fn(),
REPORT_TTI_DEFAULT_SCENE: 'init',
}));
// 导入被模拟的函数,以便在测试中访问
import { reportTti } from '../src/utils/custom-perf-metric';
describe('useReportTti', () => {
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.resetAllMocks();
});
it('should call reportTti when isLive is true', () => {
// Arrange
const params = {
isLive: true,
extra: { key: 'value' },
};
// Act
renderHook(() => useReportTti(params));
// Assert
expect(reportTti).toHaveBeenCalledTimes(1);
expect(reportTti).toHaveBeenCalledWith(params.extra, 'init');
});
it('should not call reportTti when isLive is false', () => {
// Arrange
const params = {
isLive: false,
extra: { key: 'value' },
};
// Act
renderHook(() => useReportTti(params));
// Assert
expect(reportTti).not.toHaveBeenCalled();
});
it('should call reportTti with custom scene when provided', () => {
// Arrange
const params = {
isLive: true,
extra: { key: 'value' },
scene: 'custom-scene',
};
// Act
renderHook(() => useReportTti(params));
// Assert
expect(reportTti).toHaveBeenCalledTimes(1);
expect(reportTti).toHaveBeenCalledWith(params.extra, params.scene);
});
it('should not call reportTti again if dependencies do not change', () => {
// Arrange
const params = {
isLive: true,
extra: { key: 'value' },
};
// Act
const { rerender } = renderHook(() => useReportTti(params));
rerender();
// Assert
expect(reportTti).toHaveBeenCalledTimes(1);
});
it('should call reportTti again if isLive changes from false to true', () => {
// Arrange
const initialParams = {
isLive: false,
extra: { key: 'value' },
};
// Act
const { rerender } = renderHook(props => useReportTti(props), {
initialProps: initialParams,
});
// Assert
expect(reportTti).not.toHaveBeenCalled();
// Act - change isLive to true
rerender({
isLive: true,
extra: { key: 'value' },
});
// Assert
expect(reportTti).toHaveBeenCalledTimes(1);
expect(reportTti).toHaveBeenCalledWith(initialParams.extra, 'init');
});
});

View File

@@ -0,0 +1,494 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { reporter } from '@coze-arch/logger';
const mockSlardarInstance = vi.fn();
mockSlardarInstance.config = vi.fn();
// 模拟 logger 和 reporter
vi.mock('@coze-arch/logger', () => ({
logger: {
info: vi.fn(),
},
reporter: {
info: vi.fn(),
},
getSlardarInstance: vi.fn(() => mockSlardarInstance),
}));
describe('custom-perf-metric', () => {
// 保存原始的全局对象
const originalPerformance = global.performance;
const originalDocument = global.document;
const originalPerformanceObserver = global.PerformanceObserver;
// 模拟函数
const mockObserve = vi.fn();
const mockDisconnect = vi.fn();
const mockGetEntriesByName = vi.fn();
const mockPerformanceNow = vi.fn().mockReturnValue(1000);
// 模拟路由变更条目
const mockRouteChangeEntry = {
startTime: 500,
detail: {
location: {
pathname: '/test-path',
},
},
};
// 确保数组有 at 方法
if (!Array.prototype.at) {
// 添加 at 方法的 polyfill
Object.defineProperty(Array.prototype, 'at', {
value(index) {
// 将负索引转换为从数组末尾开始的索引
return this[index < 0 ? this.length + index : index];
},
writable: true,
configurable: true,
});
}
beforeEach(() => {
vi.clearAllMocks();
vi.resetModules();
// 模拟 performance 对象
vi.stubGlobal('performance', {
now: mockPerformanceNow,
getEntriesByName: mockGetEntriesByName,
});
// 默认模拟 getEntriesByName 返回值
mockGetEntriesByName.mockImplementation(name => {
if (name === 'route_change') {
return [mockRouteChangeEntry];
}
if (name === 'first-contentful-paint') {
return [
{
startTime: 800,
},
];
}
return [];
});
// 模拟 document 对象
global.document = {
visibilityState: 'visible',
} as any;
// 模拟 PerformanceObserver
global.PerformanceObserver = vi.fn().mockImplementation(callback => ({
observe: mockObserve,
disconnect: mockDisconnect,
})) as any;
// 添加 supportedEntryTypes 属性
Object.defineProperty(global.PerformanceObserver, 'supportedEntryTypes', {
value: ['paint'],
configurable: true,
});
});
afterEach(() => {
// 恢复原始对象
global.performance = originalPerformance;
global.document = originalDocument;
global.PerformanceObserver = originalPerformanceObserver;
});
it('应该导出常量和函数', async () => {
const { PerfMetricNames, REPORT_TTI_DEFAULT_SCENE, reportTti } =
await vi.importActual<any>('../../src/utils/custom-perf-metric');
expect(PerfMetricNames).toBeDefined();
expect(PerfMetricNames.TTI).toBe('coze_custom_tti');
expect(PerfMetricNames.TTI_HOT).toBe('coze_custom_tti_hot');
expect(REPORT_TTI_DEFAULT_SCENE).toBe('init');
expect(reportTti).toBeInstanceOf(Function);
});
it('当页面可见且有 FCP 时应该调用 slardar 上报 TTI', async () => {
const { reportTti, PerfMetricNames } = await vi.importActual<any>(
'../../src/utils/custom-perf-metric',
);
// 执行
reportTti({ key: 'value' });
// 验证
expect(mockSlardarInstance).toHaveBeenCalledWith('sendCustomPerfMetric', {
value: 1000,
name: PerfMetricNames.TTI,
type: 'perf',
extra: {
key: 'value',
fcpTime: '800',
},
});
});
it('当页面处于隐藏状态时不应该上报 TTI', async () => {
const { reportTti } = await vi.importActual<any>(
'../../src/utils/custom-perf-metric',
);
// 修改 document.visibilityState
Object.defineProperty(global.document, 'visibilityState', {
value: 'hidden',
configurable: true,
});
// 执行
reportTti();
// 验证
expect(mockSlardarInstance).not.toHaveBeenCalled();
});
it('当同一路由和场景已经上报过时不应该重复上报', async () => {
const { reportTti } = await vi.importActual<any>(
'../../src/utils/custom-perf-metric',
);
// 第一次调用
reportTti({}, 'test-scene');
// 清除模拟
vi.clearAllMocks();
// 第二次调用同一路由和场景
reportTti({}, 'test-scene');
// 验证
expect(mockSlardarInstance).not.toHaveBeenCalled();
});
it('当有多个路由变更时应该上报热启动 TTI', async () => {
const { reportTti, PerfMetricNames } = await vi.importActual<any>(
'../../src/utils/custom-perf-metric',
);
// 修改 getEntriesByName 返回多个路由变更
mockGetEntriesByName.mockImplementation(name => {
if (name === 'route_change') {
const entries = [
{
startTime: 300,
detail: {
location: {
pathname: '/first-path',
},
},
},
{
startTime: 500,
detail: {
location: {
pathname: '/test-path2',
},
},
},
];
// 确保数组有 at 方法
if (!entries.at) {
entries.at = function (index) {
return this[index < 0 ? this.length + index : index];
};
}
return entries;
}
return [];
});
// 执行
reportTti({ key: 'value' });
// 验证
expect(mockSlardarInstance).toHaveBeenCalledWith('sendCustomPerfMetric', {
value: 500, // 1000 - 500 = 500
name: PerfMetricNames.TTI_HOT,
type: 'perf',
extra: {
key: 'value',
},
});
});
it('当 FCP 时间晚于当前时间时应该使用 FCP 时间作为 TTI', async () => {
const { reportTti, PerfMetricNames } = await vi.importActual<any>(
'../../src/utils/custom-perf-metric',
);
// 修改 performance.now 返回值
mockPerformanceNow.mockReturnValue(700);
// 执行
reportTti();
// 验证
expect(mockSlardarInstance).toHaveBeenCalledWith('sendCustomPerfMetric', {
value: 800, // 使用 FCP 时间
name: PerfMetricNames.TTI,
type: 'perf',
extra: {
fcpTime: '800',
},
});
});
it('当页面可见且没有 FCP 时应该设置 PerformanceObserver', async () => {
const { reportTti } = await vi.importActual<any>(
'../../src/utils/custom-perf-metric',
);
// 修改 getEntriesByName 不返回 FCP
mockGetEntriesByName.mockImplementation(name => {
if (name === 'route_change') {
return [mockRouteChangeEntry];
}
// 返回空数组表示没有 FCP
return [];
});
// 执行
reportTti();
// 验证
expect(mockObserve).toHaveBeenCalledWith({ type: 'paint', buffered: true });
expect(mockSlardarInstance).not.toHaveBeenCalled();
});
it('当 PerformanceObserver 触发回调时应该上报 TTI', async () => {
const { reportTti, PerfMetricNames } = await vi.importActual<any>(
'../../src/utils/custom-perf-metric',
);
// 修改 getEntriesByName 不返回 FCP
mockGetEntriesByName.mockImplementation(name => {
if (name === 'route_change') {
return [mockRouteChangeEntry];
}
return [];
});
// 准备模拟 PerformanceObserver 回调
let observerCallback: Function | undefined;
global.PerformanceObserver = vi.fn().mockImplementation(callback => {
observerCallback = callback;
return {
observe: mockObserve,
disconnect: mockDisconnect,
};
}) as any;
// 执行
reportTti({ key: 'value' });
// 模拟 PerformanceObserver 回调
const mockList = {
getEntriesByName: vi.fn().mockReturnValue([{ startTime: 900 }]),
};
// 确保 observerCallback 已被赋值
expect(observerCallback).toBeDefined();
// 执行回调
if (observerCallback) {
observerCallback(mockList);
}
// 验证
expect(mockSlardarInstance).toHaveBeenCalledWith('sendCustomPerfMetric', {
value: 900,
name: PerfMetricNames.TTI,
type: 'perf',
extra: {
key: 'value',
fcpTime: '900',
},
});
expect(mockDisconnect).toHaveBeenCalled();
});
it('当 PerformanceObserver.observe 抛出错误时应该尝试替代方法', async () => {
const { reportTti } = await vi.importActual<any>(
'../../src/utils/custom-perf-metric',
);
// 修改 getEntriesByName 不返回 FCP
mockGetEntriesByName.mockImplementation(name => {
if (name === 'route_change') {
return [mockRouteChangeEntry];
}
return [];
});
// 模拟 observe 抛出错误
mockObserve.mockImplementationOnce(() => {
throw new Error('Failed to execute observe');
});
// 执行
reportTti();
// 验证
expect(mockObserve).toHaveBeenCalledTimes(2);
expect(mockObserve).toHaveBeenNthCalledWith(1, {
type: 'paint',
buffered: true,
});
expect(mockObserve).toHaveBeenNthCalledWith(2, { entryTypes: ['paint'] });
expect(reporter.info).toHaveBeenCalledWith({
message: 'Failed to execute observe',
namespace: 'performance',
});
});
it('当 PerformanceObserver 不支持 paint 类型时应该处理兼容性问题', async () => {
const { reportTti } = await vi.importActual<any>(
'../../src/utils/custom-perf-metric',
);
// 修改 getEntriesByName 不返回 FCP
mockGetEntriesByName.mockImplementation(name => {
if (name === 'route_change') {
return [mockRouteChangeEntry];
}
return [];
});
// 模拟 observe 抛出错误
mockObserve.mockImplementationOnce(() => {
throw new Error('Failed to execute observe');
});
// 移除 supportedEntryTypes
Object.defineProperty(global.PerformanceObserver, 'supportedEntryTypes', {
value: [],
configurable: true,
});
// 执行
reportTti();
// 验证
expect(mockObserve).toHaveBeenCalledTimes(1);
expect(reporter.info).toHaveBeenCalledWith({
message: 'Failed to execute observe',
namespace: 'performance',
});
});
it('当 PerformanceObserver 的第二种方法也失败时应该处理错误', async () => {
const { reportTti } = await vi.importActual<any>(
'../../src/utils/custom-perf-metric',
);
// 修改 getEntriesByName 不返回 FCP
mockGetEntriesByName.mockImplementation(name => {
if (name === 'route_change') {
return [mockRouteChangeEntry];
}
return [];
});
// 模拟 observe 抛出错误
mockObserve
.mockImplementationOnce(() => {
throw new Error('First error');
})
.mockImplementationOnce(() => {
throw new Error('Second error');
});
// 执行
reportTti();
// 验证
expect(mockObserve).toHaveBeenCalledTimes(2);
expect(reporter.info).toHaveBeenCalledTimes(2);
expect(reporter.info).toHaveBeenNthCalledWith(1, {
message: 'Second error',
namespace: 'performance',
});
expect(reporter.info).toHaveBeenNthCalledWith(2, {
message: 'First error',
namespace: 'performance',
});
});
it('当有多个路由变更但最后一个路由没有startTime时应该使用默认值0', async () => {
const { reportTti, PerfMetricNames } = await vi.importActual<any>(
'../../src/utils/custom-perf-metric',
);
// 修改 getEntriesByName 返回多个路由变更但最后一个没有startTime
mockGetEntriesByName.mockImplementation(name => {
if (name === 'route_change') {
const entries = [
{
startTime: 300,
detail: {
location: {
pathname: '/first-path',
},
},
},
{
// 没有startTime属性
detail: {
location: {
pathname: '/test-path2',
},
},
},
];
// 确保数组有 at 方法
if (!entries.at) {
entries.at = function (index) {
return this[index < 0 ? this.length + index : index];
};
}
return entries;
}
return [];
});
// 执行
reportTti({ key: 'value' });
// 验证
expect(mockSlardarInstance).toHaveBeenCalledWith('sendCustomPerfMetric', {
value: 700, // 1000 - 300 = 700 (使用了第一个路由的startTime)
name: PerfMetricNames.TTI_HOT,
type: 'perf',
extra: {
key: 'value',
},
});
});
});

View File

@@ -0,0 +1,12 @@
{
"operationSettings": [
{
"operationName": "ts-check",
"outputFolderNames": ["./dist"]
},
{
"operationName": "test:cov",
"outputFolderNames": ["./coverage"]
}
]
}

View File

@@ -0,0 +1,7 @@
const { defineConfig } = require('@coze-arch/eslint-config');
module.exports = defineConfig({
packageRoot: __dirname,
preset: 'node',
rules: {},
});

View File

@@ -0,0 +1,49 @@
{
"name": "@coze-arch/report-tti",
"version": "0.0.1",
"author": "tanjizhen@bytedance.com",
"maintainers": [
"duwenhan@bytedance.com"
],
"exports": {
".": "./src/index.ts",
"./custom-perf-metric": "./src/utils/custom-perf-metric"
},
"main": "./src/index.ts",
"typesVersions": {
"*": {
"custom-perf-metric": [
"./src/utils/custom-perf-metric"
]
}
},
"scripts": {
"build": "exit 0",
"lint": "eslint ./ --fix",
"test": "vitest --run --passWithNoTests",
"test:cov": "npm run test -- --coverage"
},
"dependencies": {
"@coze-arch/logger": "workspace:*",
"react": "~18.2.0"
},
"devDependencies": {
"@coze-arch/bot-typings": "workspace:*",
"@coze-arch/eslint-config": "workspace:*",
"@coze-arch/ts-config": "workspace:*",
"@coze-arch/vitest-config": "workspace:*",
"@rsbuild/core": "1.1.13",
"@testing-library/react": "^14.1.2",
"@testing-library/react-hooks": "^8.0.1",
"@types/node": "18.18.9",
"@types/react": "18.2.37",
"@vitest/coverage-v8": "~3.0.5",
"react-dom": "~18.2.0",
"react-is": ">= 16.8.0",
"styled-components": ">= 2",
"typescript": "~5.8.2",
"vitest": "~3.0.5",
"webpack": "~5.91.0"
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <reference types='@coze-arch/bot-typings' />
declare interface Window {
// 运行 e2e 时会注入这个全局方法
REPORT_TTI_FOR_E2E?: (
timestamp: number,
performanceEntry: PerformanceEntryList,
) => void;
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useEffect } from 'react';
import {
reportTti,
REPORT_TTI_DEFAULT_SCENE,
} from './utils/custom-perf-metric';
export interface ReportTtiParams {
isLive: boolean;
extra?: Record<string, string>;
scene?: string; // 一个页面默认只上报一次tti设置不同的scene可上报多次
}
export const useReportTti = ({
isLive,
extra,
scene = REPORT_TTI_DEFAULT_SCENE,
}: ReportTtiParams) => {
useEffect(() => {
if (isLive) {
// TODO useEffect 与真实 DOM 渲染之间会有 gap需要考虑如何抹平差异
// settimeout 在网页后台会挂起,导致 TTI 严重不准
reportTti(extra, scene);
}
}, [isLive]);
};

View File

@@ -0,0 +1,142 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { logger, reporter, getSlardarInstance } from '@coze-arch/logger';
enum CustomPerfMarkNames {
RouteChange = 'route_change',
}
export enum PerfMetricNames {
TTI = 'coze_custom_tti',
TTI_HOT = 'coze_custom_tti_hot',
}
const fcpEntryName = 'first-contentful-paint';
const lastRouteNameRef: {
name: string;
reportScene: Array<string>;
} = { name: '', reportScene: [] };
export const REPORT_TTI_DEFAULT_SCENE = 'init';
export const reportTti = (extra?: Record<string, string>, scene?: string) => {
const sceneKey = scene ?? REPORT_TTI_DEFAULT_SCENE;
const value = performance.now();
const routeChangeEntries = performance.getEntriesByName(
CustomPerfMarkNames.RouteChange,
) as PerformanceMark[];
const lastRoute = routeChangeEntries.at(-1);
// 当前页面已经上报过
if (
lastRoute?.detail?.location?.pathname &&
lastRoute.detail.location.pathname === lastRouteNameRef.name &&
lastRouteNameRef.reportScene.includes(sceneKey)
) {
return;
}
if (document.visibilityState === 'hidden') {
// 页签处于后台FCP / TTI 均不准确,放弃上报
reporter.info({
message: 'page_hidden_on_tti_report',
namespace: 'performance',
});
return;
}
lastRouteNameRef.name = lastRoute?.detail?.location?.pathname;
lastRouteNameRef.reportScene.push(sceneKey);
// 首个路由视为冷启动,否则视为热启动,因为预期 TTI 时间差异会比较大,这里上报到不同的埋点上
if (routeChangeEntries.length > 1) {
// startTime 是相对于 performance.timeOrigin 的一个偏移量
executeSendTtiHot(value - (lastRoute?.startTime ?? 0), extra);
return;
}
const fcp = performance.getEntriesByName(fcpEntryName)[0];
if (fcp) {
// 已发生 FCP比较 TTI 与 FCP 时间,取耗时更长的一个
executeSendTti(value > fcp.startTime ? value : fcp.startTime, {
...extra,
fcpTime: `${fcp.startTime}`,
});
} else if (window.PerformanceObserver) {
// 还未发生 FCP 时,监听 FCP 作为 TTI 上报
const observer = new PerformanceObserver(list => {
const fcpEntry = list.getEntriesByName(fcpEntryName)[0];
if (fcpEntry) {
executeSendTti(fcpEntry.startTime, {
...extra,
fcpTime: `${fcpEntry.startTime}`,
});
observer.disconnect();
}
});
try {
observer.observe({ type: 'paint', buffered: true });
} catch (error) {
// 处理兼容性问题 Failed to execute 'observe' on 'PerformanceObserver': required member entryTypes is undefined.
if (PerformanceObserver.supportedEntryTypes?.includes('paint')) {
try {
observer.observe({ entryTypes: ['paint'] });
} catch (innerError) {
reporter.info({
message: (innerError as Error).message,
namespace: 'performance',
});
}
}
reporter.info({
message: (error as Error).message,
namespace: 'performance',
});
}
}
};
const executeSendTti = (value: number, extra?: Record<string, string>) => {
getSlardarInstance()?.('sendCustomPerfMetric', {
value,
name: PerfMetricNames.TTI,
/** 性能指标类型, perf => 传统性能, spa => SPA 性能, mf => 微前端性能 */
type: 'perf',
extra: {
...extra,
},
});
logger.info({
message: 'coze_custom_tti',
meta: { value, extra },
});
};
const executeSendTtiHot = (value: number, extra?: Record<string, string>) => {
getSlardarInstance()?.('sendCustomPerfMetric', {
value,
name: PerfMetricNames.TTI_HOT,
/** 性能指标类型, perf => 传统性能, spa => SPA 性能, mf => 微前端性能 */
type: 'perf',
extra: {
...extra,
},
});
logger.info({
message: 'coze_custom_tti_hot',
meta: { value, extra },
});
};

View File

@@ -0,0 +1,28 @@
{
"extends": "@coze-arch/ts-config/tsconfig.web.json",
"compilerOptions": {
"strictNullChecks": true,
"rootDir": "./src",
"outDir": "./dist",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo"
},
"include": ["src/**/*.ts", "src/**/*.tsx"],
"references": [
{
"path": "../bot-typings/tsconfig.build.json"
},
{
"path": "../../../config/eslint-config/tsconfig.build.json"
},
{
"path": "../../../config/ts-config/tsconfig.build.json"
},
{
"path": "../../../config/vitest-config/tsconfig.build.json"
},
{
"path": "../logger/tsconfig.build.json"
}
],
"$schema": "https://json.schemastore.org/tsconfig"
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"composite": true
},
"references": [
{
"path": "./tsconfig.build.json"
},
{
"path": "./tsconfig.misc.json"
}
],
"exclude": ["**/*"]
}

View File

@@ -0,0 +1,16 @@
{
"extends": "@coze-arch/ts-config/tsconfig.web.json",
"$schema": "https://json.schemastore.org/tsconfig",
"include": ["__tests__", "vitest.config.ts"],
"exclude": ["./dist"],
"references": [
{
"path": "./tsconfig.build.json"
}
],
"compilerOptions": {
"rootDir": "./",
"strictNullChecks": true,
"outDir": "./dist"
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineConfig } from '@coze-arch/vitest-config';
export default defineConfig({
dirname: __dirname,
preset: 'web',
test: {
globals: true,
},
});