server-marker-loader.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  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. // Wrap the server code in an async IIFE
  29. const asyncWrappedCode = `(async function() { ${nextLine.trim()} })()`;
  30. // Replace the next line with code that registers it for server execution
  31. lines[i + 1] = `
  32. // Original server code: ${nextLine.trim()}
  33. (function() {
  34. if (typeof window !== 'undefined') {
  35. if (!window.__serverMarkers) window.__serverMarkers = {};
  36. window.__serverMarkers["${id}"] = ${JSON.stringify(asyncWrappedCode)};
  37. }
  38. })();
  39. `;
  40. modified = true;
  41. }
  42. }
  43. // Process multiline blocks
  44. let inBlock = false;
  45. let blockStartLine = -1;
  46. let blockLines = [];
  47. let processedSource = [];
  48. for (let i = 0; i < lines.length; i++) {
  49. const line = lines[i];
  50. if (line.includes('@eserver-begin')) {
  51. // Start of a new block
  52. inBlock = true;
  53. blockStartLine = i;
  54. blockLines = [];
  55. processedSource.push(line); // Keep the begin marker comment
  56. }
  57. else if (inBlock && line.includes('@eserver-end')) {
  58. // End of the current block
  59. inBlock = false;
  60. const id = `${this.resourcePath.replace(/[^\w]/g, '_')}_block_${blockStartLine}`;
  61. // Get the block content
  62. const blockContent = blockLines.join('\n');
  63. // Generate a unique function name with ID
  64. const uniqueFuncName = `serverFunc_${blockStartLine}_${Math.random().toString(36).substring(2, 9)}`;
  65. // Automatically wrap in async function with unique name
  66. const wrappedContent = `
  67. async function ${uniqueFuncName}() {
  68. ${blockContent}
  69. }
  70. return ${uniqueFuncName}();`;
  71. // Create the registration code to replace the block
  72. processedSource.push(`
  73. // Original server code block: ${blockLines.length} lines
  74. (function() {
  75. if (typeof window !== 'undefined') {
  76. if (!window.__serverMarkers) window.__serverMarkers = {};
  77. window.__serverMarkers["${id}"] = ${JSON.stringify(`(async function() {${wrappedContent}})();`)};
  78. }
  79. })();
  80. `);
  81. processedSource.push(line); // Keep the end marker comment
  82. modified = true;
  83. }
  84. else if (inBlock) {
  85. // Inside a block - collect lines but don't add them to the output yet
  86. blockLines.push(line);
  87. }
  88. else {
  89. // Normal line (not in a block)
  90. processedSource.push(line);
  91. }
  92. }
  93. // If we found any multiline blocks, use the processed source
  94. if (inBlock || processedSource.length > 0) {
  95. // Warning if we ended with an unclosed block
  96. if (inBlock) {
  97. console.warn(`[server-marker-loader] Unclosed @eserver-begin block at ${this.resourcePath}:${blockStartLine}`);
  98. }
  99. // Only use the processed source if we actually processed multiline blocks
  100. if (processedSource.length > 0) {
  101. return processedSource.join('\n');
  102. }
  103. }
  104. // If we only modified single-line markers, return the modified lines
  105. if (modified) {
  106. return lines.join('\n');
  107. }
  108. // Otherwise, return the original source unchanged
  109. return source;
  110. };