pulse-js-framework 1.11.3 → 1.11.4

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.
Files changed (48) hide show
  1. package/cli/analyze.js +21 -8
  2. package/cli/build.js +83 -56
  3. package/cli/dev.js +108 -94
  4. package/cli/docs-test.js +52 -33
  5. package/cli/index.js +81 -51
  6. package/cli/mobile.js +92 -40
  7. package/cli/release.js +64 -46
  8. package/cli/scaffold.js +14 -13
  9. package/compiler/lexer.js +55 -54
  10. package/compiler/parser/core.js +1 -0
  11. package/compiler/parser/state.js +6 -12
  12. package/compiler/parser/style.js +17 -20
  13. package/compiler/parser/view.js +1 -3
  14. package/compiler/preprocessor.js +124 -262
  15. package/compiler/sourcemap.js +10 -4
  16. package/compiler/transformer/expressions.js +122 -106
  17. package/compiler/transformer/index.js +2 -4
  18. package/compiler/transformer/style.js +74 -7
  19. package/compiler/transformer/view.js +86 -36
  20. package/loader/esbuild-plugin-server-components.js +209 -0
  21. package/loader/esbuild-plugin.js +41 -93
  22. package/loader/parcel-plugin.js +37 -97
  23. package/loader/rollup-plugin-server-components.js +30 -169
  24. package/loader/rollup-plugin.js +27 -78
  25. package/loader/shared.js +362 -0
  26. package/loader/swc-plugin.js +65 -82
  27. package/loader/vite-plugin-server-components.js +30 -171
  28. package/loader/vite-plugin.js +25 -10
  29. package/loader/webpack-loader-server-components.js +21 -134
  30. package/loader/webpack-loader.js +25 -80
  31. package/package.json +52 -12
  32. package/runtime/dom-selector.js +2 -1
  33. package/runtime/form.js +4 -3
  34. package/runtime/http.js +6 -1
  35. package/runtime/logger.js +44 -24
  36. package/runtime/router/utils.js +14 -7
  37. package/runtime/security.js +13 -1
  38. package/runtime/server-components/actions-server.js +23 -19
  39. package/runtime/server-components/error-sanitizer.js +18 -18
  40. package/runtime/server-components/security.js +41 -24
  41. package/runtime/ssr-preload.js +5 -3
  42. package/runtime/testing.js +759 -0
  43. package/runtime/utils.js +3 -2
  44. package/server/utils.js +15 -9
  45. package/sw/index.js +2 -0
  46. package/types/loaders.d.ts +1043 -0
  47. package/compiler/parser/_extract.js +0 -393
  48. package/loader/README.md +0 -509
@@ -26,26 +26,24 @@
26
26
  * @module pulse-js-framework/loader/vite-plugin-server-components
27
27
  */
28
28
 
29
- import { writeFileSync, mkdirSync } from 'fs';
30
- import { dirname, relative, posix } from 'path';
29
+ import { relative } from 'path';
31
30
  import { getComponentTypeFromSource } from '../compiler/directives.js';
32
31
  import { sanitizeImportPath } from '../runtime/server-components/utils/path-sanitizer.js';
32
+ import {
33
+ extractImports, createImportViolationError, buildManifest, writeManifestToDiskAsync,
34
+ DIRECTIVE_REGEX, COMPONENT_ID_REGEX, EXPORT_CONST_REGEX, CLIENT_CHUNK_PREFIX,
35
+ DEFAULT_MANIFEST_PATH, DEFAULT_MANIFEST_FILENAME
36
+ } from './shared.js';
33
37
 
34
38
  /**
35
39
  * Default options for Server Components plugin
36
40
  */
