Skip to content

Commit

Permalink
feat(*): allow blacklist of custom directories
Browse files Browse the repository at this point in the history
Additionally allow specification of whether or not additional blacklisted directories and additional
watch folders should resolve symlinks
  • Loading branch information
bericp1 committed Oct 1, 2019
1 parent 4ae4ba0 commit 6c10658
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 30 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,14 @@ the list, you can safely use the single `applyConfigForLinkedDependencies` funct
any linked dependencies' `node_modules`. If you get naming collisions for certain modules, add those modules
by name here and restart the bundler using `--reset-cache`. A common one is `react-native` which will typically
show up as a dev dependency in react native packages since it's used in tests.
- `blacklistDirectories` (`string[]`, defaults to `[]`): a list of absolute or relative (to `projectRoot`) directories
that should be blacklisted in addition to the directories determined via `blacklistLinkedModules`.
- `resolveBlacklistDirectoriesSymlinks` (`boolean`, defaults to `true`): whether or not to resolve symlinks when
processing `blacklistDirectories`.
- `additionalWatchFolders` (`string[]`, defaults to `[]`): a list of additional absolute paths to watch, merged
directly into the `watchFolders` option.
- `resolveAdditionalWatchFoldersSymlinks` (`boolean`, defaults to `true`): whether or not to resolve symlinks when
processing `additionalWatchFolders`.
- `silent` (`boolean`, defaults to `false`): Set this to `true` to suppress warning output in the bundler that shows
up when linked dependencies are detected.
Expand Down
124 changes: 94 additions & 30 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const chalk = require('chalk');
* @see https://github.com/facebook/metro/issues/452
*
* @param args All args are simply forwarded to `metro-config`'s `mergeConfig`.
* @return {{symbolicator}|*}
* @return {object}
*/
function mergeConfig(...args) {
const mergedConfig = metroMergeConfig(...args);
Expand All @@ -44,6 +44,20 @@ function inferProjectRoot() {
return process.cwd();
}

/**
* Resolve a list of directories relative to the root of the project (if they're not already absolute paths).
*
* @param {string} projectRoot
* @param {string[]} paths
* @param {boolean} resolveSymlinks
*/
function resolvePaths(projectRoot, paths, resolveSymlinks = true) {
return paths.map((possibleRelativePath) => {
const absolutePath = path.resolve(projectRoot, possibleRelativePath);
return resolveSymlinks ? fs.realpathSync(absolutePath) : absolutePath;
});
}

/**
* Resolve all detected linked directories to unique absolute paths without a trailing slash.
*
Expand All @@ -68,14 +82,23 @@ function resolveDevPaths(projectRoot) {
*
* Returns null if there are no `resolvedDevPaths` or `blacklistLinkedModules`
*
* @param resolvedDevPaths
* @param blacklistLinkedModules
* @return {null|RegExp}
* @param {string[]=} resolvedDevPaths A list of resolved, real, absolute paths to the dependencies that are linked
* in node_modules.
* @param {string[]=} blacklistLinkedModules A list of node_modules to blacklist within linked deps.
* @param {string[]=} resolvedBlacklistDirectories Additional directories to blacklist (already resolved to absolute
* paths)
* @return {null|RegExp} A regular expression that will match all ignored directories and all of their contents.
* Ignored directories will be the blacklisted modules within node_modules/ within each linked dependency as well
* as any explicitly defined via `resolvedBlacklistDirectories`. Will return null if there's no directories
* to ignore.
*/
function generateBlacklistGroupForLinkedModules(
resolvedDevPaths = [],
blacklistLinkedModules = [],
resolvedBlacklistDirectories = [],
) {
const ignoreDirPatterns = [];

if (resolvedDevPaths.length > 0 && blacklistLinkedModules.length > 0) {
const escapedJoinedDevPaths = resolvedDevPaths
.map(escapeForRegExp)
Expand All @@ -85,29 +108,45 @@ function generateBlacklistGroupForLinkedModules(
.join('|');
const devPathsMatchingGroup = `(${escapedJoinedDevPaths})`;
const modulesMatchingGroup = `(${escapedJoinedModules})`;
return new RegExp(
`(${devPathsMatchingGroup}\\/node_modules\\/${modulesMatchingGroup}(/.*|))`,
ignoreDirPatterns.push(
`${devPathsMatchingGroup}\\/node_modules\\/${modulesMatchingGroup}`,
);
}

if (resolvedBlacklistDirectories.length > 0) {
ignoreDirPatterns.push(
...resolvedBlacklistDirectories.map(escapeForRegExp),
);
}

if (ignoreDirPatterns.length > 0) {
const ignoreDirsGroup = `(${ignoreDirPatterns.join('|')})`;
return new RegExp(`(${ignoreDirsGroup}(/.*|))`);
}

return null;
}

/**
* Generate a resolver config containing the `blacklistRE` option if there are linked dep node_modules that need
* to be blacklisted.
* to be blacklisted as well as any directories that were explicitly blacklisted via config.
*
* @param {string[]=} resolvedDevPaths
* @param {string[]=} blacklistLinkedModules
* @param {string[]=} resolvedDevPaths A list of resolved, real, absolute paths to the dependencies that are linked
* in node_modules.
* @param {string[]=} blacklistLinkedModules A list of node_modules to blacklist within linked deps.
* @param {string[]=} resolvedBlacklistDirectories Additional directories to blacklist (already resolved to absolute
* paths)
* @return {{}|{blacklistRE: RegExp}}
*/
function generateLinkedDependenciesResolverConfig(
resolvedDevPaths = [],
blacklistLinkedModules = [],
resolvedBlacklistDirectories = [],
) {
const blacklistGroup = generateBlacklistGroupForLinkedModules(
resolvedDevPaths,
blacklistLinkedModules,
resolvedBlacklistDirectories,
);

if (blacklistGroup) {
Expand All @@ -123,19 +162,21 @@ function generateLinkedDependenciesResolverConfig(
* Generate a list of watchFolders based on linked dependencies found, additional watch folders passed in as an option,
* and addition watch folders detected in the existing config.
*
* @param {string[]} resolvedDevPaths
* @param {string[]} additionalWatchFolders
* @param {string[]} resolvedDevPaths A list of resolved, real, absolute paths to the dependencies that are linked
* in node_modules.
* @param {string[]} resolvedAdditionalWatchFolders A list of additional directories to watch (already resolved to real
* absolute paths).
* @param {{watchFolders: string[]}|null} existingProjectConfig
* @return {string[]}
*/
function generateLinkedDependenciesWatchFolders(
resolvedDevPaths = [],
additionalWatchFolders = [],
resolvedAdditionalWatchFolders = [],
existingProjectConfig = null,
) {
return [
...resolvedDevPaths,
...additionalWatchFolders,
...resolvedAdditionalWatchFolders,
...((existingProjectConfig && existingProjectConfig.watchFolders) ||
[]),
];
Expand All @@ -144,7 +185,8 @@ function generateLinkedDependenciesWatchFolders(
/**
* Warn the developer about the presence of symlinked dependencies.
*
* @param {string[]} resolvedDevPaths
* @param {string[]} resolvedDevPaths A list of resolved, real, absolute paths to the dependencies that are linked
* in node_modules.
* @param {string[]} blacklistLinkedModules
*/
function warnDeveloper(resolvedDevPaths = [], blacklistLinkedModules = []) {
Expand All @@ -171,12 +213,28 @@ function warnDeveloper(resolvedDevPaths = [], blacklistLinkedModules = []) {
}
}

/**
* Transform a metro configuration object to allow it to support symlinked node_modules.
*
* @param {object=} projectConfig
* @param {string|null=} projectRoot
* @param {string[]} blacklistLinkedModules
* @param {string[]} blacklistDirectories
* @param {boolean=} resolveBlacklistDirectoriesSymlinks
* @param {string[]=} additionalWatchFolders
* @param {boolean=} resolveAdditionalWatchFoldersSymlinks
* @param {boolean=} silent
* @return {object}
*/
function applyConfigForLinkedDependencies(
projectConfig = {},
{
projectRoot = null,
blacklistLinkedModules = [],
blacklistDirectories = [],
resolveBlacklistDirectoriesSymlinks = true,
additionalWatchFolders = [],
resolveAdditionalWatchFoldersSymlinks = true,
silent = false,
} = {},
) {
Expand Down Expand Up @@ -208,26 +266,32 @@ function applyConfigForLinkedDependencies(
// Resolve all of the linked dependencies and only continue to modify config if there are any.
const resolvedDevPaths = resolveDevPaths(realProjectRoot);

if (resolvedDevPaths.length > 0) {
if (!silent) {
// Warn the user about the fact that the workaround is in effect.
warnDeveloper(resolvedDevPaths, blacklistLinkedModules);
}
if (resolvedDevPaths.length > 0 && !silent) {
// Warn the user about the fact that the workaround is in effect.
warnDeveloper(resolvedDevPaths, blacklistLinkedModules);
}

return mergeConfig(projectConfig, {
resolver: generateLinkedDependenciesResolverConfig(
resolvedDevPaths,
blacklistLinkedModules,
// Generate the metro config based on the options passed in
return mergeConfig(projectConfig, {
resolver: generateLinkedDependenciesResolverConfig(
resolvedDevPaths,
blacklistLinkedModules,
resolvePaths(
realProjectRoot,
blacklistDirectories,
resolveBlacklistDirectoriesSymlinks,
),
watchFolders: generateLinkedDependenciesWatchFolders(
resolvedDevPaths,
),
watchFolders: generateLinkedDependenciesWatchFolders(
resolvedDevPaths,
resolvePaths(
realProjectRoot,
additionalWatchFolders,
projectConfig,
resolveAdditionalWatchFoldersSymlinks,
),
});
}

return projectConfig;
projectConfig,
),
});
}

module.exports = {
Expand Down

0 comments on commit 6c10658

Please sign in to comment.