feat(perf): reduce sdk.js bundle size (#15381)

This PR reduces the web SDK's gzipped bundle size by about 30%, lowering
the amount of JavaScript downloaded and parsed on uncached SDK loads. It
also serves precompressed Brotli or gzip from the existing SDK URL and
adds repeatable bundle-analysis tooling to help catch future size
regressions.

## What changed

### Use a lightweight synchronous user fingerprint

The SDK used MD5 only as a local fingerprint for detecting whether
`setUser` data had changed. It was not used as a conversation
identifier, authentication mechanism, or part of the server API
contract. This PR replaces the MD5 dependency with a small inline
128-bit [FNV-1a](https://www.rfc-editor.org/rfc/rfc9923.html)
implementation.

FNV-1a is synchronous and works in both secure and insecure embedding
pages. This preserves the previous latest-call-wins behavior when
integrations call `setUser` repeatedly, without relying on
`SubtleCrypto`, which is unavailable on plain HTTP origins. The 128-bit
output prevents practical 32-bit collisions from causing distinct
identities to be treated as unchanged; regression coverage includes a
known pair of identifiers that collide under FNV-1a 32-bit. Existing
hash cookies produce a one-time mismatch, causing `setUser` to be sent
once before the cookie is replaced with the new fingerprint.

The ESLint environment now declares ES2020, matching the SDK's Vite
build target and allowing the implementation to use `BigInt` directly.

The SDK also used `date-fns` only to add one hour to the current time.
Native timestamp arithmetic provides the same behavior for this fixed
duration without bundling the associated date utilities.

### Minify inline SDK styles

SDK styles now pass through [Vite's inline CSS
pipeline](https://v6.vite.dev/guide/features#disabling-css-injection-into-the-page).
Vite minifies the stylesheet at build time and returns it as a string,
while the SDK continues injecting it into the embedding page as before.

### Serve precompressed bundles from the same URL

The SDK build now emits Brotli and gzip sidecars. Rails 7.2
[automatically negotiates precompressed
assets](https://api.rubyonrails.org/v7.2.3.1/classes/ActionDispatch/FileHandler.html)
and serves the best format supported by the browser from the existing
`/packs/js/sdk.js` URL. No embed-code or application URL changes are
required.

## Bundle size

| Metric | Before | After | Reduction |
|---|---:|---:|---:|
| Raw | 29.20 kB | 21.75 kB | 25.5% |
| Gzip | 9.73 kB | 6.89 kB | 29.2% |
| Brotli | — | 6.08 kB | — |

## How to test

1. Load the widget from `/packs/js/sdk.js` in a browser that supports
Brotli or gzip and confirm the same URL is returned with the
corresponding `Content-Encoding` header.
2. Embed the widget on HTTPS and plain HTTP pages, call
`$chatwoot.setUser`, and confirm the user is identified in both
contexts.
3. Call `setUser` several times consecutively with different identities
and confirm the final call remains the active widget identity.
4. Reload the embedding page with unchanged user data and confirm the
SDK does not send a redundant `setUser` update.
5. Confirm the widget layout and styles remain unchanged.

## Staging test

#### Before

<img width="2192" height="710" alt="CleanShot 2026-08-12 at 16 23 27@2x"
src="https://github.com/user-attachments/assets/b60cf6dc-bb12-460c-9bed-d8720010ccf6"
/>

#### After

<img width="2192" height="1014" alt="CleanShot 2026-08-12 at 16 57
53@2x"
src="https://github.com/user-attachments/assets/4ce97fab-35a8-44cc-8add-20a0d59d5755"
/>
This commit is contained in:
Shivam Mishra
2026-08-24 14:39:36 +05:30
committed by GitHub
parent 6154aebcfe
commit f3f06b902a
12 changed files with 612 additions and 75 deletions

View File

@@ -247,6 +247,7 @@ module.exports = {
},
env: {
browser: true,
es2020: true,
node: true,
},
globals: {

View File

@@ -0,0 +1,59 @@
import Cookies from 'js-cookie';
import { IFrameHelper } from '../sdk/IFrameHelper';
import './sdk';
vi.mock('../sdk/IFrameHelper', () => ({
IFrameHelper: {
createFrame: vi.fn(),
sendMessage: vi.fn(),
},
}));
describe('$chatwoot.setUser', () => {
beforeEach(() => {
delete window.$chatwoot;
window.chatwootSettings = {};
vi.spyOn(Cookies, 'get').mockReturnValue(undefined);
vi.spyOn(Cookies, 'set').mockImplementation(() => {});
window.chatwootSDK.run({
baseUrl: 'https://app.chatwoot.com',
websiteToken: 'website-token',
});
});
afterEach(() => {
delete window.$chatwoot;
delete window.chatwootSettings;
vi.restoreAllMocks();
});
it('updates the SDK identity synchronously', () => {
const user = { name: 'Pranav' };
const result = window.$chatwoot.setUser('first-user', user);
expect(result).toBeUndefined();
expect(window.$chatwoot.identifier).toBe('first-user');
expect(window.$chatwoot.user).toBe(user);
expect(IFrameHelper.sendMessage).toHaveBeenCalledWith('set-user', {
identifier: 'first-user',
user,
});
});
it('keeps the latest identity after consecutive calls', () => {
const firstUser = { name: 'First user' };
const secondUser = { name: 'Second user' };
window.$chatwoot.setUser('first-user', firstUser);
window.$chatwoot.setUser('second-user', secondUser);
expect(window.$chatwoot.identifier).toBe('second-user');
expect(window.$chatwoot.user).toBe(secondUser);
expect(IFrameHelper.sendMessage).toHaveBeenLastCalledWith('set-user', {
identifier: 'second-user',
user: secondUser,
});
});
});

View File

@@ -1,4 +1,4 @@
import { SDK_CSS } from './sdk.js';
import SDK_CSS from './sdk.css?inline';
import { IFrameHelper } from './IFrameHelper';
export const loadCSS = () => {

View File

@@ -35,7 +35,6 @@ import {
} from 'shared/helpers/AudioNotificationHelper';
import { isFlatWidgetStyle } from './settingsHelper';
import { popoutChatWindow } from '../widget/helpers/popoutHelper';
import addHours from 'date-fns/addHours';
const updateAuthCookie = (cookieContent, baseDomain = '') =>
setCookieWithDomain('cw_conversation', cookieContent, {
@@ -43,7 +42,7 @@ const updateAuthCookie = (cookieContent, baseDomain = '') =>
});
const updateCampaignReadStatus = baseDomain => {
const expireBy = addHours(new Date(), 1);
const expireBy = new Date(Date.now() + 60 * 60 * 1000);
setCookieWithDomain('cw_snooze_campaigns_till', Number(expireBy), {
expires: expireBy,
baseDomain,

View File

@@ -1,8 +1,9 @@
import md5 from 'md5';
import Cookies from 'js-cookie';
const REQUIRED_USER_KEYS = ['avatar_url', 'email', 'name'];
const ALLOWED_USER_ATTRIBUTES = [...REQUIRED_USER_KEYS, 'identifier_hash'];
const FNV1A_128_OFFSET_BASIS = 0x6c62272e07bb014262b821756295c58dn;
const FNV1A_128_PRIME = 0x1000000000000000000013bn;
const CONTACT_INFORMATION_ATTRIBUTES = [
'phone_number',
'company_name',
@@ -37,7 +38,20 @@ export const getUserString = ({ identifier = '', user }) => {
return JSON.stringify(userAttributes);
};
export const computeHashForUserData = (...args) => md5(getUserString(...args));
export const fnv1a128 = value => {
const data = new TextEncoder().encode(value);
let hash = FNV1A_128_OFFSET_BASIS;
data.forEach(byte => {
hash ^= BigInt(byte); // eslint-disable-line no-bitwise
hash = BigInt.asUintN(128, hash * FNV1A_128_PRIME);
});
return hash.toString(16).padStart(32, '0');
};
export const computeHashForUserData = (...args) =>
fnv1a128(getUserString(...args));
export const hasUserKeys = user =>
REQUIRED_USER_KEYS.reduce((acc, key) => acc || !!user[key], false);

View File

@@ -1,17 +1,18 @@
export const SDK_CSS = `
:root {
--b-100: #F2F3F7;
--s-700: #37546D;
--b-100: #f2f3f7;
--s-700: #37546d;
}
.woot-widget-holder {
box-shadow: 0 5px 40px rgba(0, 0, 0, .16);
box-shadow: 0 5px 40px rgba(0, 0, 0, 0.16);
opacity: 1;
will-change: transform, opacity;
transform: translateY(0);
overflow: hidden !important;
position: fixed !important;
transition: opacity 0.2s linear, transform 0.25s linear;
transition:
opacity 0.2s linear,
transform 0.25s linear;
z-index: 2147483000 !important;
}
@@ -43,7 +44,7 @@ export const SDK_CSS = `
border-radius: 100px;
border-width: 0px;
bottom: 20px;
box-shadow: 0 8px 24px rgba(0, 0, 0, .16) !important;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.16) !important;
cursor: pointer;
height: 64px;
padding: 0px;
@@ -107,7 +108,18 @@ export const SDK_CSS = `
align-items: center;
color: #fff;
display: flex;
font-family: system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen-Sans, Ubuntu, Cantarell, Helvetica Neue, Arial, sans-serif;
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
Segoe UI,
Roboto,
Oxygen-Sans,
Ubuntu,
Cantarell,
Helvetica Neue,
Arial,
sans-serif;
font-size: 16px;
font-weight: 500;
justify-content: center;
@@ -115,7 +127,8 @@ export const SDK_CSS = `
width: auto !important;
}
.woot-widget-bubble.woot-widget--expanded.woot-widget-bubble-color--lighter div{
.woot-widget-bubble.woot-widget--expanded.woot-widget-bubble-color--lighter
div {
color: var(--s-700);
}
@@ -135,7 +148,7 @@ export const SDK_CSS = `
.woot-widget-bubble:hover {
background: #1f93ff;
box-shadow: 0 8px 32px rgba(0, 0, 0, .4) !important;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4) !important;
}
.woot-widget-bubble svg {
@@ -146,24 +159,25 @@ export const SDK_CSS = `
width: 24px !important;
}
.woot-widget-bubble.woot-widget-bubble-color--lighter path{
.woot-widget-bubble.woot-widget-bubble-color--lighter path {
fill: var(--s-700);
}
@media only screen and (min-width: 667px) {
.woot-widget-holder.woot-elements--left {
left: 20px;
}
}
.woot-widget-holder.woot-elements--right {
right: 20px;
}
}
}
.woot--close:hover {
opacity: 1;
}
.woot--close::before, .woot--close::after {
.woot--close::before,
.woot--close::after {
background-color: #fff;
content: ' ';
display: inline;
@@ -174,7 +188,8 @@ export const SDK_CSS = `
width: 2px;
}
.woot-widget-bubble-color--lighter.woot--close::before, .woot-widget-bubble-color--lighter.woot--close::after {
.woot-widget-bubble-color--lighter.woot--close::before,
.woot-widget-bubble-color--lighter.woot--close::after {
background-color: var(--s-700);
}
@@ -197,52 +212,92 @@ export const SDK_CSS = `
.woot-widget--without-bubble {
bottom: 20px !important;
}
.woot-widget-holder.woot--hide{
.woot-widget-holder.woot--hide {
transform: translateY(40px);
}
.woot-widget-bubble.woot--close {
transform: translateX(0px) scale(1) rotate(0deg);
transition: transform 300ms ease, opacity 100ms ease, visibility 0ms linear 0ms, bottom 0ms linear 0ms;
transition:
transform 300ms ease,
opacity 100ms ease,
visibility 0ms linear 0ms,
bottom 0ms linear 0ms;
}
.woot-widget-bubble.woot--close.woot--hide {
transform: translateX(8px) scale(.75) rotate(45deg);
transition: transform 300ms ease, opacity 200ms ease, visibility 0ms linear 500ms, bottom 0ms ease 200ms;
transform: translateX(8px) scale(0.75) rotate(45deg);
transition:
transform 300ms ease,
opacity 200ms ease,
visibility 0ms linear 500ms,
bottom 0ms ease 200ms;
}
.woot-widget-bubble {
transform-origin: center;
will-change: transform, opacity;
transform: translateX(0) scale(1) rotate(0deg);
transition: transform 300ms ease, opacity 100ms ease, visibility 0ms linear 0ms, bottom 0ms linear 0ms;
transition:
transform 300ms ease,
opacity 100ms ease,
visibility 0ms linear 0ms,
bottom 0ms linear 0ms;
}
.woot-widget-bubble.woot--hide {
transform: translateX(8px) scale(.75) rotate(-30deg);
transition: transform 300ms ease, opacity 200ms ease, visibility 0ms linear 500ms, bottom 0ms ease 200ms;
transform: translateX(8px) scale(0.75) rotate(-30deg);
transition:
transform 300ms ease,
opacity 200ms ease,
visibility 0ms linear 500ms,
bottom 0ms ease 200ms;
}
.woot-widget-bubble.woot-widget--expanded {
transform: translateX(0px);
transition: transform 300ms ease, opacity 100ms ease, visibility 0ms linear 0ms, bottom 0ms linear 0ms;
transition:
transform 300ms ease,
opacity 100ms ease,
visibility 0ms linear 0ms,
bottom 0ms linear 0ms;
}
.woot-widget-bubble.woot-widget--expanded.woot--hide {
transform: translateX(8px);
transition: transform 300ms ease, opacity 200ms ease, visibility 0ms linear 500ms, bottom 0ms ease 200ms;
transition:
transform 300ms ease,
opacity 200ms ease,
visibility 0ms linear 500ms,
bottom 0ms ease 200ms;
}
.woot-widget-bubble.woot-widget-bubble--flat.woot--close {
transform: translateX(0px);
transition: transform 300ms ease, opacity 10ms ease, visibility 0ms linear 0ms, bottom 0ms linear 0ms;
transition:
transform 300ms ease,
opacity 10ms ease,
visibility 0ms linear 0ms,
bottom 0ms linear 0ms;
}
.woot-widget-bubble.woot-widget-bubble--flat.woot--close.woot--hide {
transform: translateX(8px);
transition: transform 300ms ease, opacity 200ms ease, visibility 0ms linear 500ms, bottom 0ms ease 200ms;
transition:
transform 300ms ease,
opacity 200ms ease,
visibility 0ms linear 500ms,
bottom 0ms ease 200ms;
}
.woot-widget-bubble.woot-widget--expanded.woot-widget-bubble--flat {
transform: translateX(0px);
transition: transform 300ms ease, opacity 200ms ease, visibility 0ms linear 0ms, bottom 0ms linear 0ms;
transition:
transform 300ms ease,
opacity 200ms ease,
visibility 0ms linear 0ms,
bottom 0ms linear 0ms;
}
.woot-widget-bubble.woot-widget--expanded.woot-widget-bubble--flat.woot--hide {
transform: translateX(8px);
transition: transform 300ms ease, opacity 200ms ease, visibility 0ms linear 500ms, bottom 0ms ease 200ms;
transition:
transform 300ms ease,
opacity 200ms ease,
visibility 0ms linear 500ms,
bottom 0ms ease 200ms;
}
@media only screen and (max-width: 667px) {
@@ -251,14 +306,13 @@ export const SDK_CSS = `
right: 0;
top: 0;
width: 100%;
}
}
.woot-widget-holder iframe {
.woot-widget-holder iframe {
min-height: 100% !important;
}
.woot-widget-holder.has-unread-view {
.woot-widget-holder.has-unread-view {
height: auto;
right: 0;
width: auto;
@@ -272,7 +326,7 @@ export const SDK_CSS = `
min-height: unset !important;
}
.woot-widget-holder.has-unread-view.woot-elements--left {
.woot-widget-holder.has-unread-view.woot-elements--left {
left: 0;
}
@@ -292,10 +346,9 @@ export const SDK_CSS = `
max-height: 640px !important;
min-height: 250px !important;
width: 400px !important;
}
}
}
.woot-hidden {
display: none !important;
}
`;

View File

@@ -1,6 +1,7 @@
import Cookies from 'js-cookie';
import {
computeHashForUserData,
fnv1a128,
getUserCookieName,
getUserString,
hasUserKeys,
@@ -55,6 +56,155 @@ describe('#getUserString', () => {
});
});
describe('#fnv1a128', () => {
// Adapted from https://github.com/sindresorhus/fnv1a/blob/main/test.js
it.each([
['', '6c62272e07bb014262b821756295c58d'],
['h', 'd228cb69681a8caf78912b704e4a80c7'],
['he', '08809533baab1be95aa0733055ac4756'],
['hel', 'a68d42edea8b5822836dbc796afba45e'],
['hell', '693c5663cb757277b806e966a3a30986'],
['hello', 'e3e1efd54283d94f7081314b599d31b3'],
['hello ', 'b25bb89a6b3c64bf6ef7a7b7446bffe1'],
['hello w', '2e209201894ff78d8abb5e8130e37d92'],
['hello wo', '43448b61f2659b29b48d48f727ec064f'],
['hello wor', 'bc7f6d8b8005ec5129d8c81e1f6bad0f'],
['hello worl', '0eeb3653ea49c7de7dbe3d10a97e58d1'],
['hello world', '6c155799fdc8eec4b91523808e7726b7'],
])('hashes %j to %s', (value, expectedHash) => {
expect(fnv1a128(value)).toBe(expectedHash);
});
it('hashes long input', () => {
const loremIpsumParagraph = [
'Lorem ipsum dolor sit amet, consectetuer adipiscing elit.',
'Aenean commodo ligula eget dolor.',
'Aenean massa.',
'Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.',
'Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.',
'Nulla consequat massa quis enim.',
'Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu.',
'In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo.',
'Nullam dictum felis eu pede mollis pretium.',
].join(' ');
const value = Array(3).fill(loremIpsumParagraph).join(' ');
expect(fnv1a128(value)).toBe('4fedf302b27b5c4dbaf35bd87a91f589');
});
it('hashes multi-byte UTF-8 characters', () => {
expect(fnv1a128('🦄🌈')).toBe('0a25841ae4659905b36cb0d359fad39f');
});
it('hashes characters from across the Unicode range', () => {
const codePoints = [
0x0000, 0x0080, 0x0100, 0x0180, 0x0250, 0x02b0, 0x0300, 0x0370, 0x0400,
0x0500, 0x0530, 0x0590, 0x0600, 0x0700, 0x0780, 0x0900, 0x0980, 0x0a00,
0x0a80, 0x0b00, 0x0b80, 0x0c00, 0x0c80, 0x0d00, 0x0d80, 0x0e00, 0x0e80,
0x0f00, 0x1000, 0x10a0, 0x1100, 0x1200, 0x13a0, 0x1400, 0x1680, 0x16a0,
0x1700, 0x1720, 0x1740, 0x1760, 0x1780, 0x1800, 0x1900, 0x1950, 0x19e0,
0x1d00, 0x1e00, 0x1f00, 0x2000, 0x2070, 0x20a0, 0x20d0, 0x2100, 0x2150,
0x2190, 0x2200, 0x2300, 0x2400, 0x2440, 0x2460, 0x2500, 0x2580, 0x25a0,
0x2600, 0x2700, 0x27c0, 0x27f0, 0x2800, 0x2900, 0x2980, 0x2a00, 0x2b00,
0x2e80, 0x2f00, 0x2ff0, 0x3000, 0x3040, 0x30a0, 0x3100, 0x3130, 0x3190,
0x31a0, 0x31f0, 0x3200, 0x3300, 0x3400, 0x4dc0, 0x4e00, 0xa000, 0xa490,
0xac00, 0xd800, 0xdc00, 0xe000, 0xf900, 0xfb00, 0xfb50, 0xfe00, 0xfe20,
0xfe30, 0xfe50, 0xfe70, 0xff00, 0xfff0, 0x10000, 0x10080, 0x10100,
0x10300, 0x10330, 0x10380, 0x10400, 0x10450, 0x10480, 0x10800, 0x1d000,
0x1d100, 0x1d300, 0x1d400, 0x20000, 0x2f800, 0xe0000, 0xe0100,
];
const value = codePoints
.map(codePoint => String.fromCodePoint(codePoint))
.join('');
expect(fnv1a128(value)).toBe('ca8f3b27d9685fa16c01b04ca12aba2d');
});
});
describe('#computeHashForUserData', () => {
it.each([
[{ user: {} }, '1aad4cb46d5db425dcc232d086a8b6be'],
[
{
identifier: '12345',
user: {
name: 'Pranav',
email: 'pranav@example.com',
avatar_url: 'https://images.chatwoot.com/placeholder',
identifier_hash: '12345',
},
},
'73b97d28ad386b211c8eed2143752b6e',
],
[
{
identifier: 42,
user: {
name: 'Jöhn 🌈',
email: '🦄@example.com',
},
},
'ca2a97a04c3bcafc20d9cdeac1e5b3f4',
],
])('hashes canonical user data synchronously', (userData, expectedHash) => {
expect(computeHashForUserData(userData)).toBe(expectedHash);
});
it('is independent of user property insertion order', () => {
const firstHash = computeHashForUserData({
identifier: '12345',
user: { name: 'Pranav', email: 'pranav@example.com' },
});
const secondHash = computeHashForUserData({
identifier: '12345',
user: { email: 'pranav@example.com', name: 'Pranav' },
});
expect(firstHash).toBe(secondHash);
});
it('ignores user properties that are not part of the SDK identity', () => {
const firstHash = computeHashForUserData({
identifier: '12345',
user: { name: 'Pranav' },
});
const secondHash = computeHashForUserData({
identifier: '12345',
user: { name: 'Pranav', role: 'administrator' },
});
expect(firstHash).toBe(secondHash);
});
it('changes when an identity property changes', () => {
const firstHash = computeHashForUserData({
identifier: '12345',
user: { name: 'Pranav' },
});
const secondHash = computeHashForUserData({
identifier: '67890',
user: { name: 'Pranav' },
});
expect(firstHash).not.toBe(secondHash);
});
it('distinguishes identities that collide under FNV-1a 32-bit', () => {
const firstHash = computeHashForUserData({
identifier: 'id-149599',
user: { name: 'User' },
});
const secondHash = computeHashForUserData({
identifier: 'id-312382',
user: { name: 'User' },
});
expect(firstHash).toHaveLength(32);
expect(secondHash).toHaveLength(32);
expect(firstHash).not.toBe(secondHash);
});
});
describe('#computeHashForUserData', () => {
const identifier = 'user-123';
const user = {

View File

@@ -13,6 +13,9 @@
"dev": "overmind start -f ./Procfile.dev",
"ruby:prettier": "bundle exec rubocop -a",
"build:sdk": "vite build --config vite.lib.config.ts",
"analyze:bundles": "pnpm analyze:bundle:sdk && pnpm analyze:bundle:widget",
"analyze:bundle:sdk": "vite build --config vite.sdk.analyze.config.mts",
"analyze:bundle:widget": "vite build --config vite.widget.analyze.config.mts",
"prepare": "husky install",
"size": "size-limit",
"story:dev": "histoire dev",
@@ -82,7 +85,6 @@
"libphonenumber-js": "^1.11.9",
"markdown-it": "^14.1.1",
"markdown-it-link-attributes": "^4.0.1",
"md5": "^2.3.0",
"mitt": "^3.0.1",
"opus-recorder": "^8.0.5",
"pinia": "^3.0.4",
@@ -141,6 +143,7 @@
"postcss-preset-env": "^8.5.1",
"prettier": "^3.3.3",
"prosemirror-model": "^1.22.3",
"rollup-plugin-visualizer": "7.0.1",
"size-limit": "^8.2.4",
"tailwindcss": "^3.4.19",
"vite": "6.4.2",

223
pnpm-lock.yaml generated
View File

@@ -168,9 +168,6 @@ importers:
markdown-it-link-attributes:
specifier: ^4.0.1
version: 4.0.1
md5:
specifier: ^2.3.0
version: 2.3.0
mitt:
specifier: ^3.0.1
version: 3.0.1
@@ -340,6 +337,9 @@ importers:
prosemirror-model:
specifier: ^1.22.3
version: 1.22.3
rollup-plugin-visualizer:
specifier: 7.0.1
version: 7.0.1(rollup@4.59.0)
size-limit:
specifier: ^8.2.4
version: 8.2.6
@@ -1677,6 +1677,10 @@ packages:
resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==}
engines: {node: '>=12'}
ansi-regex@6.2.2:
resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
engines: {node: '>=12'}
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
@@ -1815,6 +1819,10 @@ packages:
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
bundle-name@4.1.0:
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
engines: {node: '>=18'}
bytes-iec@3.1.1:
resolution: {integrity: sha512-fey6+4jDK7TFtFg/klGSvNKJctyU7n2aQdnM+CO0ruLPbqqMOM8Tio0Pc+deqUeVKX1tL5DQep1zQ7+37aTAsA==}
engines: {node: '>= 0.8'}
@@ -1878,9 +1886,9 @@ packages:
change-case@4.1.2:
resolution: {integrity: sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==}
charenc@0.0.2:
resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==}
chart.js@4.4.4:
resolution: {integrity: sha512-emICKGBABnxhMjUjlYRR12PmOXhJ2eJjEHL2/dZlWjxRAZT1D8xplLFq5M0tMQK8ja+wBS/tuVEJB5C6r7VxJA==}
engines: {pnpm: '>=8'}
check-error@2.1.1:
resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
engines: {node: '>= 16'}
@@ -1920,6 +1928,10 @@ packages:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
cliui@9.0.1:
resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==}
engines: {node: '>=20'}
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@@ -1999,9 +2011,6 @@ packages:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
crypt@0.0.2:
resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==}
css-blank-pseudo@5.0.2:
resolution: {integrity: sha512-aCU4AZ7uEcVSUzagTlA9pHciz7aWPKA/YzrEkpdSopJ2pvhIxiQ5sYeMz1/KByxlIo4XBdvMNJAVKMg/GRnhfw==}
engines: {node: ^14 || ^16 || >=18}
@@ -2148,10 +2157,22 @@ packages:
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
default-browser-id@5.0.1:
resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==}
engines: {node: '>=18'}
default-browser@5.5.0:
resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==}
engines: {node: '>=18'}
define-data-property@1.1.4:
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
engines: {node: '>= 0.4'}
define-lazy-prop@3.0.0:
resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
engines: {node: '>=12'}
define-properties@1.2.0:
resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==}
engines: {node: '>= 0.4'}
@@ -2322,10 +2343,6 @@ packages:
engines: {node: '>=18'}
hasBin: true
escalade@3.1.2:
resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==}
engines: {node: '>=6'}
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
@@ -2642,6 +2659,10 @@ packages:
resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==}
engines: {node: '>=18'}
get-east-asian-width@1.6.0:
resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==}
engines: {node: '>=18'}
get-intrinsic@1.2.4:
resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
engines: {node: '>= 0.4'}
@@ -2899,9 +2920,6 @@ packages:
resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
engines: {node: '>= 0.4'}
is-buffer@1.1.6:
resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==}
is-callable@1.2.7:
resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
engines: {node: '>= 0.4'}
@@ -2918,6 +2936,11 @@ packages:
resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==}
engines: {node: '>= 0.4'}
is-docker@3.0.0:
resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
hasBin: true
is-extendable@0.1.1:
resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}
engines: {node: '>=0.10.0'}
@@ -2941,6 +2964,15 @@ packages:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
is-in-ssh@1.0.0:
resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==}
engines: {node: '>=20'}
is-inside-container@1.0.0:
resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
engines: {node: '>=14.16'}
hasBin: true
is-installed-globally@1.0.0:
resolution: {integrity: sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ==}
engines: {node: '>=18'}
@@ -3021,6 +3053,10 @@ packages:
resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==}
engines: {node: '>=18'}
is-wsl@3.1.1:
resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==}
engines: {node: '>=16'}
isarray@2.0.5:
resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
@@ -3362,9 +3398,6 @@ packages:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
md5@2.3.0:
resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==}
mdn-data@2.12.2:
resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==}
@@ -3550,6 +3583,10 @@ packages:
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
engines: {node: '>=18'}
open@11.0.0:
resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==}
engines: {node: '>=20'}
optionator@0.9.3:
resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==}
engines: {node: '>= 0.8.0'}
@@ -3929,6 +3966,10 @@ packages:
resolution: {integrity: sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==}
engines: {node: ^10 || ^12 || >=14}
powershell-utils@0.1.0:
resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==}
engines: {node: '>=20'}
prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
@@ -4090,6 +4131,19 @@ packages:
deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true
rollup-plugin-visualizer@7.0.1:
resolution: {integrity: sha512-UJUT4+1Ho4OcWmPYU3sYXgUqI8B8Ayfe06MX7y0qCJ1K8aGoKtR/NDd/2nZqM7ADkrzny+I99Ul7GgyoiVNAgg==}
engines: {node: '>=22'}
hasBin: true
peerDependencies:
rolldown: 1.x || ^1.0.0-beta || ^1.0.0-rc
rollup: '>=4.59.0'
peerDependenciesMeta:
rolldown:
optional: true
rollup:
optional: true
rollup@4.59.0:
resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@@ -4098,6 +4152,10 @@ packages:
rope-sequence@1.3.2:
resolution: {integrity: sha512-ku6MFrwEVSVmXLvy3dYph3LAMNS0890K7fabn+0YIRQ2T96T9F4gkFf0vf0WW0JUraNWwGRtInEpH7yO4tbQZg==}
run-applescript@7.1.0:
resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==}
engines: {node: '>=18'}
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
@@ -4248,6 +4306,10 @@ packages:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
source-map@0.7.6:
resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==}
engines: {node: '>= 12'}
spark-md5@3.0.2:
resolution: {integrity: sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==}
@@ -4292,6 +4354,10 @@ packages:
resolution: {integrity: sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==}
engines: {node: '>=20'}
string-width@8.2.2:
resolution: {integrity: sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==}
engines: {node: '>=20'}
string.prototype.trim@1.2.8:
resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==}
engines: {node: '>= 0.4'}
@@ -4321,6 +4387,10 @@ packages:
resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
engines: {node: '>=12'}
strip-ansi@7.2.0:
resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==}
engines: {node: '>=12'}
strip-bom-string@1.0.0:
resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==}
engines: {node: '>=0.10.0'}
@@ -4929,6 +4999,10 @@ packages:
utf-8-validate:
optional: true
wsl-utils@0.3.1:
resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==}
engines: {node: '>=20'}
xml-name-validator@4.0.0:
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
engines: {node: '>=12'}
@@ -4972,6 +5046,10 @@ packages:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
yargs-parser@22.0.0:
resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==}
engines: {node: ^20.19.0 || ^22.12.0 || >=23}
yargs@15.4.1:
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
engines: {node: '>=8'}
@@ -4980,6 +5058,10 @@ packages:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
yargs@18.1.0:
resolution: {integrity: sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==}
engines: {node: ^20.19.0 || ^22.12.0 || >=23}
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
@@ -6418,6 +6500,8 @@ snapshots:
ansi-regex@6.0.1: {}
ansi-regex@6.2.2: {}
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
@@ -6591,6 +6675,10 @@ snapshots:
buffer-from@1.1.2:
optional: true
bundle-name@4.1.0:
dependencies:
run-applescript: 7.1.0
bytes-iec@3.1.1: {}
cac@6.7.14: {}
@@ -6671,8 +6759,9 @@ snapshots:
snake-case: 3.0.4
tslib: 2.7.0
charenc@0.0.2: {}
chart.js@4.4.4:
dependencies:
'@kurkle/color': 0.3.2
check-error@2.1.1: {}
chokidar@3.5.3:
@@ -6729,6 +6818,12 @@ snapshots:
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
cliui@9.0.1:
dependencies:
string-width: 7.2.0
strip-ansi: 7.1.0
wrap-ansi: 9.0.0
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
@@ -6802,8 +6897,6 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
crypt@0.0.2: {}
css-blank-pseudo@5.0.2(postcss@8.5.23):
dependencies:
postcss: 8.5.23
@@ -6920,12 +7013,21 @@ snapshots:
deep-is@0.1.4: {}
default-browser-id@5.0.1: {}
default-browser@5.5.0:
dependencies:
bundle-name: 4.1.0
default-browser-id: 5.0.1
define-data-property@1.1.4:
dependencies:
es-define-property: 1.0.1
es-errors: 1.3.0
gopd: 1.2.0
define-lazy-prop@3.0.0: {}
define-properties@1.2.0:
dependencies:
has-property-descriptors: 1.0.0
@@ -7192,8 +7294,6 @@ snapshots:
'@esbuild/win32-ia32': 0.25.12
'@esbuild/win32-x64': 0.25.12
escalade@3.1.2: {}
escalade@3.2.0: {}
escape-html@1.0.3: {}
@@ -7544,6 +7644,8 @@ snapshots:
get-east-asian-width@1.4.0: {}
get-east-asian-width@1.6.0: {}
get-intrinsic@1.2.4:
dependencies:
es-errors: 1.3.0
@@ -7880,8 +7982,6 @@ snapshots:
call-bind: 1.0.7
has-tostringtag: 1.0.2
is-buffer@1.1.6: {}
is-callable@1.2.7: {}
is-core-module@2.15.1:
@@ -7896,6 +7996,8 @@ snapshots:
dependencies:
has-tostringtag: 1.0.2
is-docker@3.0.0: {}
is-extendable@0.1.1: {}
is-extglob@2.1.1: {}
@@ -7912,6 +8014,12 @@ snapshots:
dependencies:
is-extglob: 2.1.1
is-in-ssh@1.0.0: {}
is-inside-container@1.0.0:
dependencies:
is-docker: 3.0.0
is-installed-globally@1.0.0:
dependencies:
global-directory: 4.0.1
@@ -7978,6 +8086,10 @@ snapshots:
is-what@5.5.0: {}
is-wsl@3.1.1:
dependencies:
is-inside-container: 1.0.0
isarray@2.0.5: {}
isexe@2.0.0: {}
@@ -8364,12 +8476,6 @@ snapshots:
math-intrinsics@1.1.0: {}
md5@2.3.0:
dependencies:
charenc: 0.0.2
crypt: 0.0.2
is-buffer: 1.1.6
mdn-data@2.12.2: {}
mdurl@1.0.1: {}
@@ -8542,6 +8648,15 @@ snapshots:
dependencies:
mimic-function: 5.0.1
open@11.0.0:
dependencies:
default-browser: 5.5.0
define-lazy-prop: 3.0.0
is-in-ssh: 1.0.0
is-inside-container: 1.0.0
powershell-utils: 0.1.0
wsl-utils: 0.3.1
optionator@0.9.3:
dependencies:
'@aashutoshrathi/word-wrap': 1.2.6
@@ -8940,6 +9055,8 @@ snapshots:
picocolors: 1.1.1
source-map-js: 1.2.1
powershell-utils@0.1.0: {}
prelude-ls@1.2.1: {}
prettier-linter-helpers@1.0.0:
@@ -9116,6 +9233,15 @@ snapshots:
dependencies:
glob: 7.2.3
rollup-plugin-visualizer@7.0.1(rollup@4.59.0):
dependencies:
open: 11.0.0
picomatch: 4.0.4
source-map: 0.7.6
yargs: 18.1.0
optionalDependencies:
rollup: 4.59.0
rollup@4.59.0:
dependencies:
'@types/estree': 1.0.8
@@ -9149,6 +9275,8 @@ snapshots:
rope-sequence@1.3.2: {}
run-applescript@7.1.0: {}
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
@@ -9322,6 +9450,8 @@ snapshots:
source-map@0.6.1:
optional: true
source-map@0.7.6: {}
spark-md5@3.0.2: {}
speakingurl@14.0.1: {}
@@ -9361,6 +9491,11 @@ snapshots:
get-east-asian-width: 1.4.0
strip-ansi: 7.1.0
string-width@8.2.2:
dependencies:
get-east-asian-width: 1.6.0
strip-ansi: 7.2.0
string.prototype.trim@1.2.8:
dependencies:
call-bind: 1.0.2
@@ -9406,6 +9541,10 @@ snapshots:
dependencies:
ansi-regex: 6.0.1
strip-ansi@7.2.0:
dependencies:
ansi-regex: 6.2.2
strip-bom-string@1.0.0: {}
strip-bom@3.0.0: {}
@@ -9677,7 +9816,7 @@ snapshots:
update-browserslist-db@1.0.16(browserslist@4.23.0):
dependencies:
browserslist: 4.23.0
escalade: 3.1.2
escalade: 3.2.0
picocolors: 1.1.1
update-browserslist-db@1.1.0(browserslist@4.23.3):
@@ -10059,6 +10198,11 @@ snapshots:
ws@8.18.3: {}
wsl-utils@0.3.1:
dependencies:
is-wsl: 3.1.1
powershell-utils: 0.1.0
xml-name-validator@4.0.0: {}
xml-name-validator@5.0.0: {}
@@ -10088,6 +10232,8 @@ snapshots:
yargs-parser@21.1.1: {}
yargs-parser@22.0.0: {}
yargs@15.4.1:
dependencies:
cliui: 6.0.0
@@ -10112,6 +10258,15 @@ snapshots:
y18n: 5.0.8
yargs-parser: 21.1.1
yargs@18.1.0:
dependencies:
cliui: 9.0.1
escalade: 3.2.0
get-caller-file: 2.0.5
string-width: 8.2.2
y18n: 5.0.8
yargs-parser: 22.0.0
yocto-queue@0.1.0: {}
yocto-queue@1.1.1: {}