37
41
  const DEFAULT_OPTIONS = {
38
- // Output directory for client manifest
39
- manifestPath: 'dist/.pulse-manifest.json',
40
-
41
- // Public base path for chunk URLs (empty for relative paths)
42
+ manifestPath: DEFAULT_MANIFEST_PATH,
42
43
  base: '',
43
-
44
- // Whether to inject manifest into SSR HTML
45
44
  injectManifest: true,
46
-
47
- // Custom manifest filename (for different environments)
48
- manifestFilename: '.pulse-manifest.json'
45
+ manifestFilename: DEFAULT_MANIFEST_FILENAME,
46
+ quiet: false
49
47
  };
50
48
 
51
49
  /**
@@ -116,13 +114,13 @@ export default function pulseServerComponentsPlugin(options = {}) {
116
114
 
117
115
  // Check if this module exports a Client Component
118
116
  // Look for: __directive: "use client"
119
- const directiveMatch = code.match(/__directive:\s*["']use client["']/);
117
+ const directiveMatch = code.match(DIRECTIVE_REGEX);
120
118
 
121
119
  if (directiveMatch) {
122
120
  // Extract component ID from export
123
121
  // Look for: export const ComponentName = {
124
- const exportMatch = code.match(/export const (\w+) = \{/);
125
- const componentIdMatch = code.match(/__componentId:\s*["'](\w+)["']/);
122
+ const exportMatch = code.match(EXPORT_CONST_REGEX);
123
+ const componentIdMatch = code.match(COMPONENT_ID_REGEX);
126
124
 
127
125
  const componentId = componentIdMatch ? componentIdMatch[1] : (exportMatch ? exportMatch[1] : null);
128
126
 
@@ -133,7 +131,9 @@ export default function pulseServerComponentsPlugin(options = {}) {
133
131
  chunk: null // Will be filled in during generateBundle
134
132
  });
135
133
 
136
- console.log(`[Pulse Server Components] Detected Client Component: ${componentId} (${relative(process.cwd(), id)})`);
134
+ if (!config.quiet) {
135
+ console.log(`[Pulse Server Components] Detected Client Component: ${componentId} (${relative(process.cwd(), id)})`);
136
+ }
137
137
  }
138
138
  }
139
139
 
@@ -202,7 +202,7 @@ export default function pulseServerComponentsPlugin(options = {}) {
202
202
  for (const [componentId, info] of clientComponents.entries()) {
203
203
  if (id === info.file) {
204
204
  // Create a separate chunk for this Client Component
205
- return `client-${componentId}`;
205
+ return `${CLIENT_CHUNK_PREFIX}${componentId}`;
206
206
  }
207
207
  }
208
208
 
@@ -230,36 +230,21 @@ export default function pulseServerComponentsPlugin(options = {}) {
230
230
  // Check if this chunk corresponds to a Client Component
231
231
  for (const [componentId, info] of clientComponents.entries()) {
232
232
  // Match by chunk name or by checking if the component file is in the chunk
233
- if (chunk.name === `client-${componentId}` ||
233
+ if (chunk.name === `${CLIENT_CHUNK_PREFIX}${componentId}` ||
234
234
  (chunk.facadeModuleId && chunk.facadeModuleId === info.file)) {
235
235
 
236
236
  // Store the chunk filename
237
237
  info.chunk = fileName;
238
238
 
239
- console.log(`[Pulse Server Components] Mapped ${componentId} → ${fileName}`);
239
+ if (!config.quiet) {
240
+ console.log(`[Pulse Server Components] Mapped ${componentId} → ${fileName}`);
241
+ }
240
242
  }
241
243
  }
242
244
  }
243
245
  }
244
246
 
245
- // Build client manifest
246
- const manifest = {
247
- version: '1.0',
248
- components: {}
249
- };
250
-
251
- for (const [componentId, info] of clientComponents.entries()) {
252
- if (info.chunk) {
253
- const base = config.base || '';
254
- const chunkUrl = posix.join(base, info.chunk);
255
-
256
- manifest.components[componentId] = {
257
- id: componentId,
258
- chunk: chunkUrl,
259
- exports: ['default', componentId] // Pulse components export both
260
- };
261
- }
262
- }
247
+ const manifest = buildManifest(clientComponents, config);
263
248
 
264
249
  // Write manifest as JSON asset
265
250
  const manifestJson = JSON.stringify(manifest, null, 2);
@@ -270,151 +255,25 @@ export default function pulseServerComponentsPlugin(options = {}) {
270
255
  source: manifestJson
271
256
  });
272
257
 
273
- console.log(`[Pulse Server Components] Generated client manifest with ${clientComponents.size} components`);
258
+ if (!config.quiet) {
259
+ console.log(`[Pulse Server Components] Generated client manifest with ${clientComponents.size} components`);
260
+ }
274
261
  },
275
262
 
276
263
  /**
277
- * Write manifest to file system after build completes
264
+ * Write manifest to file system after build completes (async)
278
265
  */
