move rrvideo to monorepo (#1181)
* first commit * rrvideo v0.1.0 First version of rrvideo. 1. Use as a Node.JS lib. 2. Use as a CLI. Features are implemented via puppeteer, ffmpeg and rrweb-player. * add readme * update publish script * add node env in cli file and change package.json bin to same like README (#4) Co-authored-by: Xu Yinjie <xuyinjie@xiaobangtouzi.com> * release 0.2.0 * fix #6 avoid assign undefined to config * Fix: Solve the inconsistency between rrvideo and the real recorded page rendering when rendering the page with a headless browser (https://github.com/rrweb-io/rrvideo/pull/26) Author: xujiujiu <906784584@qq.com> --------- Co-authored-by: xujiujiu <906784584@qq.com> * refactor rrvideo 1. refactor code 2. change monorepo config 3. remove separate TS dependencies * add changeset * fix: eslint errors --------- Co-authored-by: Yanzhen Yu <yanzhen@smartx.com> Co-authored-by: xyj <593500664@qq.com> Co-authored-by: Xu Yinjie <xuyinjie@xiaobangtouzi.com> Co-authored-by: xujiujiu <906784584@qq.com>
This commit is contained in:
@@ -10,7 +10,8 @@
|
||||
"rrdom-nodejs",
|
||||
"rrweb-player",
|
||||
"@rrweb/types",
|
||||
"@rrweb/web-extension"
|
||||
"@rrweb/web-extension",
|
||||
"rrvideo"
|
||||
]
|
||||
],
|
||||
"linked": [],
|
||||
|
||||
5
.changeset/tricky-panthers-guess.md
Normal file
5
.changeset/tricky-panthers-guess.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'rrvideo': patch
|
||||
---
|
||||
|
||||
Refactor: Move rrvideo to rrweb's monorepo
|
||||
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -19,6 +19,8 @@ body:
|
||||
- rrweb-snapshot
|
||||
- rrdom
|
||||
- rrweb-player
|
||||
- web-extension
|
||||
- rrvideo
|
||||
- Other (specify below)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -19,6 +19,8 @@ body:
|
||||
- rrweb-snapshot
|
||||
- rrdom
|
||||
- rrweb-player
|
||||
- web-extension
|
||||
- rrvideo
|
||||
- Other (specify below)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
1
.vscode/rrweb-monorepo.code-workspace
vendored
1
.vscode/rrweb-monorepo.code-workspace
vendored
@@ -28,6 +28,7 @@
|
||||
"name": "web-extension (package)",
|
||||
"path": "../packages/web-extension"
|
||||
},
|
||||
{ "name": "rrvideo (package)", "path": "../packages/rrvideo" },
|
||||
{ "name": "@rrweb/types", "path": "../packages/types" }
|
||||
],
|
||||
"settings": {
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
"eslint-plugin-compat": "^4.0.2",
|
||||
"eslint-plugin-jest": "^27.1.3",
|
||||
"eslint-plugin-tsdoc": "^0.2.16",
|
||||
"lerna": "^4.0.0",
|
||||
"markdownlint": "^0.25.1",
|
||||
"markdownlint-cli": "^0.31.1",
|
||||
"prettier": "2.8.4",
|
||||
@@ -37,7 +36,6 @@
|
||||
"typescript": "^4.7.3"
|
||||
},
|
||||
"scripts": {
|
||||
"lerna": "lerna",
|
||||
"build:all": "yarn run concurrently --success=all -r -m=1 'yarn workspaces-to-typescript-project-references' 'yarn turbo run prepublish'",
|
||||
"test": "yarn run concurrently --success=all -r -m=1 'yarn workspaces-to-typescript-project-references --check' 'yarn turbo run test'",
|
||||
"test:watch": "yarn turbo run test:watch",
|
||||
|
||||
@@ -42,8 +42,7 @@
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"rollup-plugin-typescript2": "^0.31.2",
|
||||
"rollup-plugin-web-worker-loader": "^1.6.1",
|
||||
"ts-jest": "^27.1.3",
|
||||
"typescript": "^4.7.3"
|
||||
"ts-jest": "^27.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"cssom": "^0.5.0",
|
||||
|
||||
@@ -44,8 +44,7 @@
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"rollup-plugin-typescript2": "^0.31.2",
|
||||
"rollup-plugin-web-worker-loader": "^1.6.1",
|
||||
"ts-jest": "^27.1.3",
|
||||
"typescript": "^4.7.3"
|
||||
"ts-jest": "^27.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"rrweb-snapshot": "^2.0.0-alpha.6"
|
||||
|
||||
37
packages/rrvideo/README.md
Normal file
37
packages/rrvideo/README.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# rrvideo
|
||||
|
||||
[中文文档](./README.zh_CN.md)
|
||||
|
||||
rrvideo is a tool for transforming the session recorded by [rrweb](https://github.com/rrweb-io/rrweb) into a video.
|
||||
|
||||
## Install rrvideo
|
||||
|
||||
1. Install [ffmpeg](https://ffmpeg.org/download.html)。
|
||||
2. Install [Node.JS](https://nodejs.org/en/download/)。
|
||||
3. Run `npm i -g rrvideo` to install the rrvideo CLI。
|
||||
|
||||
## Use rrvideo
|
||||
|
||||
### Transform a rrweb session(in JSON format) into a video.
|
||||
|
||||
```shell
|
||||
rrvideo --input PATH_TO_YOUR_RRWEB_EVENTS_FILE
|
||||
```
|
||||
|
||||
Running this command will output a `rrvideo-output.mp4` file in the current working directory.
|
||||
|
||||
### Config the output path
|
||||
|
||||
```shell
|
||||
rrvideo --input PATH_TO_YOUR_RRWEB_EVENTS_FILE --output OUTPUT_PATH
|
||||
```
|
||||
|
||||
### Config the replay
|
||||
|
||||
You can prepare a rrvideo config file and pass it to CLI.
|
||||
|
||||
```shell
|
||||
rrvideo --input PATH_TO_YOUR_RRWEB_EVENTS_JSON_FILE --config PATH_TO_YOUR_RRVIDEO_CONFIG_FILE
|
||||
```
|
||||
|
||||
You can find an example of the rrvideo config file [here](./rrvideo.config.example.json).
|
||||
35
packages/rrvideo/README.zh_CN.md
Normal file
35
packages/rrvideo/README.zh_CN.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# rrvideo
|
||||
|
||||
rrvideo 是用于将 [rrweb](https://github.com/rrweb-io/rrweb) 录制的数据转为视频格式的工具。
|
||||
|
||||
## 安装 rrvideo
|
||||
|
||||
1. 安装 [ffmpeg](https://ffmpeg.org/download.html)。
|
||||
2. 安装 [Node.JS](https://nodejs.org/en/download/)。
|
||||
3. 执行 `npm i -g rrvideo` 以安装 rrvideo CLI。
|
||||
|
||||
## 使用 rrvideo
|
||||
|
||||
### 将一份 rrweb 录制的数据(JSON 格式)转换为视频。
|
||||
|
||||
```shell
|
||||
rrvideo --input PATH_TO_YOUR_RRWEB_EVENTS_FILE
|
||||
```
|
||||
|
||||
运行以上命令会在执行文件夹中生成一个 `rrvideo-output.mp4` 文件。
|
||||
|
||||
### 指定输出路径
|
||||
|
||||
```shell
|
||||
rrvideo --input PATH_TO_YOUR_RRWEB_EVENTS_FILE --output OUTPUT_PATH
|
||||
```
|
||||
|
||||
### 对回放进行配置
|
||||
|
||||
通过编写一个 rrvideo 配置文件再传入 rrvideo CLI 的方式可以对回放进行一定的配置。
|
||||
|
||||
```shell
|
||||
rrvideo --input PATH_TO_YOUR_RRWEB_EVENTS_JSON_FILE --config PATH_TO_YOUR_RRVIDEO_CONFIG_FILE
|
||||
```
|
||||
|
||||
rrvideo 配置文件可参考[示例](./rrvideo.config.example.json)。
|
||||
28
packages/rrvideo/package.json
Normal file
28
packages/rrvideo/package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "rrvideo",
|
||||
"version": "2.0.0-alpha.6",
|
||||
"description": "transform rrweb session into video",
|
||||
"main": "build/index.js",
|
||||
"bin": {
|
||||
"rrvideo": "build/cli.js"
|
||||
},
|
||||
"files": [
|
||||
"build"
|
||||
],
|
||||
"types": "build/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"prepublish": "yarn build"
|
||||
},
|
||||
"author": "yanzhen@smartx.com",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/minimist": "^1.2.1",
|
||||
"@rrweb/types": "^2.0.0-alpha.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.5",
|
||||
"puppeteer": "^19.7.2",
|
||||
"rrweb-player": "^2.0.0-alpha.6"
|
||||
}
|
||||
}
|
||||
10
packages/rrvideo/rrvideo.config.example.json
Normal file
10
packages/rrvideo/rrvideo.config.example.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"width": 1400,
|
||||
"height": 900,
|
||||
"speed": 4,
|
||||
"skipInactive": true,
|
||||
"mouseTail": {
|
||||
"strokeStyle": "green",
|
||||
"lineWidth": 2
|
||||
}
|
||||
}
|
||||
39
packages/rrvideo/src/cli.ts
Normal file
39
packages/rrvideo/src/cli.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env node
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import minimist from 'minimist';
|
||||
import type { RRwebPlayerOptions } from 'rrweb-player';
|
||||
import { transformToVideo } from './index';
|
||||
|
||||
const argv = minimist(process.argv.slice(2));
|
||||
|
||||
if (!argv.input) {
|
||||
throw new Error('please pass --input to your rrweb events file');
|
||||
}
|
||||
|
||||
let config = {};
|
||||
|
||||
if (argv.config) {
|
||||
const configPathStr = argv.config as string;
|
||||
const configPath = path.isAbsolute(configPathStr)
|
||||
? configPathStr
|
||||
: path.resolve(process.cwd(), configPathStr);
|
||||
config = JSON.parse(fs.readFileSync(configPath, 'utf-8')) as Omit<
|
||||
RRwebPlayerOptions['props'],
|
||||
'events'
|
||||
>;
|
||||
}
|
||||
|
||||
transformToVideo({
|
||||
input: argv.input as string,
|
||||
output: argv.output as string,
|
||||
rrwebPlayer: config,
|
||||
})
|
||||
.then((file) => {
|
||||
console.log(`Successfully transformed into "${file}".`);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('Failed to transform this session.');
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
217
packages/rrvideo/src/index.ts
Normal file
217
packages/rrvideo/src/index.ts
Normal file
@@ -0,0 +1,217 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { spawn } from 'child_process';
|
||||
import puppeteer from 'puppeteer';
|
||||
import type { Page, Browser } from 'puppeteer';
|
||||
import type { eventWithTime } from '@rrweb/types';
|
||||
import type { RRwebPlayerOptions } from 'rrweb-player';
|
||||
|
||||
const rrwebScriptPath = path.resolve(
|
||||
require.resolve('rrweb-player'),
|
||||
'../../dist/index.js',
|
||||
);
|
||||
const rrwebStylePath = path.resolve(rrwebScriptPath, '../style.css');
|
||||
const rrwebRaw = fs.readFileSync(rrwebScriptPath, 'utf-8');
|
||||
const rrwebStyle = fs.readFileSync(rrwebStylePath, 'utf-8');
|
||||
|
||||
type RRvideoConfig = {
|
||||
input: string;
|
||||
output?: string;
|
||||
headless?: boolean;
|
||||
fps?: number;
|
||||
cb?: (file: string, error: null | Error) => void;
|
||||
// start playback delay time
|
||||
startDelayTime?: number;
|
||||
rrwebPlayer?: Omit<RRwebPlayerOptions['props'], 'events'>;
|
||||
};
|
||||
|
||||
const defaultConfig: Required<RRvideoConfig> = {
|
||||
input: '',
|
||||
output: 'rrvideo-output.mp4',
|
||||
headless: true,
|
||||
fps: 15,
|
||||
cb: () => {
|
||||
//
|
||||
},
|
||||
startDelayTime: 1000,
|
||||
rrwebPlayer: {},
|
||||
};
|
||||
|
||||
function getHtml(
|
||||
events: Array<eventWithTime>,
|
||||
config?: Omit<RRwebPlayerOptions['props'], 'events'>,
|
||||
): string {
|
||||
return `
|
||||
<html>
|
||||
<head>
|
||||
<style>${rrwebStyle}</style>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
${rrwebRaw};
|
||||
/*<!--*/
|
||||
const events = ${JSON.stringify(events).replace(
|
||||
/<\/script>/g,
|
||||
'<\\/script>',
|
||||
)};
|
||||
/*-->*/
|
||||
const userConfig = ${JSON.stringify(config || {})};
|
||||
window.replayer = new rrwebPlayer({
|
||||
target: document.body,
|
||||
props: {
|
||||
...userConfig,
|
||||
events,
|
||||
showController: false,
|
||||
autoPlay: false, // autoPlay off by default
|
||||
},
|
||||
});
|
||||
window.replayer.addEventListener('finish', () => window.onReplayFinish());
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
export class RRvideo {
|
||||
private browser!: Browser;
|
||||
private page!: Page;
|
||||
private state: 'idle' | 'recording' | 'closed' = 'idle';
|
||||
private config = {
|
||||
...defaultConfig,
|
||||
};
|
||||
|
||||
constructor(config: RRvideoConfig) {
|
||||
this.updateConfig(config);
|
||||
}
|
||||
|
||||
public async transform() {
|
||||
try {
|
||||
this.browser = await puppeteer.launch({
|
||||
headless: this.config.headless,
|
||||
});
|
||||
this.page = await this.browser.newPage();
|
||||
await this.page.goto('about:blank');
|
||||
|
||||
await this.page.exposeFunction('onReplayFinish', () => {
|
||||
void this.finishRecording();
|
||||
});
|
||||
|
||||
const eventsPath = path.isAbsolute(this.config.input)
|
||||
? this.config.input
|
||||
: path.resolve(process.cwd(), this.config.input);
|
||||
const events = JSON.parse(
|
||||
fs.readFileSync(eventsPath, 'utf-8'),
|
||||
) as eventWithTime[];
|
||||
|
||||
await this.page.setContent(getHtml(events, this.config.rrwebPlayer));
|
||||
|
||||
setTimeout(() => {
|
||||
void this.startRecording().then(() => {
|
||||
return this.page.evaluate('window.replayer.play();');
|
||||
});
|
||||
}, this.config.startDelayTime);
|
||||
} catch (error) {
|
||||
this.config.cb('', error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
public updateConfig(config: RRvideoConfig) {
|
||||
if (!config.input) throw new Error('input is required');
|
||||
config.output = config.output || defaultConfig.output;
|
||||
Object.assign(this.config, defaultConfig, config);
|
||||
}
|
||||
|
||||
private async startRecording() {
|
||||
this.state = 'recording';
|
||||
let wrapperSelector = '.replayer-wrapper';
|
||||
if (this.config.rrwebPlayer.width && this.config.rrwebPlayer.height) {
|
||||
wrapperSelector = '.rr-player';
|
||||
}
|
||||
const wrapperEl = await this.page.$(wrapperSelector);
|
||||
|
||||
if (!wrapperEl) {
|
||||
throw new Error('failed to get replayer element');
|
||||
}
|
||||
|
||||
// start ffmpeg
|
||||
const args = [
|
||||
// fps
|
||||
'-framerate',
|
||||
this.config.fps.toString(),
|
||||
// input
|
||||
'-f',
|
||||
'image2pipe',
|
||||
'-i',
|
||||
'-',
|
||||
// output
|
||||
'-y',
|
||||
this.config.output,
|
||||
];
|
||||
|
||||
const ffmpegProcess = spawn('ffmpeg', args);
|
||||
ffmpegProcess.stderr.setEncoding('utf-8');
|
||||
ffmpegProcess.stderr.on('data', console.log);
|
||||
|
||||
let processError: Error | null = null;
|
||||
|
||||
const timer = setInterval(() => {
|
||||
if (this.state === 'recording' && !processError) {
|
||||
void wrapperEl
|
||||
.screenshot({
|
||||
encoding: 'binary',
|
||||
})
|
||||
.then((buffer) => ffmpegProcess.stdin.write(buffer))
|
||||
.catch();
|
||||
} else {
|
||||
clearInterval(timer);
|
||||
if (this.state === 'closed' && !processError) {
|
||||
ffmpegProcess.stdin.end();
|
||||
}
|
||||
}
|
||||
}, 1000 / this.config.fps);
|
||||
|
||||
const outputPath = path.isAbsolute(this.config.output)
|
||||
? this.config.output
|
||||
: path.resolve(process.cwd(), this.config.output);
|
||||
ffmpegProcess.on('close', () => {
|
||||
if (processError) {
|
||||
return;
|
||||
}
|
||||
this.config.cb(outputPath, null);
|
||||
});
|
||||
ffmpegProcess.on('error', (error) => {
|
||||
if (processError) {
|
||||
return;
|
||||
}
|
||||
processError = error;
|
||||
this.config.cb(outputPath, error);
|
||||
});
|
||||
ffmpegProcess.stdin.on('error', (error) => {
|
||||
if (processError) {
|
||||
return;
|
||||
}
|
||||
processError = error;
|
||||
this.config.cb(outputPath, error);
|
||||
});
|
||||
}
|
||||
|
||||
private async finishRecording() {
|
||||
this.state = 'closed';
|
||||
await this.browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
export function transformToVideo(config: RRvideoConfig): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const rrvideo = new RRvideo({
|
||||
...config,
|
||||
cb(file, error) {
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
resolve(file);
|
||||
},
|
||||
});
|
||||
void rrvideo.transform();
|
||||
});
|
||||
}
|
||||
27
packages/rrvideo/tsconfig.json
Normal file
27
packages/rrvideo/tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"target": "ES6",
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "./build",
|
||||
"rootDir": "./src",
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"strictBindCallApply": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../rrweb-player"
|
||||
},
|
||||
{
|
||||
"path": "../types"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -20,8 +20,7 @@
|
||||
"svelte": "^3.2.0",
|
||||
"svelte-check": "^1.4.0",
|
||||
"svelte-preprocess": "^4.0.0",
|
||||
"tslib": "^2.0.0",
|
||||
"typescript": "^4.7.3"
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tsconfig/svelte": "^1.0.0",
|
||||
|
||||
@@ -220,7 +220,7 @@
|
||||
export const playRange = (
|
||||
timeOffset: number,
|
||||
endTimeOffset: number,
|
||||
startLooping: boolean = false,
|
||||
startLooping = false,
|
||||
afterHook: undefined | (() => void) = undefined,
|
||||
) => {
|
||||
if (startLooping) {
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
export const playRange = (
|
||||
timeOffset: number,
|
||||
endTimeOffset: number,
|
||||
startLooping: boolean = false,
|
||||
startLooping = false,
|
||||
afterHook: undefined | (() => void) = undefined,
|
||||
) => {
|
||||
controller.playRange(timeOffset, endTimeOffset, startLooping, afterHook);
|
||||
|
||||
@@ -56,7 +56,6 @@
|
||||
"rollup-plugin-typescript2": "^0.31.2",
|
||||
"ts-jest": "^27.0.5",
|
||||
"ts-node": "^7.0.1",
|
||||
"tslib": "^1.9.3",
|
||||
"typescript": "^4.7.3"
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,8 +74,7 @@
|
||||
"simple-peer-light": "^9.10.0",
|
||||
"ts-jest": "^27.1.3",
|
||||
"ts-node": "^10.9.1",
|
||||
"tslib": "^2.3.1",
|
||||
"typescript": "^4.7.3"
|
||||
"tslib": "^2.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@rrweb/types": "^2.0.0-alpha.6",
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
"dist"
|
||||
],
|
||||
"devDependencies": {
|
||||
"typescript": "^4.7.3",
|
||||
"vite": "^3.2.0-beta.2",
|
||||
"vite-plugin-dts": "^1.6.6"
|
||||
},
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
"@vitejs/plugin-react": "^2.1.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"type-fest": "^2.19.0",
|
||||
"typescript": "^4.7.3",
|
||||
"vite": "^3.1.8",
|
||||
"vite-plugin-web-extension": "^1.4.5",
|
||||
"vite-plugin-zip-pack": "^1.0.5",
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"baseUrl": ".",
|
||||
"module": "ESNext",
|
||||
"target": "es2016",
|
||||
"lib": [
|
||||
"DOM",
|
||||
"ESNext"
|
||||
],
|
||||
"lib": ["DOM", "ESNext"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"incremental": false,
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
@@ -17,16 +15,11 @@
|
||||
"strictNullChecks": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"paths": {
|
||||
"~/*": [
|
||||
"src/*"
|
||||
]
|
||||
"~/*": ["src/*"]
|
||||
},
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"exclude": [
|
||||
"dist",
|
||||
"node_modules"
|
||||
],
|
||||
"exclude": ["dist", "node_modules"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../rrweb"
|
||||
|
||||
@@ -17,6 +17,12 @@
|
||||
},
|
||||
{
|
||||
"path": "packages/types"
|
||||
},
|
||||
{
|
||||
"path": "packages/rrvideo"
|
||||
},
|
||||
{
|
||||
"path": "packages/web-extension"
|
||||
}
|
||||
],
|
||||
"files": [],
|
||||
|
||||
Reference in New Issue
Block a user