Compare commits

..

10 Commits

Author SHA1 Message Date
xugp
ba125e95a6 Fix local rrweb playback flow and add demo page
Some checks failed
ESLint Check / ESLint Check and Report Upload (push) Has been cancelled
ESLint Check / Build Base for Bundle Size Comparison (push) Has been cancelled
Prettier Check / Format Check (push) Has been cancelled
Prettier Check / Format Code (push) Has been cancelled
Tests / Tests (push) Has been cancelled
2026-04-08 16:01:21 +08:00
Justin Halsall
3ae040ddf6 ci: restore bundle size badges (#1805) 2026-03-19 16:12:39 +01:00
Justin Halsall
8eeaba7449 Fix release changeset package names (#1807) 2026-03-19 13:27:55 +01:00
Justin Halsall
acba854f30 Fix security vulnerability in workflows (#1804)
* Fix a security hole in #1787 found by Arun Murugesan:

"The workflow .github/workflows/eslint-check.yml contained a critical "pwn request" vulnerability that allows any GitHub user to execute arbitrary code with access to repository secrets by opening a pull request."

See https://github.com/preactjs/compressed-size-action/issues/54 for why that action shouldn't be used with pull_request_target

This change in this PR drops compressed-size-action in favour of executing the steps ourselves in two workflows, one which produces the size artifact, and the other which reads the artifact has the permissions to write the message back to the original PR (which is in a third party repo)

* The annotate action also needed pull-requests: write permission (fixes failing run 'ESLint Annotation')

* ci(bundle-size): extract bundle size scripts and simplify workflow

- Add `.github/scripts/measure-bundle-sizes.js` and
  `render-bundle-size-comment.js` to replace inline node scripts
  embedded in workflow YAML, improving readability and reusability
- Refactor `eslint-check.yml` to use the new script files and fix
  checkout steps to handle both PR and non-PR triggers correctly
- Refactor `pr-checks-privileged.yml` to replace the large
  `github-script` block with `render-bundle-size-comment.js` and
  the `marocchino/sticky-pull-request-comment` action; remove the
  now-unnecessary `pr_number.txt` artifact by reading the PR number
  directly from the workflow_run event
- Pin `ataylorme/eslint-annotate-action` to a specific commit SHA
- Add `actions: read` permission where needed for artifact downloads

* ci: add fork PR support and harden workflow

- Look up PR number via API when workflow_run.pull_requests is empty
  (GitHub leaves it empty for fork PRs), falling back gracefully
- Use head SHA instead of branch name for PR checkout to avoid TOCTOU
- Fix formatSignedSize to produce +0 instead of -0 for zero values
- Gate comment steps on successful PR number lookup

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Eoghan Murray <eoghan@getthere.ie>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 22:23:34 +01:00
Justin Halsall
bcf93ca926 docs: revamp installation docs for esm and umd (#1788)
* docs: revamp installation docs for esm and umd

Document recommended install paths across the main guides and package
READMEs for rrweb, @rrweb/all, @rrweb/record, @rrweb/replay, and
rrweb-player.

Clarify three usage modes: bundler/npm, browser no-build with
import maps and +esm, and legacy UMD fallback.

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Apply formatting changes

* Apply suggestion from @eoghanmurray

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

* Apply formatting changes

* docs(all): streamline README usage section

Move the guide link next to the import example and remove the
duplicated Usage section to keep docs concise and easier to scan.

* docs(readme): update gzip size badges in zh-cn readme

* docs(plugins): update readme imports to scoped esm packages

Replace `rrweb` default imports and `rrweb.Replayer` usage with
`@rrweb/record` `record` and `@rrweb/replay` `Replayer` in plugin
usage examples.

Also update canvas WebRTC plugin imports to scoped `@rrweb/*`
package names to keep docs aligned with current package structure.

* docs: update docs to prefer scoped esm packages

replace `rrweb` default import examples with `@rrweb/record` and
`@rrweb/replay` across recipes and guides in en/zh-CN.

clarify package selection for new integrations, add `@rrweb/all`
convenience guidance, and refresh CDN/style import snippets for ESM and legacy UMD compatibility.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Eoghan Murray <eoghan@getthere.ie>
2026-02-17 13:59:02 +01:00
Justin Halsall
dab8c29da8 Fix typo in turbo.json 2026-02-16 12:03:22 +01:00
Eoghan Murray
33e01f5f00 Umd folder (#1704)
* Don't allow video autoplay to automatically unfreeze page. If it's a 'real' playback, there should be a mount or a keyboard event which will serve to unfreeze the page. Also add other non-user events to the list (we really should have an `isUserEvent` function)

* Apply formatting changes

* Create a new `umd` folder alongside `dist` for output of UMD files with a plain `.js` instead of `.cjs` extension, as the latter won't be served with the correct mime type by jsdelivr

 - #1687 (just rename `.cjs` to `.js`) was rejected due to the the 'dual package hazard' [1], and produces a warning when run through publint.dev (which was the original motivation for changing to \.cjs)
 - jsdelivr won't be serving `.cjs` with the correct mime type: https://github.com/jsdelivr/jsdelivr/issues/18584

[1] https://nodejs.org/en/learn/modules/publishing-a-package#the-dual-package-hazard

* Update to point to alpha.19 as presumably that's when the umd folder will be available after the changes in this PR

* Apply formatting changes

* Don't try to create the same directory twice (was failing on packages/packer/umd)

* Create thirty-shirts-grow.md

* Revert something that shouldn't have gotten into the UMD branch folder

* Apply formatting changes

* Update vite.config.default.ts

* Apply formatting changes

* build: include umd builds in published packages

Add umd directory to the files array in package.json for all packages to include UMD builds in npm publications. Also update .gitignore to exclude
umd folders from version control.

* Docs: point to correct file

* Remove unused code

* docs: update rrweb cdn urls to umd bundles

Align README and guide examples with published UMD file locations for
rrweb, @rrweb/record, and @rrweb/replay.

Update versioned rrweb script examples from 2.0.0-alpha.19 to
2.0.0-alpha.21 in both English and Chinese guides.

* build(all): include umd folder in package files

---------

Co-authored-by: eoghanmurray <eoghanmurray@users.noreply.github.com>
Co-authored-by: Justin Halsall <Juice10@users.noreply.github.com>
2026-02-13 15:03:23 +01:00
Eoghan Murray
f0d25990c7 Update filesize badges (might need further evolution before 2.0.0) (#1787)
* Update filesize badges (might need further evolution before 2.0.0)

* Don't run full CI/CD when only .md docs have changed in the PR

 - move eslint checks into their own file so they can also ignore .md changes
 - prettier checks don't need the same perms as eslint, so we can demote pull_request_target -> pull_request

* Add empty changeset

* Implement the bundle size change originally originally added in #1784 - adding here also to show how the conflicts would resolve

* Update .github/workflows/eslint-check.yml

---------

Co-authored-by: Justin Halsall <Juice10@users.noreply.github.com>
2026-02-13 11:37:08 +01:00
Justin Halsall
22bc4c334e 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>
2026-02-06 15:11:36 +01:00
Alailson
ad5ac17422 fix: ensure empty string replace/replaceSync clears stylesheets (#1774)
* fix: ensure empty string replace/replaceSync clears stylesheets

---------

Co-authored-by: Justin Halsall <Juice10@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-06 11:43:30 +01:00
86 changed files with 2633 additions and 1222 deletions

View File

@@ -0,0 +1,6 @@
---
"@rrweb/record": major
"@rrweb/replay": major
---
BREAKING CHANGE: Rename UMD global names from `rrweb` to `rrwebRecord` for the recorder and `rrwebReplay` for the replayer. This avoids conflicts when both are loaded on the same page.

View File

@@ -0,0 +1,10 @@
---
"@rrweb/all": patch
"@rrweb/packer": patch
"@rrweb/record": patch
"rrweb-snapshot": patch
"rrweb": patch
"@rrweb/web-extension": patch
---
Drop base64 inlined worker source from all bundles

View File

@@ -0,0 +1,4 @@
---
---
Docs-only update: clarify package recommendation order (`@rrweb/record` + `@rrweb/replay` first, `@rrweb/all` as convenience), and fix example typos.

View File

@@ -0,0 +1,2 @@
---
---

View File

@@ -0,0 +1,5 @@
---
"rrvideo": patch
---
Adjust replay timeout to be based on video duration plus a 2-minute buffer instead of a fixed 2-minute timeout. This prevents timeout errors for longer recordings.

View File

@@ -0,0 +1,21 @@
---
"@rrweb/all": patch
"@rrweb/packer": patch
"@rrweb/rrweb-plugin-canvas-webrtc-record": patch
"@rrweb/rrweb-plugin-canvas-webrtc-replay": patch
"@rrweb/rrweb-plugin-console-record": patch
"@rrweb/rrweb-plugin-console-replay": patch
"@rrweb/rrweb-plugin-sequential-id-record": patch
"@rrweb/rrweb-plugin-sequential-id-replay": patch
"@rrweb/record": patch
"@rrweb/replay": patch
"rrdom": patch
"rrdom-nodejs": patch
"rrweb": patch
"rrweb-player": patch
"rrweb-snapshot": patch
"@rrweb/types": patch
"@rrweb/utils": patch
---
Provide a /umd/ output folder alongside the /dist/ one so that we can serve UMD (Universal Module Definition) files with a .js extension, without upsetting expectations set by package.json that all .js files in /dist/ are modules

View File

@@ -0,0 +1,5 @@
---
"rrvideo": patch
---
Add better logging on playback and fix the use of rrweb-player so it doesn't stall and fail playback

52
.github/scripts/measure-bundle-sizes.js vendored Normal file
View File

@@ -0,0 +1,52 @@
const fs = require('fs');
const path = require('path');
const outputPath = process.argv[2];
if (!outputPath) {
console.error(
'Usage: node .github/scripts/measure-bundle-sizes.js <output-path>',
);
process.exit(1);
}
const rootDir = process.cwd();
const sizes = {};
function normalizePath(filePath) {
return path.relative(rootDir, filePath).replace(/\\/g, '/');
}
function shouldTrack(filePath) {
const normalizedPath = normalizePath(filePath);
if (normalizedPath.startsWith('packages/packer/dist/')) {
return false;
}
return /(^|\/)dist\/[^/]+\.(js|cjs|mjs|css)$/.test(normalizedPath);
}
function walk(dirPath) {
for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
if (entry.name === 'node_modules') {
continue;
}
const entryPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
walk(entryPath);
continue;
}
if (!shouldTrack(entryPath)) {
continue;
}
sizes[normalizePath(entryPath)] = fs.statSync(entryPath).size;
}
}
walk(rootDir);
fs.writeFileSync(outputPath, `${JSON.stringify(sizes, null, 2)}\n`);

View File

@@ -0,0 +1,227 @@
const fs = require('fs');
const prPath = process.argv[2];
const basePath = process.argv[3];
if (!prPath || !basePath) {
console.error(
'Usage: node .github/scripts/render-bundle-size-comment.js <pr-sizes.json> <base-sizes.json>',
);
process.exit(1);
}
function readJson(filePath) {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
}
function formatSize(bytes) {
if (bytes == null) {
return '-';
}
if (Math.abs(bytes) < 1024) {
return `${bytes} B`;
}
return `${(bytes / 1024).toFixed(2)} kB`;
}
function formatSignedSize(bytes) {
const absoluteBytes = Math.abs(bytes);
if (absoluteBytes < 1024) {
return `${bytes >= 0 ? '+' : '-'}${absoluteBytes} B`;
}
return `${bytes >= 0 ? '+' : '-'}${(absoluteBytes / 1024).toFixed(2)} kB`;
}
function formatDiff(diff, baseValue) {
if (diff === 0) {
return '-';
}
const percentage =
baseValue > 0
? ` (${diff > 0 ? '+' : ''}${((diff / baseValue) * 100).toFixed(2)}%)`
: '';
return `${formatSignedSize(diff)}${percentage}`;
}
const BUNDLE_SIZE_BADGES = {
deleted: ' 🗑️',
new: ' 🆕',
improved: ' 🎉',
investigate: ' 🔍',
};
function getChangeBadge(prValue, baseValue) {
if (prValue == null) {
return BUNDLE_SIZE_BADGES.deleted;
}
if (baseValue == null) {
return BUNDLE_SIZE_BADGES.new;
}
if (baseValue <= 0) {
return '';
}
const percentage = ((prValue - baseValue) / baseValue) * 100;
if (percentage <= -10) {
return BUNDLE_SIZE_BADGES.improved;
}
if (percentage >= 5) {
return BUNDLE_SIZE_BADGES.investigate;
}
return '';
}
function getPackageBadge(prTotal, baseTotal) {
if (baseTotal === 0 && prTotal > 0) {
return BUNDLE_SIZE_BADGES.new;
}
if (baseTotal <= 0) {
return '';
}
const percentage = ((prTotal - baseTotal) / baseTotal) * 100;
if (percentage <= -10) {
return BUNDLE_SIZE_BADGES.improved;
}
if (percentage >= 5) {
return BUNDLE_SIZE_BADGES.investigate;
}
return '';
}
function getPackageName(filePath) {
const match = filePath.match(/^packages\/([^/]+)\//);
return match ? match[1] : '(root)';
}
function escapeHtml(value) {
return value
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\|/g, '&#124;')
.replace(/\r?\n/g, ' ');
}
function formatCode(value) {
return `<code>${escapeHtml(String(value))}</code>`;
}
function getFileLabel(filePath, packageName) {
const packagePrefix = `packages/${packageName}/dist/`;
if (packageName !== '(root)' && filePath.startsWith(packagePrefix)) {
return filePath.slice(packagePrefix.length);
}
return filePath;
}
const prSizes = readJson(prPath);
const baseSizes = readJson(basePath);
const allFiles = [
...new Set([...Object.keys(prSizes), ...Object.keys(baseSizes)]),
].sort();
const changedFiles = allFiles.filter(
(filePath) => prSizes[filePath] !== baseSizes[filePath],
);
if (changedFiles.length === 0) {
process.stdout.write('## Bundle Size Changes\n\nNo bundle size changes.\n');
process.exit(0);
}
const totalPrSize = allFiles.reduce(
(sum, filePath) => sum + (prSizes[filePath] ?? 0),
0,
);
const totalBaseSize = allFiles.reduce(
(sum, filePath) => sum + (baseSizes[filePath] ?? 0),
0,
);
const totalDiff = totalPrSize - totalBaseSize;
const filesByPackage = new Map();
for (const filePath of changedFiles) {
const packageName = getPackageName(filePath);
const files = filesByPackage.get(packageName) ?? [];
files.push(filePath);
filesByPackage.set(packageName, files);
}
const sections = [...filesByPackage.entries()]
.sort(([left], [right]) => left.localeCompare(right))
.map(([packageName, files]) => {
const packagePrSize = files.reduce(
(sum, filePath) => sum + (prSizes[filePath] ?? 0),
0,
);
const packageBaseSize = files.reduce(
(sum, filePath) => sum + (baseSizes[filePath] ?? 0),
0,
);
const packageDiff = packagePrSize - packageBaseSize;
const packageBadge = getPackageBadge(packagePrSize, packageBaseSize);
const rows = files
.map((filePath) => {
const prSize = prSizes[filePath];
const baseSize = baseSizes[filePath];
const fileDiff = (prSizes[filePath] ?? 0) - (baseSizes[filePath] ?? 0);
return `| ${formatCode(
getFileLabel(filePath, packageName),
)} | ${formatSize(baseSize)} | ${formatSize(prSize)} | ${formatDiff(
fileDiff,
baseSize ?? 0,
)}${getChangeBadge(prSize, baseSize)} |`;
})
.join('\n');
return [
'<details>',
`<summary>${formatCode(packageName)}${packageBadge} - ${formatSize(
packageBaseSize,
)} -> ${formatSize(packagePrSize)} (${formatDiff(
packageDiff,
packageBaseSize,
)})</summary>`,
'',
'| File | Base | PR | Diff |',
'|------|------|----|------|',
rows,
'',
'</details>',
].join('\n');
});
const body = [
'## Bundle Size Changes',
'',
`**Size change:** ${formatDiff(
totalDiff,
totalBaseSize,
)} | **Total size:** ${formatSize(totalPrSize)}`,
'',
sections.join('\n\n'),
'',
].join('\n');
process.stdout.write(body);

View File

@@ -1,6 +1,12 @@
name: Tests
on: [push, pull_request]
on:
push:
paths-ignore:
- '**/*.md'
pull_request:
paths-ignore:
- '**/*.md'
concurrency: ${{ github.workflow }}-${{ github.ref }}
@@ -31,6 +37,9 @@ jobs:
- name: Build Project
run: NODE_OPTIONS='--max-old-space-size=4096' yarn build:all
- name: Install Playwright browsers
run: cd packages/rrvideo && yarn playwright install chromium
- name: Check types
run: yarn check-types
@@ -38,16 +47,6 @@ jobs:
# run: PUPPETEER_EXECUTABLE_PATH=${{ steps.setup-chrome.outputs.chrome-path }} PUPPETEER_HEADLESS=true xvfb-run --server-args="-screen 0 1920x1080x24" yarn test
run: PUPPETEER_HEADLESS=true xvfb-run --server-args="-screen 0 1920x1080x24" yarn test
- name: Check bundle sizes
uses: preactjs/compressed-size-action@v2
with:
install-script: 'yarn install --frozen-lockfile'
build-script: 'build:all'
compression: 'none'
pattern: '**/dist/*.{js,cjs,mjs,css}'
env:
PUPPETEER_SKIP_DOWNLOAD: true
- name: Upload diff images to GitHub
uses: actions/upload-artifact@v4
if: failure()

102
.github/workflows/eslint-check.yml vendored Normal file
View File

@@ -0,0 +1,102 @@
name: ESLint Check
on:
push:
pull_request:
jobs:
eslint_check_upload:
runs-on: ubuntu-latest
permissions:
contents: read
name: ESLint Check and Report Upload
steps:
- name: Checkout pull request head
if: github.event_name == 'pull_request'
uses: actions/checkout@v4
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.sha }}
- name: Checkout current branch
if: github.event_name != 'pull_request'
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: lts/*
cache: 'yarn'
- name: Install Dependencies
run: yarn install --frozen-lockfile
env:
PUPPETEER_SKIP_DOWNLOAD: true
- name: Build Packages
run: NODE_OPTIONS='--max-old-space-size=4096' yarn build:all
- name: Eslint Check
run: yarn turbo run lint
- name: Save Code Linting Report JSON
run: yarn lint:report
# Continue to the next step even if this fails
continue-on-error: true
- name: Upload ESLint Report
uses: actions/upload-artifact@v4
with:
name: eslint_report.json
path: eslint_report.json
- name: Measure PR bundle sizes
if: github.event_name == 'pull_request'
run: node .github/scripts/measure-bundle-sizes.js pr-sizes.json
- name: Upload PR bundle sizes
if: github.event_name == 'pull_request'
uses: actions/upload-artifact@v4
with:
name: pr-sizes
path: pr-sizes.json
bundle_size_build:
# Only runs on PRs. Reuses the PR build from eslint_check_upload (via the
# pr-sizes artifact) and only builds the base branch itself. The privileged
# bundle-size-comment workflow then posts the PR comment without ever
# executing fork code.
if: github.event_name == 'pull_request'
needs: eslint_check_upload
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
name: Build Base for Bundle Size Comparison
steps:
- name: Checkout workflow ref
uses: actions/checkout@v4
- name: Prepare bundle size helper
run: |
cp .github/scripts/measure-bundle-sizes.js /tmp/measure-bundle-sizes.js
# --- Base branch ---
- uses: actions/checkout@v4
with:
ref: ${{ github.base_ref }}
- name: Download PR bundle sizes
uses: actions/download-artifact@v4
with:
name: pr-sizes
- uses: actions/setup-node@v3
with:
node-version: lts/*
cache: 'yarn'
- name: Install base dependencies
run: yarn install --frozen-lockfile
env:
PUPPETEER_SKIP_DOWNLOAD: true
- name: Build base branch
run: NODE_OPTIONS='--max-old-space-size=4096' yarn build:all
env:
PUPPETEER_SKIP_DOWNLOAD: true
- name: Measure base bundle sizes
run: node /tmp/measure-bundle-sizes.js base-sizes.json
- uses: actions/upload-artifact@v4
with:
name: bundle-size-data
path: |
pr-sizes.json
base-sizes.json

View File

@@ -0,0 +1,85 @@
name: PR Checks (privileged)
# Runs in the base-repo context (privileged) after eslint-check.yml completes.
# Downloads pre-built artifacts and posts PR comments/annotations.
# Never checks out or executes fork code.
on:
workflow_run:
workflows: ['ESLint Check']
types: [completed]
jobs:
comment:
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
pull-requests: write
steps:
- name: Checkout trusted workflow helpers
uses: actions/checkout@v4
with:
ref: ${{ github.event.repository.default_branch }}
- uses: actions/download-artifact@v4
with:
name: bundle-size-data
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
- name: Find PR number
id: find-pr
uses: actions/github-script@v7
with:
script: |
const run = context.payload.workflow_run;
if (run.pull_requests && run.pull_requests.length > 0) {
return run.pull_requests[0].number;
}
// Fallback for fork PRs (pull_requests is empty for forks)
const { data: prs } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
head: `${run.head_repository.full_name}:${run.head_branch}`,
state: 'open',
});
if (prs.length === 0) {
core.setFailed('Could not determine PR number');
return;
}
return prs[0].number;
result-encoding: string
- name: Render bundle size comment
if: steps.find-pr.outputs.result
run: |
node .github/scripts/render-bundle-size-comment.js pr-sizes.json base-sizes.json > bundle-size-comment.md
- name: Post bundle size comment
if: steps.find-pr.outputs.result
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405
with:
header: bundle-size
path: bundle-size-comment.md
number: ${{ steps.find-pr.outputs.result }}
annotate:
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
permissions:
actions: read
checks: write
steps:
- uses: actions/download-artifact@v4
with:
name: eslint_report.json
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
- name: Annotate Code Linting Results
uses: ataylorme/eslint-annotate-action@5f4dc2e3af8d3c21b727edb597e5503510b1dc9c
with:
repo-token: '${{ secrets.GITHUB_TOKEN }}'
report-json: 'eslint_report.json'

View File

@@ -1,60 +1,8 @@
name: Code Style Check
name: Prettier Check
on: [push, pull_request_target]
on: [push, pull_request]
jobs:
eslint_check_upload:
runs-on: ubuntu-latest
permissions:
contents: read
name: ESLint Check and Report Upload
steps:
- uses: actions/checkout@v4
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.head_ref }}
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: lts/*
cache: 'yarn'
- name: Install Dependencies
run: yarn install --frozen-lockfile
env:
PUPPETEER_SKIP_DOWNLOAD: true
- name: Build Packages
run: NODE_OPTIONS='--max-old-space-size=4096' yarn build:all
- name: Eslint Check
run: yarn turbo run lint
- name: Save Code Linting Report JSON
run: yarn lint:report
# Continue to the next step even if this fails
continue-on-error: true
- name: Upload ESLint Report
uses: actions/upload-artifact@v4
with:
name: eslint_report.json
path: eslint_report.json
annotation:
# Skip the annotation action in push events
if: github.event_name == 'pull_request_target'
permissions:
checks: write
needs: eslint_check_upload
runs-on: ubuntu-latest
name: ESLint Annotation
steps:
- uses: actions/download-artifact@v4
with:
name: eslint_report.json
- name: Annotate Code Linting Results
uses: ataylorme/eslint-annotate-action@v2
with:
repo-token: '${{ secrets.GITHUB_TOKEN }}'
report-json: 'eslint_report.json'
prettier_check:
# In the forked PR, it's hard to format code and push to the branch directly, so the action only check the format correctness.
if: github.event_name != 'push' && github.event.pull_request.head.repo.full_name != 'rrweb-io/rrweb'

1
.gitignore vendored
View File

@@ -16,6 +16,7 @@ temp
# output of `yarn build`
build
dist
umd
# turbo cache
.turbo

View File

@@ -12,8 +12,8 @@
[![Join the chat at slack](https://img.shields.io/badge/slack-@rrweb-teal.svg?logo=slack)](https://join.slack.com/t/rrweb/shared_invite/zt-siwoc6hx-uWay3s2wyG8t5GpZVb8rWg)
[![Twitter Follow](https://img.shields.io/badge/twitter-@rrweb__io-teal.svg?logo=twitter)](https://twitter.com/rrweb_io)
[![Reddit](https://img.shields.io/badge/reddit-r/rrweb-teal.svg?logo=reddit)](https://www.reddit.com/r/rrweb)
![total gzip size](https://img.badgesize.io/https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.min.cjs?compression=gzip&label=total%20gzip%20size)
![recorder gzip size](https://img.badgesize.io/https://cdn.jsdelivr.net/npm/rrweb@latest/dist/record/rrweb-record.min.cjs?compression=gzip&label=recorder%20gzip%20size)
![recorder gzip size](https://img.badgesize.io/https://cdn.jsdelivr.net/npm/@rrweb/record@latest/umd/record.min.js?compression=gzip&label=recorder%20gzip%20size&max=200000&softmax=100000)
![replayer gzip size](https://img.badgesize.io/https://cdn.jsdelivr.net/npm/@rrweb/replay@latest/umd/replay.min.js?compression=gzip&label=replayer%20gzip%20size&max=200000&softmax=100000)
[![](https://data.jsdelivr.com/v1/package/npm/rrweb/badge)](https://www.jsdelivr.com/package/npm/rrweb)
[中文文档](./README.zh_CN.md)

View File

@@ -11,8 +11,8 @@
[![Join the chat at slack](https://img.shields.io/badge/slack-@rrweb-teal.svg?logo=slack)](https://join.slack.com/t/rrweb/shared_invite/zt-siwoc6hx-uWay3s2wyG8t5GpZVb8rWg)
[![Reddit](https://img.shields.io/badge/reddit-r/rrweb-teal.svg?logo=reddit)](https://www.reddit.com/r/rrweb)
![total gzip size](https://img.badgesize.io/https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.min.cjs?compression=gzip&label=total%20gzip%20size)
![recorder gzip size](https://img.badgesize.io/https://cdn.jsdelivr.net/npm/rrweb@latest/dist/record/rrweb-record.min.cjs?compression=gzip&label=recorder%20gzip%20size)
![recorder gzip size](https://img.badgesize.io/https://cdn.jsdelivr.net/npm/@rrweb/record@latest/umd/record.min.js?compression=gzip&label=recorder%20gzip%20size&max=200000&softmax=100000)
![replayer gzip size](https://img.badgesize.io/https://cdn.jsdelivr.net/npm/@rrweb/replay@latest/umd/replay.min.js?compression=gzip&label=replayer%20gzip%20size&max=200000&softmax=100000)
[![](https://data.jsdelivr.com/v1/package/npm/rrweb/badge)](https://www.jsdelivr.com/package/npm/rrweb)
> 我已开通 Github Sponsor 您可以通过赞助的形式帮助 rrweb 的开发。

View File

@@ -6,7 +6,9 @@ There are some options for recording and replaying Canvas.
Enable recording Canvas
```js
rrweb.record({
import { record } from '@rrweb/record';
record({
emit(event) {},
recordCanvas: true,
});
@@ -15,7 +17,9 @@ rrweb.record({
Alternatively enable image snapshot recording of Canvas at a maximum of 15 frames per second
```js
rrweb.record({
import { record } from '@rrweb/record';
record({
emit(event) {},
recordCanvas: true,
sampling: {
@@ -32,7 +36,9 @@ rrweb.record({
Enable replaying Canvas
```js
const replayer = new rrweb.Replayer(events, {
import { Replayer } from '@rrweb/replay';
const replayer = new Replayer(events, {
UNSAFE_replayCanvas: true,
});
replayer.play();

View File

@@ -5,7 +5,9 @@ Canvas 是一种特殊的 HTML 元素,默认情况下其内容不会被 rrweb
录制时包含 Canvas 内的内容:
```js
rrweb.record({
import { record } from '@rrweb/record';
record({
emit(event) {},
// 对 canvas 进行录制
recordCanvas: true,
@@ -15,7 +17,9 @@ rrweb.record({
或者启用每秒 15 帧的 Canvas 图像快照记录:
```js
rrweb.record({
import { record } from '@rrweb/record';
record({
emit(event) {},
recordCanvas: true,
sampling: {
@@ -32,7 +36,9 @@ rrweb.record({
回放时对 Canvas 进行回放:
```js
const replayer = new rrweb.Replayer(events, {
import { Replayer } from '@rrweb/replay';
const replayer = new Replayer(events, {
UNSAFE_replayCanvas: true,
});
replayer.play();

View File

@@ -8,10 +8,10 @@ This feature aims to provide developers with more information about the bug scen
You can enable the logger using default option like this:
```js
import rrweb from 'rrweb';
import { record } from '@rrweb/record';
import { getRecordConsolePlugin } from '@rrweb/rrweb-plugin-console-record';
rrweb.record({
record({
emit: function emit(event) {
// you should use console.log in this way to avoid errors.
const defaultLog = console.log['__rrweb_original__']
@@ -30,10 +30,10 @@ You should call console.log.\_\_rrweb_original\_\_() instead.
You can also customize the behavior of logger like this:
```js
import rrweb from 'rrweb';
import { record } from '@rrweb/record';
import { getRecordConsolePlugin } from '@rrweb/rrweb-plugin-console-record';
rrweb.record({
record({
emit: function emit(event) {
// you should use console.log in this way to avoid errors.
const defaultLog = console.log['__rrweb_original__']
@@ -70,10 +70,10 @@ All options are described below:
If recorded events include data of console log type, we will automatically play them.
```js
import rrweb from 'rrweb';
import { Replayer } from '@rrweb/replay';
import { getReplayConsolePlugin } from '@rrweb/rrweb-plugin-console-replay';
const replayer = new rrweb.Replayer(events, {
const replayer = new Replayer(events, {
plugins: [
getReplayConsolePlugin({
level: ['info', 'log', 'warn', 'error'],

View File

@@ -7,10 +7,10 @@
可以通过如下代码使用默认的配置选项
```js
import rrweb from 'rrweb';
import { record } from '@rrweb/record';
import { getRecordConsolePlugin } from '@rrweb/rrweb-plugin-console-record';
rweb.record({
record({
emit: function emit(event) {
// 如果要使用console来输出信息请使用如下的写法
const defaultLog = console.log['__rrweb_original__']
@@ -29,10 +29,10 @@ rweb.record({
你也可以定制录制 console 的选项
```js
import rrweb from 'rrweb';
import { record } from '@rrweb/record';
import { getRecordConsolePlugin } from '@rrweb/rrweb-plugin-console-record';
rrweb.record({
record({
emit: function emit(event) {
// 如果要使用console来输出信息请使用如下的写法
const defaultLog = console.log['__rrweb_original__']
@@ -69,10 +69,10 @@ rrweb.record({
如果 replayer 传入的 events 中包含了 console 类型的数据,我们将自动播放这些数据。
```js
import rrweb from 'rrweb';
import { Replayer } from '@rrweb/replay';
import { getReplayConsolePlugin } from '@rrweb/rrweb-plugin-console-replay';
const replayer = new rrweb.Replayer(events, {
const replayer = new Replayer(events, {
plugins: [
getReplayConsolePlugin({
level: ['info', 'log', 'warn', 'error'],

View File

@@ -8,7 +8,9 @@ Since if you allow recording cross origin iframes, any malicious website can emb
Enable recording cross-origin iframes in your parent page:
```js
rrweb.record({
import { record } from '@rrweb/record';
record({
emit(event) {}, // all events will be emitted here, including events from cross origin iframes
recordCrossOriginIframes: true,
});
@@ -17,7 +19,9 @@ rrweb.record({
Enable replaying cross-origin iframes in your child page:
```js
rrweb.record({
import { record } from '@rrweb/record';
record({
emit(event) {}, // this is required for rrweb, but the child page will not emit any events
recordCrossOriginIframes: true,
});

View File

@@ -8,7 +8,9 @@
在父页面中启用录制跨域 iframe:
```js
rrweb.record({
import { record } from '@rrweb/record';
record({
emit(event) {}, // 所有事件都将在此处发出,包括来自跨源 iframe 的事件
recordCrossOriginIframes: true,
});
@@ -17,7 +19,9 @@ rrweb.record({
在您的子页面中启用重放跨域 iframe:
```js
rrweb.record({
import { record } from '@rrweb/record';
record({
emit(event) {}, // 这是 rrweb 所必需的,但子页面不会发出任何事件
recordCrossOriginIframes: true,
});

View File

@@ -5,19 +5,21 @@ You may need to record some custom events along with the rrweb events, and let t
After starting the recording, we can call the `record.addCustomEvent` API to add a custom event.
```js
import { record } from '@rrweb/record';
// start recording
rrweb.record({
record({
emit(event) {
...
}
})
// record some custom events at any time
rrweb.record.addCustomEvent('submit-form', {
record.addCustomEvent('submit-form', {
name: 'Adam',
age: 18
})
rrweb.record.addCustomEvent('some-error', {
record.addCustomEvent('some-error', {
error
})
```
@@ -29,7 +31,9 @@ During the replay, we can add an event listener to custom events, or configure t
**Listen to custom events**
```js
const replayer = new rrweb.Replayer(events);
import { Replayer } from '@rrweb/replay';
const replayer = new Replayer(events);
replayer.on('custom-event', (event) => {
console.log(event.tag, event.payload);

View File

@@ -5,19 +5,21 @@
开始录制后,我们就可以通过 `record.addCustomEvent` API 添加自定义事件:
```js
import { record } from '@rrweb/record';
// 开始录制
rrweb.record({
record({
emit(event) {
...
}
})
// 在开始录制后的任意时间点记录自定义事件,例如:
rrweb.record.addCustomEvent('submit-form', {
record.addCustomEvent('submit-form', {
name: '姓名',
age: 18
})
rrweb.record.addCustomEvent('some-error', {
record.addCustomEvent('some-error', {
error
})
```
@@ -29,7 +31,9 @@ rrweb.record.addCustomEvent('some-error', {
**获取对应事件**
```js
const replayer = new rrweb.Replayer(events);
import { Replayer } from '@rrweb/replay';
const replayer = new Replayer(events);
replayer.on('custom-event', (event) => {
console.log(event.tag, event.payload);

View File

@@ -1,13 +1,13 @@
# Customize the Replayer
When rrweb's Replayer and the [rrweb-player](../../packages/rrweb-player/) UI do not fit your need, you can customize your replayer UI.
When `Replayer` and the [rrweb-player](../../packages/rrweb-player/) UI do not fit your need, you can customize your replayer UI.
There are several ways to do this:
1. Use [rrweb-player](../../packages/rrweb-player/), and customize its CSS.
2. Use [rrweb-player](../../packages/rrweb-player/), and set `showController: false` to hide the controller UI. With this config, you can implement your controller UI.
3. Use the `insertStyleRules` options to inject some CSS into the replay iframe.
4. Develop a new replayer UI with rrweb's Replayer.
4. Develop a new replayer UI with `Replayer`.
## Implement Your Controller UI
@@ -69,6 +69,6 @@ rrwebPlayer.addEventListener('ui-update-progress', (event) => {
});
```
## Develop a new replayer UI with rrweb's Replayer.
## Develop a new replayer UI with `Replayer`.
Please refer [rrweb-player](https://github.com/rrweb-io/rrweb/tree/master/packages/rrweb-player/).

View File

@@ -1,13 +1,13 @@
# 自定义回放 UI
rrweb Replayer 和 [rrweb-player](../../packages/rrweb-player/) 的 UI 不能满足需求时,可以通过自定义回放 UI 制作属于你自己的回放器。
`Replayer` 和 [rrweb-player](../../packages/rrweb-player/) 的 UI 不能满足需求时,可以通过自定义回放 UI 制作属于你自己的回放器。
你可以通过以下几种方式从不同角度自定义回放 UI
1. 使用 [rrweb-player](../../packages/rrweb-player/) 时,通过覆盖 CSS 样式表定制 UI。
2. 使用 [rrweb-player](../../packages/rrweb-player/) 时,通过 `showController: false` 隐藏控制器 UI重新实现控制器 UI。
3. 通过 `insertStyleRules` 在回放页面iframe内定制 CSS 样式。
4. 基于 rrweb Replayer 开发自己的回放器 UI。
4. 基于 `Replayer` 开发自己的回放器 UI。
## 实现控制器 UI
@@ -69,6 +69,6 @@ rrwebPlayer.addEventListener('ui-update-progress', (event) => {
});
```
## 基于 rrweb Replayer 开发自己的回放器 UI
## 基于 `Replayer` 开发自己的回放器 UI
可以参照 [rrweb-player](https://github.com/rrweb-io/rrweb/tree/master/packages/rrweb-player/) 的方式进行开发。

View File

@@ -42,7 +42,7 @@ By default, the UI could not interact during replay. But you can use API to enab
### Customize The Replayer
When rrweb's Replayer and the rrweb-player UI do not fit your need, you can customize your own replayer UI.
When `Replayer` and the rrweb-player UI do not fit your need, you can customize your own replayer UI.
[link](./customize-replayer.md)

View File

@@ -42,7 +42,7 @@
### 自定义回放 UI
rrweb Replayer 和 rrweb-player 的 UI 不能满足需求时,可以通过自定义回放 UI 制作属于你自己的回放器。
`Replayer` 和 rrweb-player 的 UI 不能满足需求时,可以通过自定义回放 UI 制作属于你自己的回放器。
[链接](./customize-replayer.zh_CN.md)

View File

@@ -3,7 +3,9 @@
By default, the UI could not interact during replay. But you can use API to enable/disable this programmatically.
```js
const replayer = new rrweb.Replayer(events);
import { Replayer } from '@rrweb/replay';
const replayer = new Replayer(events);
// enable user interact with the UI
replayer.enableInteract();

View File

@@ -3,7 +3,9 @@
回放时的 UI 默认不可交互,但在特定场景下也可以通过 API 允许用户与回放场景进行交互。
```js
const replayer = new rrweb.Replayer(events);
import { Replayer } from '@rrweb/replay';
const replayer = new Replayer(events);
// 允许用户在回放的 UI 中进行交互
replayer.enableInteract();

View File

@@ -2,10 +2,12 @@
If you want to replay the events in a real-time way, you can use the live mode API. This API is also useful for some real-time collaboration usage.
When you are using rrweb's Replayer to do a real-time replay, you need to configure `liveMode: true` and call the `startLive` API to enable the live mode.
When you use `Replayer` for real-time replay, configure `liveMode: true` and call `startLive()` to enable live mode.
```js
const replayer = new rrweb.Replayer([], {
import { Replayer } from '@rrweb/replay';
const replayer = new Replayer([], {
liveMode: true,
});
replayer.startLive();
@@ -22,7 +24,9 @@ function onReceive(event) {
If you have an ongoing recording that already has events, and wish to initiate play from a 'live' time, it's also possible to use the `play` function, supplied with an offset which corresponds to the current time:
```js
const replayer = new rrweb.Replayer(EXISTING_EVENTS, {
import { Replayer } from '@rrweb/replay';
const replayer = new Replayer(EXISTING_EVENTS, {
liveMode: true,
});
replayer.play(Date.now() - EXISTING_EVENTS[0].timestamp);

View File

@@ -2,10 +2,12 @@
如果希望持续、实时地看到录制的数据,达到类似直播的效果,则可以使用实时回放 API。这个方式也适用于一些实时协同的场景。
使用 rrweb Replayer 进行实时回放时,需要传入 `liveMode: true` 配置,并通过 `startLive` API 启动直播模式。
使用 `Replayer` 进行实时回放时,需要传入 `liveMode: true` 配置,并通过 `startLive` API 启动直播模式。
```js
const replayer = new rrweb.Replayer([], {
import { Replayer } from '@rrweb/replay';
const replayer = new Replayer([], {
liveMode: true,
});

View File

@@ -26,7 +26,9 @@ Use the sampling config in the recording can reduce the storage size by dropping
**Scenario 1**
```js
rrweb.record({
import { record } from '@rrweb/record';
record({
emit(event) {},
sampling: {
// do not record mouse movement
@@ -46,7 +48,9 @@ rrweb.record({
**Scenario 2**
```js
rrweb.record({
import { record } from '@rrweb/record';
record({
emit(event) {},
sampling: {
// Configure which kinds of mouse interaction should be recorded
@@ -76,7 +80,7 @@ You can use it by passing it as the `packFn` in the recording.
```js
import { pack } from '@rrweb/packer';
rrweb.record({
record({
emit(event) {},
packFn: pack,
});
@@ -86,8 +90,9 @@ And you need to pass packer.unpack as the `unpackFn` in replaying.
```js
import { unpack } from '@rrweb/packer';
import { Replayer } from '@rrweb/replay';
const replayer = new rrweb.Replayer(events, {
const replayer = new Replayer(events, {
unpackFn: unpack,
});
```

View File

@@ -26,7 +26,9 @@
**示例 1**
```js
rrweb.record({
import { record } from '@rrweb/record';
record({
emit(event) {},
sampling: {
// 不录制鼠标移动事件
@@ -46,7 +48,9 @@ rrweb.record({
**示例 2**
```js
rrweb.record({
import { record } from '@rrweb/record';
record({
emit(event) {},
sampling: {
// 定义不录制的鼠标交互事件类型,可以细粒度的开启或关闭对应交互录制
@@ -74,9 +78,9 @@ rrweb 提供了一个基于 fflate 的简单压缩函数,在 [@rrweb/packer](.
```js
import { pack } from '@rrweb/packer';
rrweb.record({
record({
emit(event) {},
packFn: rrweb.pack,
packFn: pack,
});
```
@@ -84,9 +88,10 @@ rrweb.record({
```js
import { unpack } from '@rrweb/packer';
import { Replayer } from '@rrweb/replay';
const replayer = new rrweb.Replayer(events, {
unpackFn: rrweb.unpack,
const replayer = new Replayer(events, {
unpackFn: unpack,
});
```

View File

@@ -5,7 +5,9 @@ When the size of the recorded events increased, load them in one request is not
rrweb's API for loading async events is quite simple:
```js
const replayer = new rrweb.Replayer(events);
import { Replayer } from '@rrweb/replay';
const replayer = new Replayer(events);
replayer.addEvent(NEW_EVENT);
```
@@ -15,7 +17,9 @@ When calling the `addEvent` API to add a new event, rrweb will resolve its times
If you need to load several events, you can do a loop like this:
```js
const replayer = new rrweb.Replayer(events);
import { Replayer } from '@rrweb/replay';
const replayer = new Replayer(events);
for (const event of NEW_EVENTS) {
replayer.addEvent(event);

View File

@@ -5,7 +5,9 @@
rrweb 中用于实现异步加载数据的 API 非常简单直观:
```js
const replayer = new rrweb.Replayer(events);
import { Replayer } from '@rrweb/replay';
const replayer = new Replayer(events);
replayer.addEvent(NEW_EVENT);
```
@@ -15,7 +17,9 @@ replayer.addEvent(NEW_EVENT);
如果需要异步加载多个数据,只需这样使用:
```js
const replayer = new rrweb.Replayer(events);
import { Replayer } from '@rrweb/replay';
const replayer = new Replayer(events);
for (const event of NEW_EVENTS) {
replayer.addEvent(event);

View File

@@ -38,6 +38,8 @@ Both record and replay plugins have a type interface.
#### record plugin
```ts
import { record } from '@rrweb/record';
const exampleRecordPlugin: RecordPlugin<{ foo: string }> = {
name: 'my-scope/example@1',
observer(cb, options) {
@@ -54,7 +56,7 @@ const exampleRecordPlugin: RecordPlugin<{ foo: string }> = {
},
};
rrweb.record({
record({
emit: emit(event) {},
plugins: [exampleRecordPlugin],
});
@@ -79,6 +81,8 @@ In this example, the record plugin will emit events like this:
#### replay plugin
```ts
import { Replayer } from '@rrweb/replay';
const exampleReplayPlugin: ReplayPlugin = {
handler(event, isSync, context) {
if (event.type === EventType.Plugin) {
@@ -90,7 +94,7 @@ const exampleReplayPlugin: ReplayPlugin = {
},
};
const replayer = new rrweb.Replayer(events, {
const replayer = new Replayer(events, {
plugins: [exampleReplayPlugin],
});
```

View File

@@ -38,6 +38,8 @@ export type ReplayPlugin = {
#### 录制侧插件
```ts
import { record } from '@rrweb/record';
const exampleRecordPlugin: RecordPlugin<{ foo: string }> = {
name: 'my-scope/example@1',
observer(cb, options) {
@@ -54,7 +56,7 @@ const exampleRecordPlugin: RecordPlugin<{ foo: string }> = {
},
};
rrweb.record({
record({
emit: emit(event) {},
plugins: [exampleRecordPlugin],
});
@@ -79,6 +81,8 @@ rrweb.record({
#### 回放侧插件
```ts
import { Replayer } from '@rrweb/replay';
const exampleReplayPlugin: ReplayPlugin = {
handler(event, isSync, context) {
if (event.type === EventType.Plugin) {
@@ -90,7 +94,7 @@ const exampleReplayPlugin: ReplayPlugin = {
},
};
const replayer = new rrweb.Replayer(events, {
const replayer = new Replayer(events, {
plugins: [exampleReplayPlugin],
});
```

View File

@@ -5,7 +5,9 @@ Record and Replay is the most common use case, which is suitable for any scenari
You only need a simple API call to record the website:
```js
const stopFn = rrweb.record({
import { record } from '@rrweb/record';
const stopFn = record({
emit(event) {
// save the event
},
@@ -21,11 +23,13 @@ But you should guarantee:
You can use the `stopFn` to stop the recording.
The replay is also as simple as putting events into rrweb's Replayer.
Replay is also as simple as passing events into `Replayer`.
```js
import { Replayer } from '@rrweb/replay';
const events = GET_YOUR_EVENTS;
const replayer = new rrweb.Replayer(events);
const replayer = new Replayer(events);
replayer.play();
```

View File

@@ -5,7 +5,9 @@
仅需一个函数调用就可以录制当前页面:
```js
const stopFn = rrweb.record({
import { record } from '@rrweb/record';
const stopFn = record({
emit(event) {
// 保存获取到的 event 数据
},
@@ -19,11 +21,13 @@ const stopFn = rrweb.record({
如果需要手动停止录制,可以调用返回的 `stopFn` 函数。
回放时只需要获取一段录制数据,并传入 rrweb 提供的 Replayer
回放时只需要获取一段录制数据,并传入 `Replayer`
```js
import { Replayer } from '@rrweb/replay';
const events = GET_YOUR_EVENTS;
const replayer = new rrweb.Replayer(events);
const replayer = new Replayer(events);
replayer.play();
```

193
guide.md
View File

@@ -6,43 +6,119 @@
## Installation
### Direct `<script>` include
| Goal | Recommended package(s) |
| ------------------------------- | --------------------------------- |
| Most projects (record + replay) | `@rrweb/record` + `@rrweb/replay` |
| Single-package convenience | `@rrweb/all` |
| Legacy compatibility only | `rrweb` |
You are recommended to install rrweb via jsdelivr's CDN service:
In most production setups, recorder and replayer are deployed to different pages/apps. Use `@rrweb/record` on recorded pages and `@rrweb/replay` (or `rrweb-player`) on replay pages. Use `@rrweb/all` when you intentionally want one package for convenience (for example demos, tooling, or simplified setups).
### 1) Bundler / npm (Recommended)
```shell
npm install @rrweb/record @rrweb/replay
```
```js
import { record } from '@rrweb/record';
import { Replayer } from '@rrweb/replay';
import '@rrweb/replay/dist/style.css';
```
Use `@rrweb/all` as a convenience package if you want a single import:
```shell
npm install @rrweb/all
```
```js
import { record, Replayer } from '@rrweb/all';
import '@rrweb/all/dist/style.css';
```
`require(...)` / CommonJS remains available for compatibility via each package's `exports`/`main`, but ESM imports are the primary path for 2.x.
### 2) Browser Without Bundler (No-Build)
Use ES modules and import maps with jsDelivr `+esm`:
```html
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/style.css"
href="https://cdn.jsdelivr.net/npm/@rrweb/replay@latest/dist/style.css"
/>
<script src="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.umd.min.cjs"></script>
<script type="importmap">
{
"imports": {
"@rrweb/record": "https://cdn.jsdelivr.net/npm/@rrweb/record@latest/+esm",
"@rrweb/replay": "https://cdn.jsdelivr.net/npm/@rrweb/replay@latest/+esm"
}
}
</script>
<script type="module">
import { record } from '@rrweb/record';
record({
emit(event) {
console.log(event);
},
});
</script>
```
Also, you can link to a specific version number that you can update manually:
Or use `@rrweb/all` as a convenience browser ESM import:
```html
<script src="https://cdn.jsdelivr.net/npm/rrweb@2.0.0-alpha.14/dist/rrweb.umd.min.cjs"></script>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@rrweb/all@latest/dist/style.css"
/>
<script type="importmap">
{
"imports": {
"@rrweb/all": "https://cdn.jsdelivr.net/npm/@rrweb/all@latest/+esm"
}
}
</script>
<script type="module">
import { record, Replayer } from '@rrweb/all';
</script>
```
#### Only include the recorder code
### 3) Legacy Direct `<script>` Include (UMD Fallback)
rrweb's code includes both the record and the replay parts. Most of the time you only need to include the record part into your targeted web Apps.
This also can be done by using the `@rrweb/record` package and the CDN service:
Use this only for compatibility with non-module environments.
```html
<script src="https://cdn.jsdelivr.net/npm/@rrweb/record@latest/dist/record.umd.min.cjs"></script>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/rrweb@2.0.0-alpha.20/dist/style.css"
/>
<script src="https://cdn.jsdelivr.net/npm/rrweb@2.0.0-alpha.20/umd/rrweb.min.js"></script>
```
The UMD build exposes global `rrweb`.
Legacy single-purpose UMD bundles:
```html
<script src="https://cdn.jsdelivr.net/npm/@rrweb/record@2.0.0-alpha.20/umd/record.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@rrweb/replay@2.0.0-alpha.20/umd/replay.min.js"></script>
```
The UMD globals are `rrwebRecord` and `rrwebReplay`.
#### Other packages
Besides the `rrweb` and `@rrweb/record` packages, rrweb also provides other packages for different usage.
Besides the `@rrweb/record` and `@rrweb/replay` packages, rrweb also provides other packages for different usage.
- [rrweb](packages/rrweb): The core package of rrweb, including record and replay functions.
- [rrweb-player](packages/rrweb-player): A GUI for rrweb, providing a timeline and buttons for things like pause, fast-forward, and speedup.
- [rrweb-snapshot](packages/rrweb-snapshot): Handles snapshot and rebuilding features, converting the DOM and its state into a serializable data structure.
- [rrdom](packages/rrdom): A virtual dom package rrweb.
- [rrdom-nodejs](packages/rrdom-nodejs): The Node.js version of rrdom for server-side DOM operations.
- [@rrweb/all](packages/all): A package that includes `rrweb` and `@rrweb/packer` for easy install.
- [@rrweb/all](packages/all): A convenience package that includes `rrweb` and `@rrweb/packer`.
- [@rrweb/record](packages/record): A package for recording rrweb sessions.
- [@rrweb/replay](packages/replay): A package for replaying rrweb sessions.
- [@rrweb/packer](packages/packer): A package for packing and unpacking rrweb data.
@@ -57,14 +133,6 @@ Besides the `rrweb` and `@rrweb/record` packages, rrweb also provides other pack
- [@rrweb/rrweb-plugin-canvas-webrtc-record](packages/plugins/rrweb-plugin-canvas-webrtc-record): A plugin for stream `<canvas>` via WebRTC.
- [@rrweb/rrweb-plugin-canvas-webrtc-replay](packages/plugins/rrweb-plugin-canvas-webrtc-replay): A plugin for playing streamed `<canvas>` via WebRTC.
### NPM
```shell
npm install --save rrweb
```
rrweb provides both commonJS and ES modules bundles, which are easy to use with the popular bundlers.
### Compatibility Note
rrweb does **not** support IE11 and below because it uses the `MutationObserver` API which was supported by [these browsers](https://caniuse.com/#feat=mutationobserver).
@@ -73,10 +141,14 @@ rrweb does **not** support IE11 and below because it uses the `MutationObserver`
### Record
The following sample code will use the variable `rrweb` which is the default exporter of this library.
Use `record` from `@rrweb/record` in modern setups:
```js
rrweb.record({
import { record } from '@rrweb/record';
```
```js
record({
emit(event) {
// store the event in any way you like
},
@@ -88,7 +160,7 @@ During recording, the recorder will emit when there is some event incurred, all
The `record` method returns a function which can be called to stop events from firing:
```js
let stopFn = rrweb.record({
let stopFn = record({
emit(event) {
if (events.length > 100) {
// stop after 100 events
@@ -103,7 +175,7 @@ A more real-world usage may look like this:
```js
let events = [];
rrweb.record({
record({
emit(event) {
// push event into the events array
events.push(event);
@@ -129,7 +201,7 @@ setInterval(save, 10 * 1000);
#### Options
The parameter of `rrweb.record` accepts the following options.
The `record` function accepts the following options.
| key | default | description |
| ------------------------ | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -182,7 +254,7 @@ By default, all the emitted events are required to replay a session and if you d
// We use a two-dimensional array to store multiple events array
const eventsMatrix = [[]];
rrweb.record({
record({
emit(event, isCheckout) {
// isCheckout is a flag to tell you the events has been checkout
if (isCheckout) {
@@ -217,7 +289,7 @@ Similarly, you can also configure `checkoutEveryNms` to capture the last N minut
// We use a two-dimensional array to store multiple events array
const eventsMatrix = [[]];
rrweb.record({
record({
emit(event, isCheckout) {
// isCheckout is a flag to tell you the events has been checkout
if (isCheckout) {
@@ -248,28 +320,36 @@ With the sample code above, you will finally get the last 5 to 10 minutes of eve
### Replay
You need to include the style sheet before replay:
For bundler usage, include the style sheet in your app entry:
```js
import '@rrweb/replay/dist/style.css';
```
For browser/no-build usage, include the style sheet in HTML:
```html
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/style.css"
href="https://cdn.jsdelivr.net/npm/@rrweb/replay@latest/dist/style.css"
/>
```
And then initialize the replayer with the following code:
And then initialize the replayer:
```js
import { Replayer } from '@rrweb/replay';
const events = YOUR_EVENTS;
const replayer = new rrweb.Replayer(events);
const replayer = new Replayer(events);
replayer.play();
```
#### Control the replayer by API
```js
const replayer = new rrweb.Replayer(events);
const replayer = new Replayer(events);
// play
replayer.play();
@@ -315,29 +395,48 @@ The replayer accepts options as its constructor's second parameter, and it has t
#### Use rrweb-player
Since rrweb's replayer ([@rrweb/replay](packages/replay/)) only provides a basic UI, you can choose [rrweb-player](packages/rrweb-player/) which is based on rrweb's public APIs but has a feature-rich replayer UI.
Since `Replayer` from [@rrweb/replay](packages/replay/) only provides a basic UI, you can choose [rrweb-player](packages/rrweb-player/), which is based on rrweb's public APIs and provides a feature-rich replayer UI.
##### Installation
rrweb-player can also be included with `<script>`
Bundler / npm (recommended):
```shell
npm install rrweb-player
```
```js
import rrwebPlayer from 'rrweb-player';
import 'rrweb-player/dist/style.css';
```
Browser without bundler (ESM + import maps):
```html
<link
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.js"></script>
<script type="importmap">
{
"imports": {
"rrweb-player": "https://cdn.jsdelivr.net/npm/rrweb-player@latest/+esm"
}
}
</script>
<script type="module">
import rrwebPlayer from 'rrweb-player';
</script>
```
Or installed by using NPM
Legacy direct `<script>` include (UMD fallback):
```shell
npm install --save rrweb-player
```
```js
import rrwebPlayer from 'rrweb-player';
import 'rrweb-player/dist/style.css';
```html
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/rrweb-player@2.0.0-alpha.20/dist/style.css"
/>
<script src="https://cdn.jsdelivr.net/npm/rrweb-player@2.0.0-alpha.20/umd/rrweb-player.min.js"></script>
```
##### Usage
@@ -363,15 +462,15 @@ new rrwebPlayer({
| speedOption | [1, 2, 4, 8] | speed options in UI |
| showController | true | whether to show the controller UI |
| tags | {} | customize the custom events style with a key-value map |
| ... | - | all the rrweb Replayer options will be bypassed |
| ... | - | all other Replayer options are forwarded |
#### Events
Developers may want to extend the rrweb's replayer or respond to its events. Such as giving notification when the replayer starts to skip inactive time.
So rrweb expose a public API `on` which allow developers to listen to the events and customize the reactions, and it has the following events:
Developers may want to extend the replayer or respond to its events, for example to notify users when inactive time starts being skipped.
`Replayer` exposes a public API `on` that lets developers listen for events and customize behavior:
```js
const replayer = new rrweb.Replayer(events);
const replayer = new Replayer(events);
replayer.on(EVENT_NAME, (payload) => {
...
})

View File

@@ -4,42 +4,119 @@
## 安装
### 直接通过 `<script>` 引入
| 目标 | 推荐包 |
| ------------------------- | --------------------------------- |
| 大多数项目(录制 + 回放) | `@rrweb/record` + `@rrweb/replay` |
| 单包便捷接入 | `@rrweb/all` |
| 仅遗留兼容 | `rrweb` |
推荐通过 jsdelivr 的 CDN 安装:
在绝大多数生产架构中,录制端和回放端运行在不同的运行时/页面。请在被录制应用中安装 `@rrweb/record`,在回放应用中安装 `@rrweb/replay`(或 `rrweb-player`)。除非有明确的高级场景,一般不要在同一页面同时引入两者。
### 1) Bundler / npm推荐
```shell
npm install @rrweb/record @rrweb/replay
```
```js
import { record } from '@rrweb/record';
import { Replayer } from '@rrweb/replay';
import '@rrweb/replay/dist/style.css';
```
如果你希望使用单一入口,也可以使用便捷包 `@rrweb/all`
```shell
npm install @rrweb/all
```
```js
import { record, Replayer } from '@rrweb/all';
import '@rrweb/all/dist/style.css';
```
`require(...)` / CommonJS 仍可作为兼容方案使用(由各包的 `exports`/`main` 提供),但 2.x 的主路径是 ESM。
### 2) 无 Bundler 的浏览器场景(推荐 no-build
推荐使用 ES modules + import map + jsDelivr `+esm`
```html
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/style.css"
href="https://cdn.jsdelivr.net/npm/@rrweb/replay@latest/dist/style.css"
/>
<script src="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.umd.min.cjs"></script>
<script type="importmap">
{
"imports": {
"@rrweb/record": "https://cdn.jsdelivr.net/npm/@rrweb/record@latest/+esm",
"@rrweb/replay": "https://cdn.jsdelivr.net/npm/@rrweb/replay@latest/+esm"
}
}
</script>
<script type="module">
import { record } from '@rrweb/record';
record({
emit(event) {
console.log(event);
},
});
</script>
```
也可以在 URL 中指定具体的版本号,例如
也可以通过 `@rrweb/all` 便捷引入
```html
<script src="https://cdn.jsdelivr.net/npm/rrweb@2.0.0-alpha.14/dist/rrweb.umd.min.cjs"></script>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@rrweb/all@latest/dist/style.css"
/>
<script type="importmap">
{
"imports": {
"@rrweb/all": "https://cdn.jsdelivr.net/npm/@rrweb/all@latest/+esm"
}
}
</script>
<script type="module">
import { record, Replayer } from '@rrweb/all';
</script>
```
#### 仅引入录制部分
### 3) 传统直接 `<script>` 引入Legacy / UMD 兼容)
rrweb 代码分为录制和回放两部分,大多数时候用户在被录制的应用中只需要引入录制部分代码。同样可以通过使用 @rrweb/record 包和 CDN 服务来实现:
仅在不支持 ESM 的兼容场景中建议使用。
```html
<script src="https://cdn.jsdelivr.net/npm/@rrweb/record@latest/dist/record.umd.min.cjs"></script>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/rrweb@2.0.0-alpha.20/dist/style.css"
/>
<script src="https://cdn.jsdelivr.net/npm/rrweb@2.0.0-alpha.20/umd/rrweb.min.js"></script>
```
该 UMD 构建会暴露全局变量 `rrweb`
仅录制 / 仅回放的 UMD 兼容包:
```html
<script src="https://cdn.jsdelivr.net/npm/@rrweb/record@2.0.0-alpha.20/umd/record.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@rrweb/replay@2.0.0-alpha.20/umd/replay.min.js"></script>
```
对应全局变量分别是 `rrwebRecord``rrwebReplay`
#### 其他包
除了 `rrweb``@rrweb/record` 包之外rrweb 还提供了其他不同用途的包。
除了 `@rrweb/record``@rrweb/replay` 包之外rrweb 还提供了其他不同用途的包。
- [rrweb](packages/rrweb)rrweb 的核心包,包括录制和回放功能。
- [rrweb-player](packages/rrweb-player)rrweb 的图形用户界面,提供时间线和暂停、快进、加速等按钮。
- [rrweb-snapshot](packages/rrweb-snapshot):处理快照和重建功能,将 DOM 及其状态转换为可序列化的数据结构。
- [rrdom](packages/rrdom)rrweb 的虚拟 dom 包。
- [rrdom-nodejs](packages/rrdom-nodejs):用于服务器端 DOM 操作的 rrdom 的 Node.js 版本。
- [@rrweb/all](packages/all):一个包含 `rrweb``@rrweb/packer`,便于安装的包。
- [@rrweb/all](packages/all):一个包含 `rrweb``@rrweb/packer` 的便捷包。
- [@rrweb/record](packages/record):一个用于录制 rrweb 会话的包。
- [@rrweb/replay](packages/replay):一个用于回放 rrweb 会话的包。
- [@rrweb/packer](packages/packer):一个用于打包和解包 rrweb 数据的包。
@@ -54,14 +131,6 @@ rrweb 代码分为录制和回放两部分,大多数时候用户在被录制
- [@rrweb/rrweb-plugin-canvas-webrtc-record](packages/plugins/rrweb-plugin-canvas-webrtc-record):一个用于通过 WebRTC 流式传输 `<canvas>` 的插件。
- [@rrweb/rrweb-plugin-canvas-webrtc-replay](packages/plugins/rrweb-plugin-canvas-webrtc-replay):一个用于通过 WebRTC 播放流式 `<canvas>` 的插件。
### 通过 npm 引入
```shell
npm install --save rrweb
```
rrweb 同时提供 commonJS 和 ES modules 两种格式的打包文件,易于和常见的打包工具配合使用。
### 兼容性
由于使用 `MutationObserver` APIrrweb 不支持 IE11 以下的浏览器。可以从[这里](https://caniuse.com/#feat=mutationobserver)找到兼容的浏览器列表。
@@ -70,10 +139,14 @@ rrweb 同时提供 commonJS 和 ES modules 两种格式的打包文件,易于
### 录制
如果通过 `<script>` 的方式仅引入录制部分,那么可以访问到全局变量 `rrwebRecord`,它和全量引入时的 `rrweb.record` 使用方式完全一致,以下示例代码将使用后者。
现代用法建议直接使用 `@rrweb/record``record`
```js
rrweb.record({
import { record } from '@rrweb/record';
```
```js
record({
emit(event) {
// 用任意方式存储 event
},
@@ -85,7 +158,7 @@ rrweb 在录制时会不断将各类 event 传递给配置的 emit 方法,你
调用 `record` 方法将返回一个函数,调用该函数可以终止录制:
```js
let stopFn = rrweb.record({
let stopFn = record({
emit(event) {
if (events.length > 100) {
// 当事件数量大于 100 时停止录制
@@ -100,7 +173,7 @@ let stopFn = rrweb.record({
```js
let events = [];
rrweb.record({
record({
emit(event) {
// 将 event 存入 events 数组中
events.push(event);
@@ -126,7 +199,7 @@ setInterval(save, 10 * 1000);
#### 配置参数
`rrweb.record(config)` 的 config 部分接受以下参数
`record(config)` 的 config 部分接受以下参数
| key | 默认值 | 功能 |
| ------------------------ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -178,7 +251,7 @@ setInterval(save, 10 * 1000);
// 使用二维数组来存放多个 event 数组
const eventsMatrix = [[]];
rrweb.record({
record({
emit(event, isCheckout) {
// isCheckout 是一个标识,告诉你重新制作了快照
if (isCheckout) {
@@ -213,7 +286,7 @@ window.onerror = function () {
// 使用二维数组来存放多个 event 数组
const eventsMatrix = [[]];
rrweb.record({
record({
emit(event, isCheckout) {
// isCheckout 是一个标识,告诉你重新制作了快照
if (isCheckout) {
@@ -244,28 +317,36 @@ window.onerror = function () {
### 回放
回放时需要引入对应的 CSS 文件
在 bundler 场景下,可在入口文件中引入 CSS
```js
import '@rrweb/replay/dist/style.css';
```
在浏览器 no-build 场景下,也可以在 HTML 中引入 CSS
```html
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/style.css"
href="https://cdn.jsdelivr.net/npm/@rrweb/replay@latest/dist/style.css"
/>
```
通过以下 JS 代码初始化 replayer
然后通过以下 JS 代码初始化 replayer
```js
import { Replayer } from '@rrweb/replay';
const events = YOUR_EVENTS;
const replayer = new rrweb.Replayer(events);
const replayer = new Replayer(events);
replayer.play();
```
#### 使用 API 控制回放
```js
const replayer = new rrweb.Replayer(events);
const replayer = new Replayer(events);
// 播放
replayer.play();
@@ -285,7 +366,7 @@ replayer.destroy();
#### 配置参数
可以通过 `new rrweb.Replayer(events, options)` 的方式向 rrweb 传递回放时的配置参数,具体配置如下:
可以通过 `new Replayer(events, options)` 的方式向 rrweb 传递回放时的配置参数,具体配置如下:
| key | 默认值 | 功能 |
| ------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -313,25 +394,44 @@ rrweb 自带的回放只提供所有的 JS API 以及最基本的 UI如果需
##### 安装
rrweb-player 同样可以使用 CDN 方式安装
Bundler / npm推荐
```shell
npm install rrweb-player
```
```js
import rrwebPlayer from 'rrweb-player';
import 'rrweb-player/dist/style.css';
```
无 bundler 的浏览器场景ESM + import maps
```html
<link
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.js"></script>
<script type="importmap">
{
"imports": {
"rrweb-player": "https://cdn.jsdelivr.net/npm/rrweb-player@latest/+esm"
}
}
</script>
<script type="module">
import rrwebPlayer from 'rrweb-player';
</script>
```
或者通过 npm 安装
Legacy 直接 `<script>` 引入UMD 兼容)
```shell
npm install --save rrweb-player
```
```js
import rrwebPlayer from 'rrweb-player';
import 'rrweb-player/dist/style.css';
```html
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/rrweb-player@2.0.0-alpha.20/dist/style.css"
/>
<script src="https://cdn.jsdelivr.net/npm/rrweb-player@2.0.0-alpha.20/umd/rrweb-player.min.js"></script>
```
##### 使用
@@ -359,16 +459,16 @@ new rrwebPlayer({
| speedOption | [1, 2, 4, 8] | 倍速播放可选值 |
| showController | true | 是否显示播放器控制 UI |
| tags | {} | 可以以 key value 的形式展示自定义事件在时间轴上的颜色 |
| ... | - | 其它所有 rrweb Replayer 的配置参数均可透传 |
| ... | - | 其它所有 Replayer 的配置参数均可透传 |
#### 事件
开发者可能希望监听回放时的各类事件,例如在跳过无用户操作的时间时给用户一些提示。
rrweb 的 Replayer 提供了 `on` API 用于提供该功能
Replayer 提供了 `on` API 用于实现该功能
```js
const replayer = new rrweb.Replayer(events);
const replayer = new Replayer(events);
replayer.on(EVENT_NAME, (payload) => {
...
})

329
index.html Normal file
View File

@@ -0,0 +1,329 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>rrweb 示例</title>
<style>
body {
font-family: 'Segoe UI', Arial, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background: #f5f5f5;
}
h1 {
color: #333;
border-bottom: 3px solid #007bff;
padding-bottom: 10px;
}
.container {
display: flex;
gap: 20px;
}
.panel {
flex: 1;
border: 1px solid #ddd;
padding: 20px;
border-radius: 8px;
background: white;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.test-area {
min-height: 300px;
border: 2px dashed #007bff;
padding: 20px;
margin: 15px 0;
border-radius: 8px;
background: #f8f9fa;
}
button {
padding: 12px 24px;
margin: 8px;
cursor: pointer;
background: #007bff;
color: white;
border: none;
border-radius: 6px;
font-size: 15px;
font-weight: 600;
}
button:hover {
background: #0056b3;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,123,255,0.4);
}
button:disabled {
background: #6c757d;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.record-btn {
background: #28a745;
min-width: 120px;
}
.record-btn:hover {
background: #1e7e34;
}
.stop-btn {
background: #dc3545;
min-width: 120px;
}
.stop-btn:hover {
background: #b02a37;
}
.status-bar {
padding: 15px;
margin: 15px 0;
border-radius: 6px;
font-size: 16px;
font-weight: 600;
text-align: center;
}
.status-bar.idle {
background: #e2e3e5;
color: #383d41;
}
.status-bar.recording {
background: #d4edda;
color: #155724;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
.info-box {
padding: 15px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 8px;
color: white;
margin-top: 15px;
}
.info-box h3 {
margin-top: 0;
}
.info-box ul {
margin: 10px 0;
padding-left: 20px;
}
</style>
</head>
<body>
<h1>🎥 rrweb 录制与回放</h1>
<div class="container">
<div class="panel">
<h2>📹 录制区域</h2>
<div class="test-area">
<h3 style="margin-top:0;">在此区域进行操作</h3>
<p>点击按钮、输入文字,所有操作都会被记录:</p>
<button onclick="changeColor()">🎨 随机变色</button>
<button onclick="addCounter()">🔢 添加计数器</button>
<button onclick="showAlert()">💬 测试弹窗</button>
<div id="counters" style="display:flex; gap:10px; flex-wrap:wrap; margin-top:15px;"></div>
</div>
<div id="status-bar" class="status-bar idle">
⚪ 等待录制
</div>
<div style="margin: 15px 0;">
<button id="start-btn" class="record-btn" onclick="startRecording()">▶ 开始录制</button>
<button id="stop-btn" class="stop-btn" onclick="stopRecording()" disabled>⏹ 停止录制</button>
<button onclick="clearAll()">🗑️ 清空</button>
</div>
<div class="info-box">
<h3>✨ 功能说明</h3>
<ul>
<li>点击"开始录制"开始记录</li>
<li>点击"停止录制"结束记录</li>
<li>右侧会自动回放录制的操作</li>
<li>支持播放速度控制</li>
</ul>
</div>
</div>
<div class="panel">
<h2>▶️ 回放区域</h2>
<div id="replayer" style="width:100%; height:400px; border:2px solid #007bff; border-radius:8px; background:#f8f9fa;"></div>
<div class="info-box" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">
<h3>🎬 回放控制</h3>
<p>播放器提供完整控制:</p>
<ul>
<li>时间轴拖动:点击任意位置跳转</li>
<li>播放/暂停:控制播放状态</li>
<li>速度控制0.5x、1x、2x、4x</li>
</ul>
</div>
</div>
</div>
<!-- rrweb CDN - 使用非模块化版本 -->
<link rel="stylesheet" href="./packages/rrweb-player/dist/style.css">
<script src="./packages/rrweb/dist/rrweb.umd.cjs"></script>
<script src="./packages/rrweb-player/dist/rrweb-player.umd.cjs"></script>
<script>
let events = [];
let stopRecordingFn = null;
let player = null;
// 等待 rrweb 加载完成
function initWhenReady() {
if (
typeof rrweb !== 'undefined' &&
typeof rrweb.record === 'function' &&
typeof rrwebPlayer !== 'undefined'
) {
console.log('rrweb 与 rrweb-player 已加载,准备就绪');
setupButtons();
} else {
console.log('等待 rrweb / rrweb-player 加载...');
setTimeout(initWhenReady, 100);
}
}
function getPlayerConstructor() {
if (typeof rrwebPlayer === 'function') {
return rrwebPlayer;
}
if (rrwebPlayer && typeof rrwebPlayer.Player === 'function') {
return rrwebPlayer.Player;
}
if (rrwebPlayer && typeof rrwebPlayer.default === 'function') {
return rrwebPlayer.default;
}
return null;
}
function setupButtons() {
document.getElementById('start-btn').onclick = startRecording;
document.getElementById('stop-btn').onclick = stopRecording;
}
function destroyPlayer() {
const target = document.getElementById('replayer');
if (player && typeof player.$destroy === 'function') {
player.$destroy();
}
player = null;
target.innerHTML = '';
}
function renderReplay() {
if (!events.length) {
return;
}
const PlayerConstructor = getPlayerConstructor();
if (!PlayerConstructor) {
throw new Error('rrweb-player 未正确加载');
}
destroyPlayer();
const target = document.getElementById('replayer');
player = new PlayerConstructor({
target,
props: {
events,
autoPlay: true,
showController: true,
speed: 1,
speedOption: [0.5, 1, 2, 4],
width: 1000,
height: 400,
},
});
}
// 开始录制
function startRecording() {
events = [];
destroyPlayer();
try {
stopRecordingFn = rrweb.record({
emit(event) {
events.push(event);
},
recordCanvas: true,
recordCrossOriginIframes: true,
recordAfter: 'DOMContentLoaded',
ignoreSelector: '.status-bar, .info-box, #replayer',
});
updateStatus('🔴 正在录制...', 'recording');
document.getElementById('start-btn').disabled = true;
document.getElementById('stop-btn').disabled = false;
console.log('录制已启动');
} catch (error) {
updateStatus('⚪ 等待录制', 'idle');
console.error('启动录制失败:', error);
alert('启动录制失败: ' + error.message);
}
}
// 停止录制
function stopRecording() {
if (typeof stopRecordingFn === 'function') {
stopRecordingFn();
stopRecordingFn = null;
updateStatus(`✅ 已录制 ${events.length} 个事件`, 'idle');
document.getElementById('start-btn').disabled = false;
document.getElementById('stop-btn').disabled = true;
try {
renderReplay();
console.log('回放已初始化');
} catch (error) {
console.error('初始化回放失败:', error);
alert('初始化回放失败: ' + error.message);
return;
}
console.log('录制完成,事件数量:', events.length);
console.log('事件列表:', events);
alert(`录制完成!\n共记录了 ${events.length} 个事件。\n请在右侧查看回放与控制条。`);
}
}
// 更新状态
function updateStatus(text, type) {
const statusEl = document.getElementById('status-bar');
statusEl.className = `status-bar ${type}`;
statusEl.textContent = text;
}
// 测试函数
function changeColor() {
const colors = ['#f8f9fa', '#e3f2fd', '#fff3cd', '#d1ecf1d', '#f8d7da', '#d6d8db', '#cce5ff', '#e2d9f7'];
document.querySelector('.test-area').style.background = colors[Math.floor(Math.random() * colors.length)];
}
function addCounter() {
const counters = document.getElementById('counters');
const count = counters.children.length;
const div = document.createElement('div');
div.style.cssText = 'padding:10px 20px; background:white; border:2px solid #007bff; border-radius:8px; font-size:24px; font-weight:bold; cursor:pointer;';
div.innerHTML = `<span>#${count + 1}</span>`;
div.onclick = function() {
const span = this.querySelector('span');
span.innerText = parseInt(span.innerText) + 1;
};
counters.appendChild(div);
}
function showAlert() {
alert('这是一个测试弹窗!\nrrweb 会记录并回放这个弹窗操作。');
}
function clearAll() {
events = [];
destroyPlayer();
updateStatus('⚪ 等待录制', 'idle');
}
// 开始初始化
setTimeout(initWhenReady, 100);
</script>
</body>
</html>

View File

@@ -1,6 +1,15 @@
# @rrweb/all
Convenience package that includes a bundle of rrweb packages.
For most new integrations, prefer `@rrweb/record` + `@rrweb/replay` first, and use `@rrweb/all` when you want a single-package setup.
| Use case | Package choice |
| --------------------------------------------------- | --------------------------------- |
| Most new apps (explicit record/replay dependencies) | `@rrweb/record` + `@rrweb/replay` |
| Quick setup with one import | `@rrweb/all` |
| Legacy compatibility | `rrweb` |
In most production setups, recorder and replayer are deployed to different pages/apps. Use `@rrweb/record` on recorded pages and `@rrweb/replay` (or `rrweb-player`) on replay pages. Use `@rrweb/all` when you intentionally want one package for convenience (for example demos, tooling, or simplified setups).
Includes the following packages:
@@ -11,19 +20,47 @@ Includes the following packages:
## Installation
### 1) Bundler / npm
```bash
npm install @rrweb/all
```
## Usage
```js
import { record, replay, pack, unpack } from '@rrweb/all';
// use record, replay, pack, unpack as you would with the individual packages.
import { record, Replayer, pack, unpack } from '@rrweb/all';
import '@rrweb/all/dist/style.css';
```
See the [guide](../../guide.md) for more info on rrweb.
For API details and examples, see the [guide](../../guide.md).
### 2) Browser Without Bundler (ESM + import maps)
```html
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@rrweb/all@latest/dist/style.css"
/>
<script type="importmap">
{
"imports": {
"@rrweb/all": "https://cdn.jsdelivr.net/npm/@rrweb/all@latest/+esm"
}
}
</script>
<script type="module">
import { record, Replayer, pack, unpack } from '@rrweb/all';
</script>
```
### 3) Legacy Direct `<script>` Include (UMD fallback)
Use this only for compatibility with non-module environments.
```html
<script src="https://cdn.jsdelivr.net/npm/@rrweb/all@latest/umd/all.min.js"></script>
```
The legacy UMD global is `rrweb`, so you will need to prefix the example APIs, e.g. `rrweb.record`, `new rrweb.Replayer(...)`, `rrweb.pack`, and `rrweb.unpack`, rather than using these functions directly.
## Sponsors

View File

@@ -44,13 +44,14 @@
}
},
"files": [
"umd",
"build",
"dist",
"package.json"
],
"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

@@ -13,8 +13,8 @@ https://user-images.githubusercontent.com/4106/186701616-fd71a107-5d53-423c-ba09
```js
// Record side
import rrweb from 'rrweb';
import { RRWebPluginCanvasWebRTCRecord } from 'rrweb-plugin-canvas-webrtc-record';
import { record } from '@rrweb/record';
import { RRWebPluginCanvasWebRTCRecord } from '@rrweb/rrweb-plugin-canvas-webrtc-record';
const webRTCRecordPlugin = new RRWebPluginCanvasWebRTCRecord({
signalSendCallback: (msg) => {
@@ -24,7 +24,7 @@ const webRTCRecordPlugin = new RRWebPluginCanvasWebRTCRecord({
},
});
rrweb.record({
record({
emit: (event) => {
// send these events to the `replayer.addEvent(event)`, how you do that is up to you
// you can send them to a server for example which can then send them to the replayer
@@ -42,8 +42,8 @@ rrweb.record({
```js
// Replay side
import rrweb from 'rrweb';
import { RRWebPluginCanvasWebRTCReplay } from 'rrweb-plugin-canvas-webrtc-replay';
import { Replayer } from '@rrweb/replay';
import { RRWebPluginCanvasWebRTCReplay } from '@rrweb/rrweb-plugin-canvas-webrtc-replay';
const webRTCReplayPlugin = new RRWebPluginCanvasWebRTCReplay({
canvasFoundCallback(canvas, context) {
@@ -59,7 +59,7 @@ const webRTCReplayPlugin = new RRWebPluginCanvasWebRTCReplay({
},
});
const replayer = new rrweb.Replayer([], {
const replayer = new Replayer([], {
UNSAFE_replayCanvas: true, // turn canvas replay on!
liveMode: true, // live mode is needed to stream events to the replayer
plugins: [webRTCReplayPlugin.initPlugin()],

View File

@@ -20,6 +20,7 @@
}
},
"files": [
"umd",
"dist",
"package.json"
],
@@ -45,7 +46,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

@@ -13,7 +13,7 @@ https://user-images.githubusercontent.com/4106/186701616-fd71a107-5d53-423c-ba09
```js
// Record side
import rrweb from 'rrweb';
import { record } from '@rrweb/record';
import { RRWebPluginCanvasWebRTCRecord } from '@rrweb/rrweb-plugin-canvas-webrtc-record';
const webRTCRecordPlugin = new RRWebPluginCanvasWebRTCRecord({
@@ -24,7 +24,7 @@ const webRTCRecordPlugin = new RRWebPluginCanvasWebRTCRecord({
},
});
rrweb.record({
record({
emit: (event) => {
// send these events to the `replayer.addEvent(event)`, how you do that is up to you
// you can send them to a server for example which can then send them to the replayer
@@ -42,7 +42,7 @@ rrweb.record({
```js
// Replay side
import rrweb from 'rrweb';
import { Replayer } from '@rrweb/replay';
import { RRWebPluginCanvasWebRTCReplay } from '@rrweb/rrweb-plugin-canvas-webrtc-replay';
const webRTCReplayPlugin = new RRWebPluginCanvasWebRTCReplay({
@@ -59,7 +59,7 @@ const webRTCReplayPlugin = new RRWebPluginCanvasWebRTCReplay({
},
});
const replayer = new rrweb.Replayer([], {
const replayer = new Replayer([], {
UNSAFE_replayCanvas: true, // turn canvas replay on!
liveMode: true, // live mode is needed to stream events to the replayer
plugins: [webRTCReplayPlugin.initPlugin()],

View File

@@ -20,6 +20,7 @@
}
},
"files": [
"umd",
"dist",
"package.json"
],
@@ -45,7 +46,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

@@ -20,6 +20,7 @@
}
},
"files": [
"umd",
"dist",
"package.json"
],
@@ -47,7 +48,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

@@ -20,6 +20,7 @@
}
},
"files": [
"umd",
"dist",
"package.json"
],
@@ -46,7 +47,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

@@ -12,10 +12,10 @@ npm install @rrweb/rrweb-plugin-sequential-id-record
## Usage
```js
import rrweb from 'rrweb';
import { record } from '@rrweb/record';
import { getRecordSequentialIdPlugin } from '@rrweb/rrweb-plugin-sequential-id-record';
rrweb.record({
record({
emit: function emit(event) {
// send events to server
},

View File

@@ -20,6 +20,7 @@
}
},
"files": [
"umd",
"dist",
"package.json"
],
@@ -45,7 +46,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

@@ -12,10 +12,10 @@ npm install @rrweb/rrweb-plugin-sequential-id-replay
## Usage
```js
import rrweb from 'rrweb';
import { Replayer } from '@rrweb/replay';
import { getReplaySequentialIdPlugin } from '@rrweb/rrweb-plugin-sequential-id-replay';
const replayer = new rrweb.Replayer(events, {
const replayer = new Replayer(events, {
plugins: [
getReplaySequentialIdPlugin({
// make sure this is the same as the record side

View File

@@ -20,6 +20,7 @@
}
},
"files": [
"umd",
"dist",
"package.json"
],
@@ -46,7 +47,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

@@ -5,10 +5,41 @@ See the [guide](../../guide.md) for more info on rrweb.
## Installation
### 1) Bundler / npm (Recommended)
```bash
npm install @rrweb/record
```
```js
import { record } from '@rrweb/record';
```
### 2) Browser Without Bundler (ESM + import maps)
```html
<script type="importmap">
{
"imports": {
"@rrweb/record": "https://cdn.jsdelivr.net/npm/@rrweb/record@latest/+esm"
}
}
</script>
<script type="module">
import { record } from '@rrweb/record';
</script>
```
### 3) Legacy Direct `<script>` Include (UMD fallback)
Use this only for compatibility with non-module environments.
```html
<script src="https://cdn.jsdelivr.net/npm/@rrweb/record@latest/umd/record.min.js"></script>
```
The legacy UMD global is `rrwebRecord`.
## Usage
```js

View File

@@ -44,12 +44,13 @@
}
},
"files": [
"umd",
"dist",
"package.json"
],
"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

@@ -5,14 +5,55 @@ See the [guide](../../guide.md) for more info on rrweb.
## Installation
### 1) Bundler / npm (Recommended)
```bash
npm install @rrweb/replay
```
```js
import { Replayer } from '@rrweb/replay';
import '@rrweb/replay/dist/style.css';
```
### 2) Browser Without Bundler (ESM + import maps)
```html
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@rrweb/replay@latest/dist/style.css"
/>
<script type="importmap">
{
"imports": {
"@rrweb/replay": "https://cdn.jsdelivr.net/npm/@rrweb/replay@latest/+esm"
}
}
</script>
<script type="module">
import { Replayer } from '@rrweb/replay';
</script>
```
### 3) Legacy Direct `<script>` Include (UMD fallback)
Use this only for compatibility with non-module environments.
```html
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@rrweb/replay@latest/dist/style.css"
/>
<script src="https://cdn.jsdelivr.net/npm/@rrweb/replay@latest/umd/replay.min.js"></script>
```
The legacy UMD global is `rrwebReplay`.
## Usage
```js
import { Replayer } from '@rrweb/replay';
import '@rrweb/replay/dist/style.css';
const replayer = new Replayer(events, {
// options

View File

@@ -45,12 +45,13 @@
"./dist/style.css": "./dist/style.css"
},
"files": [
"umd",
"dist",
"package.json"
],
"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

@@ -33,6 +33,7 @@
}
},
"files": [
"umd",
"dist",
"package.json"
],
@@ -46,7 +47,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

@@ -21,6 +21,7 @@
}
},
"files": [
"umd",
"dist",
"package.json"
],
@@ -48,7 +49,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

@@ -26,261 +26,179 @@
* ```
*/
declare module '$env/static/private' {
export const SUDO_GID: string;
export const GITHUB_STATE: string;
export const COPILOT_AGENT_ACTION: string;
export const npm_package_scripts_test_cross_platform_build: string;
export const npm_package_devDependencies_rollup: string;
export const npm_package_devDependencies__types_node: string;
export const COPILOT_AGENT_START_TIME_SEC: string;
export const CURL_CA_BUNDLE: string;
export const DOTNET_NOLOGO: string;
export const npm_package_devDependencies_vitest: string;
export const MAIL: string;
export const NODE_EXTRA_CA_CERTS: string;
export const USER: string;
export const npm_package_bin_svelte_kit: string;
export const npm_package_dependencies_sirv: string;
export const npm_package_dependencies_sade: string;
export const npm_package_dependencies_mrmime: string;
export const npm_package_dependencies_magic_string: string;
export const npm_config_version_commit_hooks: string;
export const npm_config_user_agent: string;
export const SHOULD_CONTINUE: string;
export const CI: string;
export const npm_package_scripts_generate_version: string;
export const npm_package_dependencies__types_cookie: string;
export const npm_config_bin_links: string;
export const XDG_SESSION_TYPE: string;
export const RUNNER_ENVIRONMENT: string;
export const GITHUB_ENV: string;
export const COPILOT_AGENT_ONLINE_EVALUATION_DISABLED: string;
export const PIPX_HOME: string;
export const npm_node_execpath: string;
export const npm_package_devDependencies_vite: string;
export const npm_package_devDependencies__sveltejs_vite_plugin_svelte: string;
export const npm_config_init_version: string;
export const JAVA_HOME_8_X64: string;
export const SHLVL: string;
export const npm_package_exports___node_types: string;
export const npm_package_files_0: string;
export const COPILOT_AGENT_RUNTIME_VERSION: string;
export const ACLOCAL_PATH: string;
export const ALLUSERSPROFILE: string;
export const ANTHROPIC_AUTH_TOKEN: string;
export const ANTHROPIC_BASE_URL: string;
export const APPDATA: string;
export const CLAUDECODE: string;
export const CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: string;
export const CLAUDE_CODE_ENTRYPOINT: string;
export const COMMONPROGRAMFILES: string;
export const CommonProgramW6432: string;
export const COMPUTERNAME: string;
export const COMSPEC: string;
export const CONFIG_SITE: string;
export const COREPACK_ENABLE_AUTO_PIN: string;
export const DISPLAY: string;
export const DriverData: string;
export const EXEPATH: string;
export const GIT_EDITOR: string;
export const HOME: string;
export const OLDPWD: string;
export const npm_package_files_1: string;
export const npm_package_repository_directory: string;
export const RUNNER_TEMP: string;
export const GITHUB_EVENT_PATH: string;
export const CAROOT: string;
export const COPILOT_AGENT_FIREWALL_RULESET_ALLOW_LIST: string;
export const npm_package_files_2: string;
export const JAVA_HOME_11_X64: string;
export const COPILOT_AGENT_MCP_SERVER_TEMP: string;
export const PIPX_BIN_DIR: string;
export const GITHUB_REPOSITORY_OWNER: string;
export const npm_package_engines_node: string;
export const npm_package_exports___vite_import: string;
export const npm_package_files_3: string;
export const npm_package_devDependencies_svelte_preprocess: string;
export const npm_config_init_license: string;
export const GRADLE_HOME: string;
export const ANDROID_NDK_LATEST_HOME: string;
export const JAVA_HOME_21_X64: string;
export const GITHUB_RETENTION_DAYS: string;
export const npm_package_files_4: string;
export const npm_config_version_tag_prefix: string;
export const GITHUB_REPOSITORY_OWNER_ID: string;
export const POWERSHELL_DISTRIBUTION_CHANNEL: string;
export const SSL_CERT_FILE: string;
export const AZURE_EXTENSION_DIR: string;
export const GITHUB_HEAD_REF: string;
export const npm_package_scripts_check: string;
export const npm_package_files_5: string;
export const npm_package_dependencies_tiny_glob: string;
export const SYSTEMD_EXEC_PID: string;
export const DBUS_SESSION_BUS_ADDRESS: string;
export const npm_package_scripts_postinstall: string;
export const npm_package_files_6: string;
export const GITHUB_GRAPHQL_URL: string;
export const GITHUB_DOWNLOADS_URL: string;
export const npm_package_devDependencies_typescript: string;
export const npm_package_devDependencies__types_connect: string;
export const npm_package_description: string;
export const JAVA_HOME_25_X64: string;
export const NVM_DIR: string;
export const npm_package_readmeFilename: string;
export const npm_package_types: string;
export const npm_package_homepage: string;
export const DOTNET_SKIP_FIRST_TIME_EXPERIENCE: string;
export const COPILOT_JOB_EVENT_TYPE: string;
export const JAVA_HOME_17_X64: string;
export const ImageVersion: string;
export const SUDO_UID: string;
export const npm_package_exports___hooks_types: string;
export const npm_package_devDependencies__playwright_test: string;
export const BLACKBIRD_MODE: string;
export const LOGNAME: string;
export const COPILOT_AGENT_PR_COMMIT_COUNT: string;
export const RUNNER_OS: string;
export const GITHUB_API_URL: string;
export const GOROOT_1_22_X64: string;
export const COPILOT_AGENT_COMMIT_LOGIN: string;
export const SWIFT_PATH: string;
export const npm_package_type: string;
export const COPILOT_USE_SESSIONS: string;
export const CHROMEWEBDRIVER: string;
export const COPILOT_AGENT_CONTENT_FILTER_MODE: string;
export const GOROOT_1_23_X64: string;
export const JOURNAL_STREAM: string;
export const GITHUB_WORKFLOW: string;
export const _: string;
export const COPILOT_AGENT_BRANCH_NAME: string;
export const MEMORY_PRESSURE_WATCH: string;
export const XDG_SESSION_CLASS: string;
export const GOROOT_1_24_X64: string;
export const npm_package_scripts_lint: string;
export const npm_config_registry: string;
export const ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE: string;
export const COPILOT_AGENT_FIREWALL_ENABLE_RULESET_ALLOW_LIST: string;
export const GOROOT_1_25_X64: string;
export const GITHUB_RUN_ID: string;
export const TERM: string;
export const XDG_SESSION_ID: string;
export const GITHUB_REF_TYPE: string;
export const BOOTSTRAP_HASKELL_NONINTERACTIVE: string;
export const GITHUB_WORKFLOW_SHA: string;
export const GITHUB_BASE_REF: string;
export const ImageOS: string;
export const COPILOT_MCP_ENABLED: string;
export const npm_package_exports___import: string;
export const npm_package_devDependencies_dts_buddy: string;
export const npm_package_dependencies_kleur: string;
export const npm_package_dependencies_devalue: string;
export const npm_config_ignore_scripts: string;
export const COPILOT_AGENT_CALLBACK_URL: string;
export const GITHUB_WORKFLOW_REF: string;
export const GITHUB_ACTION_REPOSITORY: string;
export const ENABLE_RUNNER_TRACING: string;
export const npm_package_exports___package_json: string;
export const npm_package_peerDependencies_svelte: string;
export const PATH: string;
export const NODE: string;
export const COPILOT_AGENT_INJECTED_SECRET_NAMES: string;
export const ANT_HOME: string;
export const DOTNET_MULTILEVEL_LOOKUP: string;
export const RUNNER_TRACKING_ID: string;
export const INVOCATION_ID: string;
export const RUNNER_TOOL_CACHE: string;
export const GITHUB_UPLOADS_URL: string;
export const REQUESTS_CA_BUNDLE: string;
export const npm_package_repository_type: string;
export const npm_package_name: string;
export const GITHUB_ACTION: string;
export const GITHUB_RUN_NUMBER: string;
export const GITHUB_TRIGGERING_ACTOR: string;
export const COPILOT_EXPERIMENTS: string;
export const RUNNER_ARCH: string;
export const XDG_RUNTIME_DIR: string;
export const AGENT_TOOLSDIRECTORY: string;
export const npm_package_scripts_test_integration: string;
export const npm_package_exports___node_polyfills_import: string;
export const npm_package_devDependencies__types_set_cookie_parser: string;
export const SSL_CERT_DIR: string;
export const npm_package_scripts_test_unit: string;
export const npm_package_exports___vite_types: string;
export const npm_config_ignore_path: string;
export const LANG: string;
export const VCPKG_INSTALLATION_ROOT: string;
export const CONDA: string;
export const RUNNER_NAME: string;
export const XDG_CONFIG_HOME: string;
export const GITHUB_REF_NAME: string;
export const GITHUB_REPOSITORY: string;
export const npm_lifecycle_script: string;
export const npm_package_scripts_test_cross_platform_dev: string;
export const SUDO_COMMAND: string;
export const ANDROID_NDK_ROOT: string;
export const GITHUB_ACTION_REF: string;
export const DEBIAN_FRONTEND: string;
export const npm_package_scripts_test: string;
export const npm_package_dependencies_esm_env: string;
export const npm_config_version_git_message: string;
export const SHELL: string;
export const GITHUB_REPOSITORY_ID: string;
export const GITHUB_ACTIONS: string;
export const CPD_SAVE_TRAJECTORY_OUTPUT: string;
export const npm_lifecycle_event: string;
export const npm_package_repository_url: string;
export const npm_package_version: string;
export const GITHUB_REF_PROTECTED: string;
export const npm_config_argv: string;
export const npm_package_scripts_generate_types: string;
export const npm_package_scripts_check_all: string;
export const npm_package_devDependencies_svelte: string;
export const npm_package_dependencies_cookie: string;
export const GITHUB_WORKSPACE: string;
export const SUDO_USER: string;
export const ACCEPT_EULA: string;
export const DOTNET_SYSTEM_NET_DISABLEIPV6: string;
export const GITHUB_JOB: string;
export const YARN_IGNORE_PATH: string;
export const npm_package_exports___node_import: string;
export const GITHUB_SHA: string;
export const GITHUB_RUN_ATTEMPT: string;
export const COPILOT_AGENT_DEBUG: string;
export const npm_package_devDependencies__types_sade: string;
export const npm_config_version_git_tag: string;
export const npm_config_version_git_sign: string;
export const GITHUB_REF: string;
export const COPILOT_AGENT_ISSUE_NUMBER: string;
export const COPILOT_AGENT_SOURCE_ENVIRONMENT: string;
export const GITHUB_ACTOR: string;
export const FIREWALL_RULESET_CONTENT: string;
export const ANDROID_SDK_ROOT: string;
export const npm_package_license: string;
export const npm_config_strict_ssl: string;
export const npm_package_scripts_format: string;
export const GITHUB_PATH: string;
export const HOMEDRIVE: string;
export const HOMEPATH: string;
export const HOSTNAME: string;
export const INFOPATH: string;
export const INIT_CWD: string;
export const JAVA_HOME: string;
export const PWD: string;
export const GITHUB_ACTOR_ID: string;
export const RUNNER_WORKSPACE: string;
export const LANG: string;
export const LOCALAPPDATA: string;
export const LOGONSERVER: string;
export const MANPATH: string;
export const MINGW_CHOST: string;
export const MINGW_PACKAGE_PREFIX: string;
export const MINGW_PREFIX: string;
export const MSYSTEM: string;
export const MSYSTEM_CARCH: string;
export const MSYSTEM_CHOST: string;
export const MSYSTEM_PREFIX: string;
export const NODE: string;
export const NoDefaultCurrentDirectoryInExePath: string;
export const npm_config_argv: string;
export const npm_config_bin_links: string;
export const npm_config_ignore_optional: string;
export const npm_config_ignore_path: string;
export const npm_config_ignore_scripts: string;
export const npm_config_init_license: string;
export const npm_config_init_version: string;
export const npm_config_registry: string;
export const npm_config_save_prefix: string;
export const npm_config_strict_ssl: string;
export const npm_config_user_agent: string;
export const npm_config_version_commit_hooks: string;
export const npm_config_version_git_message: string;
export const npm_config_version_git_sign: string;
export const npm_config_version_git_tag: string;
export const npm_config_version_tag_prefix: string;
export const npm_execpath: string;
export const npm_lifecycle_event: string;
export const npm_lifecycle_script: string;
export const npm_node_execpath: string;
export const npm_package_bin_svelte_kit: string;
export const npm_package_dependencies_cookie: string;
export const npm_package_dependencies_devalue: string;
export const npm_package_dependencies_esm_env: string;
export const npm_package_dependencies_import_meta_resolve: string;
export const npm_package_dependencies_kleur: string;
export const npm_package_dependencies_magic_string: string;
export const npm_package_dependencies_mrmime: string;
export const npm_package_dependencies_sade: string;
export const npm_package_dependencies_set_cookie_parser: string;
export const COPILOT_AGENT_PR_NUMBER: string;
export const HOMEBREW_CLEANUP_PERIODIC_FULL_DAYS: string;
export const GITHUB_EVENT_NAME: string;
export const HOMEBREW_NO_AUTO_UPDATE: string;
export const ANDROID_HOME: string;
export const GITHUB_SERVER_URL: string;
export const GECKOWEBDRIVER: string;
export const GHCUP_INSTALL_BASE_PREFIX: string;
export const GITHUB_OUTPUT: string;
export const npm_package_dependencies_sirv: string;
export const npm_package_dependencies_tiny_glob: string;
export const npm_package_dependencies__types_cookie: string;
export const npm_package_description: string;
export const npm_package_devDependencies_dts_buddy: string;
export const npm_package_devDependencies_rollup: string;
export const npm_package_devDependencies_svelte: string;
export const npm_package_devDependencies_svelte_preprocess: string;
export const npm_package_devDependencies_typescript: string;
export const npm_package_devDependencies_vite: string;
export const npm_package_devDependencies_vitest: string;
export const npm_package_devDependencies__playwright_test: string;
export const npm_package_devDependencies__sveltejs_vite_plugin_svelte: string;
export const npm_package_devDependencies__types_connect: string;
export const npm_package_devDependencies__types_node: string;
export const npm_package_devDependencies__types_sade: string;
export const npm_package_devDependencies__types_set_cookie_parser: string;
export const npm_package_engines_node: string;
export const npm_package_exports___hooks_import: string;
export const npm_package_exports___hooks_types: string;
export const npm_package_exports___import: string;
export const npm_package_exports___node_import: string;
export const npm_package_exports___node_polyfills_import: string;
export const npm_package_exports___node_polyfills_types: string;
export const npm_package_exports___node_types: string;
export const npm_package_exports___package_json: string;
export const npm_package_exports___types: string;
export const EDGEWEBDRIVER: string;
export const COPILOT_EXPERIMENT_ASSIGNMENT_CONTEXT: string;
export const npm_package_exports___vite_import: string;
export const npm_package_exports___vite_types: string;
export const npm_package_files_0: string;
export const npm_package_files_1: string;
export const npm_package_files_2: string;
export const npm_package_files_3: string;
export const npm_package_files_4: string;
export const npm_package_files_5: string;
export const npm_package_files_6: string;
export const npm_package_homepage: string;
export const npm_package_license: string;
export const npm_package_name: string;
export const npm_package_peerDependencies_svelte: string;
export const npm_package_peerDependencies_vite: string;
export const npm_package_peerDependencies__sveltejs_vite_plugin_svelte: string;
export const npm_config_save_prefix: string;
export const npm_config_ignore_optional: string;
export const ANDROID_NDK: string;
export const SGX_AESM_ADDR: string;
export const CHROME_BIN: string;
export const npm_package_readmeFilename: string;
export const npm_package_repository_directory: string;
export const npm_package_repository_type: string;
export const npm_package_repository_url: string;
export const npm_package_scripts_check: string;
export const npm_package_scripts_check_all: string;
export const npm_package_scripts_format: string;
export const npm_package_scripts_generate_types: string;
export const npm_package_scripts_generate_version: string;
export const npm_package_scripts_lint: string;
export const npm_package_scripts_postinstall: string;
export const npm_package_scripts_test: string;
export const npm_package_scripts_test_cross_platform_build: string;
export const npm_package_scripts_test_cross_platform_dev: string;
export const npm_package_scripts_test_integration: string;
export const npm_package_scripts_test_unit: string;
export const npm_package_type: string;
export const npm_package_types: string;
export const npm_package_version: string;
export const NUMBER_OF_PROCESSORS: string;
export const OLDPWD: string;
export const OneDrive: string;
export const ORIGINAL_PATH: string;
export const ORIGINAL_TEMP: string;
export const ORIGINAL_TMP: string;
export const OS: string;
export const OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE: string;
export const PATH: string;
export const PATHEXT: string;
export const PKG_CONFIG_PATH: string;
export const PKG_CONFIG_SYSTEM_INCLUDE_PATH: string;
export const PKG_CONFIG_SYSTEM_LIBRARY_PATH: string;
export const PLINK_PROTOCOL: string;
export const PROCESSOR_ARCHITECTURE: string;
export const PROCESSOR_IDENTIFIER: string;
export const PROCESSOR_LEVEL: string;
export const PROCESSOR_REVISION: string;
export const ProgramData: string;
export const PROGRAMFILES: string;
export const ProgramW6432: string;
export const PROMPT: string;
export const PSModulePath: string;
export const PUBLIC: string;
export const PUPPETEER_SKIP_DOWNLOAD: string;
export const SELENIUM_JAR_PATH: string;
export const MEMORY_PRESSURE_WRITE: string;
export const COPILOT_AGENT_COMMIT_EMAIL: string;
export const COPILOT_AGENT_FIREWALL_LOG_FILE: string;
export const COPILOT_FEATURE_FLAGS: string;
export const npm_package_exports___node_polyfills_types: string;
export const INIT_CWD: string;
export const COPILOT_API_URL: string;
export const ANDROID_NDK_HOME: string;
export const GITHUB_STEP_SUMMARY: string;
export const COPILOT_AGENT_BASE_COMMIT: string;
export const COPILOT_AGENT_TIMEOUT_MIN: string;
export const npm_package_exports___hooks_import: string;
export const npm_package_dependencies_import_meta_resolve: string;
export const PWD: string;
export const SHELL: string;
export const SHLVL: string;
export const SSH_ASKPASS: string;
export const SYSTEMDRIVE: string;
export const SYSTEMROOT: string;
export const TEMP: string;
export const TERM: string;
export const TMP: string;
export const TMPDIR: string;
export const USERDOMAIN: string;
export const USERDOMAIN_ROAMINGPROFILE: string;
export const USERNAME: string;
export const USERPROFILE: string;
export const VS140COMNTOOLS: string;
export const WINDIR: string;
export const WXDRIVE_START_ARGS: string;
export const YARN_IGNORE_PATH: string;
export const ZES_ENABLE_SYSMAN: string;
}
/**
@@ -312,261 +230,179 @@ declare module '$env/static/public' {
*/
declare module '$env/dynamic/private' {
export const env: {
SUDO_GID: string;
GITHUB_STATE: string;
COPILOT_AGENT_ACTION: string;
npm_package_scripts_test_cross_platform_build: string;
npm_package_devDependencies_rollup: string;
npm_package_devDependencies__types_node: string;
COPILOT_AGENT_START_TIME_SEC: string;
CURL_CA_BUNDLE: string;
DOTNET_NOLOGO: string;
npm_package_devDependencies_vitest: string;
MAIL: string;
NODE_EXTRA_CA_CERTS: string;
USER: string;
npm_package_bin_svelte_kit: string;
npm_package_dependencies_sirv: string;
npm_package_dependencies_sade: string;
npm_package_dependencies_mrmime: string;
npm_package_dependencies_magic_string: string;
npm_config_version_commit_hooks: string;
npm_config_user_agent: string;
SHOULD_CONTINUE: string;
CI: string;
npm_package_scripts_generate_version: string;
npm_package_dependencies__types_cookie: string;
npm_config_bin_links: string;
XDG_SESSION_TYPE: string;
RUNNER_ENVIRONMENT: string;
GITHUB_ENV: string;
COPILOT_AGENT_ONLINE_EVALUATION_DISABLED: string;
PIPX_HOME: string;
npm_node_execpath: string;
npm_package_devDependencies_vite: string;
npm_package_devDependencies__sveltejs_vite_plugin_svelte: string;
npm_config_init_version: string;
JAVA_HOME_8_X64: string;
SHLVL: string;
npm_package_exports___node_types: string;
npm_package_files_0: string;
COPILOT_AGENT_RUNTIME_VERSION: string;
ACLOCAL_PATH: string;
ALLUSERSPROFILE: string;
ANTHROPIC_AUTH_TOKEN: string;
ANTHROPIC_BASE_URL: string;
APPDATA: string;
CLAUDECODE: string;
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: string;
CLAUDE_CODE_ENTRYPOINT: string;
COMMONPROGRAMFILES: string;
CommonProgramW6432: string;
COMPUTERNAME: string;
COMSPEC: string;
CONFIG_SITE: string;
COREPACK_ENABLE_AUTO_PIN: string;
DISPLAY: string;
DriverData: string;
EXEPATH: string;
GIT_EDITOR: string;
HOME: string;
OLDPWD: string;
npm_package_files_1: string;
npm_package_repository_directory: string;
RUNNER_TEMP: string;
GITHUB_EVENT_PATH: string;
CAROOT: string;
COPILOT_AGENT_FIREWALL_RULESET_ALLOW_LIST: string;
npm_package_files_2: string;
JAVA_HOME_11_X64: string;
COPILOT_AGENT_MCP_SERVER_TEMP: string;
PIPX_BIN_DIR: string;
GITHUB_REPOSITORY_OWNER: string;
npm_package_engines_node: string;
npm_package_exports___vite_import: string;
npm_package_files_3: string;
npm_package_devDependencies_svelte_preprocess: string;
npm_config_init_license: string;
GRADLE_HOME: string;
ANDROID_NDK_LATEST_HOME: string;
JAVA_HOME_21_X64: string;
GITHUB_RETENTION_DAYS: string;
npm_package_files_4: string;
npm_config_version_tag_prefix: string;
GITHUB_REPOSITORY_OWNER_ID: string;
POWERSHELL_DISTRIBUTION_CHANNEL: string;
SSL_CERT_FILE: string;
AZURE_EXTENSION_DIR: string;
GITHUB_HEAD_REF: string;
npm_package_scripts_check: string;
npm_package_files_5: string;
npm_package_dependencies_tiny_glob: string;
SYSTEMD_EXEC_PID: string;
DBUS_SESSION_BUS_ADDRESS: string;
npm_package_scripts_postinstall: string;
npm_package_files_6: string;
GITHUB_GRAPHQL_URL: string;
GITHUB_DOWNLOADS_URL: string;
npm_package_devDependencies_typescript: string;
npm_package_devDependencies__types_connect: string;
npm_package_description: string;
JAVA_HOME_25_X64: string;
NVM_DIR: string;
npm_package_readmeFilename: string;
npm_package_types: string;
npm_package_homepage: string;
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: string;
COPILOT_JOB_EVENT_TYPE: string;
JAVA_HOME_17_X64: string;
ImageVersion: string;
SUDO_UID: string;
npm_package_exports___hooks_types: string;
npm_package_devDependencies__playwright_test: string;
BLACKBIRD_MODE: string;
LOGNAME: string;
COPILOT_AGENT_PR_COMMIT_COUNT: string;
RUNNER_OS: string;
GITHUB_API_URL: string;
GOROOT_1_22_X64: string;
COPILOT_AGENT_COMMIT_LOGIN: string;
SWIFT_PATH: string;
npm_package_type: string;
COPILOT_USE_SESSIONS: string;
CHROMEWEBDRIVER: string;
COPILOT_AGENT_CONTENT_FILTER_MODE: string;
GOROOT_1_23_X64: string;
JOURNAL_STREAM: string;
GITHUB_WORKFLOW: string;
_: string;
COPILOT_AGENT_BRANCH_NAME: string;
MEMORY_PRESSURE_WATCH: string;
XDG_SESSION_CLASS: string;
GOROOT_1_24_X64: string;
npm_package_scripts_lint: string;
npm_config_registry: string;
ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE: string;
COPILOT_AGENT_FIREWALL_ENABLE_RULESET_ALLOW_LIST: string;
GOROOT_1_25_X64: string;
GITHUB_RUN_ID: string;
TERM: string;
XDG_SESSION_ID: string;
GITHUB_REF_TYPE: string;
BOOTSTRAP_HASKELL_NONINTERACTIVE: string;
GITHUB_WORKFLOW_SHA: string;
GITHUB_BASE_REF: string;
ImageOS: string;
COPILOT_MCP_ENABLED: string;
npm_package_exports___import: string;
npm_package_devDependencies_dts_buddy: string;
npm_package_dependencies_kleur: string;
npm_package_dependencies_devalue: string;
npm_config_ignore_scripts: string;
COPILOT_AGENT_CALLBACK_URL: string;
GITHUB_WORKFLOW_REF: string;
GITHUB_ACTION_REPOSITORY: string;
ENABLE_RUNNER_TRACING: string;
npm_package_exports___package_json: string;
npm_package_peerDependencies_svelte: string;
PATH: string;
NODE: string;
COPILOT_AGENT_INJECTED_SECRET_NAMES: string;
ANT_HOME: string;
DOTNET_MULTILEVEL_LOOKUP: string;
RUNNER_TRACKING_ID: string;
INVOCATION_ID: string;
RUNNER_TOOL_CACHE: string;
GITHUB_UPLOADS_URL: string;
REQUESTS_CA_BUNDLE: string;
npm_package_repository_type: string;
npm_package_name: string;
GITHUB_ACTION: string;
GITHUB_RUN_NUMBER: string;
GITHUB_TRIGGERING_ACTOR: string;
COPILOT_EXPERIMENTS: string;
RUNNER_ARCH: string;
XDG_RUNTIME_DIR: string;
AGENT_TOOLSDIRECTORY: string;
npm_package_scripts_test_integration: string;
npm_package_exports___node_polyfills_import: string;
npm_package_devDependencies__types_set_cookie_parser: string;
SSL_CERT_DIR: string;
npm_package_scripts_test_unit: string;
npm_package_exports___vite_types: string;
npm_config_ignore_path: string;
LANG: string;
VCPKG_INSTALLATION_ROOT: string;
CONDA: string;
RUNNER_NAME: string;
XDG_CONFIG_HOME: string;
GITHUB_REF_NAME: string;
GITHUB_REPOSITORY: string;
npm_lifecycle_script: string;
npm_package_scripts_test_cross_platform_dev: string;
SUDO_COMMAND: string;
ANDROID_NDK_ROOT: string;
GITHUB_ACTION_REF: string;
DEBIAN_FRONTEND: string;
npm_package_scripts_test: string;
npm_package_dependencies_esm_env: string;
npm_config_version_git_message: string;
SHELL: string;
GITHUB_REPOSITORY_ID: string;
GITHUB_ACTIONS: string;
CPD_SAVE_TRAJECTORY_OUTPUT: string;
npm_lifecycle_event: string;
npm_package_repository_url: string;
npm_package_version: string;
GITHUB_REF_PROTECTED: string;
npm_config_argv: string;
npm_package_scripts_generate_types: string;
npm_package_scripts_check_all: string;
npm_package_devDependencies_svelte: string;
npm_package_dependencies_cookie: string;
GITHUB_WORKSPACE: string;
SUDO_USER: string;
ACCEPT_EULA: string;
DOTNET_SYSTEM_NET_DISABLEIPV6: string;
GITHUB_JOB: string;
YARN_IGNORE_PATH: string;
npm_package_exports___node_import: string;
GITHUB_SHA: string;
GITHUB_RUN_ATTEMPT: string;
COPILOT_AGENT_DEBUG: string;
npm_package_devDependencies__types_sade: string;
npm_config_version_git_tag: string;
npm_config_version_git_sign: string;
GITHUB_REF: string;
COPILOT_AGENT_ISSUE_NUMBER: string;
COPILOT_AGENT_SOURCE_ENVIRONMENT: string;
GITHUB_ACTOR: string;
FIREWALL_RULESET_CONTENT: string;
ANDROID_SDK_ROOT: string;
npm_package_license: string;
npm_config_strict_ssl: string;
npm_package_scripts_format: string;
GITHUB_PATH: string;
HOMEDRIVE: string;
HOMEPATH: string;
HOSTNAME: string;
INFOPATH: string;
INIT_CWD: string;
JAVA_HOME: string;
PWD: string;
GITHUB_ACTOR_ID: string;
RUNNER_WORKSPACE: string;
LANG: string;
LOCALAPPDATA: string;
LOGONSERVER: string;
MANPATH: string;
MINGW_CHOST: string;
MINGW_PACKAGE_PREFIX: string;
MINGW_PREFIX: string;
MSYSTEM: string;
MSYSTEM_CARCH: string;
MSYSTEM_CHOST: string;
MSYSTEM_PREFIX: string;
NODE: string;
NoDefaultCurrentDirectoryInExePath: string;
npm_config_argv: string;
npm_config_bin_links: string;
npm_config_ignore_optional: string;
npm_config_ignore_path: string;
npm_config_ignore_scripts: string;
npm_config_init_license: string;
npm_config_init_version: string;
npm_config_registry: string;
npm_config_save_prefix: string;
npm_config_strict_ssl: string;
npm_config_user_agent: string;
npm_config_version_commit_hooks: string;
npm_config_version_git_message: string;
npm_config_version_git_sign: string;
npm_config_version_git_tag: string;
npm_config_version_tag_prefix: string;
npm_execpath: string;
npm_lifecycle_event: string;
npm_lifecycle_script: string;
npm_node_execpath: string;
npm_package_bin_svelte_kit: string;
npm_package_dependencies_cookie: string;
npm_package_dependencies_devalue: string;
npm_package_dependencies_esm_env: string;
npm_package_dependencies_import_meta_resolve: string;
npm_package_dependencies_kleur: string;
npm_package_dependencies_magic_string: string;
npm_package_dependencies_mrmime: string;
npm_package_dependencies_sade: string;
npm_package_dependencies_set_cookie_parser: string;
COPILOT_AGENT_PR_NUMBER: string;
HOMEBREW_CLEANUP_PERIODIC_FULL_DAYS: string;
GITHUB_EVENT_NAME: string;
HOMEBREW_NO_AUTO_UPDATE: string;
ANDROID_HOME: string;
GITHUB_SERVER_URL: string;
GECKOWEBDRIVER: string;
GHCUP_INSTALL_BASE_PREFIX: string;
GITHUB_OUTPUT: string;
npm_package_dependencies_sirv: string;
npm_package_dependencies_tiny_glob: string;
npm_package_dependencies__types_cookie: string;
npm_package_description: string;
npm_package_devDependencies_dts_buddy: string;
npm_package_devDependencies_rollup: string;
npm_package_devDependencies_svelte: string;
npm_package_devDependencies_svelte_preprocess: string;
npm_package_devDependencies_typescript: string;
npm_package_devDependencies_vite: string;
npm_package_devDependencies_vitest: string;
npm_package_devDependencies__playwright_test: string;
npm_package_devDependencies__sveltejs_vite_plugin_svelte: string;
npm_package_devDependencies__types_connect: string;
npm_package_devDependencies__types_node: string;
npm_package_devDependencies__types_sade: string;
npm_package_devDependencies__types_set_cookie_parser: string;
npm_package_engines_node: string;
npm_package_exports___hooks_import: string;
npm_package_exports___hooks_types: string;
npm_package_exports___import: string;
npm_package_exports___node_import: string;
npm_package_exports___node_polyfills_import: string;
npm_package_exports___node_polyfills_types: string;
npm_package_exports___node_types: string;
npm_package_exports___package_json: string;
npm_package_exports___types: string;
EDGEWEBDRIVER: string;
COPILOT_EXPERIMENT_ASSIGNMENT_CONTEXT: string;
npm_package_exports___vite_import: string;
npm_package_exports___vite_types: string;
npm_package_files_0: string;
npm_package_files_1: string;
npm_package_files_2: string;
npm_package_files_3: string;
npm_package_files_4: string;
npm_package_files_5: string;
npm_package_files_6: string;
npm_package_homepage: string;
npm_package_license: string;
npm_package_name: string;
npm_package_peerDependencies_svelte: string;
npm_package_peerDependencies_vite: string;
npm_package_peerDependencies__sveltejs_vite_plugin_svelte: string;
npm_config_save_prefix: string;
npm_config_ignore_optional: string;
ANDROID_NDK: string;
SGX_AESM_ADDR: string;
CHROME_BIN: string;
npm_package_readmeFilename: string;
npm_package_repository_directory: string;
npm_package_repository_type: string;
npm_package_repository_url: string;
npm_package_scripts_check: string;
npm_package_scripts_check_all: string;
npm_package_scripts_format: string;
npm_package_scripts_generate_types: string;
npm_package_scripts_generate_version: string;
npm_package_scripts_lint: string;
npm_package_scripts_postinstall: string;
npm_package_scripts_test: string;
npm_package_scripts_test_cross_platform_build: string;
npm_package_scripts_test_cross_platform_dev: string;
npm_package_scripts_test_integration: string;
npm_package_scripts_test_unit: string;
npm_package_type: string;
npm_package_types: string;
npm_package_version: string;
NUMBER_OF_PROCESSORS: string;
OLDPWD: string;
OneDrive: string;
ORIGINAL_PATH: string;
ORIGINAL_TEMP: string;
ORIGINAL_TMP: string;
OS: string;
OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE: string;
PATH: string;
PATHEXT: string;
PKG_CONFIG_PATH: string;
PKG_CONFIG_SYSTEM_INCLUDE_PATH: string;
PKG_CONFIG_SYSTEM_LIBRARY_PATH: string;
PLINK_PROTOCOL: string;
PROCESSOR_ARCHITECTURE: string;
PROCESSOR_IDENTIFIER: string;
PROCESSOR_LEVEL: string;
PROCESSOR_REVISION: string;
ProgramData: string;
PROGRAMFILES: string;
ProgramW6432: string;
PROMPT: string;
PSModulePath: string;
PUBLIC: string;
PUPPETEER_SKIP_DOWNLOAD: string;
SELENIUM_JAR_PATH: string;
MEMORY_PRESSURE_WRITE: string;
COPILOT_AGENT_COMMIT_EMAIL: string;
COPILOT_AGENT_FIREWALL_LOG_FILE: string;
COPILOT_FEATURE_FLAGS: string;
npm_package_exports___node_polyfills_types: string;
INIT_CWD: string;
COPILOT_API_URL: string;
ANDROID_NDK_HOME: string;
GITHUB_STEP_SUMMARY: string;
COPILOT_AGENT_BASE_COMMIT: string;
COPILOT_AGENT_TIMEOUT_MIN: string;
npm_package_exports___hooks_import: string;
npm_package_dependencies_import_meta_resolve: string;
PWD: string;
SHELL: string;
SHLVL: string;
SSH_ASKPASS: string;
SYSTEMDRIVE: string;
SYSTEMROOT: string;
TEMP: string;
TERM: string;
TMP: string;
TMPDIR: string;
USERDOMAIN: string;
USERDOMAIN_ROAMINGPROFILE: string;
USERNAME: string;
USERPROFILE: string;
VS140COMNTOOLS: string;
WINDIR: string;
WXDRIVE_START_ARGS: string;
YARN_IGNORE_PATH: string;
ZES_ENABLE_SYSMAN: string;
[key: `PUBLIC_${string}`]: undefined;
[key: `${string}`]: string | undefined;
}

View File

@@ -12,25 +12,46 @@ rrweb-player uses rrweb's Replayer under the hood, but as Replayer doesn't inclu
## Installation
rrweb-player can also be included with `<script>`
### 1) Bundler / npm (Recommended)
```shell
npm install rrweb-player
```
```js
import rrwebPlayer from 'rrweb-player';
import 'rrweb-player/dist/style.css';
```
### 2) Browser Without Bundler (ESM + import maps)
```html
<link
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 type="importmap">
{
"imports": {
"rrweb-player": "https://cdn.jsdelivr.net/npm/rrweb-player@latest/+esm"
}
}
</script>
<script type="module">
import rrwebPlayer from 'rrweb-player';
</script>
```
Or installed by using NPM
### 3) Legacy Direct `<script>` Include (UMD fallback)
```shell
npm install --save rrweb-player
```
Use this only for compatibility with non-module environments.
```js
import rrwebPlayer from 'rrweb-player';
import 'rrweb-player/dist/style.css';
```html
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/rrweb-player@latest/dist/style.css"
/>
<script src="https://cdn.jsdelivr.net/npm/rrweb-player@latest/umd/rrweb-player.min.js"></script>
```
## Usage

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",
@@ -51,6 +51,7 @@
"./dist/style.css": "./dist/style.css"
},
"files": [
"umd",
"dist",
"package.json"
],

View File

@@ -28,6 +28,15 @@
export let inactiveColor: NonNullable<RRwebPlayerOptions['props']['inactiveColor']> = '#D4D4D4';
let replayer: Replayer;
const pendingEventListeners: Array<{
event: string;
handler: (detail: unknown) => unknown;
}> = [];
const controllerEvents = new Set([
'ui-update-current-time',
'ui-update-progress',
'ui-update-player-state',
]);
export const getMirror = () => replayer.getMirror();
@@ -83,14 +92,13 @@
event: string,
handler: (detail: unknown) => unknown,
) => {
if (!replayer) {
pendingEventListeners.push({ event, handler });
return;
}
replayer.on(event, handler);
switch (event) {
case 'ui-update-current-time':
case 'ui-update-progress':
case 'ui-update-player-state':
controller.$on(event, ({ detail }) => handler(detail));
default:
break;
if (controllerEvents.has(event) && controller) {
controller.$on(event, ({ detail }) => handler(detail));
}
};
@@ -130,6 +138,14 @@
};
onMount(() => {
const debugTarget = window as Window & {
__rrwebPlayerDebug?: Record<string, unknown>;
};
debugTarget.__rrwebPlayerDebug = {
stage: 'mounted',
hasFrame: !!frame,
eventsLength: events?.length,
};
// runtime type check
if (speedOption !== undefined && typeOf(speedOption) !== 'array') {
throw new Error('speedOption must be array');
@@ -157,8 +173,23 @@
unpackFn: unpack,
...$$props,
});
debugTarget.__rrwebPlayerDebug = {
...debugTarget.__rrwebPlayerDebug,
stage: 'replayer-created',
hasWrapper: !!replayer.wrapper,
hasIframe: !!replayer.iframe,
};
pendingEventListeners.splice(0).forEach(({ event, handler }) => {
replayer.on(event, handler);
});
replayer.on('resize', (dimension) => {
debugTarget.__rrwebPlayerDebug = {
...debugTarget.__rrwebPlayerDebug,
stage: 'resized',
dimension,
};
updateScale(
replayer.wrapper,
dimension as { width: number; height: number },

View File

@@ -34,6 +34,9 @@ function viteSvelteDts(): Plugin {
console.log('Generating .d.ts files for Svelte components...');
const { input } = options;
if (!input) {
return;
}
if (typeof input === 'string') {
await generateDts(input);
} else if (Array.isArray(input)) {

View File

@@ -44,6 +44,7 @@
}
},
"files": [
"umd",
"dist",
"package.json"
],
@@ -63,7 +64,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

@@ -19,12 +19,85 @@ rrweb refers to 'record and replay the web', which is a tool for recording and r
[**🍳 Recipes 🍳**](../../docs/recipes/index.md)
## Installation
`rrweb` is kept mainly for backward compatibility. For new integrations, prefer package-specific entrypoints (`@rrweb/record` and `@rrweb/replay`) first, or use `@rrweb/all` as a convenience package.
### 1) Bundler / npm (Recommended)
For new projects:
```shell
npm install @rrweb/record @rrweb/replay
```
```js
import { record } from '@rrweb/record';
import { Replayer } from '@rrweb/replay';
import '@rrweb/replay/dist/style.css';
```
Convenience single-package option:
```shell
npm install @rrweb/all
```
```js
import { record, Replayer, pack, unpack } from '@rrweb/all';
import '@rrweb/all/dist/style.css';
```
Legacy compatibility package:
```shell
npm install rrweb
```
```js
import { record, Replayer } from 'rrweb';
import 'rrweb/dist/style.css';
```
### 2) Browser Without Bundler (ESM + import maps)
```html
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@rrweb/replay@latest/dist/style.css"
/>
<script type="importmap">
{
"imports": {
"@rrweb/record": "https://cdn.jsdelivr.net/npm/@rrweb/record@latest/+esm",
"@rrweb/replay": "https://cdn.jsdelivr.net/npm/@rrweb/replay@latest/+esm"
}
}
</script>
<script type="module">
import { record } from '@rrweb/record';
import { Replayer } from '@rrweb/replay';
</script>
```
### 3) Legacy Direct `<script>` Include (UMD fallback)
Use this only for compatibility with non-module environments; modern browsers support the importmap method [since 2023](https://caniuse.com/?search=import+map)
```html
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/style.css"
/>
<script src="https://cdn.jsdelivr.net/npm/rrweb@latest/umd/rrweb.min.js"></script>
```
## Project Structure
**[rrweb](https://github.com/rrweb-io/rrweb)** mainly includes two funtions:
- **Record**: The record function is used to record all the mutations in the DOM
- **Replay**: The replay function is to replay the recorded mutations one by one according to the corresponding timestamp.
- **Replayer**: The replay function is to replay the recorded mutations one by one according to the corresponding timestamp.
## Roadmap

View File

@@ -49,6 +49,7 @@
"./dist/style.css": "./dist/style.css"
},
"files": [
"umd",
"dist",
"package.json"
],
@@ -76,7 +77,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

@@ -1,261 +0,0 @@
import typescript from 'rollup-plugin-typescript2';
import esbuild from 'rollup-plugin-esbuild';
import resolve from '@rollup/plugin-node-resolve';
import postcss from 'rollup-plugin-postcss';
import renameNodeModules from 'rollup-plugin-rename-node-modules';
import webWorkerLoader from 'rollup-plugin-web-worker-loader';
import pkg from './package.json';
function toRecordPath(path) {
return path
.replace(/^([\w]+)\//, '$1/record/')
.replace('rrweb', 'rrweb-record');
}
function toRecordPackPath(path) {
return path
.replace(/^([\w]+)\//, '$1/record/')
.replace('rrweb', 'rrweb-record-pack');
}
function toReplayPath(path) {
return path
.replace(/^([\w]+)\//, '$1/replay/')
.replace('rrweb', 'rrweb-replay');
}
function toReplayUnpackPath(path) {
return path
.replace(/^([\w]+)\//, '$1/replay/')
.replace('rrweb', 'rrweb-replay-unpack');
}
function toAllPath(path) {
return path.replace('rrweb', 'rrweb-all');
}
function toPluginPath(pluginName, stage) {
return (path) =>
path
.replace(/^([\w]+)\//, '$1/plugins/')
.replace('rrweb', `${pluginName}-${stage}`);
}
function toMinPath(path) {
return path.replace(/\.js$/, '.min.js');
}
const baseConfigs = [
// all in one
{
input: './src/entries/all.ts',
name: 'rrweb',
pathFn: toAllPath,
esm: true,
},
// record only
{
input: './src/record/index.ts',
name: 'rrwebRecord',
pathFn: toRecordPath,
},
// record and pack
{
input: './src/entries/record-pack.ts',
name: 'rrwebRecord',
pathFn: toRecordPackPath,
},
// replay only
{
input: './src/replay/index.ts',
name: 'rrwebReplay',
pathFn: toReplayPath,
},
// replay and unpack
{
input: './src/entries/replay-unpack.ts',
name: 'rrwebReplay',
pathFn: toReplayUnpackPath,
},
// record and replay
{
input: './src/index.ts',
name: 'rrweb',
pathFn: (p) => p,
},
// plugins
{
input: './src/plugins/console/record/index.ts',
name: 'rrwebConsoleRecord',
pathFn: toPluginPath('console', 'record'),
},
{
input: './src/plugins/canvas-webrtc/record/index.ts',
name: 'rrwebCanvasWebRTCRecord',
pathFn: toPluginPath('canvas-webrtc', 'record'),
},
{
input: './src/plugins/canvas-webrtc/replay/index.ts',
name: 'rrwebCanvasWebRTCReplay',
pathFn: toPluginPath('canvas-webrtc', 'replay'),
},
{
input: './src/plugins/console/replay/index.ts',
name: 'rrwebConsoleReplay',
pathFn: toPluginPath('console', 'replay'),
},
{
input: './src/plugins/sequential-id/record/index.ts',
name: 'rrwebSequentialIdRecord',
pathFn: toPluginPath('sequential-id', 'record'),
},
{
input: './src/plugins/sequential-id/replay/index.ts',
name: 'rrwebSequentialIdReplay',
pathFn: toPluginPath('sequential-id', 'replay'),
},
];
let configs = [];
function getPlugins(options = {}) {
const { minify = false, sourceMap = false } = options;
return [
resolve({ browser: true }),
webWorkerLoader({
targetPlatform: 'browser',
inline: true,
preserveSource: true,
sourceMap,
}),
esbuild({
minify,
}),
postcss({
extract: true,
inject: false,
minimize: minify,
sourceMap,
}),
];
}
for (const c of baseConfigs) {
const basePlugins = [
resolve({ browser: true }),
// supports bundling `web-worker:..filename`
webWorkerLoader({
targetPlatform: 'browser',
inline: true,
preserveSource: true,
}),
typescript(),
];
const plugins = basePlugins.concat(
postcss({
extract: false,
inject: false,
}),
);
// browser
configs.push({
input: c.input,
plugins: getPlugins(),
output: [
{
name: c.name,
format: 'iife',
file: c.pathFn(pkg.unpkg),
},
],
});
// browser + minify
configs.push({
input: c.input,
plugins: getPlugins({ minify: true, sourceMap: true }),
output: [
{
name: c.name,
format: 'iife',
file: toMinPath(c.pathFn(pkg.unpkg)),
sourcemap: true,
},
],
});
// CommonJS
configs.push({
input: c.input,
plugins,
output: [
{
format: 'cjs',
file: c.pathFn('lib/rrweb.cjs'),
},
],
});
if (c.esm) {
// ES module
configs.push({
input: c.input,
plugins,
preserveModules: true,
output: [
{
format: 'esm',
dir: 'es/rrweb',
plugins: [renameNodeModules('ext')],
},
],
});
}
}
if (process.env.BROWSER_ONLY) {
const browserOnlyBaseConfigs = [
{
input: './src/index.ts',
name: 'rrweb',
pathFn: (p) => p,
},
{
input: './src/entries/all.ts',
name: 'rrweb',
pathFn: toAllPath,
},
{
input: './src/plugins/console/record/index.ts',
name: 'rrwebConsoleRecord',
pathFn: toPluginPath('console', 'record'),
},
{
input: './src/plugins/canvas-webrtc/record/index.ts',
name: 'rrwebCanvasWebRTCRecord',
pathFn: toPluginPath('canvas-webrtc', 'record'),
},
{
input: './src/plugins/canvas-webrtc/replay/index.ts',
name: 'rrwebCanvasWebRTCReplay',
pathFn: toPluginPath('canvas-webrtc', 'replay'),
},
];
configs = [];
for (const c of browserOnlyBaseConfigs) {
configs.push({
input: c.input,
plugins: getPlugins(),
output: [
{
name: c.name,
format: 'iife',
file: c.pathFn(pkg.unpkg),
},
],
});
}
}
export default configs;

View File

@@ -20,9 +20,14 @@ const emitter = new EventEmitter();
async function injectRecording(frame, serverURL) {
try {
await frame.addScriptTag({ url: `${serverURL}/rrweb.umd.cjs` });
await frame.addScriptTag({
url: `${serverURL}/plugins/rrweb-plugin-canvas-webrtc-record.js`,
path: path.resolve(__dirname, '../dist/rrweb.umd.cjs'),
});
await frame.addScriptTag({
path: path.resolve(
__dirname,
'../../plugins/rrweb-plugin-canvas-webrtc-record/dist/rrweb-plugin-canvas-webrtc-record.umd.cjs',
),
});
await frame.evaluate(() => {
const win = window;
@@ -80,9 +85,14 @@ async function startReplay(page, serverURL, recordedPage) {
}, id);
});
await page.addScriptTag({ url: `${serverURL}/rrweb.umd.cjs` });
await page.addScriptTag({
url: `${serverURL}/plugins/rrweb-plugin-canvas-webrtc-replay.js`,
path: path.resolve(__dirname, '../dist/rrweb.umd.cjs'),
});
await page.addScriptTag({
path: path.resolve(
__dirname,
'../../plugins/rrweb-plugin-canvas-webrtc-replay/dist/rrweb-plugin-canvas-webrtc-replay.umd.cjs',
),
});
return page.evaluate(() => {
@@ -130,13 +140,18 @@ void (async () => {
server = await startServer();
serverURL = getServerURL(server);
const { url } = await inquirer.prompt([
{
type: 'input',
name: 'url',
message: `Enter the url you want to record, e.g ${defaultURL}: `,
},
]);
const autoUrl = process.env.RRWEB_STREAM_URL;
let url = autoUrl;
if (!url) {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'url',
message: `Enter the url you want to record, e.g ${defaultURL}: `,
},
]);
url = answers.url;
}
await record(url);
@@ -163,6 +178,7 @@ void (async () => {
const replayingBrowser = await puppeteer.launch({
headless: false,
devtools,
executablePath: process.env.PUPPETEER_EXECUTABLE_PATH,
defaultViewport: {
width: 1600,
height: 900,
@@ -184,6 +200,7 @@ void (async () => {
const recordingBrowser = await puppeteer.launch({
headless: false,
devtools,
executablePath: process.env.PUPPETEER_EXECUTABLE_PATH,
defaultViewport: {
width: 1600,
height: 900,

View File

@@ -37,7 +37,10 @@ export const startServer = (defaultPort = 3030) =>
try {
const data = fs.readFileSync(pathname);
const ext = path.parse(pathname).ext;
res.setHeader('Content-type', mimeType[ext] || 'text/plain');
const contentType = /\.js$/.test(sanitizePath)
? 'text/javascript'
: mimeType[ext] || 'text/plain';
res.setHeader('Content-type', contentType);
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET');
res.setHeader('Access-Control-Allow-Headers', 'Content-type');

View File

@@ -2026,14 +2026,14 @@ export class Replayer {
}
});
if (data.replace)
if (typeof data.replace === 'string')
try {
void styleSheet.replace?.(data.replace);
} catch (e) {
// for safety
}
if (data.replaceSync)
if (typeof data.replaceSync === 'string')
try {
styleSheet.replaceSync?.(data.replaceSync);
} catch (e) {

View File

@@ -0,0 +1,211 @@
import { EventType, IncrementalSource } from '@rrweb/types';
import type { eventWithTime } from '@rrweb/types';
/**
* Test events for validating that empty string replace/replaceSync clears stylesheets.
* This tests the fix for the bug where `if (data.replace)` would skip empty strings.
*/
const now = Date.now();
export const emptyReplaceSyncEvents: eventWithTime[] = [
{
type: EventType.Meta,
data: {
href: 'about:blank',
width: 1920,
height: 1080,
},
timestamp: now,
},
{
type: EventType.FullSnapshot,
data: {
node: {
type: 0,
childNodes: [
{
type: 1,
name: 'html',
publicId: '',
systemId: '',
id: 2,
},
{
type: 2,
tagName: 'html',
attributes: {},
childNodes: [
{
type: 2,
tagName: 'head',
attributes: {},
childNodes: [],
id: 4,
},
{
type: 2,
tagName: 'body',
attributes: {},
childNodes: [
{
type: 2,
tagName: 'div',
attributes: {},
childNodes: [
{
type: 3,
textContent: 'test element',
id: 6,
},
],
id: 5,
},
],
id: 3,
},
],
id: 7,
},
],
id: 1,
},
initialOffset: {
left: 0,
top: 0,
},
},
timestamp: now + 100,
},
// Adopt a stylesheet with initial styles
{
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.AdoptedStyleSheet,
id: 1,
styles: [
{
styleId: 1,
rules: [
{
rule: 'div { background: red; color: white; }',
index: 0,
},
],
},
],
styleIds: [1],
},
timestamp: now + 200,
},
// Clear stylesheet using replaceSync('') - this was the bug!
{
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.StyleSheetRule,
styleId: 1,
replaceSync: '',
},
timestamp: now + 300,
},
];
export const emptyReplaceEvents: eventWithTime[] = [
{
type: EventType.Meta,
data: {
href: 'about:blank',
width: 1920,
height: 1080,
},
timestamp: now,
},
{
type: EventType.FullSnapshot,
data: {
node: {
type: 0,
childNodes: [
{
type: 1,
name: 'html',
publicId: '',
systemId: '',
id: 2,
},
{
type: 2,
tagName: 'html',
attributes: {},
childNodes: [
{
type: 2,
tagName: 'head',
attributes: {},
childNodes: [],
id: 4,
},
{
type: 2,
tagName: 'body',
attributes: {},
childNodes: [
{
type: 2,
tagName: 'div',
attributes: {},
childNodes: [
{
type: 3,
textContent: 'test element',
id: 6,
},
],
id: 5,
},
],
id: 3,
},
],
id: 7,
},
],
id: 1,
},
initialOffset: {
left: 0,
top: 0,
},
},
timestamp: now + 100,
},
// Adopt a stylesheet with initial styles
{
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.AdoptedStyleSheet,
id: 1,
styles: [
{
styleId: 1,
rules: [
{
rule: 'div { background: blue; color: yellow; }',
index: 0,
},
],
},
],
styleIds: [1],
},
timestamp: now + 200,
},
// Clear stylesheet using replace('') - this was the bug!
{
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.StyleSheetRule,
styleId: 1,
replace: '',
},
timestamp: now + 300,
},
];

View File

@@ -25,6 +25,10 @@ import StyleSheetTextMutation from './events/style-sheet-text-mutation';
import canvasInIframe from './events/canvas-in-iframe';
import adoptedStyleSheet from './events/adopted-style-sheet';
import adoptedStyleSheetModification from './events/adopted-style-sheet-modification';
import {
emptyReplaceSyncEvents,
emptyReplaceEvents,
} from './events/adopted-style-sheet-empty-replace';
import nestedStyleDeclarationEvents from './events/nested-style-declaration';
import styleDeclarationMissingRuleEvents from './events/style-declaration-missing-rule';
import documentReplacementEvents from './events/document-replacement';
@@ -1066,14 +1070,76 @@ describe('replayer', function () {
await check600ms();
});
it('can replay StyleDeclaration events on nested CSS rules inside @media', async () => {
it('can clear adopted stylesheets with empty replaceSync', async () => {
await page.evaluate(`
events = ${JSON.stringify(nestedStyleDeclarationEvents)};
events = ${JSON.stringify(emptyReplaceSyncEvents)};
const { Replayer } = rrweb;
var replayer = new Replayer(events, { showDebug: true });
replayer.pause(0);
`);
const iframe = await page.$('iframe');
const contentDocument = await iframe!.contentFrame()!;
// At 250ms, stylesheet should have rules
await page.evaluate('replayer.pause(250);');
expect(
await contentDocument!.evaluate(
() =>
document.adoptedStyleSheets.length === 1 &&
document.adoptedStyleSheets[0].cssRules.length === 1,
),
).toBeTruthy();
// At 350ms, stylesheet should be empty after replaceSync('')
await page.evaluate('replayer.pause(350);');
expect(
await contentDocument!.evaluate(
() =>
document.adoptedStyleSheets.length === 1 &&
document.adoptedStyleSheets[0].cssRules.length === 0,
),
).toBeTruthy();
});
it('can clear adopted stylesheets with empty replace', async () => {
await page.evaluate(`
events = ${JSON.stringify(emptyReplaceEvents)};
const { Replayer } = rrweb;
var replayer = new Replayer(events, { showDebug: true });
replayer.pause(0);
`);
const iframe = await page.$('iframe');
const contentDocument = await iframe!.contentFrame()!;
// At 250ms, stylesheet should have rules
await page.evaluate('replayer.pause(250);');
expect(
await contentDocument!.evaluate(
() =>
document.adoptedStyleSheets.length === 1 &&
document.adoptedStyleSheets[0].cssRules.length === 1,
),
).toBeTruthy();
// At 350ms, stylesheet should be empty after replace('')
await page.evaluate('replayer.pause(350);');
await contentDocument!.waitForFunction(
() =>
document.adoptedStyleSheets.length === 1 &&
document.adoptedStyleSheets[0].cssRules.length === 0,
);
});
it('can replay StyleDeclaration events on nested CSS rules inside @media', async () => {
await page.evaluate(`
events = ${JSON.stringify(nestedStyleDeclarationEvents)};
const { Replayer } = rrweb;
var replayer = new Replayer(events, { showDebug: true });
replayer.pause(0);
`);
// At 250ms, setProperty on [0, 0] should change background-color to red
const bgColorAfterSet = await page.evaluate(`
replayer.pause(250);

View File

@@ -42,11 +42,12 @@
}
},
"files": [
"umd",
"dist",
"package.json"
],
"devDependencies": {
"vite": "^5.3.1",
"vite": "^6.0.1",
"vite-plugin-dts": "^3.9.1"
},
"browserslist": [

View File

@@ -42,11 +42,12 @@
}
},
"files": [
"umd",
"dist",
"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"

View File

@@ -4,7 +4,7 @@
"globalDependencies": [
".eslintrc.js",
".prettierrc",
"vite.config.defaults.ts",
"vite.config.default.ts",
"tsconfig.json"
],
"globalPassThroughEnv": ["PUPPETEER_HEADLESS", "DISABLE_WORKER_INLINING"],

View File

@@ -1,9 +1,9 @@
/// <reference types="vite/client" />
import dts from 'vite-plugin-dts';
import { copyFileSync } from 'node:fs';
import { copyFileSync, mkdirSync, existsSync } from 'node:fs';
import { defineConfig, LibraryOptions, LibraryFormats, Plugin } from 'vite';
import { build, Format } from 'esbuild';
import { resolve } from 'path';
import { resolve, dirname, relative, basename } from 'path';
import { umdWrapper } from 'esbuild-plugin-umd-wrapper';
import * as fs from 'node:fs';
import { visualizer } from 'rollup-plugin-visualizer';
@@ -51,22 +51,38 @@ function minifyAndUMDPlugin({
outDir,
});
} else {
const relativeOutputPath = relative(outputOptions.dir!, outputFilePath);
const umdBasePath = resolve(
dirname(outputOptions.dir!),
'umd',
relativeOutputPath,
);
const umdDir = dirname(umdBasePath);
if (!existsSync(umdDir)) {
mkdirSync(umdDir, { recursive: true });
}
const outUmd = `${outputFilePath}.umd.cjs`;
await buildFile({
name,
input: inputFilePath,
output: `${outputFilePath}.umd.cjs`,
output: outUmd,
minify: false,
isCss: false,
outDir,
});
// Workaround because jsdelivr does use correct mime types for .umd.cjs
// More info: https://github.com/jsdelivr/jsdelivr/issues/18584 https://github.com/rrweb-io/rrweb/pull/1704
copyFileSync(outUmd, `${umdBasePath}.js`);
const outUmdMin = `${outputFilePath}.umd.min.cjs`;
await buildFile({
name,
input: inputFilePath,
output: `${outputFilePath}.umd.min.cjs`,
output: outUmdMin,
minify: true,
isCss: false,
outDir,
});
copyFileSync(outUmdMin, `${umdBasePath}.min.js`);
}
}
}
@@ -103,7 +119,7 @@ async function buildFile({
}),
],
});
const filename = output.replace(new RegExp(`^.+/(${outDir}/)`), '$1');
const filename = relative(process.cwd(), output).split(/\\/g).join('/');
console.log(filename);
console.log(`${filename}.map`);
}
@@ -121,6 +137,7 @@ export default function (
build: {
// See https://vitejs.dev/guide/build.html#library-mode
lib: {
cssFileName: 'style', // maintain same file output name as Vite 5 after upgrade to 6
entry,
name,
fileName,
@@ -139,12 +156,6 @@ export default function (
minify: false,
sourcemap: true,
// rollupOptions: {
// output: {
// manualChunks: {},
// },
// },
},
plugins: [
dts({

415
yarn.lock
View File

@@ -1510,230 +1510,240 @@
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537"
integrity sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==
"@esbuild/aix-ppc64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f"
integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==
"@esbuild/aix-ppc64@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz#38848d3e25afe842a7943643cbcd387cc6e13461"
integrity sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==
"@esbuild/android-arm64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz#db1c9202a5bc92ea04c7b6840f1bbe09ebf9e6b9"
integrity sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==
"@esbuild/android-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052"
integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==
"@esbuild/android-arm64@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz#f592957ae8b5643129fa889c79e69cd8669bb894"
integrity sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==
"@esbuild/android-arm@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.2.tgz#3b488c49aee9d491c2c8f98a909b785870d6e995"
integrity sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==
"@esbuild/android-arm@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28"
integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==
"@esbuild/android-arm@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.24.2.tgz#72d8a2063aa630308af486a7e5cbcd1e134335b3"
integrity sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==
"@esbuild/android-x64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.2.tgz#3b1628029e5576249d2b2d766696e50768449f98"
integrity sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==
"@esbuild/android-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e"
integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==
"@esbuild/android-x64@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.24.2.tgz#9a7713504d5f04792f33be9c197a882b2d88febb"
integrity sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==
"@esbuild/darwin-arm64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz#6e8517a045ddd86ae30c6608c8475ebc0c4000bb"
integrity sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==
"@esbuild/darwin-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a"
integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==
"@esbuild/darwin-arm64@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz#02ae04ad8ebffd6e2ea096181b3366816b2b5936"
integrity sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==
"@esbuild/darwin-x64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz#90ed098e1f9dd8a9381695b207e1cff45540a0d0"
integrity sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==
"@esbuild/darwin-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22"
integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==
"@esbuild/darwin-x64@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz#9ec312bc29c60e1b6cecadc82bd504d8adaa19e9"
integrity sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==
"@esbuild/freebsd-arm64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz#d71502d1ee89a1130327e890364666c760a2a911"
integrity sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==
"@esbuild/freebsd-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e"
integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==
"@esbuild/freebsd-arm64@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz#5e82f44cb4906d6aebf24497d6a068cfc152fa00"
integrity sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==
"@esbuild/freebsd-x64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz#aa5ea58d9c1dd9af688b8b6f63ef0d3d60cea53c"
integrity sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==
"@esbuild/freebsd-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261"
integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==
"@esbuild/freebsd-x64@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz#3fb1ce92f276168b75074b4e51aa0d8141ecce7f"
integrity sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==
"@esbuild/linux-arm64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz#055b63725df678379b0f6db9d0fa85463755b2e5"
integrity sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==
"@esbuild/linux-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b"
integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==
"@esbuild/linux-arm64@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz#856b632d79eb80aec0864381efd29de8fd0b1f43"
integrity sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==
"@esbuild/linux-arm@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz#76b3b98cb1f87936fbc37f073efabad49dcd889c"
integrity sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==
"@esbuild/linux-arm@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9"
integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==
"@esbuild/linux-arm@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz#c846b4694dc5a75d1444f52257ccc5659021b736"
integrity sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==
"@esbuild/linux-ia32@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz#c0e5e787c285264e5dfc7a79f04b8b4eefdad7fa"
integrity sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==
"@esbuild/linux-ia32@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2"
integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==
"@esbuild/linux-ia32@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz#f8a16615a78826ccbb6566fab9a9606cfd4a37d5"
integrity sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==
"@esbuild/linux-loong64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz#a6184e62bd7cdc63e0c0448b83801001653219c5"
integrity sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==
"@esbuild/linux-loong64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df"
integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==
"@esbuild/linux-loong64@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz#1c451538c765bf14913512c76ed8a351e18b09fc"
integrity sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==
"@esbuild/linux-mips64el@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz#d08e39ce86f45ef8fc88549d29c62b8acf5649aa"
integrity sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==
"@esbuild/linux-mips64el@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe"
integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==
"@esbuild/linux-mips64el@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz#0846edeefbc3d8d50645c51869cc64401d9239cb"
integrity sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==
"@esbuild/linux-ppc64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz#8d252f0b7756ffd6d1cbde5ea67ff8fd20437f20"
integrity sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==
"@esbuild/linux-ppc64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4"
integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==
"@esbuild/linux-ppc64@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz#8e3fc54505671d193337a36dfd4c1a23b8a41412"
integrity sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==
"@esbuild/linux-riscv64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz#19f6dcdb14409dae607f66ca1181dd4e9db81300"
integrity sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==
"@esbuild/linux-riscv64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc"
integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==
"@esbuild/linux-riscv64@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz#6a1e92096d5e68f7bb10a0d64bb5b6d1daf9a694"
integrity sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==
"@esbuild/linux-s390x@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz#3c830c90f1a5d7dd1473d5595ea4ebb920988685"
integrity sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==
"@esbuild/linux-s390x@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de"
integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==
"@esbuild/linux-s390x@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz#ab18e56e66f7a3c49cb97d337cd0a6fea28a8577"
integrity sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==
"@esbuild/linux-x64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz#86eca35203afc0d9de0694c64ec0ab0a378f6fff"
integrity sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==
"@esbuild/linux-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0"
integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==
"@esbuild/linux-x64@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz#8140c9b40da634d380b0b29c837a0b4267aff38f"
integrity sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==
"@esbuild/netbsd-arm64@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz#65f19161432bafb3981f5f20a7ff45abb2e708e6"
integrity sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==
"@esbuild/netbsd-x64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz#e771c8eb0e0f6e1877ffd4220036b98aed5915e6"
integrity sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==
"@esbuild/netbsd-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047"
integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==
"@esbuild/netbsd-x64@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz#7a3a97d77abfd11765a72f1c6f9b18f5396bcc40"
integrity sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==
"@esbuild/openbsd-arm64@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz#58b00238dd8f123bfff68d3acc53a6ee369af89f"
integrity sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==
"@esbuild/openbsd-x64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz#9a795ae4b4e37e674f0f4d716f3e226dd7c39baf"
integrity sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==
"@esbuild/openbsd-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70"
integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==
"@esbuild/openbsd-x64@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz#0ac843fda0feb85a93e288842936c21a00a8a205"
integrity sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==
"@esbuild/sunos-x64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz#7df23b61a497b8ac189def6e25a95673caedb03f"
integrity sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==
"@esbuild/sunos-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b"
integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==
"@esbuild/sunos-x64@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz#8b7aa895e07828d36c422a4404cc2ecf27fb15c6"
integrity sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==
"@esbuild/win32-arm64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz#f1ae5abf9ca052ae11c1bc806fb4c0f519bacf90"
integrity sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==
"@esbuild/win32-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d"
integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==
"@esbuild/win32-arm64@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz#c023afb647cabf0c3ed13f0eddfc4f1d61c66a85"
integrity sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==
"@esbuild/win32-ia32@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz#241fe62c34d8e8461cd708277813e1d0ba55ce23"
integrity sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==
"@esbuild/win32-ia32@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b"
integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==
"@esbuild/win32-ia32@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz#96c356132d2dda990098c8b8b951209c3cd743c2"
integrity sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==
"@esbuild/win32-x64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz#9c907b21e30a52db959ba4f80bb01a0cc403d5cc"
integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==
"@esbuild/win32-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c"
integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==
"@esbuild/win32-x64@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz#34aa0b52d0fbb1a654b596acfa595f0c7b77a77b"
integrity sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
version "4.4.0"
@@ -2300,81 +2310,176 @@
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz#bbd0e616b2078cd2d68afc9824d1fadb2f2ffd27"
integrity sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==
"@rollup/rollup-android-arm-eabi@4.32.0":
version "4.32.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.32.0.tgz#42a8e897c7b656adb4edebda3a8b83a57526452f"
integrity sha512-G2fUQQANtBPsNwiVFg4zKiPQyjVKZCUdQUol53R8E71J7AsheRMV/Yv/nB8giOcOVqP7//eB5xPqieBYZe9bGg==
"@rollup/rollup-android-arm64@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz#97255ef6384c5f73f4800c0de91f5f6518e21203"
integrity sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==
"@rollup/rollup-android-arm64@4.32.0":
version "4.32.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.32.0.tgz#846a73eef25b18ff94bac1e52acab6a7c7ac22fa"
integrity sha512-qhFwQ+ljoymC+j5lXRv8DlaJYY/+8vyvYmVx074zrLsu5ZGWYsJNLjPPVJJjhZQpyAKUGPydOq9hRLLNvh1s3A==
"@rollup/rollup-darwin-arm64@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz#b6dd74e117510dfe94541646067b0545b42ff096"
integrity sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==
"@rollup/rollup-darwin-arm64@4.32.0":
version "4.32.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.32.0.tgz#014ed37f1f7809fdf3442a6b689d3a074a844058"
integrity sha512-44n/X3lAlWsEY6vF8CzgCx+LQaoqWGN7TzUfbJDiTIOjJm4+L2Yq+r5a8ytQRGyPqgJDs3Rgyo8eVL7n9iW6AQ==
"@rollup/rollup-darwin-x64@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz#e07d76de1cec987673e7f3d48ccb8e106d42c05c"
integrity sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==
"@rollup/rollup-darwin-x64@4.32.0":
version "4.32.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.32.0.tgz#dde6ed3e56d0b34477fa56c4a199abe5d4b9846b"
integrity sha512-F9ct0+ZX5Np6+ZDztxiGCIvlCaW87HBdHcozUfsHnj1WCUTBUubAoanhHUfnUHZABlElyRikI0mgcw/qdEm2VQ==
"@rollup/rollup-freebsd-arm64@4.32.0":
version "4.32.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.32.0.tgz#8ad634f462a6b7e338257cf64c7baff99618a08e"
integrity sha512-JpsGxLBB2EFXBsTLHfkZDsXSpSmKD3VxXCgBQtlPcuAqB8TlqtLcbeMhxXQkCDv1avgwNjF8uEIbq5p+Cee0PA==
"@rollup/rollup-freebsd-x64@4.32.0":
version "4.32.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.32.0.tgz#9d4d1dbbafcb0354d52ba6515a43c7511dba8052"
integrity sha512-wegiyBT6rawdpvnD9lmbOpx5Sph+yVZKHbhnSP9MqUEDX08G4UzMU+D87jrazGE7lRSyTRs6NEYHtzfkJ3FjjQ==
"@rollup/rollup-linux-arm-gnueabihf@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz#9f1a6d218b560c9d75185af4b8bb42f9f24736b8"
integrity sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==
"@rollup/rollup-linux-arm-gnueabihf@4.32.0":
version "4.32.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.32.0.tgz#3bd5fcbab92a66e032faef1078915d1dbf27de7a"
integrity sha512-3pA7xecItbgOs1A5H58dDvOUEboG5UfpTq3WzAdF54acBbUM+olDJAPkgj1GRJ4ZqE12DZ9/hNS2QZk166v92A==
"@rollup/rollup-linux-arm-musleabihf@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz#53618b92e6ffb642c7b620e6e528446511330549"
integrity sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==
"@rollup/rollup-linux-arm-musleabihf@4.32.0":
version "4.32.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.32.0.tgz#a77838b9779931ce4fa01326b585eee130f51e60"
integrity sha512-Y7XUZEVISGyge51QbYyYAEHwpGgmRrAxQXO3siyYo2kmaj72USSG8LtlQQgAtlGfxYiOwu+2BdbPjzEpcOpRmQ==
"@rollup/rollup-linux-arm64-gnu@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz#99a7ba5e719d4f053761a698f7b52291cefba577"
integrity sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==
"@rollup/rollup-linux-arm64-gnu@4.32.0":
version "4.32.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.32.0.tgz#ec1b1901b82d57a20184adb61c725dd8991a0bf0"
integrity sha512-r7/OTF5MqeBrZo5omPXcTnjvv1GsrdH8a8RerARvDFiDwFpDVDnJyByYM/nX+mvks8XXsgPUxkwe/ltaX2VH7w==
"@rollup/rollup-linux-arm64-musl@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz#f53db99a45d9bc00ce94db8a35efa7c3c144a58c"
integrity sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==
"@rollup/rollup-linux-arm64-musl@4.32.0":
version "4.32.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.32.0.tgz#7aa23b45bf489b7204b5a542e857e134742141de"
integrity sha512-HJbifC9vex9NqnlodV2BHVFNuzKL5OnsV2dvTw6e1dpZKkNjPG6WUq+nhEYV6Hv2Bv++BXkwcyoGlXnPrjAKXw==
"@rollup/rollup-linux-loongarch64-gnu@4.32.0":
version "4.32.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.32.0.tgz#7bf0ebd8c5ad08719c3b4786be561d67f95654a7"
integrity sha512-VAEzZTD63YglFlWwRj3taofmkV1V3xhebDXffon7msNz4b14xKsz7utO6F8F4cqt8K/ktTl9rm88yryvDpsfOw==
"@rollup/rollup-linux-powerpc64le-gnu@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz#cbb0837408fe081ce3435cf3730e090febafc9bf"
integrity sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==
"@rollup/rollup-linux-powerpc64le-gnu@4.32.0":
version "4.32.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.32.0.tgz#e687dfcaf08124aafaaebecef0cc3986675cb9b6"
integrity sha512-Sts5DST1jXAc9YH/iik1C9QRsLcCoOScf3dfbY5i4kH9RJpKxiTBXqm7qU5O6zTXBTEZry69bGszr3SMgYmMcQ==
"@rollup/rollup-linux-riscv64-gnu@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz#8ed09c1d1262ada4c38d791a28ae0fea28b80cc9"
integrity sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==
"@rollup/rollup-linux-riscv64-gnu@4.32.0":
version "4.32.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.32.0.tgz#19fce2594f9ce73d1cb0748baf8cd90a7bedc237"
integrity sha512-qhlXeV9AqxIyY9/R1h1hBD6eMvQCO34ZmdYvry/K+/MBs6d1nRFLm6BOiITLVI+nFAAB9kUB6sdJRKyVHXnqZw==
"@rollup/rollup-linux-s390x-gnu@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz#938138d3c8e0c96f022252a28441dcfb17afd7ec"
integrity sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==
"@rollup/rollup-linux-s390x-gnu@4.32.0":
version "4.32.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.32.0.tgz#fd99b335bb65c59beb7d15ae82be0aafa9883c19"
integrity sha512-8ZGN7ExnV0qjXa155Rsfi6H8M4iBBwNLBM9lcVS+4NcSzOFaNqmt7djlox8pN1lWrRPMRRQ8NeDlozIGx3Omsw==
"@rollup/rollup-linux-x64-gnu@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz#1a7481137a54740bee1ded4ae5752450f155d942"
integrity sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==
"@rollup/rollup-linux-x64-gnu@4.32.0":
version "4.32.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.32.0.tgz#4e8c697bbaa2e2d7212bd42086746c8275721166"
integrity sha512-VDzNHtLLI5s7xd/VubyS10mq6TxvZBp+4NRWoW+Hi3tgV05RtVm4qK99+dClwTN1McA6PHwob6DEJ6PlXbY83A==
"@rollup/rollup-linux-x64-musl@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz#f1186afc601ac4f4fc25fac4ca15ecbee3a1874d"
integrity sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==
"@rollup/rollup-linux-x64-musl@4.32.0":
version "4.32.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.32.0.tgz#0d2f74bd9cfe0553f20f056760a95b293e849ab2"
integrity sha512-qcb9qYDlkxz9DxJo7SDhWxTWV1gFuwznjbTiov289pASxlfGbaOD54mgbs9+z94VwrXtKTu+2RqwlSTbiOqxGg==
"@rollup/rollup-win32-arm64-msvc@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz#ed6603e93636a96203c6915be4117245c1bd2daf"
integrity sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==
"@rollup/rollup-win32-arm64-msvc@4.32.0":
version "4.32.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.32.0.tgz#6534a09fcdd43103645155cedb5bfa65fbf2c23f"
integrity sha512-pFDdotFDMXW2AXVbfdUEfidPAk/OtwE/Hd4eYMTNVVaCQ6Yl8et0meDaKNL63L44Haxv4UExpv9ydSf3aSayDg==
"@rollup/rollup-win32-ia32-msvc@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz#14e0b404b1c25ebe6157a15edb9c46959ba74c54"
integrity sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==
"@rollup/rollup-win32-ia32-msvc@4.32.0":
version "4.32.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.32.0.tgz#8222ccfecffd63a6b0ddbe417d8d959e4f2b11b3"
integrity sha512-/TG7WfrCAjeRNDvI4+0AAMoHxea/USWhAzf9PVDFHbcqrQ7hMMKp4jZIy4VEjk72AAfN5k4TiSMRXRKf/0akSw==
"@rollup/rollup-win32-x64-msvc@4.18.0":
version "4.18.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz#5d694d345ce36b6ecf657349e03eb87297e68da4"
integrity sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==
"@rollup/rollup-win32-x64-msvc@4.32.0":
version "4.32.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.32.0.tgz#1a40b4792c08094b6479c48c90fe7f4b10ec2f54"
integrity sha512-5hqO5S3PTEO2E5VjCePxv40gIgyS2KvO7E7/vvC/NbIW4SIRamkMr1hqj+5Y67fbBWv/bQLB6KelBQmXlyCjWA==
"@rushstack/node-core-library@4.0.2":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@rushstack/node-core-library/-/node-core-library-4.0.2.tgz#e26854a3314b279d57e8abdb4acce7797d02f554"
@@ -2630,6 +2735,10 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
"@types/estree@1.0.6":
version "1.0.6"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50"
integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==
"@types/filesystem@*":
version "0.0.36"
resolved "https://registry.yarnpkg.com/@types/filesystem/-/filesystem-0.0.36.tgz#7227c2d76bfed1b21819db310816c7821d303857"
@@ -4909,34 +5018,36 @@ esbuild@^0.20.1:
"@esbuild/win32-ia32" "0.20.2"
"@esbuild/win32-x64" "0.20.2"
esbuild@^0.21.3:
version "0.21.5"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d"
integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==
esbuild@^0.24.2:
version "0.24.2"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.24.2.tgz#b5b55bee7de017bff5fb8a4e3e44f2ebe2c3567d"
integrity sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==
optionalDependencies:
"@esbuild/aix-ppc64" "0.21.5"
"@esbuild/android-arm" "0.21.5"
"@esbuild/android-arm64" "0.21.5"
"@esbuild/android-x64" "0.21.5"
"@esbuild/darwin-arm64" "0.21.5"
"@esbuild/darwin-x64" "0.21.5"
"@esbuild/freebsd-arm64" "0.21.5"
"@esbuild/freebsd-x64" "0.21.5"
"@esbuild/linux-arm" "0.21.5"
"@esbuild/linux-arm64" "0.21.5"
"@esbuild/linux-ia32" "0.21.5"
"@esbuild/linux-loong64" "0.21.5"
"@esbuild/linux-mips64el" "0.21.5"
"@esbuild/linux-ppc64" "0.21.5"
"@esbuild/linux-riscv64" "0.21.5"
"@esbuild/linux-s390x" "0.21.5"
"@esbuild/linux-x64" "0.21.5"
"@esbuild/netbsd-x64" "0.21.5"
"@esbuild/openbsd-x64" "0.21.5"
"@esbuild/sunos-x64" "0.21.5"
"@esbuild/win32-arm64" "0.21.5"
"@esbuild/win32-ia32" "0.21.5"
"@esbuild/win32-x64" "0.21.5"
"@esbuild/aix-ppc64" "0.24.2"
"@esbuild/android-arm" "0.24.2"
"@esbuild/android-arm64" "0.24.2"
"@esbuild/android-x64" "0.24.2"
"@esbuild/darwin-arm64" "0.24.2"
"@esbuild/darwin-x64" "0.24.2"
"@esbuild/freebsd-arm64" "0.24.2"
"@esbuild/freebsd-x64" "0.24.2"
"@esbuild/linux-arm" "0.24.2"
"@esbuild/linux-arm64" "0.24.2"
"@esbuild/linux-ia32" "0.24.2"
"@esbuild/linux-loong64" "0.24.2"
"@esbuild/linux-mips64el" "0.24.2"
"@esbuild/linux-ppc64" "0.24.2"
"@esbuild/linux-riscv64" "0.24.2"
"@esbuild/linux-s390x" "0.24.2"
"@esbuild/linux-x64" "0.24.2"
"@esbuild/netbsd-arm64" "0.24.2"
"@esbuild/netbsd-x64" "0.24.2"
"@esbuild/openbsd-arm64" "0.24.2"
"@esbuild/openbsd-x64" "0.24.2"
"@esbuild/sunos-x64" "0.24.2"
"@esbuild/win32-arm64" "0.24.2"
"@esbuild/win32-ia32" "0.24.2"
"@esbuild/win32-x64" "0.24.2"
escalade@^3.1.1, escalade@^3.1.2:
version "3.1.2"
@@ -7737,6 +7848,11 @@ nanoid@^3.3.7:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
nanoid@^3.3.8:
version "3.3.8"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf"
integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==
nanoid@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-4.0.2.tgz#140b3c5003959adbebf521c170f282c5e7f9fb9e"
@@ -8195,6 +8311,11 @@ picocolors@^1.0.0, picocolors@^1.0.1:
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1"
integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==
picocolors@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
@@ -8302,6 +8423,15 @@ postcss@^8.4.38:
picocolors "^1.0.0"
source-map-js "^1.2.0"
postcss@^8.4.49:
version "8.5.1"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.1.tgz#e2272a1f8a807fafa413218245630b5db10a3214"
integrity sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==
dependencies:
nanoid "^3.3.8"
picocolors "^1.1.1"
source-map-js "^1.2.1"
preferred-pm@^3.0.0:
version "3.1.3"
resolved "https://registry.yarnpkg.com/preferred-pm/-/preferred-pm-3.1.3.tgz#4125ea5154603136c3b6444e5f5c94ecf90e4916"
@@ -8906,6 +9036,34 @@ rollup@^4.13.0:
"@rollup/rollup-win32-x64-msvc" "4.18.0"
fsevents "~2.3.2"
rollup@^4.23.0:
version "4.32.0"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.32.0.tgz#c405bf6fca494d1999d9088f7736d7f03e5cac5a"
integrity sha512-JmrhfQR31Q4AuNBjjAX4s+a/Pu/Q8Q9iwjWBsjRH1q52SPFE2NqRMK6fUZKKnvKO6id+h7JIRf0oYsph53eATg==
dependencies:
"@types/estree" "1.0.6"
optionalDependencies:
"@rollup/rollup-android-arm-eabi" "4.32.0"
"@rollup/rollup-android-arm64" "4.32.0"
"@rollup/rollup-darwin-arm64" "4.32.0"
"@rollup/rollup-darwin-x64" "4.32.0"
"@rollup/rollup-freebsd-arm64" "4.32.0"
"@rollup/rollup-freebsd-x64" "4.32.0"
"@rollup/rollup-linux-arm-gnueabihf" "4.32.0"
"@rollup/rollup-linux-arm-musleabihf" "4.32.0"
"@rollup/rollup-linux-arm64-gnu" "4.32.0"
"@rollup/rollup-linux-arm64-musl" "4.32.0"
"@rollup/rollup-linux-loongarch64-gnu" "4.32.0"
"@rollup/rollup-linux-powerpc64le-gnu" "4.32.0"
"@rollup/rollup-linux-riscv64-gnu" "4.32.0"
"@rollup/rollup-linux-s390x-gnu" "4.32.0"
"@rollup/rollup-linux-x64-gnu" "4.32.0"
"@rollup/rollup-linux-x64-musl" "4.32.0"
"@rollup/rollup-win32-arm64-msvc" "4.32.0"
"@rollup/rollup-win32-ia32-msvc" "4.32.0"
"@rollup/rollup-win32-x64-msvc" "4.32.0"
fsevents "~2.3.2"
run-async@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/run-async/-/run-async-3.0.0.tgz#42a432f6d76c689522058984384df28be379daad"
@@ -9259,6 +9417,11 @@ source-map-js@^1.0.1, source-map-js@^1.2.0:
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af"
integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
source-map-js@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
source-map-support@0.5.21, source-map-support@^0.5.6:
version "0.5.21"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
@@ -10365,14 +10528,14 @@ vite@^5.0.0, "vite@^5.0.0 || ^4.1.4":
optionalDependencies:
fsevents "~2.3.3"
vite@^5.2.8, vite@^5.3.1:
version "5.3.1"
resolved "https://registry.yarnpkg.com/vite/-/vite-5.3.1.tgz#bb2ca6b5fd7483249d3e86b25026e27ba8a663e6"
integrity sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==
vite@^6.0.1:
version "6.0.11"
resolved "https://registry.yarnpkg.com/vite/-/vite-6.0.11.tgz#224497e93e940b34c3357c9ebf2ec20803091ed8"
integrity sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==
dependencies:
esbuild "^0.21.3"
postcss "^8.4.38"
rollup "^4.13.0"
esbuild "^0.24.2"
postcss "^8.4.49"
rollup "^4.23.0"
optionalDependencies:
fsevents "~2.3.3"