279
- closeBundle() {
266
+ async closeBundle() {
280
267
  // Only in client builds
281
268
  if (isSsrBuild || clientComponents.size === 0) {
282
269
  return;
283
270
  }
284
271
 
285
- // Build manifest object
286
- const manifest = {
287
- version: '1.0',
288
- components: {}
289
- };
290
-
291
- for (const [componentId, info] of clientComponents.entries()) {
292
- if (info.chunk) {
293
- const base = config.base || '';
294
- const chunkUrl = posix.join(base, info.chunk);
295
-
296
- manifest.components[componentId] = {
297
- id: componentId,
298
- chunk: chunkUrl,
299
- exports: ['default', componentId]
300
- };
301
- }
302
- }
303
-
304
- // Write to file system (in addition to emitted asset)
305
- if (config.manifestPath) {
306
- try {
307
- const manifestDir = dirname(config.manifestPath);
308
- mkdirSync(manifestDir, { recursive: true });
309
- writeFileSync(config.manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');
310
- console.log(`[Pulse Server Components] Manifest written to ${config.manifestPath}`);
311
- } catch (error) {
312
- console.warn(`[Pulse Server Components] Failed to write manifest: ${error.message}`);
313
- }
314
- }
272
+ const manifest = buildManifest(clientComponents, config);
273
+ await writeManifestToDiskAsync(manifest, config);
315
274
  }
316
275
  };
317
276
  }
318
277
 
