server-marker-loader.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. // ./server-marker-loader.js
  2. /**
  3. * Server Marker Webpack Loader for React App Rewired
  4. *
  5. * This loader identifies server code markers and processes them
  6. * to prevent client-side execution of server code.
  7. *
  8. * Supports:
  9. * - @eserver-register-next-line for single line markers
  10. * - @eserver-begin and @eserver-end for multiline blocks
  11. *
  12. * Automatically wraps code in IIFEs for proper execution.
  13. */
  14. module.exports = function(source) {
  15. // Skip if not a JavaScript file
  16. if (!this.resourcePath.match(/\.(js|jsx|ts|tsx)$/)) {
  17. return source;
  18. }
  19. const lines = source.split('\n');
  20. let modified = false;
  21. // Process single-line markers
  22. for (let i = 0; i < lines.length - 1; i++) {
  23. const line = lines[i];
  24. // Find our marker comment
  25. if (line.includes('@eserver-register-next-line')) {
  26. const nextLine = lines[i + 1];
  27. const id = `${this.resourcePath.replace(/[^\w]/g, '_')}_${i}`;
  28. // Replace the next line with code that registers it for server execution
  29. lines[i + 1] = `
  30. // Original server code: ${nextLine.trim()}
  31. (function() {
  32. if (typeof window !== 'undefined') {
  33. if (!window.__serverMarkers) window.__serverMarkers = {};
  34. window.__serverMarkers["${id}"] = ${JSON.stringify(nextLine.trim())};
  35. }
  36. })();
  37. `;
  38. modified = true;
  39. }
  40. }
  41. // Process multiline blocks
  42. let inBlock = false;
  43. let blockStartLine = -1;
  44. let blockLines = [];
  45. let processedSource = [];
  46. for (let i = 0; i < lines.length; i++) {
  47. const line = lines[i];
  48. if (line.includes('@eserver-begin')) {
  49. // Start of a new block
  50. inBlock = true;
  51. blockStartLine = i;
  52. blockLines = [];
  53. processedSource.push(line); // Keep the begin marker comment
  54. }
  55. else if (inBlock && line.includes('@eserver-end')) {
  56. // End of the current block
  57. inBlock = false;
  58. const id = `${this.resourcePath.replace(/[^\w]/g, '_')}_block_${blockStartLine}`;
  59. // Get the block content
  60. const blockContent = blockLines.join('\n');
  61. // Generate a unique function name with ID
  62. const uniqueFuncName = `serverFunc_${blockStartLine}_${Math.random().toString(36).substring(2, 9)}`;
  63. // Automatically wrap in async function with unique name
  64. const wrappedContent = `
  65. async function ${uniqueFuncName}() {
  66. ${blockContent}
  67. }
  68. return ${uniqueFuncName}();`;
  69. // Create the registration code to replace the block
  70. processedSource.push(`
  71. // Original server code block: ${blockLines.length} lines
  72. (function() {
  73. if (typeof window !== 'undefined') {
  74. if (!window.__serverMarkers) window.__serverMarkers = {};
  75. window.__serverMarkers["${id}"] = ${JSON.stringify(`(async function() {${wrappedContent}})();`)};
  76. }
  77. })();
  78. `);
  79. processedSource.push(line); // Keep the end marker comment
  80. modified = true;
  81. }
  82. else if (inBlock) {
  83. // Inside a block - collect lines but don't add them to the output yet
  84. blockLines.push(line);
  85. }
  86. else {
  87. // Normal line (not in a block)
  88. processedSource.push(line);
  89. }
  90. }
  91. // If we found any multiline blocks, use the processed source
  92. if (inBlock || processedSource.length > 0) {
  93. // Warning if we ended with an unclosed block
  94. if (inBlock) {
  95. console.warn(`[server-marker-loader] Unclosed @eserver-begin block at ${this.resourcePath}:${blockStartLine}`);
  96. }
  97. // Only use the processed source if we actually processed multiline blocks
  98. if (processedSource.length > 0) {
  99. return processedSource.join('\n');
  100. }
  101. }
  102. // If we only modified single-line markers, return the modified lines
  103. if (modified) {
  104. return lines.join('\n');
  105. }
  106. // Otherwise, return the original source unchanged
  107. return source;
  108. };