9
10
import {join} from 'path';
11
import {pathToFileURL} from 'url';
12
-
12
import asyncLib from 'neo-async';
13
+import * as acorn from 'acorn-loose';
14
15
import ModuleDependency from 'webpack/lib/dependencies/ModuleDependency';
16
import NullDependency from 'webpack/lib/dependencies/NullDependency';
117
PLUGIN_NAME,
118
({contextModuleFactory}, callback) => {
119
const contextResolver = compiler.resolverFactory.get('context', {});
120
+ const normalResolver = compiler.resolverFactory.get('normal');
121
122
_this.resolveAllClientFiles(
123
compiler.context,
124
contextResolver,
125
+ normalResolver,
126
compiler.inputFileSystem,
127
contextModuleFactory,
128
function (err, resolvedClientRefs) {
221
return;
222
}
223
224
+ const resolvedClientFiles = new Set(
225
+ (resolvedClientReferences || []).map(ref => ref.request),
226
+ );
227
+
228
const clientManifest: {
229
[string]: {chunks: $FlowFixMe, id: string, name: string},
230
} = {};
243
// TODO: Hook into deps instead of the target module.
244
// That way we know by the type of dep whether to include.
245
// It also resolves conflicts when the same module is in multiple chunks.
240
-
241
- if (!/\.(js|ts)x?$/.test(module.resource)) {
246
+ if (!resolvedClientFiles.has(module.resource)) {
247
return;
248
}
249
333
resolveAllClientFiles(
334
context: string,
335
contextResolver: any,
336
+ normalResolver: any,
337
fs: any,
338
contextModuleFactory: any,
339
callback: (
341
result?: $ReadOnlyArray<ClientReferenceDependency>,
342
) => void,
343
) {
344
+ function hasUseClientDirective(source: string): boolean {
345
+ if (source.indexOf('use client') === -1) {
346
+ return false;
347
+ }
348
+ let body;
349
+ try {
350
+ body = acorn.parse(source, {
351
+ ecmaVersion: '2024',
352
+ sourceType: 'module',
353
+ }).body;
354
+ } catch (x) {
355
+ return false;
356
+ }
357
+ for (let i = 0; i < body.length; i++) {
358
+ const node = body[i];
359
+ if (node.type !== 'ExpressionStatement' || !node.directive) {
360
+ break;
361
+ }
362
+ if (node.directive === 'use client') {
363
+ return true;
364
+ }
365
+ }
366
+ return false;
367
+ }
368
+
369
asyncLib.map(
370
this.clientReferences,
371
(
404
options,
405
(err2: null | Error, deps: Array<any /*ModuleDependency*/>) => {
406
if (err2) return cb(err2);
407
+
408
const clientRefDeps = deps.map(dep => {
409
// use userRequest instead of request. request always end with undefined which is wrong
410
const request = join(resolvedDirectory, dep.userRequest);
412
clientRefDep.userRequest = dep.userRequest;
413
return clientRefDep;
414
});
383
- cb(null, clientRefDeps);
415
+
416
+ asyncLib.filter(
417
+ clientRefDeps,
418
+ (
419
+ clientRefDep: ClientReferenceDependency,
420
+ filterCb: (err: null | Error, truthValue: boolean) => void,
421
+ ) => {
422
+ normalResolver.resolve(
423
+ {},
424
+ context,
425
+ clientRefDep.request,
426
+ {},
427
+ (err3: null | Error, resolvedPath: mixed) => {
428
+ if (err3 || typeof resolvedPath !== 'string') {
429
+ return filterCb(null, false);
430
+ }
431
+ fs.readFile(
432
+ resolvedPath,
433
+ 'utf-8',
434
+ (err4: null | Error, content: string) => {
435
+ if (err4 || typeof content !== 'string') {
436
+ return filterCb(null, false);
437
+ }
438
+ const useClient = hasUseClientDirective(content);
439
+ filterCb(null, useClient);
440
+ },
441
+ );
442
+ },
443
+ );
444
+ },
445
+ cb,
446
+ );
447
},
448
);
449
},