319
- // ============================================================================
320
- // Helper Functions
321
- // ============================================================================
322
-
323
- /**
324
- * Extract import statements from source code
325
- * @param {string} code - Source code
326
- * @returns {Array<string>} Import sources
327
- */
328
- function extractImports(code) {
329
- const imports = [];
330
-
331
- // Match ES6 import statements
332
- const importRegex = /import\s+(?:{[^}]*}|[\w$]+|\*\s+as\s+[\w$]+)\s+from\s+['"]([^'"]+)['"]/g;
333
- let match;
334
-
335
- while ((match = importRegex.exec(code)) !== null) {
336
- imports.push(match[1]);
337
- }
338
-
339
- // Match dynamic imports
340
- const dynamicImportRegex = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
341
- while ((match = dynamicImportRegex.exec(code)) !== null) {
342
- imports.push(match[1]);
343
- }
344
-
345
- return imports;
346
- }
347
-
348
- /**
349
- * Create import violation error message
350
- * @param {string} clientPath - Client Component path
351
- * @param {string} serverPath - Server Component path
352
- * @param {string} importSource - Import statement source
353
- * @returns {string} Error message
354
- */
355
- function createImportViolationError(clientPath, serverPath, importSource) {
356
- const clientRelative = relative(process.cwd(), clientPath);
357
- const serverRelative = relative(process.cwd(), serverPath);
358
-
359
- return `
360
- [Pulse] Import Violation: Client Component cannot import Server Component
361
- at ${clientRelative}
362
- importing ${importSource}
363
- resolved to ${serverRelative}
364
-
365
- Client Components can only import:
366
- • Other Client Components ('use client')
367
- • Shared utilities (no directive)
368
- • Third-party packages
369
-
370
- → Move shared logic to a Client Component
371
- → Use Server Actions for server-side operations
372
- → Create a wrapper Client Component that calls Server Actions
373
-
374
- See: https://pulse-js.fr/server-components#import-rules
375
- `.trim();
376
- }
377
-
378
- /**
379
- * Helper function to load client manifest (for SSR)
380
- *
381
- * @param {string} manifestPath - Path to manifest file
382
- * @returns {Object} Client manifest
383
- *
384
- * @example
385
- * import { loadClientManifest } from 'pulse-js-framework/vite/server-components';
386
- *
387
- * const manifest = loadClientManifest('./dist/.pulse-manifest.json');
388
- * // Use manifest for SSR: renderServerComponent(Component, props, { clientManifest: manifest.components })
389
- */
390
- export function loadClientManifest(manifestPath) {
391
- try {
392
- const { readFileSync } = require('fs');
393
- const content = readFileSync(manifestPath, 'utf-8');
394
- return JSON.parse(content);
395
- } catch (error) {
396
- console.warn(`Failed to load client manifest from ${manifestPath}:`, error.message);
397
- return { version: '1.0', components: {} };
398
- }
399
- }
400
-
401
- /**
402
- * Helper function to get client component chunk URL
403
- *
404
- * @param {Object} manifest - Client manifest
405
- * @param {string} componentId - Component ID
406
- * @returns {string|null} Chunk URL or null if not found
407
- */
408
- export function getComponentChunk(manifest, componentId) {
409
- return manifest.components[componentId]?.chunk || null;
410
- }
411
-
412
- /**
413
- * Helper function to get all client component IDs
414
- *
415
- * @param {Object} manifest - Client manifest
416
- * @returns {Set<string>} Set of component IDs
417
- */
418
- export function getClientComponentIds(manifest) {
419
- return new Set(Object.keys(manifest.components));
420
- }
278
+ // Re-export shared manifest helpers
279
+ export { loadClientManifest, getComponentChunk, getClientComponentIds } from './shared.js';
@@ -15,6 +15,7 @@ import { compile } from '../compiler/index.js';
15
15
  import { existsSync } from 'fs';
16
16
  import { resolve, dirname } from 'path';
17
17
  import { preprocessStylesSync, isSassAvailable, getSassVersion } from '../compiler/preprocessor.js';
18
+ import { STYLES_MATCH_REGEX, removeInlineStyles } from './shared.js';
18
19
 
19
20
  // Virtual module ID for extracted CSS (uses .css extension so Vite treats it as CSS)
20
21
  const VIRTUAL_CSS_SUFFIX = '.pulse.css';
