main
js 109 lines 3.02 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 const acorn = require('acorn-loose');
11
12 const url = require('url');
13
14 const Module = require('module');
15
16 module.exports = function register() {
17 const Server: any = require('react-server-dom-webpack/server');
18 const registerServerReference = Server.registerServerReference;
19 const createClientModuleProxy = Server.createClientModuleProxy;
20
21 // $FlowFixMe[prop-missing] found when upgrading Flow
22 const originalCompile = Module.prototype._compile;
23
24 // $FlowFixMe[prop-missing] found when upgrading Flow
25 Module.prototype._compile = function (
26 this: any,
27 content: string,
28 filename: string,
29 ): void {
30 // Do a quick check for the exact string. If it doesn't exist, don't
31 // bother parsing.
32 if (
33 content.indexOf('use client') === -1 &&
34 content.indexOf('use server') === -1
35 ) {
36 return originalCompile.apply(this, arguments);
37 }
38
39 let body;
40 try {
41 body = acorn.parse(content, {
42 ecmaVersion: '2024',
43 sourceType: 'source',
44 }).body;
45 } catch (x) {
46 console['error']('Error parsing %s %s', url, x.message);
47 return originalCompile.apply(this, arguments);
48 }
49
50 let useClient = false;
51 let useServer = false;
52 for (let i = 0; i < body.length; i++) {
53 const node = body[i];
54 if (node.type !== 'ExpressionStatement' || !node.directive) {
55 break;
56 }
57 if (node.directive === 'use client') {
58 useClient = true;
59 }
60 if (node.directive === 'use server') {
61 useServer = true;
62 }
63 }
64
65 if (!useClient && !useServer) {
66 return originalCompile.apply(this, arguments);
67 }
68
69 if (useClient && useServer) {
70 throw new Error(
71 'Cannot have both "use client" and "use server" directives in the same file.',
72 );
73 }
74
75 if (useClient) {
76 const moduleId: string = url.pathToFileURL(filename).href as any;
77 this.exports = createClientModuleProxy(moduleId);
78 }
79
80 if (useServer) {
81 originalCompile.apply(this, arguments);
82
83 const moduleId: string = url.pathToFileURL(filename).href as any;
84
85 const exports = this.exports;
86
87 // This module is imported server to server, but opts in to exposing functions by
88 // reference. If there are any functions in the export.
89 if (typeof exports === 'function') {
90 // The module exports a function directly,
91 registerServerReference(
92 exports as any,
93 moduleId,
94 // Represents the whole Module object instead of a particular import.
95 null,
96 );
97 } else {
98 const keys = Object.keys(exports);
99 for (let i = 0; i < keys.length; i++) {
100 const key = keys[i];
101 const value = exports[keys[i]];
102 if (typeof value === 'function') {
103 registerServerReference(value as any, moduleId, key);
104 }
105 }
106 }
107 }
108 };
109 };