-
Notifications
You must be signed in to change notification settings - Fork 46.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Flight Plugin] Scan for "use client" #26474
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,8 +9,8 @@ | |
|
||
import {join} from 'path'; | ||
import {pathToFileURL} from 'url'; | ||
|
||
import asyncLib from 'neo-async'; | ||
import * as acorn from 'acorn-loose'; | ||
|
||
import ModuleDependency from 'webpack/lib/dependencies/ModuleDependency'; | ||
import NullDependency from 'webpack/lib/dependencies/NullDependency'; | ||
|
@@ -117,10 +117,12 @@ export default class ReactFlightWebpackPlugin { | |
PLUGIN_NAME, | ||
({contextModuleFactory}, callback) => { | ||
const contextResolver = compiler.resolverFactory.get('context', {}); | ||
const normalResolver = compiler.resolverFactory.get('normal'); | ||
|
||
_this.resolveAllClientFiles( | ||
compiler.context, | ||
contextResolver, | ||
normalResolver, | ||
compiler.inputFileSystem, | ||
contextModuleFactory, | ||
function (err, resolvedClientRefs) { | ||
|
@@ -219,6 +221,10 @@ export default class ReactFlightWebpackPlugin { | |
return; | ||
} | ||
|
||
const resolvedClientFiles = new Set( | ||
(resolvedClientReferences || []).map(ref => ref.request), | ||
); | ||
|
||
const clientManifest: { | ||
[string]: {chunks: $FlowFixMe, id: string, name: string}, | ||
} = {}; | ||
|
@@ -237,8 +243,7 @@ export default class ReactFlightWebpackPlugin { | |
// TODO: Hook into deps instead of the target module. | ||
// That way we know by the type of dep whether to include. | ||
// It also resolves conflicts when the same module is in multiple chunks. | ||
|
||
if (!/\.(js|ts)x?$/.test(module.resource)) { | ||
if (!resolvedClientFiles.has(module.resource)) { | ||
return; | ||
} | ||
|
||
|
@@ -328,13 +333,39 @@ export default class ReactFlightWebpackPlugin { | |
resolveAllClientFiles( | ||
context: string, | ||
contextResolver: any, | ||
normalResolver: any, | ||
fs: any, | ||
contextModuleFactory: any, | ||
callback: ( | ||
err: null | Error, | ||
result?: $ReadOnlyArray<ClientReferenceDependency>, | ||
) => void, | ||
) { | ||
function hasUseClientDirective(source: string): boolean { | ||
if (source.indexOf('use client') === -1) { | ||
return false; | ||
} | ||
let body; | ||
try { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is copypasta from the Node code we already have. |
||
body = acorn.parse(source, { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like this file should already be parsed by Webpack somewhere... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It’s too early because this is pre-AST. In order to feed into webpack parsing process we’d have to add each file as a dependency — but this gets us back to where we started (we don’t want them all as dependencies cause that would everything everything in the world to the build). |
||
ecmaVersion: '2024', | ||
sourceType: 'module', | ||
}).body; | ||
} catch (x) { | ||
return false; | ||
} | ||
for (let i = 0; i < body.length; i++) { | ||
const node = body[i]; | ||
if (node.type !== 'ExpressionStatement' || !node.directive) { | ||
break; | ||
} | ||
if (node.directive === 'use client') { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
asyncLib.map( | ||
this.clientReferences, | ||
( | ||
|
@@ -373,14 +404,46 @@ export default class ReactFlightWebpackPlugin { | |
options, | ||
(err2: null | Error, deps: Array<any /*ModuleDependency*/>) => { | ||
if (err2) return cb(err2); | ||
|
||
const clientRefDeps = deps.map(dep => { | ||
// use userRequest instead of request. request always end with undefined which is wrong | ||
const request = join(resolvedDirectory, dep.userRequest); | ||
const clientRefDep = new ClientReferenceDependency(request); | ||
clientRefDep.userRequest = dep.userRequest; | ||
return clientRefDep; | ||
}); | ||
cb(null, clientRefDeps); | ||
|
||
asyncLib.filter( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't love the structure of this but I tried to write similar to the surrounding code. |
||
clientRefDeps, | ||
( | ||
clientRefDep: ClientReferenceDependency, | ||
filterCb: (err: null | Error, truthValue: boolean) => void, | ||
) => { | ||
normalResolver.resolve( | ||
{}, | ||
context, | ||
clientRefDep.request, | ||
{}, | ||
(err3: null | Error, resolvedPath: mixed) => { | ||
if (err3 || typeof resolvedPath !== 'string') { | ||
return filterCb(null, false); | ||
} | ||
fs.readFile( | ||
resolvedPath, | ||
'utf-8', | ||
(err4: null | Error, content: string) => { | ||
if (err4 || typeof content !== 'string') { | ||
return filterCb(null, false); | ||
} | ||
const useClient = hasUseClientDirective(content); | ||
filterCb(null, useClient); | ||
}, | ||
); | ||
}, | ||
); | ||
}, | ||
cb, | ||
); | ||
}, | ||
); | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if it's copypasta :), why not make it more readable at least in this part of the code... WDYT?