View File

@@ -9,11 +9,42 @@
*
* The `assets:precompile` rake task runs this alongside the main app build.
*/
import { defineConfig } from 'vite';
import { brotliCompressSync, constants, gzipSync } from 'node:zlib';
import { defineConfig, type Plugin } from 'vite';
import path from 'path';
import { aliases } from './vite.shared';
const compressedSdkPlugin = {
name: 'compress-sdk',
generateBundle(_options, bundle) {
const sdkBundle = bundle['js/sdk.js'];
if (sdkBundle?.type !== 'chunk') {
this.error('SDK bundle was not generated');
}
this.emitFile({
type: 'asset',
fileName: 'js/sdk.js.br',
source: brotliCompressSync(sdkBundle.code, {
params: {
[constants.BROTLI_PARAM_QUALITY]: constants.BROTLI_MAX_QUALITY,
},
}),
});
this.emitFile({
type: 'asset',
fileName: 'js/sdk.js.gz',
source: gzipSync(sdkBundle.code, {
level: constants.Z_BEST_COMPRESSION,
}),
});
},
} satisfies Plugin;
export default defineConfig({
plugins: [compressedSdkPlugin],
build: {
rollupOptions: {
output: {

View File

@@ -0,0 +1,34 @@
import path from 'path';
import { defineConfig, type PluginOption } from 'vite';
import { visualizer } from 'rollup-plugin-visualizer';
import { aliases } from './vite.shared';
export default defineConfig({
plugins: [
visualizer({
filename: path.resolve(__dirname, 'tmp/bundle-analysis/sdk.html'),
title: 'Chatwoot SDK bundle analysis',
template: 'treemap',
gzipSize: true,
brotliSize: true,
projectRoot: __dirname,
}) as PluginOption,
],
build: {
outDir: 'tmp/bundle-analysis/sdk',
emptyOutDir: true,
copyPublicDir: false,
rollupOptions: {
output: {
entryFileNames: 'sdk.js',
inlineDynamicImports: true,
},
},
lib: {
entry: path.resolve(__dirname, './app/javascript/entrypoints/sdk.js'),
formats: ['iife'],
name: 'sdk',
},
},
resolve: { alias: aliases },
});

View File

@@ -0,0 +1,38 @@
import path from 'path';
import yaml from '@rollup/plugin-yaml';
import vue from '@vitejs/plugin-vue';
import { defineConfig, type PluginOption } from 'vite';
import { visualizer } from 'rollup-plugin-visualizer';
import { aliases, vueOptions } from './vite.shared';
export default defineConfig({
plugins: [
vue(vueOptions),
yaml(),
visualizer({
filename: path.resolve(__dirname, 'tmp/bundle-analysis/widget.html'),
title: 'Chatwoot widget bundle analysis',
template: 'treemap',
gzipSize: true,
brotliSize: true,
projectRoot: __dirname,
}) as PluginOption,
],
css: {
preprocessorOptions: {
scss: {
api: 'modern-compiler',
},
},
},
publicDir: false,
build: {
outDir: 'tmp/bundle-analysis/widget',
emptyOutDir: true,
copyPublicDir: false,
rollupOptions: {
input: path.resolve(__dirname, './app/javascript/entrypoints/widget.js'),
},
},
resolve: { alias: aliases },
});