@petschki Congrats! Looks nice! Just curious, are you using @plone/client for the client calls?
No, but it would be interesting to implement this here. Is there a react dependency when using @plone/client ?
No react direct deps. Some other people already is using it with Svelte.
yes i was using it too for my Sveltekit prototype.
Before we change that i would like to analise the preos and cons compaired to a direct fetch, we have right now. Or is plone client using fetch as well? I remember that was not the case in the past.
but yeah using plone/client might shrink our coe base a bit down.
hm, from what claude tells me, Iβm voting against it for now.
Maybe we can make the client more lightwight and maybe also using modern fetch and TUS.
I see the benefits if we could use it, but it is a lot of weight right now and also contains code we canβt even use. The validation layer we should think about adapting, maybe that could be also more modular, so we could just use that.
Iβm open for suggestions.
Should pat-filemanager adopt @plone/client?
Short answer: No β it would grow the bundle by ~27β42 KB gzip, not shrink it, and it can't even use the library's main value (the React-Query hooks) because our patterns are Svelte.
- What we have today
pat-filemanager has its own thin restapi layer in src/api/ β 8 files, 744 lines (mostly JSDoc):
βββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββββββββββ¬βββββββββββ
β File β Purpose β min+gzip β
βββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββ€
β client.js β fetch wrapper (the whole transport) β 0.62 KB β
βββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββ€
β contents.js β @querystring-search + criteria builders β 0.55 KB β
βββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββ€
β operations.js β @move/@copy/DELETE/PATCH/ordering/linkintegrity β 0.57 KB β
βββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββ€
β querystring.js β @querystring config + pure option-shaping helpers β 0.45 KB β
βββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββ€
β breadcrumbs.js β @breadcrumbs + multilingual trail rebuild β 0.34 KB β
βββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββ€
β workflow.js β @workflow β 0.38 KB β
βββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββ€
β vocabularies.js β @vocabularies β 0.19 KB β
βββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββ€
β upload.js β resumable @tus-upload + base64 fallback β 1.18 KB β
βββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββββββββββ΄βββββββββββ
Total β 3.5 KB gzip (measured with terser). It is already pure fetch, same-origin cookie auth, no jQuery.
- What @plone/client actually is
Package: @plone/client (repo plone/plone.restapi-client), current 1.0.0-alpha.10. Its production dependencies:
@tanstack/react-query axios query-string zod peerDependencies: react, react-dom
Three findings that decide the question:
- It does NOT use fetch β it uses axios. src/API.ts is import axios from 'axios'; evequery-string for serialization, and each endpoint runs a zod .parse() for runtimevalidation. 2. The public entry is React-coupled. index.ts exports only the PloneClient class β clh statically imports useQuery/useMutation from @tanstack/react-query. So import β¦ from'@plone/client' unconditionally pulls react-query (and React) into the bundle. There are framework-agnostic raw functions (postQuerystringSearch({data, config})), but they're only reachable viaimports (@plone/client/src/restapi/β¦) β fragile, unsupported usage.
- The hooks are unusable for us anyway. useGetContentQuery etc. require a React render tree. Our patterns are Svelte 5, so we could only ever call the raw async functions β meaning react-queryadd zero functional value while still costing bundle weight.
Plus two integration mismatches:
- No @tus-upload endpoint. @plone/client only has content/add (base64 POST). Our upload.js β the single biggest API file, with chunked resumable upload β has no equivalent and must stay custom regardless.
- It hard-codes a /++api++/ traversal into every URL (getBackendURL), whereas we call context-relative endpoints directly.
- Bundle-size math (min+gzip)
mockup builds one always-loaded bundle.min (from src/index.js); splitChunks only carves out async chunks for tinymce/datatables/select2/jquery. Any new dependency lands in that synchronously-loaded main bundle.
βββββββββββββββββββββββββ¬ββββββββββββββββββββ
β Dependency β min+gzip (approx) β βββββββββββββββββββββββββΌββββββββββββββββββββ€
β axios β ~13.7 KB β βββββββββββββββββββββββββΌββββββββββββββββββββ€
β zod β ~13 KB β βββββββββββββββββββββββββΌββββββββββββββββββββ€
β query-string β ~3 KB β βββββββββββββββββββββββββΌββββββββββββββββββββ€
β @tanstack/react-query β ~12 KB β βββββββββββββββββββββββββΌββββββββββββββββββββ€
β react β ~6.4 KB β βββββββββββββββββββββββββ΄ββββββββββββββββββββ
Scenario A β current pat-filemanager only:
- Replace ~2.2 KB of replaceable custom code (the tus upload + the pure option-shaping/breadcrumb-trail helpers have no library equivalent, so ~1.3 KB stays). - Via the supported main entry: +~42 KB (axios+zod+qs+react-query+react) β 2.2 KB β +4
- Via deep raw-function imports (axios+zod+qs only β 30 KB): +~28 KB gzip.
Scenario B β shared across pat-filemanager + pat-contentbrowser ("count the deps once"): - The deps are paid once either way (~30 KB deep-import / ~44 KB main entry). - The custom code they'd replace is only ~2.2 KB (filemanager) + ~1.2 KB (contentbrowser request layer) β 3.4 KB. - Net is still β +27 KB gzip, and it requires refactoring pat-contentbrowser off its lbulary JSON view onto @querystring-search first.
The "share it across two components" idea is sound in principle, but it can't rescue tg deduplicated (our custom code) is ~3 KB, while the thing being added(axios+zod+react-query) is an order of magnitude larger.
- Benefits vs downsides
Benefits of adopting: - Maintained, typed (zod-validated) client tracking restapi changes upstream β less fo
- Shared endpoint knowledge with Volto; consistent query/mutation shapes. - Runtime response validation (zod) we don't have today.
Downsides: - +27β42 KB gzip to the always-loaded bundle β opposite of the hoped-for shrink. - Brings axios (we're fetch-only today) and react/react-query that Svelte can't use.
- Still alpha (1.0.0-alpha.10) with a React-coupled public API; framework-agnostic use means unsupported deep imports. - Doesn't cover tus upload β our biggest file stays custom either way.
- The /++api++/ URL assumption needs working around for Classic Plone.
Recommendation
Keep the custom fetch layer. It's ~3.5 KB, already framework-agnostic, fetch-based, covers tus upload, and matches our Svelte/no-jQuery direction. @plone/client would add bundle weight and a React/axios dependency tree for benefits (typing, upstream maintenance) that don't outweigh the co
If you want the maintenance/typing upside without the weight, the better move is to exch-based restapi helper that both pat-filemanager and pat-contentbrowser import β thatgenuinely dedupes (and the contentbrowserβ@querystring-search refactor is worth doing on its own merits), at roughly 3β4 KB shared instead of +27 KB.
I analized what could be improved, to make it more attractive for other projects like this:
I've now read the transport (API.ts), the class (client.ts), the entry (index.ts), the React provider, the config schema, and representative query/mutation endpoints. Here's what would make @plone/client actually attractive for a Svelte pattern like this one.
The key insight: the core is almost framework-agnostic already
Every endpoint ships three layers:
getContent(args) // 1. raw async fn β pure, no React
getContentQuery(args) β ({ queryKey, queryFn }) // 2. TanStack descriptor β pure, no React
useGetContentQuery(args) // 3. React hook β wraps useQuery
Layers 1 and 2 have zero React dependency, and layer 2 returns a plain TanStack {queryKey, queryFn} descriptor β which @tanstack/svelte-query consumes unchanged:
const query = createQuery(getContentQuery({ path, config }));
So the library is much closer to Svelte-usable than its packaging suggests. The blockers are all packaging and dependency choices, not architecture. That's the framing for the recommendations: don't rewrite the core, unbundle it.
Recommendations, by impact
- Decouple the core from React (the unblocker)
Today index.ts exports only the PloneClient class β client.ts β utils/misc.ts, which imports @tanstack/react-query at module top level. So import β¦ from '@plone/client' drags in react-query and React even if you only want one raw function.
- Ship a framework-agnostic entry β @plone/client (or @plone/client/core) β exporting the raw functions and the *Query/*Mutation descriptor factories with no react-query import.
- Move queryHookFromQuery / use* hooks and the provider into @plone/client/react.
- Optionally add a paper-thin @plone/client/svelte that re-exports createQuery/createMutation bindings.
This alone removes ~18 KB (react + react-query) for a Svelte consumer.
- Replace the monolithic class with tree-shakeable named exports
client.ts statically imports every endpoint (actions, addons, controlpanels, workingcopy, β¦). Using the PloneClient class therefore bundles the entire REST API surface. For us that's dozens of endpoints to use ~6.
- Make functional named exports the primary API (import { postQuerystringSearch, getBreadcrumbs } from '@plone/client').
- Add "sideEffects": false to package.json (currently absent) so bundlers can drop unused endpoints and zod schemas.
A consumer using only @querystring-search + @breadcrumbs + @workflow should pay for only those.
- Make the transport pluggable, default to fetch
API.ts hard-wires axios and does axios.create() per request. Everything axios provides here is trivial with fetch: validateStatus β check response.ok; paramsSerializer (query-string) β URLSearchParams; interceptors β a wrapper.
- Abstract apiRequest behind a transport interface and let consumers inject one; default to a fetch transport.
- Dropping axios + query-string saves ~16 KB gzip and makes it fetch-native β matching what this pattern already does.
- Support Classic-Plone auth and URL conventions
The config schema is only { apiPath, token? }, and getBackendURL hard-codes /++api++/. That's Volto/headless-shaped. In-page mockup patterns call context-relative endpoints with the session cookie.
- Add credentials: 'same-origin' (cookie session) as an auth mode alongside bearer token; optional CSRF token header.
- Make the traversal suffix configurable (apiSuffix: '' | '/++api++') or allow a base URL used verbatim, so context-relative calls work without forced ++api++.
- Make zod validation optional
Every call runs schema.parse(args). zod is ~13 KB and not tree-shakeable, plus per-call CPU β and it's validating our own outgoing args, low value in production.
- Gate .parse() behind a dev flag, or ship a validation-free production build, or move to TS-types-only / a tree-shakeable validator.
- Add the endpoint we can't drop: @tus-upload
The library has only content/add (base64 POST). This pattern's biggest API file is resumable chunked @tus-upload. Without it, we'd keep custom upload code regardless β so a @tus-upload helper would close the last gap.
What this buys us
With #1β#3 done, a Svelte consumer o@plone @plone/client would pull roughly fetch (0) + zod (~13 KB, or 0 with #5) + the handful of endpoints used (~1β2 KB) instead of today's ~28β42 KB axios+zod+react-query+react. At that point the trade flips: you'd get a maintained, upstream-tracked, optionally-typed client at a size comparable to β and shared more widely than β the ~3.5 KB of custom code, which is the case for adopting it.
The single highest-leverage change is #1 (a React-free entry). #2 and #3 turn "acceptable" into "lighter than what we hand-roll." If you want, I can write this up as a GitHub issue against plone/plone.restapi-client with the concrete exports-map and transport-interface proposal.
@MrTango You pointed the machine to 1.0.0-alpha. We never did a final release of the 1.x series, since it was βexperimentalβ, and since then, we improved it a lot!! Point the machine to the Aurora repo main branch and try again (2.x series, also in alpha).
The Axios choice, we can discuss it. From the discord channel:
Fetch vs Axios was always a question on the table. We opted for Axios because back in the day, there were still some oddities in the Browser/Node support for Fetch, and Axios already had this settled and from my point of view the API is far more human, let's say... Like using requests or the built in http in Python
But I guess it's open for discussion. I remember @pnicolli and @Rob Gietema made me the same exact question back in the day
thx, updated analysis:
looks much better already. But i would like to address more of the optimization steps, before rebuilding it in the pattern.
Updated Analysis 1 β Adopt @plone/client in pat-filemanager? (v2 / aurora)
The gap narrowed a lot, but the answer for filemanager alone is still no β just by less.
Revised bundle math (min+gzip), what v2 unavoidably pulls:
βββββββββββββββββββββββ¬βββββββββββββββββββββββ
β Dep β ~gzip β
βββββββββββββββββββββββΌβββββββββββββββββββββββ€
β axios ^1.15 β ~14 KB β
βββββββββββββββββββββββΌβββββββββββββββββββββββ€
β zod ^3.22 β ~13 KB β
βββββββββββββββββββββββΌβββββββββββββββββββββββ€
β query-string ^9 β ~2.5 KB β
βββββββββββββββββββββββΌβββββββββββββββββββββββ€
β debug β ~1 KB β
βββββββββββββββββββββββΌβββββββββββββββββββββββ€
β react / react-query β 0 (no longer pulled) β
βββββββββββββββββββββββΌβββββββββββββββββββββββ€
β Total β ~30 KB β
βββββββββββββββββββββββ΄βββββββββββββββββββββββ
That's down from v1's ~50 KB β react + react-query (~19 KB) are gone. But it still addKB of custom fetch code in the always-loaded bundle.min.
Remaining frictions, all still present in v2:
- Still axios, not fetch β we're fetch-only today; this re-introduces a transport dependency.
- zod still runs on every call and ships in full (~13 KB).
- No tree-shaking: the PloneClient class does import * as restapi and references all ~40 endpoints, sideEffects is unset, single bundled entry β so using ~6 endpoints still bundles all 40 + all schemas.
- Still no @tus-upload β filemanager's resumable upload (its biggest API file) stays c
What genuinely improved for us: apiSuffix: '' lets it do context-relative Classic-Plon the pure-promise method API (client.getContent(...)) drops straight into Svelte (wrapin @tanstack/svelte-query's createQuery yourself, or just call it).
The real swing factor: ~27 KB is only a "cost" because we'd be the ones adding it. If plone.staticresources eventually ships @plone/client in the global bundle (plausible as Plone's official client
matures), our incremental cost drops to ~0 and adoption flips to clearly worthwhile β rolled layer is the worse choice. Worth tracking, not adopting today for filemanager in isolation.
Updated Analysis 2 β Improvements to make it attractive for Svelte (v2 status)
v2 already delivered the top recommendation. Re-scored:
- #1 Decouple React/react-query β
DONE. react-query removed; hooks moved out; clienllow-up: drop the now-vestigial react/react-dom peerDependencies so they stop showingup in dependency audits and attw checks. - #5 Classic-Plone URL/auth β
PARTIAL. config.apiSuffix added (great). Still missine-origin' auth mode β config only supports a bearer token, which doesn't fit in-pagemockup usage on a logged-in Plone session. - #3 Pluggable transport / fetch default β
not done. Still import axios from 'axioseate(). A fetch default (status check + URLSearchParams) would drop axios (~14 KB) +query-string (~2.5 KB) and match our stack. Biggest remaining size lever. - #4 Optional zod β
not done. Every endpoint still schema.parse()-es our own outgoiag or ship a validation-free prod build (~13 KB + per-call CPU). - #2 Tree-shakeable packaging β
not done. Monolithic PloneClient (import * as restapi) + no "sideEffects": false + single entry = all 40 endpoints always bundled. Per-endpoint named exports + side-effect-free ESM would let a 6-endpoint consumer pay for 6. - #6 @tus-upload β
not done. Still absent; until then we keep custom resumable upload no matter what.
Net for v2: with #3 (fetch) + #2 (tree-shaking) + #4 (optional zod), a Svelte consumer of @plone/client would drop from ~30 KB to roughly the handful of endpoints used (~2 KB) + optional zod. Combined with the React decoupling already shipped, that's the point where adopting it beats halone/types-typed, upstream-tracked client at a size comparable to our custom code. Thehighest-leverage remaining fix is now #3 (fetch transport), with #2 (tree-shaking) close behind.
Memory updated to point at plone/aurora packages/client v2.0.0-alpha.4 with the revised ~30 KB / ~27-KB-net figures and the recommendation scorecard. Want me to open an issue on plone/volto (the v2 repository field still points there) covering the three undone items β fetch transportional zod β plus cookie auth and dropping the vestigial React peer deps?
I always do opt for fetch. Is axios APIβ really easier to read and understand? AFAIKβ fetch requires you to do one step more
async function something() {
try {
const request = await fetch(url);
const result = await request.json();
} catch(e) {
log.error("...");
}
return result;
}
The only difference is the extra call to request.json(), no?
One advantage of axios is that you can configure it centrally. E.g. base urls, headers, etc, like youβre doing in @plone/client api.ts. Well, with that helper library pattern fetch could also be used.
Anyways, using @plone/client would make much sense!
I've started to analyze the implementation here
The existing replacements are already done. The missing gaps are analyzed here: