@decocms/blocks 7.0.2 → 7.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/blocks",
3
- "version": "7.0.2",
3
+ "version": "7.1.1",
4
4
  "type": "module",
5
5
  "description": "Deco framework-agnostic core: CMS block resolution, section registry, matchers, portable SDK utilities",
6
6
  "repository": {
@@ -12,6 +12,7 @@
12
12
  "exports": {
13
13
  ".": "./src/index.ts",
14
14
  "./cms": "./src/cms/index.ts",
15
+ "./cms/client": "./src/cms/client.ts",
15
16
  "./cms/loadDecofileDirectory": "./src/cms/loadDecofileDirectory.ts",
16
17
  "./hooks": "./src/hooks/index.ts",
17
18
  "./types": "./src/types/index.ts",
@@ -92,6 +93,7 @@
92
93
  "devDependencies": {
93
94
  "@types/react": "^19.0.0",
94
95
  "@types/react-dom": "^19.0.0",
96
+ "esbuild": "^0.27.3",
95
97
  "knip": "^5.86.0",
96
98
  "typescript": "^5.9.0"
97
99
  },
@@ -0,0 +1,44 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { execFileSync } from "node:child_process";
3
+ import { fileURLToPath } from "node:url";
4
+ import { dirname, join } from "node:path";
5
+
6
+ const here = dirname(fileURLToPath(import.meta.url));
7
+ // esbuild's JS API relies on `new TextEncoder().encode("") instanceof
8
+ // Uint8Array` internally, which fails across Vitest's module-isolation VM
9
+ // boundary (a realm mismatch, not a real environment problem — `bun -e`
10
+ // confirms the invariant holds outside Vitest). Shelling out to the esbuild
11
+ // CLI binary sidesteps this: it's a separate OS process, no shared JS realm.
12
+ const esbuildBin = join(here, "../../../../node_modules/.bin/esbuild");
13
+
14
+ function bundleForBrowser(entry: string) {
15
+ return execFileSync(
16
+ esbuildBin,
17
+ [entry, "--bundle", "--platform=browser", "--format=esm", "--external:react"],
18
+ { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] },
19
+ );
20
+ }
21
+
22
+ /**
23
+ * Regression test for the bug that motivated splitting `client.ts` out of
24
+ * `index.ts`: importing ANYTHING from the full `cms` barrel forces a
25
+ * bundler to evaluate `loader.ts` and `middleware/observability.ts`, which
26
+ * import `node:async_hooks` at the module level. Turbopack rejects that
27
+ * outright when bundling for a browser target; this test catches the same
28
+ * class of failure in CI, deterministically, without needing a real Next.js
29
+ * app to reproduce it.
30
+ */
31
+ describe("cms/client browser bundle", () => {
32
+ it("bundles for a browser target with no Node built-ins", () => {
33
+ const output = bundleForBrowser(join(here, "client.ts"));
34
+ expect(output).not.toMatch(/node:async_hooks|node:fs|node:path/);
35
+ });
36
+
37
+ it("sanity check: the full cms barrel does NOT bundle for a browser target", () => {
38
+ // Confirms the test above is actually discriminating — if this ever
39
+ // stops throwing, either the barrel no longer has the leak (great,
40
+ // update this test) or esbuild's Node-builtin detection changed
41
+ // (investigate before trusting the test above).
42
+ expect(() => bundleForBrowser(join(here, "index.ts"))).toThrow();
43
+ });
44
+ });
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Client-safe subset of `@decocms/blocks/cms`.
3
+ *
4
+ * `@decocms/blocks/cms` (the full barrel in `./index.ts`) re-exports
5
+ * `loader.ts` and `resolve.ts`, which transitively import `node:async_hooks`
6
+ * and `node:fs/promises`. Bundling ANY export from that barrel for a browser
7
+ * target — even one that itself has zero Node dependencies, like
8
+ * `getResolvedComponent` — drags the whole module graph in, since ES module
9
+ * imports are evaluated at the file level, not per-export. Turbopack rejects
10
+ * this outright in production builds ("the chunking context does not
11
+ * support external modules"); webpack has historically let it through
12
+ * uncaught, which just defers the failure to whoever notices the bloated
13
+ * client bundle or a runtime error.
14
+ *
15
+ * This entry point exists so Client Components can import section-registry
16
+ * lookups (e.g. to resolve and render a section by name, given data already
17
+ * fetched server-side) without pulling in the resolver/loader machinery.
18
+ *
19
+ * Verified client-safe by import-graph inspection, transitively:
20
+ * - `registry.ts` imports only React types.
21
+ * - `sectionMixins.ts` imports `useDevice.ts`, which goes through
22
+ * `requestContext.ts` — that module's only `node:async_hooks` dependency
23
+ * (`requestContextStorage.ts`) is itself swapped for a no-op browser stub
24
+ * via this package's `"browser"` export condition (see
25
+ * `sdk/requestContextStorage.browser.ts`), so it's already safe for a
26
+ * browser bundle.
27
+ * - `schema.ts` has no imports at all.
28
+ *
29
+ * Deliberately NOT re-exported here: `loader.ts`, `resolve.ts`,
30
+ * `sectionLoaders.ts`, `loadDecofileDirectory.ts`, `blockSource.ts`, and
31
+ * `applySectionConventions.ts` (which itself pulls in `resolve.ts` and
32
+ * `sectionLoaders.ts` for section-registration setup). Those are resolver/
33
+ * storage concerns that only make sense server-side — import them from
34
+ * `@decocms/blocks/cms` instead.
35
+ */
36
+ export type { OnBeforeResolveProps, SectionModule, SectionOptions } from "./registry";
37
+ export {
38
+ getResolvedComponent,
39
+ getSection,
40
+ getSectionOptions,
41
+ getSectionRegistry,
42
+ getSyncComponent,
43
+ listRegisteredSections,
44
+ preloadSectionComponents,
45
+ preloadSectionModule,
46
+ registerOnBeforeResolveProps,
47
+ registerSection,
48
+ registerSections,
49
+ registerSectionsSync,
50
+ setResolvedComponent,
51
+ } from "./registry";
52
+ export type { SectionLoaderFn } from "./sectionLoaders";
53
+ export { compose, withDevice, withMobile, withSearchParam, withSectionLoader } from "./sectionMixins";
54
+ export type { ActionConfig, LoaderConfig, MatcherConfig, MetaResponse } from "./schema";
55
+ export {
56
+ composeMeta,
57
+ getRegisteredLoaders,
58
+ getRegisteredMatchers,
59
+ registerActionSchema,
60
+ registerActionSchemas,
61
+ registerLoaderSchema,
62
+ registerLoaderSchemas,
63
+ registerMatcherSchema,
64
+ registerMatcherSchemas,
65
+ } from "./schema";