@@ -26,6 +27,7 @@ export default function pulsePlugin(options = {}) {
26
27
  const {
27
28
  exclude = /node_modules/,
28
29
  sourceMap = true,
30
+ quiet = false,
29
31
  // SASS options
30
32
  sass: sassOptions = {}
31
33
  } = options;
@@ -52,7 +54,9 @@ export default function pulsePlugin(options = {}) {
52
54
  sassAvailable = isSassAvailable();
53
55
  if (sassAvailable) {
54
56
  sassVersion = getSassVersion();
55
- console.log(`[Pulse] SASS support enabled (sass ${sassVersion || 'unknown'})`);
57
+ if (!quiet) {
58
+ console.log(`[Pulse] SASS support enabled (sass ${sassVersion || 'unknown'})`);
59
+ }
56
60
  }
57
61
  },
58
62
 
@@ -132,7 +136,7 @@ export default function pulsePlugin(options = {}) {
132
136
  let outputCode = result.code;
133
137
 
134
138
  // Extract CSS from compiled output and move to virtual CSS module
135
- const stylesMatch = outputCode.match(/const styles = `([\s\S]*?)`;/);
139
+ const stylesMatch = outputCode.match(STYLES_MATCH_REGEX);
136
140
  if (stylesMatch) {
137
141
  let css = stylesMatch[1];
138
142
  const virtualCssId = id + '.css';
@@ -160,10 +164,7 @@ export default function pulsePlugin(options = {}) {
160
164
 
161
165
  // Replace inline style injection with CSS import
162
166
  // Vite will process this through its CSS pipeline (not JS minifier)
163
- outputCode = outputCode.replace(
164
- /\/\/ Styles\nconst styles = `[\s\S]*?`;\n\/\/ Inject styles\nconst styleEl = document\.createElement\("style"\);\nstyleEl\.textContent = styles;\ndocument\.head\.appendChild\(styleEl\);/,
165
- `// Styles extracted to virtual CSS module\nimport "${virtualCssId}";`
166
- );
167
+ outputCode = removeInlineStyles(outputCode, `// Styles extracted to virtual CSS module\nimport "${virtualCssId}";`);
167
168
  }
168
169
 
169
170
  return {
@@ -177,11 +178,20 @@ export default function pulsePlugin(options = {}) {
177
178
  },
178
179
 
179
180
  /**
180
- * Handle hot module replacement
181
+ * Handle hot module replacement for .pulse files.
182
+ * Invalidates both the JS module and its virtual CSS module, then
183
+ * sends an HMR update to the client instead of triggering a full reload.
184
+ *
185
+ * @param {Object} ctx - Vite HMR context
186
+ * @param {string} ctx.file - Absolute path of the changed file
187
+ * @param {Object} ctx.server - Vite dev server instance
188
+ * @returns {Array|undefined} Empty array to prevent default HMR handling
181
189
  */
182
190
  handleHotUpdate({ file, server }) {
183
191
  if (file.endsWith('.pulse')) {
184
- console.log(`[Pulse] HMR update: ${file}`);
192
+ if (!quiet) {
193
+ console.log(`[Pulse] HMR update: ${file}`);
194
+ }
185
195
 
186
196
  // Invalidate the module in Vite's module graph
187
197
  const module = server.moduleGraph.getModuleById(file);
@@ -213,7 +223,10 @@ export default function pulsePlugin(options = {}) {
213
223
  },
214
224
 
215
225
  /**
216
- * Configure dev server - log sass status on start
226
+ * Configure dev server middleware and check SASS availability on start.
227
+ * Called once when the Vite dev server is created.
228
+ *
229
+ * @param {Object} server - Vite dev server instance
217
230
  */
218
231
  configureServer(server) {
219
232
  // Check sass on server start if not already checked
@@ -221,7 +234,9 @@ export default function pulsePlugin(options = {}) {
221
234
  sassAvailable = isSassAvailable();
222
235
  if (sassAvailable) {
223
236
  sassVersion = getSassVersion();
224
- console.log(`[Pulse] SASS support enabled (sass ${sassVersion || 'unknown'})`);
237
+ if (!quiet) {
238
+ console.log(`[Pulse] SASS support enabled (sass ${sassVersion || 'unknown'})`);
239
+ }
225
240
  }
226
241
  }
227
242
 
@@ -34,22 +34,23 @@
34
34
  * @module pulse-js-framework/loader/webpack-loader-server-components
35
35
  */
36
36
 
37
- import { writeFileSync, mkdirSync } from 'fs';
38
- import { dirname, posix, relative } from 'path';
37
+ import { relative } from 'path';
39
38
  import { getComponentTypeFromSource } from '../compiler/directives.js';
39
+ import {
40
+ createImportViolationError, buildManifest, writeManifestToDisk,
41
+ loadClientManifest, getComponentChunk, getClientComponentIds,
42
+ DIRECTIVE_REGEX, COMPONENT_ID_REGEX, EXPORT_CONST_REGEX, CLIENT_CHUNK_PREFIX,
43
+ DEFAULT_MANIFEST_PATH, DEFAULT_MANIFEST_FILENAME
44
+ } from './shared.js';
40
45
 
41
46
  /**
42
47
  * Default options for Server Components Webpack plugin
43
48
  */
44
49
  const DEFAULT_OPTIONS = {
45
- // Output directory for client manifest
46
- manifestPath: 'dist/.pulse-manifest.json',
47
-
48
- // Public base path for chunk URLs (empty for relative paths)
50
+ manifestPath: DEFAULT_MANIFEST_PATH,
49
51
  base: '',
50
-
51
- // Custom manifest filename
52
- manifestFilename: '.pulse-manifest.json'
52
+ manifestFilename: DEFAULT_MANIFEST_FILENAME,
53
+ quiet: false
53
54
  };
54
55
 
55
56
  /**
@@ -106,12 +107,12 @@ class PulseServerComponentsPlugin {
106
107
 
107
108
  // Check if this module exports a Client Component
108
109
  // Look for: __directive: "use client"
109
- const directiveMatch = source.match(/__directive:\s*["']use client["']/);
110
+ const directiveMatch = source.match(DIRECTIVE_REGEX);
110
111
 
111
112
  if (directiveMatch) {
112
113
  // Extract component ID
113
- const componentIdMatch = source.match(/__componentId:\s*["'](\w+)["']/);
114
- const exportMatch = source.match(/export const (\w+) = \{/);
114
+ const componentIdMatch = source.match(COMPONENT_ID_REGEX);
115
+ const exportMatch = source.match(EXPORT_CONST_REGEX);
115
116
 
116
117
  const componentId = componentIdMatch ? componentIdMatch[1] : (exportMatch ? exportMatch[1] : null);
117
118
 
@@ -124,7 +125,7 @@ class PulseServerComponentsPlugin {
124
125
  });
125
126
 
126
127
  const relativePath = relative(process.cwd(), module.resource);
127
- console.log(`[Pulse Server Components] Detected Client Component: ${componentId} (${relativePath})`);
128
+ if (!this.options.quiet) console.log(`[Pulse Server Components] Detected Client Component: ${componentId} (${relativePath})`);
128
129
  }
129
130
  }
130
131
  });
@@ -181,7 +182,7 @@ class PulseServerComponentsPlugin {
181
182
 
182
183
  if (jsFile) {
183
184
  info.chunk = jsFile;
184
- console.log(`[Pulse Server Components] Mapped ${componentId} → ${jsFile}`);
185
+ if (!this.options.quiet) console.log(`[Pulse Server Components] Mapped ${componentId} → ${jsFile}`);
185
186
  }
186
187
  break;
187
188
  }
@@ -189,24 +190,7 @@ class PulseServerComponentsPlugin {
189
190
  }
190
191
  }
191
192
 
192
- // Build client manifest
193
- const manifest = {
194
- version: '1.0',
195
- components: {}
196
- };
197
-
198
- for (const [componentId, info] of clientComponents.entries()) {
199
- if (info.chunk) {
200
- const base = this.options.base || '';
201
- const chunkUrl = posix.join(base, info.chunk);
202
-
203
- manifest.components[componentId] = {
204
- id: componentId,
205
- chunk: chunkUrl,
206
- exports: ['default', componentId]
207
- };
208
- }
209
- }
193
+ const manifest = buildManifest(clientComponents, { base: this.options.base });
210
194
 
211
195
  // Emit manifest as asset
212
196
  const manifestJson = JSON.stringify(manifest, null, 2);
@@ -217,7 +201,7 @@ class PulseServerComponentsPlugin {
217
201
  new RawSource(manifestJson)
218
202
  );
219
203
 
220
- console.log(`[Pulse Server Components] Generated client manifest with ${clientComponents.size} components`);
204
+ if (!this.options.quiet) console.log(`[Pulse Server Components] Generated client manifest with ${clientComponents.size} components`);
221
205
  }
222
206
  );
223
207
 
@@ -227,75 +211,13 @@ class PulseServerComponentsPlugin {
227
211
  return;
228
212
  }
229
213
 
230
- // Build manifest object
231
- const manifest = {
232
- version: '1.0',
233
- components: {}
234
- };
235
-
236
- for (const [componentId, info] of clientComponents.entries()) {
237
- if (info.chunk) {
238
- const base = this.options.base || '';
239
- const chunkUrl = posix.join(base, info.chunk);
240
-
241
- manifest.components[componentId] = {
242
- id: componentId,
243
- chunk: chunkUrl,
244
- exports: ['default', componentId]
245
- };
246
- }
247
- }
248
-
249
- // Write to file system
250
- if (this.options.manifestPath) {
251
- try {
252
- const manifestDir = dirname(this.options.manifestPath);
253
- mkdirSync(manifestDir, { recursive: true });
254
- writeFileSync(this.options.manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');
255
- console.log(`[Pulse Server Components] Manifest written to ${this.options.manifestPath}`);
256
- } catch (error) {
257
- console.warn(`[Pulse Server Components] Failed to write manifest: ${error.message}`);
258
- }
259
- }
214
+ const manifest = buildManifest(clientComponents, { base: this.options.base });
215
+ writeManifestToDisk(manifest, this.options);
260
216
  });
261
217
  });
262
218
  }
263
219
  }
264
220
 
265
- // ============================================================================
266
- // Helper Functions
267
- // ============================================================================
268
-
269
- /**
270
- * Create import violation error message
271
- * @param {string} clientPath - Client Component path
272
- * @param {string} serverPath - Server Component path
273
- * @param {string} importSource - Import statement source
274
- * @returns {string} Error message
275
- */
276
- function createImportViolationError(clientPath, serverPath, importSource) {
277
- const clientRelative = relative(process.cwd(), clientPath);
278
- const serverRelative = relative(process.cwd(), serverPath);
279
-
280
- return `
281
- [Pulse] Import Violation: Client Component cannot import Server Component
282
- at ${clientRelative}
283
- importing ${importSource}
284
- resolved to ${serverRelative}
285
-
286
- Client Components can only import:
287
- • Other Client Components ('use client')
288
- • Shared utilities (no directive)
289
- • Third-party packages
290
-
291
- → Move shared logic to a Client Component
292
- → Use Server Actions for server-side operations
293
- → Create a wrapper Client Component that calls Server Actions
294
-
295
- See: https://pulse-js.fr/server-components#import-rules
296
- `.trim();
297
- }
298
-
299
221
  /**
300
222
  * Helper function to add Server Components support to Webpack config
301
223
  *
@@ -315,42 +237,7 @@ export function addServerComponentsSupport(options = {}) {
315
237
  return new PulseServerComponentsPlugin(options);
316
238
  }
317
239
 
318
- /**
319
- * Helper function to load client manifest (for SSR)
320
- *
321
- * @param {string} manifestPath - Path to manifest file
322
- * @returns {Object} Client manifest
323
- */
324
- export function loadClientManifest(manifestPath) {
325
- try {
326
- const { readFileSync } = require('fs');
327
- const content = readFileSync(manifestPath, 'utf-8');
328
- return JSON.parse(content);
329
- } catch (error) {
330
- console.warn(`Failed to load client manifest from ${manifestPath}:`, error.message);
331
- return { version: '1.0', components: {} };
332
- }
333
- }
334
-
335
- /**
336
- * Helper function to get client component chunk URL
337
- *
338
- * @param {Object} manifest - Client manifest
339
- * @param {string} componentId - Component ID
340
- * @returns {string|null} Chunk URL or null if not found
341
- */
342
- export function getComponentChunk(manifest, componentId) {
343
- return manifest.components[componentId]?.chunk || null;
344
- }
345
-
346
- /**
347
- * Helper function to get all client component IDs
348
- *
349
- * @param {Object} manifest - Client manifest
350
- * @returns {Set<string>} Set of component IDs
351
- */
352
- export function getClientComponentIds(manifest) {
353
- return new Set(Object.keys(manifest.components));
354
- }
240
+ // Re-export shared manifest helpers
241
+ export { loadClientManifest, getComponentChunk, getClientComponentIds } from './shared.js';
355
242
 
356
243
  export default PulseServerComponentsPlugin;