react 0.7.0
This release published @cookieyes/react@0.7.0, @cookieyes/nextjs@0.5.4, @cookieyes/core@0.6.0 and @cookieyes/scripts@0.2.1.
Install
Minor changes
Load the integration runner on demand. It is the largest single subsystem in the package and does nothing unless integrations is configured, which most consumers never do. Measured with pnpm size: the compressed initial download drops 1.20 KB, taking @cookieyes/core from 8.42 KB to 7.41 KB over an empty Next.js app.
This is a deferral, not a deletion, and the size report says so. initial falls by 1.20 KB; total rises by 0.42 KB, because the runner now ships as its own chunk plus the machinery to fetch it. Someone who configures integrations downloads slightly more in total, just not before first paint. Both numbers are budgeted separately from now on so a future change cannot look like a saving while only moving bytes around.
New: integrationsReady on the runtime, a promise that resolves once configured integrations have been loaded and wired up. It resolves immediately when none are configured. This exists because the deferral is observable: for a short window after setup, getIntegrations() returns [] and no integration has been set up. Making that window awaitable is better than leaving it as a race: the two tests that caught this were asserting against a fixed setTimeout(0), which the chunk load outruns, and a test that guesses a tick count passes or fails on machine speed.
Consent gating is unaffected: an integration cannot run before its category is granted whether or not it has loaded yet. Nothing loads that would not have loaded.
Two things were needed to make this work at all, and both are easy to get wrong:
integrations.tsis now a separate build entry. Without that, Rollup flattens the dynamic import back into the main chunk: the barrel inindex.tsre-exportsrunIntegrations, which keeps the module statically reachable. The first attempt did exactly this and madedist/index.js317 bytes larger while emitting no second chunk.- Core exposes
_loadIntegrations()(@internal) so@cookieyes/reactcan defer the same module without a static import ofrunIntegrations. A dynamicimport("@cookieyes/core")from the adapter would pull the whole barrel and defeat the split.
runIntegrations, warnOverlappingVendors and warnUnknownCategories remain exported from the package root; nothing is removed from the public API.
One behaviour change to be aware of: the overlapping-vendor and unknown-category warnings are emitted when the chunk arrives rather than during setup, so they appear a tick later in the console.
In @cookieyes/core.
The network blocker now ships as its own entry point, @cookieyes/core/network-blocker, so customers who do not use it no longer download it.
Action required if you configure networkBlocker. Add one import before your setup call:
import { registerNetworkBlocker } from "@cookieyes/core/network-blocker";
registerNetworkBlocker();Configuring networkBlocker without registering logs an error naming the missing import and blocks nothing. That is a deliberate choice over throwing, taking the page down is not proportionate, but it means the console is the only thing that surfaces it, so check after upgrading. The config shape itself is unchanged.
Measured saving (pnpm size, compressed delta over an empty Next.js app): @cookieyes/core 7.41 KB → 6.95 KB, the React layer 15.41 KB → 14.92 KB. Unlike the integration-runner split, total falls too: this is a genuine deletion from the bundle, not a deferral. Verified by a bundle breakdown rather than inferred: onRequestBlocked, logBlockedRequests, pathIncludes, _cyUrl, sendBeacon and the blocked-request message are all absent from a build that never registers it.
Why a separate entry point and not a dynamic import. The obvious reading of "load it only when it is used" is import(), and it is the wrong one here. The blocker exists to have the browser's networking already replaced when the page starts; between the page starting and a chunk arriving, nothing is patched and an early-firing tag gets through. That is not a performance regression, it is a hole in the thing the feature does, on a compliance product: for about 600 bytes. A separate entry point saves the same bytes and, because it is reached by an ordinary static import, the blocker is loaded before setup runs and patches immediately. Timing is unchanged for anyone who uses it.
Behaviour is otherwise identical. All four transports, fetch, XMLHttpRequest.prototype.open/send and navigator.sendBeacon, are still replaced and still restored on uninstall, now covered by a test that asserts all four in both directions.
One related fix: installNetworkBlocker now records its own teardown, so resetConsentRuntime() un-patches the transports whether the blocker was installed through config or by a direct call. Previously a direct call left them patched after a reset, and because a second install is a silent no-op while one is active, the next setup would have run on the old rules and the old consent closure while appearing to accept new ones.
installNetworkBlocker, uninstallNetworkBlocker and the NetworkBlocker* types remain exported from the package root as well, so direct callers are unaffected. @cookieyes/react and @cookieyes/nextjs still do not re-export them; the config key is the only path there.
In @cookieyes/core, @cookieyes/react.
Take a further 2.37 KB of gzip out of the initial download: the banner goes from 17.24 KB to 14.87 KB over an empty Next.js app, and banner + preferences + recall from 17.78 KB to 15.41 KB.
Integration runner deferred (≈2.1 KB). The adapter now loads it through core's _loadIntegrations() instead of importing runIntegrations statically. That static import was the reason splitting the runner in core alone changed this layer's measurement by nothing: core emitted a separate chunk and the adapter pulled it straight back in. integrationsReady is exposed on the runtime for the same reason it is on core's; see that package's changeset.
Developer diagnostics stripped from production builds (≈0.27 KB). Three checks, the CSP-violation listener, the untested-React-version warning, and the WCAG contrast check on a configured theme, are now behind process.env.NODE_ENV !== "production" at their call sites, not inside their function bodies. Guarding the call sites is what makes them removable: with nothing referencing them, the bundler drops the functions, their de-dupe sets, their message strings and contrastRatio entirely, rather than keeping empty shells. Verified absent from a production bundle.
All three are unchanged in development and in tests. None of them does anything a visitor can act on.
Worth recording for anyone tempted to chase this further: the three diagnostic modules are 7.4 KB of source and worth only 0.27 KB compressed, because most of that is comments and the contrast checker shares tokens.ts. Source size is a poor guide to shipped size, which is why every figure here comes from pnpm size (tools/size/README.md) rather than from reading the files.
In @cookieyes/react.
Patch changes
Stop shipping deprecation-warning text to visitors. The two warnings, mode: "offline" and the builtInIntegrations field, now return immediately when process.env.NODE_ENV is "production", so a production bundler folds the check and drops the message strings as dead code. Measured with pnpm size: 224 bytes of gzip out of core, 175 out of the interface layer.
The warnings are unchanged in development, in tests, and anywhere NODE_ENV is not production. Nothing about who gets warned changes: a deprecation warning is for whoever is writing the integration, and a visitor can neither act on one nor see it.
The guard is deliberately written as the bare literal process.env.NODE_ENV === "production" at the top of each function rather than hoisted into a shared constant, because that is the form bundlers recognise and replace. Two forms that read as equivalent were tried and measured first, and neither works: hoisting it into a file-level const DEV folds the constant but leaves every guarded body intact, and wrapping it as typeof process === "undefined" || process.env.NODE_ENV !== "production" stops the expression folding altogether; that version made core 10 bytes larger and kept every warning string in the bundle. The behaviour is now asserted in deprecations.test.ts, since the source looks correct in all three cases and only measurement tells them apart.
The consequence is that these functions need process to exist, which in practice means a bundler or Node. That is already true of every supported install path, the package advertises only import/require conditions and no browser-global build, and it is the same trade React makes for the same reason.
In @cookieyes/core.
Re-export registerNetworkBlocker from @cookieyes/react and @cookieyes/nextjs, so the networkBlocker config key on those packages is actually usable from those packages.
Moving the network blocker to @cookieyes/core/network-blocker left a hole: the docs told React and Next.js users to import registration from @cookieyes/core, and for most of them that import does not resolve. Under pnpm's strict layout a consumer who installed only @cookieyes/react has no @cookieyes/core in their node_modules root, so the documented instruction fails with MODULE_NOT_FOUND, while networkBlocker is advertised on the React package's own config type.
It went unnoticed because apps/web carries @cookieyes/core as a devDependency, so the documentation examples typechecked in an environment no consumer has.
Import registerNetworkBlocker from whichever package you installed. Only the registration function is re-exported; installNetworkBlocker/uninstallNetworkBlocker stay at the core subpath, so the config key remains the only declarative path on the adapters. Measured with pnpm size: no change to either layer; the blocker still reaches a bundle only if the export is used.
In @cookieyes/nextjs, @cookieyes/react.
Take 1.11 KB of gzip out of the banner, with no visual, behavioural or API change.
The "Powered by CookieYes" wordmark (786 bytes). It is an inline SVG of the letterforms, and it arrived from a design tool carrying about fourteen significant digits per coordinate, 5.48703 1.81738C8.08615 3.20915…, for a mark whose viewBox is 78×13 and which renders at 78 CSS pixels. Every one of those digits is a shipped byte on every page load, in all three components that render the badge. Rounding the path data to two decimals moved the furthest point by 0.005 of a viewBox unit: a two-hundredth of a CSS pixel, under a third of a device pixel even at 3× DPR. The badge is unchanged, still on by default, and there is no new configuration.
Gating the badge behind a config flag was considered first and measured at zero saving, which is the point worth recording: a runtime flag cannot remove the bytes, because the icon stays imported and therefore stays in the bundle regardless of what the flag says. Only making the asset smaller, or not shipping it at all, which is a commercial decision and not this change, moves the number. icon-precision.test.ts now fails if long decimals come back, because re-exporting the asset restores them, the diff reads as a routine asset update, and nothing about the rendered banner looks different.
Rounding to one decimal was measured too, at a further 0.36 KB. It is not taken here: 0.05px of drift per letter is still invisible, but this is a brand wordmark, and that is the brand owner's call rather than a size decision.
The builder deprecation warning (325 bytes). createCookieYes()'s deprecation message is now dropped from production bundles by the same process.env.NODE_ENV guard @cookieyes/core uses, and for the same reason: see that package's changeset for why the guard is written the way it is. Unchanged in development and in tests.
Both figures are from pnpm size, which measures the compressed client-JS delta against an empty Next.js app; see tools/size/README.md.
In @cookieyes/react.
Source
Read the full diff and commit history for react 0.7.0 on GitHub.