Skip to content

Inconsitant handling of path seperators resulting in generateWildcardRequire generating invalid conditions #154

@yichi-yang

Description

@yichi-yang

Bug Description

On Windows, generateWildcardRequire can generate invalid conditions due to inconsistent handling of path separators (/ vs \).

There's an active issue about this bug here: vercel/ncc#593. An example of generated buggy code (borrowed from /urbdyn/ncc-bug-1):

/***/ 71330:
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {

function __ncc_wildcard$0 (arg) {
  if (arg === "C:/code/urbdyn/ncc-bug-1/node_modules/knex/lib/dialects/") return __webpack_require__(19427);
  else if (arg === "C:/code/urbdyn/ncc-bug-1/node_modules/knex/lib/dialects/") return __webpack_require__(75941);
  else if (arg === "C:/code/urbdyn/ncc-bug-1/node_modules/knex/lib/dialects/") return __webpack_require__(17653);
  else if (arg === "C:/code/urbdyn/ncc-bug-1/node_modules/knex/lib/dialects/") return __webpack_require__(9051);
  else if (arg === "C:/code/urbdyn/ncc-bug-1/node_modules/knex/lib/dialects/") return __webpack_require__(25310);
  else if (arg === "C:/code/urbdyn/ncc-bug-1/node_modules/knex/lib/dialects/") return __webpack_require__(64482);
  else if (arg === "C:/code/urbdyn/ncc-bug-1/node_modules/knex/lib/dialects/") return __webpack_require__(97332);
  else if (arg === "C:/code/urbdyn/ncc-bug-1/node_modules/knex/lib/dialects/") return __webpack_require__(85370);
}

Cause

function generateWildcardRequire(dir, wildcardPath, wildcardParam, wildcardBlocks, log) {
const wildcardBlockIndex = wildcardBlocks.length;
const trailingWildcard = wildcardPath.endsWith(WILDCARD);
const wildcardIndex = wildcardPath.indexOf(WILDCARD);
const wildcardPrefix = wildcardPath.substr(0, wildcardIndex);
const wildcardSuffix = wildcardPath.substr(wildcardIndex + 1);
const endPattern = wildcardSuffix ? '?(.@(js|json|node))' : '.@(js|json|node)';
// sync to support no emission case
if (log)
console.log('Generating wildcard requires for ' + wildcardPath.replace(WILDCARD, '*'));
let options = glob.sync(wildcardPrefix + '**' + wildcardSuffix + endPattern, { mark: true, ignore: 'node_modules/**/*' });
if (!options.length)
return;
const optionConditions = options.map((file, index) => {
const arg = JSON.stringify(file.substring(wildcardPrefix.length, file.lastIndexOf(wildcardSuffix)));
let relPath = path.relative(dir, file).replace(/\\/g, '/');
if (!relPath.startsWith('../'))
relPath = './' + relPath;
let condition = index === 0 ? ' ' : ' else ';
if (trailingWildcard && arg.endsWith('.js"'))
condition += `if (arg === ${arg} || arg === ${arg.substr(0, arg.length - 4)}")`;
else if (trailingWildcard && arg.endsWith('.json"'))
condition += `if (arg === ${arg} || arg === ${arg.substr(0, arg.length - 6)}")`;
else if (trailingWildcard && arg.endsWith('.node"'))
condition += `if (arg === ${arg} || arg === ${arg.substr(0, arg.length - 6)}")`;
else
condition += `if (arg === ${arg})`;
condition += ` return require(${JSON.stringify(relPath)});`;
return condition;
}).join('\n');
wildcardBlocks.push(`function __ncc_wildcard$${wildcardBlockIndex} (arg) {\n${optionConditions}\n}`);
return `__ncc_wildcard$${wildcardBlockIndex}(${wildcardParam})`;
}

On Windows, wildcardPath uses \ as separators. In the example above:

wildcardPath = "C:\\SOME\\PATH\\node_modules\\knex\\lib\\dialects\\*\\index.js";
wildcardPrefix = "C:\\SOME\\PATH\\node_modules\\knex\\lib\\dialects\\";
wildcardSuffix = "\\index.js";

The options returned by glob.sync, however, are in POSIX format.

options = [
  "C:/SOME/PATH/node_modules/knex/lib/dialects/better-sqlite3/index.js",
  "C:/SOME/PATH/node_modules/knex/lib/dialects/cockroachdb/index.js",
  "C:/SOME/PATH/node_modules/knex/lib/dialects/mssql/index.js",
  // ...
];

As the result, file.lastIndexOf(wildcardSuffix) will always be -1, and file.substring(...) is just the prefix (but in POSIX format), which is obviously not right.

Also, the glob implementation asks for / regardless of POSIX or Windows, and passing Windows style wildcardPrefix + '**' + wildcardSuffix + endPattern could be considered invalid.

Proposed Fix

We normalize dir and wildcardPath to POSIX style, and use path.posix.relative instead of path.relative.
I implemented this very simple fix in this PR: #153
I'd like some code review to make sure I haven't messed up.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions