Migrates to vite@6 to drop base64 inlined worker source from all bundles (#1762)

* chore: maintain CSS output file name in vite@6.0.1

Without this change, build would fail because the produced stylesheet assumes
the `package.json['name']` i.e., `styles/rrweb.css`. To maintain the existing
behavior, these changes are required.

See https://vite.dev/guide/migration.html#customize-css-output-file-name-in-library-mode
* build(rrvideo): upgrade playwright from 1.32.1 to 1.56.1

Update playwright dependency to latest version and refactor test execution options to use a shared configuration with increased timeout for stability.

* debug(rrvideo): add comprehensive logging to video transformation process

Add detailed console.log statements throughout the transformToVideo function to track execution flow and debug potential issues. Logging covers browser launch, context creation, page navigation, replay progress, and video file operations.

* ci(rrvideo): install playwright browsers and improve test output visibility

- Add Playwright Chromium installation step to CI workflow
- Change test execution stdio from 'pipe' to 'inherit' for better debugging

* fix(rrvideo): prevent autoplay and manually start playback after event listeners

Set autoPlay to false in replayer configuration and manually call play() after all event listeners are attached. This ensures event handlers are properly registered before playback begins, preventing potential race conditions.

Also refactor test execution options to separate stdio configuration from timeout settings for better control over test output visibility.

* fix(rrvideo): add timeout and error handling to replay process

Add comprehensive error handling to prevent hanging during video transformation:
- Add 2-minute timeout for replay finish event
- Add console and error listeners for better debugging
- Improve promise chain with proper error catching
- Clear timeout on successful completion or error

This prevents the process from hanging indefinitely when the replay finish event never fires.

* fix(rrvideo): add error handling and restructure replayer initialization

Wrap replayer initialization in try-catch block to handle potential errors gracefully. Restructure Player instantiation to use rrwebPlayer directly instead of rrwebPlayer.Player, and move width/height into props object for correct API usage. On error, log to console and trigger onReplayFinish callback to prevent hanging state.

* build(umd): rename record and replay globals

Update UMD build globals for recorder and replayer and
refresh documentation accordingly.

BREAKING CHANGE: UMD global names changed to rrwebRecord
and rrwebReplay.

* fix(rrvideo): adjust replay timeout to duration

* docs: update rrweb-player CDN script path

* Update vite.config.default.ts

Co-authored-by: Eoghan Murray <eoghan@getthere.ie>

---------

Co-authored-by: Rui <rui@conti.sh>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Eoghan Murray <eoghan@getthere.ie>
This commit is contained in:
Justin Halsall
2026-04-01 12:00:00 +08:00
committed by GitHub
parent 360ed54d2d
commit e4e73596ec
32 changed files with 441 additions and 181 deletions

View File

@@ -50,7 +50,7 @@
],
"devDependencies": {
"puppeteer": "^20.9.0",
"vite": "^5.3.1",
"vite": "^6.0.1",
"vite-plugin-dts": "^3.9.1",
"vitest": "^1.4.0",
"typescript": "^5.4.5"

View File

@@ -71,7 +71,7 @@
"package.json"
],
"devDependencies": {
"vite": "^5.3.1",
"vite": "^6.0.1",
"vite-plugin-dts": "^3.9.1",
"vitest": "^1.4.0",
"typescript": "^5.4.5"

View File

@@ -45,7 +45,7 @@
"devDependencies": {
"rrweb": "^2.0.0-alpha.20",
"typescript": "^5.4.5",
"vite": "^5.3.1",
"vite": "^6.0.1",
"vite-plugin-dts": "^3.9.1"
},
"peerDependencies": {

View File

@@ -45,7 +45,7 @@
"devDependencies": {
"rrweb": "^2.0.0-alpha.20",
"typescript": "^5.4.5",
"vite": "^5.3.1",
"vite": "^6.0.1",
"vite-plugin-dts": "^3.9.1"
},
"peerDependencies": {

View File

@@ -47,7 +47,7 @@
"devDependencies": {
"rrweb": "^2.0.0-alpha.20",
"typescript": "^5.4.5",
"vite": "^5.3.1",
"vite": "^6.0.1",
"vite-plugin-dts": "^3.9.1",
"vitest": "^1.4.0",
"puppeteer": "^20.9.0"

View File

@@ -46,7 +46,7 @@
"@rrweb/rrweb-plugin-console-record": "^2.0.0-alpha.20",
"rrweb": "^2.0.0-alpha.20",
"typescript": "^5.4.5",
"vite": "^5.3.1",
"vite": "^6.0.1",
"vite-plugin-dts": "^3.9.1"
},
"peerDependencies": {

View File

@@ -45,7 +45,7 @@
"devDependencies": {
"rrweb": "^2.0.0-alpha.20",
"typescript": "^5.4.5",
"vite": "^5.3.1",
"vite": "^6.0.1",
"vite-plugin-dts": "^3.9.1"
},
"peerDependencies": {

View File

@@ -46,7 +46,7 @@
"@rrweb/rrweb-plugin-sequential-id-record": "^2.0.0-alpha.20",
"rrweb": "^2.0.0-alpha.20",
"typescript": "^5.4.5",
"vite": "^5.3.1",
"vite": "^6.0.1",
"vite-plugin-dts": "^3.9.1"
},
"peerDependencies": {

View File

@@ -49,7 +49,7 @@
],
"devDependencies": {
"puppeteer": "^20.9.0",
"vite": "^5.3.1",
"vite": "^6.0.1",
"vite-plugin-dts": "^3.9.1",
"vitest": "^1.4.0",
"typescript": "^5.4.5"

View File

@@ -1,4 +1,4 @@
import path from 'path';
import config from '../../vite.config.default';
export default config(path.resolve(__dirname, 'src/index.ts'), 'rrweb');
export default config(path.resolve(__dirname, 'src/index.ts'), 'rrwebRecord');

View File

@@ -50,7 +50,7 @@
],
"devDependencies": {
"puppeteer": "^20.9.0",
"vite": "^5.3.1",
"vite": "^6.0.1",
"vite-plugin-dts": "^3.9.1",
"vitest": "^1.4.0",
"typescript": "^5.4.5"

View File

@@ -1,4 +1,4 @@
import path from 'path';
import config from '../../vite.config.default';
export default config(path.resolve(__dirname, 'src/index.ts'), 'rrweb');
export default config(path.resolve(__dirname, 'src/index.ts'), 'rrwebReplay');

View File

@@ -46,7 +46,7 @@
"compare-versions": "^4.1.3",
"eslint": "^8.15.0",
"puppeteer": "^9.1.1",
"vite": "^5.3.1",
"vite": "^6.0.1",
"vite-plugin-dts": "^3.9.1",
"vitest": "^1.4.0",
"typescript": "^5.4.5"

View File

@@ -48,7 +48,7 @@
"eslint": "^8.15.0",
"puppeteer": "^17.1.3",
"typescript": "^5.4.5",
"vite": "^5.3.1",
"vite": "^6.0.1",
"vite-plugin-dts": "^3.9.1"
},
"dependencies": {

View File

@@ -57,22 +57,27 @@ function getHtml(events: Array<eventWithTime>, config?: RRvideoConfig): string {
)};
/*-->*/
const userConfig = ${JSON.stringify(config?.rrwebPlayer || {})};
window.replayer = new rrwebPlayer.Player({
target: document.body,
width: userConfig.width,
height: userConfig.height,
props: {
...userConfig,
events,
showController: false,
},
});
window.replayer.addEventListener('finish', () => window.onReplayFinish());
window.replayer.addEventListener('ui-update-progress', (payload)=> window.onReplayProgressUpdate
(payload));
window.replayer.addEventListener('resize',()=>document.querySelector('.replayer-wrapper').style.transform = 'scale(${
(config?.resolutionRatio ?? 1) * MaxScaleValue
}) translate(-50%, -50%)');
try {
window.replayer = new rrwebPlayer({
target: document.body,
props: {
...userConfig,
events,
showController: false,
autoPlay: false,
},
});
window.replayer.addEventListener('finish', () => window.onReplayFinish());
window.replayer.addEventListener('ui-update-progress', (payload)=> window.onReplayProgressUpdate(payload));
window.replayer.addEventListener('resize', () => document.querySelector('.replayer-wrapper').style.transform = 'scale(${
(config?.resolutionRatio ?? 1) * MaxScaleValue
}) translate(-50%, -50%)');
// Start playback after event listeners are attached
window.replayer.play();
} catch (error) {
console.error('Error initializing replayer:', error);
window.onReplayFinish();
}
</script>
</body>
</html>
@@ -139,6 +144,16 @@ export async function transformToVideo(options: RRvideoConfig) {
});
const page = await context.newPage();
await page.goto('about:blank');
// Listen to console messages from the page
page.on('console', (msg) => {
console.log('[PAGE CONSOLE]', msg.type(), msg.text());
});
// Listen to page errors
page.on('pageerror', (error) => {
console.error('[PAGE ERROR]', error.message);
});
await page.exposeFunction(
'onReplayProgressUpdate',
(data: { payload: number }) => {
@@ -147,12 +162,41 @@ export async function transformToVideo(options: RRvideoConfig) {
);
// Wait for the replay to finish
await new Promise<void>(
(resolve) =>
void page
.exposeFunction('onReplayFinish', () => resolve())
.then(() => page.setContent(getHtml(events, config))),
);
await new Promise<void>((resolve, reject) => {
const timeoutBuffer = 120000; // 2 minute timeout buffer
const videoStartTime = events[0]?.timestamp;
const videoEndTime = events[events.length - 1]?.timestamp;
const videoDuration = videoEndTime - videoStartTime;
const videoPlaybackSpeed = options.rrwebPlayer?.speed || 1;
const expectedPlaybackTime = videoDuration / videoPlaybackSpeed;
console.log(
`[DEBUG] Expected playback time: ${expectedPlaybackTime}ms (video duration: ${videoDuration}ms, playback speed: ${videoPlaybackSpeed}x)`,
);
const totalTimeout = expectedPlaybackTime + timeoutBuffer;
const timeout = setTimeout(() => {
console.error('[DEBUG] Replay timeout - finish event never fired');
reject(new Error('Replay timeout'));
}, totalTimeout); // playback + 2 minute timeout
void page
.exposeFunction('onReplayFinish', () => {
console.log('[DEBUG] Replay finished');
clearTimeout(timeout);
resolve();
})
.then(() => {
console.log('[DEBUG] Setting page content');
return page.setContent(getHtml(events, config));
})
.then(() => {
console.log('[DEBUG] Page content set successfully');
})
.catch((err) => {
console.error('[DEBUG] Error setting page content:', err);
clearTimeout(timeout);
reject(err);
});
});
const videoPath = (await page.video()?.path()) || '';
const cleanFiles = async (videoPath: string) => {
await fs.remove(videoPath);

View File

@@ -18,18 +18,19 @@ describe('should be able to run cli', () => {
await fs.remove(path.resolve(__dirname, './generated'));
});
const execOptions = { stdio: 'pipe', timeout: 60_000 } as const;
const execOptions = { timeout: 60_000 } as const;
const execOptionsWithOutput = { ...execOptions, stdio: 'inherit' } as const;
it('should throw error without input path', () => {
expect(() => {
execSync('node ./build/cli.js', execOptions);
execSync('node ./build/cli.js', { ...execOptions, stdio: 'pipe' });
}).toThrowError(/.*please pass --input to your rrweb events file.*/);
});
it('should generate a video without output path', () => {
execSync(
'node ./build/cli.js --input ./test/generated/example.json',
execOptions,
execOptionsWithOutput,
);
const outputFile = path.resolve(__dirname, '../rrvideo-output.webm');
expect(fs.existsSync(outputFile)).toBe(true);
@@ -40,7 +41,7 @@ describe('should be able to run cli', () => {
const outputFile = path.resolve(__dirname, './generated/output.webm');
execSync(
`node ./build/cli.js --input ./test/generated/example.json --output ${outputFile}`,
execOptions,
execOptionsWithOutput,
);
expect(fs.existsSync(outputFile)).toBe(true);
fs.removeSync(outputFile);

View File

@@ -19,7 +19,7 @@ rrweb-player can also be included with `<script>`
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/rrweb-player@latest/dist/style.css"
/>
<script src="https://cdn.jsdelivr.net/npm/rrweb-player@latest/dist/index.umd.cjs"></script>
<script src="https://cdn.jsdelivr.net/npm/rrweb-player@latest/dist/rrweb-player.umd.cjs"></script>
```
Or installed by using NPM

View File

@@ -16,7 +16,7 @@
"svelte-preprocess": "^5.0.3",
"svelte2tsx": "^0.7.30",
"tslib": "^2.0.0",
"vite": "^5.3.1"
"vite": "^6.0.1"
},
"dependencies": {
"@tsconfig/svelte": "^1.0.0",

View File

@@ -63,7 +63,7 @@
"ts-node": "^7.0.1",
"tslib": "^1.9.3",
"typescript": "^5.4.5",
"vite": "^5.3.1",
"vite": "^6.0.1",
"vite-plugin-dts": "^3.9.1",
"vitest": "^1.4.0"
},

View File

@@ -76,7 +76,7 @@
"ts-node": "^10.9.1",
"tslib": "^2.3.1",
"typescript": "^5.4.5",
"vite": "^5.3.1",
"vite": "^6.0.1",
"vite-plugin-dts": "^3.9.1"
},
"dependencies": {

View File

@@ -46,7 +46,7 @@
"package.json"
],
"devDependencies": {
"vite": "^5.3.1",
"vite": "^6.0.1",
"vite-plugin-dts": "^3.9.1"
},
"browserslist": [

View File

@@ -46,7 +46,7 @@
"package.json"
],
"devDependencies": {
"vite": "^5.2.8",
"vite": "^6.0.1",
"vite-plugin-dts": "^3.8.1"
},
"dependencies": {}

View File

@@ -26,7 +26,7 @@
"@vitejs/plugin-react": "^4.2.1",
"semver": "^7.6.3",
"type-fest": "^2.19.0",
"vite": "^5.3.1",
"vite": "^6.0.1",
"vite-plugin-web-extension": "^4.1.3",
"vite-plugin-zip-pack": "^1.2.2",
"webextension-polyfill": "^0.10.0"