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.
- package/cli/analyze.js +21 -8
- package/cli/build.js +83 -56
- package/cli/dev.js +108 -94
- package/cli/docs-test.js +52 -33
- package/cli/index.js +81 -51
- package/cli/mobile.js +92 -40
- package/cli/release.js +64 -46
- package/cli/scaffold.js +14 -13
- package/compiler/lexer.js +55 -54
- package/compiler/parser/core.js +1 -0
- package/compiler/parser/state.js +6 -12
- package/compiler/parser/style.js +17 -20
- package/compiler/parser/view.js +1 -3
- package/compiler/preprocessor.js +124 -262
- package/compiler/sourcemap.js +10 -4
- package/compiler/transformer/expressions.js +122 -106
- package/compiler/transformer/index.js +2 -4
- package/compiler/transformer/style.js +74 -7
- package/compiler/transformer/view.js +86 -36
- package/loader/esbuild-plugin-server-components.js +209 -0
- package/loader/esbuild-plugin.js +41 -93
- package/loader/parcel-plugin.js +37 -97
- package/loader/rollup-plugin-server-components.js +30 -169
- package/loader/rollup-plugin.js +27 -78
- package/loader/shared.js +362 -0
- package/loader/swc-plugin.js +65 -82
- package/loader/vite-plugin-server-components.js +30 -171
- package/loader/vite-plugin.js +25 -10
- package/loader/webpack-loader-server-components.js +21 -134
- package/loader/webpack-loader.js +25 -80
- package/package.json +52 -12
- package/runtime/dom-selector.js +2 -1
- package/runtime/form.js +4 -3
- package/runtime/http.js +6 -1
- package/runtime/logger.js +44 -24
- package/runtime/router/utils.js +14 -7
- package/runtime/security.js +13 -1
- package/runtime/server-components/actions-server.js +23 -19
- package/runtime/server-components/error-sanitizer.js +18 -18
- package/runtime/server-components/security.js +41 -24
- package/runtime/ssr-preload.js +5 -3
- package/runtime/testing.js +759 -0
- package/runtime/utils.js +3 -2
- package/server/utils.js +15 -9
- package/sw/index.js +2 -0
- package/types/loaders.d.ts +1043 -0
- package/compiler/parser/_extract.js +0 -393
- 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 {
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
125
|
-
const componentIdMatch = code.match(
|
|
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
|
-
|
|
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
|
|
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 ===
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
286
|
-
|
|
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
|
-
|
|
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';
|
package/loader/vite-plugin.js
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
110
|
+
const directiveMatch = source.match(DIRECTIVE_REGEX);
|
|
110
111
|
|
|
111
112
|
if (directiveMatch) {
|
|
112
113
|
// Extract component ID
|
|
113
|
-
const componentIdMatch = source.match(
|
|
114
|
-
const exportMatch = source.match(
|
|
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
|
-
|
|
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
|
-
|
|
231
|
-
|
|
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
|
-
|
|
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;
|