Bug description
resources/dist-frontend/js/helpers.js includes marked and DOMPurify. Neither is reachable from anything the bundle exposes. Dropping the single import that carries them takes 71% of the bundle with it.
What the bundle exposes
helpers.js is the file sites drop on their front-end pages with <script src="/vendor/statamic/frontend/js/helpers.js">. Its entry sets window.Statamic to an object with exactly two properties, so everything a visitor's browser can reach goes through one of them:
$conditions.showField(conditions, data, currentFieldPath), which constructs a Validator and returns passesConditions(). That path uses Converter, ParentResolver, Constants, data_get, and isObject / intersection from lodash-es.
$passkeys, with configure, authenticate, register, cancel and initAutofill. That path uses startAuthentication, startRegistration, browserSupportsWebAuthn and WebAuthnAbortService from @simplewebauthn/browser.
Nothing on either path renders Markdown or sanitizes HTML.
How they get in
Both come in through a single import. field-conditions/Validator.js takes data_get from bootstrap/globals.js, and globals.js also imports renderMarkdown from util/markdown.js, which imports marked and dompurify.
Asking for one named export (data_get) still pulls the whole module (globals.js) into the graph.
The unused branches (everything in globals.js except data_get, and everything those exports import in turn) then only drop out if the bundler (Rolldown, under Vite 8) can prove they're free of side effects.
The bundler can't prove marked and dompurify are free of side effects. Neither declares "sideEffects": false, so it has to assume importing them does work. It does: marked's ESM build runs var markedInstance = new Marked() at module scope, and dompurify's runs var purify = createDOMPurify(). Both survive into the shipped file. markedInstance is in it, and so are DOMPurify's IS_ALLOWED_URI and SAFE_FOR_TEMPLATES.
data_get is three statements, and Validator.js is the only public code that touches globals.js. Converter.js imports just Constants.js, and ParentResolver.js imports nothing. DOMPurify also comes in through PreviewHtml.js, but globals.js imports that too, so it goes the same way.
What it costs
Building with vite-frontend.config.js against the v6.26.0 sources, at the versions your package-lock.json pins, reproduces the published file exactly: 98,735 bytes. Moving data_get into a leaf module and pointing Validator.js at it, changing nothing else:
|
Raw |
Gzipped |
| As shipped |
98,735 |
30,404 |
With data_get moved |
28,190 |
8,429 |
That's 71% off the file and 72% off the wire, from one import line. It still exports $conditions and $passkeys, and there's no marked or DOMPurify left in it.
The whole saving is those two libraries. Stub out the two modules that import them, leaving the barrel import in place, and you still get 28,190. Everything else globals.js touches is shaking out fine.
For conditional fields, the docs say to put the script in your layout, so this lands on every page of the site. For passkeys, it's the login page. Either way, the visitor downloads both libraries.
How to reproduce
Confirm the libraries are in the published file:
grep -c lheading public/vendor/statamic/frontend/js/helpers.js
grep -c IS_ALLOWED_URI public/vendor/statamic/frontend/js/helpers.js
Both match. blockquote, fences, gfm, markedInstance and SAFE_FOR_TEMPLATES are in there too.
Confirm nothing on the public side calls them: search resources/js/frontend/ for anything invoking markdown() or a sanitizer. Nothing does. The only two exports are $conditions and $passkeys.
To reproduce the sizes, run npm run frontend-build for the baseline, then move data_get verbatim into a leaf module and repoint Validator.js at it:
// resources/js/components/field-conditions/Validator.js
- import { data_get } from '../../bootstrap/globals.js';
+ import { data_get } from '../../util/data_get.js';
Build again and compare.
Logs
Nothing is logged. This is a bundling issue rather than a runtime error.
Environment
Environment
Laravel Version: 13.24.0
PHP Version: 8.4.20
Composer Version: 2.10.1
Environment: local
Debug Mode: ENABLED
Maintenance Mode: OFF
Timezone: UTC
Locale: en
Cache
Config: NOT CACHED
Events: NOT CACHED
Routes: NOT CACHED
Views: NOT CACHED
Drivers
Broadcasting: log
Cache: file
Database: sqlite
Logs: stack / single
Mail: smtp
Queue: sync
Session: file
Storage
C:\Users\<redacted>\Herd\uams-statamic-b\public\storage: NOT LINKED
Statamic
Addons: 9
License Key: Not set
Sites: 255 (list redacted)
Stache Watcher: Enabled (auto)
Static Caching: Disabled
Version: 6.26.0 PRO
Statamic Addons
el-schneider/statamic-admin-bar: 0.3.2
jacksleight/statamic-bard-mutator: 3.0.5
jacksleight/statamic-bard-texstyle: 4.1.1
mitydigital/sitemapamic: 3.5.1
statamic/eloquent-driver: 5.10.0
statamic/ssg: 4.1.0
stillat/relationships: 2.4.0
studio1902/statamic-peak-tools: 9.2.0
uams-web/wordpress-importer: 0.10.0
Statamic Eloquent Driver
Addon Settings: file
Asset Containers: file
Assets: eloquent
Blueprints: file
Collection Trees: file
Collections: file
Entries: file
Fieldsets: file
Form Submissions: file
Forms: file
Global Sets: file
Global Variables: file
Navigation Trees: file
Navigations: file
Revisions: file
Sites: file
Taxonomies: file
Terms: file
Tokens: file
Installation
Existing Laravel app
Additional details
Moving data_get somewhere leaf-level, or having Validator.js take it from a module that doesn't reach util/markdown.js, looks like it would be enough.
Bug description
resources/dist-frontend/js/helpers.jsincludesmarkedandDOMPurify. Neither is reachable from anything the bundle exposes. Dropping the single import that carries them takes 71% of the bundle with it.What the bundle exposes
helpers.jsis the file sites drop on their front-end pages with<script src="/vendor/statamic/frontend/js/helpers.js">. Its entry setswindow.Statamicto an object with exactly two properties, so everything a visitor's browser can reach goes through one of them:$conditions.showField(conditions, data, currentFieldPath), which constructs aValidatorand returnspassesConditions(). That path usesConverter,ParentResolver,Constants,data_get, andisObject/intersectionfromlodash-es.$passkeys, withconfigure,authenticate,register,cancelandinitAutofill. That path usesstartAuthentication,startRegistration,browserSupportsWebAuthnandWebAuthnAbortServicefrom@simplewebauthn/browser.Nothing on either path renders Markdown or sanitizes HTML.
How they get in
Both come in through a single import.
field-conditions/Validator.jstakesdata_getfrombootstrap/globals.js, andglobals.jsalso importsrenderMarkdownfromutil/markdown.js, which importsmarkedanddompurify.Asking for one named export (
data_get) still pulls the whole module (globals.js) into the graph.The unused branches (everything in
globals.jsexceptdata_get, and everything those exports import in turn) then only drop out if the bundler (Rolldown, under Vite 8) can prove they're free of side effects.The bundler can't prove
markedanddompurifyare free of side effects. Neither declares"sideEffects": false, so it has to assume importing them does work. It does:marked's ESM build runsvar markedInstance = new Marked()at module scope, anddompurify's runsvar purify = createDOMPurify(). Both survive into the shipped file.markedInstanceis in it, and so are DOMPurify'sIS_ALLOWED_URIandSAFE_FOR_TEMPLATES.data_getis three statements, andValidator.jsis the only public code that touchesglobals.js.Converter.jsimports justConstants.js, andParentResolver.jsimports nothing. DOMPurify also comes in throughPreviewHtml.js, butglobals.jsimports that too, so it goes the same way.What it costs
Building with
vite-frontend.config.jsagainst the v6.26.0 sources, at the versions yourpackage-lock.jsonpins, reproduces the published file exactly: 98,735 bytes. Movingdata_getinto a leaf module and pointingValidator.jsat it, changing nothing else:data_getmovedThat's 71% off the file and 72% off the wire, from one import line. It still exports
$conditionsand$passkeys, and there's no marked or DOMPurify left in it.The whole saving is those two libraries. Stub out the two modules that import them, leaving the barrel import in place, and you still get 28,190. Everything else
globals.jstouches is shaking out fine.For conditional fields, the docs say to put the script in your layout, so this lands on every page of the site. For passkeys, it's the login page. Either way, the visitor downloads both libraries.
How to reproduce
Confirm the libraries are in the published file:
Both match.
blockquote,fences,gfm,markedInstanceandSAFE_FOR_TEMPLATESare in there too.Confirm nothing on the public side calls them: search
resources/js/frontend/for anything invokingmarkdown()or a sanitizer. Nothing does. The only two exports are$conditionsand$passkeys.To reproduce the sizes, run
npm run frontend-buildfor the baseline, then movedata_getverbatim into a leaf module and repointValidator.jsat it:Build again and compare.
Logs
Nothing is logged. This is a bundling issue rather than a runtime error.
Environment
Installation
Existing Laravel app
Additional details
Moving
data_getsomewhere leaf-level, or havingValidator.jstake it from a module that doesn't reachutil/markdown.js, looks like it would be enough.