Skip to content
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

feat(jsconfig-paths-plugin): Resolve paths synchronously #29467

Merged
merged 10 commits into from
Oct 13, 2021
92 changes: 62 additions & 30 deletions packages/next/build/webpack/plugins/jsconfig-paths-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,36 @@ export function patternText({ prefix, suffix }: Pattern): string {
return `${prefix}*${suffix}`
}

/**
* Calls the iterator function for each entry of the array
* until the first result or error is reached
*/
function forEachBail<TEntry>(
array: TEntry[],
iterator: (
entry: TEntry,
entryCallback: (err?: any, result?: any) => void
) => void,
callback: (err?: any, result?: any) => void
): void {
if (array.length === 0) return callback()

let i = 0
const next = () => {
let loop: boolean | undefined = undefined
iterator(array[i++], (err, result) => {
if (err || result !== undefined || i >= array.length) {
return callback(err, result)
}
if (loop === false) while (next());
loop = true
})
if (!loop) loop = false
return loop
}
while (next());
}

const NODE_MODULES_REGEX = /node_modules/

type Paths = { [match: string]: string[] }
Expand Down Expand Up @@ -159,28 +189,32 @@ export class JsConfigPathsPlugin implements webpack.ResolvePlugin {
const target = resolver.ensureHook('resolve')
resolver
.getHook('described-resolve')
.tapPromise(
.tapAsync(
'JsConfigPathsPlugin',
async (request: any, resolveContext: any) => {
(
request: any,
resolveContext: any,
callback: (err?: any, result?: any) => void
) => {
const moduleName = request.request

// Exclude node_modules from paths support (speeds up resolving)
if (request.path.match(NODE_MODULES_REGEX)) {
log('skipping request as it is inside node_modules %s', moduleName)
return
return callback()
}

if (
path.posix.isAbsolute(moduleName) ||
(process.platform === 'win32' && path.win32.isAbsolute(moduleName))
) {
log('skipping request as it is an absolute path %s', moduleName)
return
return callback()
}

if (pathIsRelative(moduleName)) {
log('skipping request as it is a relative path %s', moduleName)
return
return callback()
}

// log('starting to resolve request %s', moduleName)
Expand All @@ -189,7 +223,7 @@ export class JsConfigPathsPlugin implements webpack.ResolvePlugin {
const matchedPattern = matchPatternOrExact(pathsKeys, moduleName)
if (!matchedPattern) {
log('moduleName did not match any paths pattern %s', moduleName)
return
return callback()
}

const matchedStar = isString(matchedPattern)
Expand All @@ -201,18 +235,18 @@ export class JsConfigPathsPlugin implements webpack.ResolvePlugin {

let triedPaths = []

for (const subst of paths[matchedPatternText]) {
const curPath = matchedStar
? subst.replace('*', matchedStar)
: subst

// Ensure .d.ts is not matched
if (curPath.endsWith('.d.ts')) {
continue
}

const candidate = path.join(baseDirectory, curPath)
const [err, result] = await new Promise((resolve) => {
forEachBail(
paths[matchedPatternText],
(subst, pathCallback) => {
const curPath = matchedStar
? subst.replace('*', matchedStar)
: subst
// Ensure .d.ts is not matched
if (curPath.endsWith('.d.ts')) {
// try next path candidate
return pathCallback()
}
const candidate = path.join(baseDirectory, curPath)
const obj = Object.assign({}, request, {
request: candidate,
})
Expand All @@ -221,20 +255,18 @@ export class JsConfigPathsPlugin implements webpack.ResolvePlugin {
obj,
`Aliased with tsconfig.json or jsconfig.json ${matchedPatternText} to ${candidate}`,
resolveContext,
(resolverErr: any, resolverResult: any | undefined) => {
resolve([resolverErr, resolverResult])
(resolverErr: any, resolverResult: any) => {
if (resolverErr || resolverResult === undefined) {
triedPaths.push(candidate)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

triedPaths doesn't seem to be used at all...

// try next path candidate
return pathCallback()
}
return pathCallback(resolverErr, resolverResult)
}
)
})

// There's multiple paths values possible, so we first have to iterate them all first before throwing an error
if (err || result === undefined) {
triedPaths.push(candidate)
continue
}

return result
}
},
callback
)
}
)
}
Expand Down