Skip to content
This repository has been archived by the owner on Feb 25, 2022. It is now read-only.

Commit

Permalink
symlink the entire node_modules for npm and yarn (#77)
Browse files Browse the repository at this point in the history
  • Loading branch information
xiongemi committed Jun 29, 2021
1 parent 64188a2 commit 00e3c99
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 17 deletions.
10 changes: 10 additions & 0 deletions packages/react-native/builders.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
"implementation": "./src/executors/sync-deps/sync-deps.impl",
"schema": "./src/executors/sync-deps/schema.json",
"description": "Syncs dependencies to package.json (required for autolinking)."
},
"ensure-symlink": {
"implementation": "./src/executors/ensure-symlink/ensure-symlink.impl",
"schema": "./src/executors/ensure-symlink//schema.json",
"description": "Ensure workspace node_modules is symlink under app's node_modules folder."
}
},
"builders": {
Expand Down Expand Up @@ -61,6 +66,11 @@
"implementation": "./src/executors/sync-deps/compat",
"schema": "./src/executors/sync-deps/schema.json",
"description": "Syncs dependencies to package.json (required for autolinking)."
},
"ensure-symlink": {
"implementation": "./src/executors/ensure-symlink/compat",
"schema": "./src/executors/ensure-symlink//schema.json",
"description": "Ensure workspace node_modules is symlink under app's node_modules folder."
}
}
}
2 changes: 1 addition & 1 deletion packages/react-native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,4 @@
"migrations": "./migrations.json"
},
"schematics": "./collection.json"
}
}
5 changes: 5 additions & 0 deletions packages/react-native/src/executors/ensure-symlink/compat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { convertNxExecutor } from '@nrwl/devkit';

import ensureSymlinkExecutor from './ensure-symlink.impl';

export default convertNxExecutor(ensureSymlinkExecutor);
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ExecutorContext } from '@nrwl/devkit';
import { ensureNodeModulesSymlink } from '../../utils/ensure-node-modules-symlink';

export interface ReactNativeEnsureSymlinkOutput {
success: boolean;
}

export default async function* ensureSymlinkExecutor(
_,
context: ExecutorContext
): AsyncGenerator<ReactNativeEnsureSymlinkOutput> {
const projectRoot = context.workspace.projects[context.projectName].root;

ensureNodeModulesSymlink(context.root, projectRoot);

yield { success: true };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"title": "Ensure Symlink for React Native",
"description": "Ensure workspace node_modules is symlink under app's node_modules folder.",
"type": "object",
"properties": {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,10 @@ function getTargets(options: NormalizedSchema) {
options: {},
};

architect['ensure-symlink'] = {
executor: '@nrwl/react-native:ensure-symlink',
options: {},
};

return architect;
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,31 @@ describe('ensureNodeModulesSymlink', () => {
expectSymlinkToExist('@babel/runtime');
});

it('should throw error if package symlink is invalid', () => {
expect(() => {
ensureNodeModulesSymlink(workspaceDir, appDir);
}).toThrow(/Invalid symlink/);
it('should add packages listed in workspace package.json', () => {
fs.writeFileSync(
join(workspaceDir, 'package.json'),
JSON.stringify({
name: 'workspace',
dependencies: {
random: '9999.9.9',
},
})
);
createNpmDirectory('@nrwl/react-native', '9999.9.9');
createNpmDirectory(
'@react-native-community/cli-platform-android',
'7777.7.7'
);
createNpmDirectory('@react-native-community/cli-platform-ios', '7777.7.7');
createNpmDirectory('hermes-engine', '3333.3.3');
createNpmDirectory('react-native', '0.9999.0');
createNpmDirectory('jsc-android', '888888.0.0');
createNpmDirectory('@babel/runtime', '5555.0.0');
createNpmDirectory('random', '9999.9.9');

ensureNodeModulesSymlink(workspaceDir, appDir);

expectSymlinkToExist('random');
});

it('should support pnpm', () => {
Expand Down Expand Up @@ -136,7 +157,10 @@ describe('ensureNodeModulesSymlink', () => {
function createPnpmDirectory(packageName, version) {
const dir = join(
workspaceDir,
`node_modules/.pnpm/${packageName}@${version}/node_modules/${packageName}`
`node_modules/.pnpm/${packageName.replace(
'/',
'+'
)}@${version}/node_modules/${packageName}`
);
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(
Expand Down
65 changes: 54 additions & 11 deletions packages/react-native/src/utils/ensure-node-modules-symlink.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import * as chalk from 'chalk';
import { join } from 'path';
import { platform } from 'os';
import * as fs from 'fs';
import { createDirectory, readJsonFile } from '@nrwl/workspace';
import chalk = require('chalk');

const packagesToSymlink = [
const requiredPackages = [
'react-native',
'jsc-android',
'@react-native-community/cli-platform-ios',
Expand All @@ -14,14 +14,59 @@ const packagesToSymlink = [
'@babel/runtime',
];

/**
* This function symlink workspace node_modules folder with app project's node_mdules folder.
* For yarn and npm, it will symlink the entire node_modules folder.
* If app project's node_modules already exist, it will remove it first then symlink it.
* For pnpm, it will go through the package.json' dependencies and devDependencies, and also the required packages listed above.
* @param workspaceRoot path of the workspace root
* @param projectRoot path of app project root
*/
export function ensureNodeModulesSymlink(
workspaceRoot: string,
projectRoot: string
): void {
const worksapceNodeModulesPath = join(workspaceRoot, 'node_modules');
if (!fs.existsSync(worksapceNodeModulesPath)) {
throw new Error(`Cannot find ${worksapceNodeModulesPath}`);
}

const appNodeModulesPath = join(projectRoot, 'node_modules');
// `mklink /D` requires admin privilege in Windows so we need to use junction
const symlinkType = platform() === 'win32' ? 'junction' : 'dir';

if (fs.existsSync(appNodeModulesPath)) {
fs.rmdirSync(appNodeModulesPath, { recursive: true });
}
fs.symlinkSync(worksapceNodeModulesPath, appNodeModulesPath, symlinkType);

if (isPnpm(workspaceRoot)) {
symlinkPnpm(workspaceRoot, appNodeModulesPath, symlinkType);
}
}

function isPnpm(workspaceRoot: string): boolean {
const pnpmDir = join(workspaceRoot, 'node_modules/.pnpm');
return fs.existsSync(pnpmDir);
}

function symlinkPnpm(
workspaceRoot: string,
appNodeModulesPath: string,
symlinkType: 'junction' | 'dir'
) {
const worksapcePackageJsonPath = join(workspaceRoot, 'package.json');
const workspacePackageJson = readJsonFile(worksapcePackageJsonPath);
const workspacePackages = Object.keys({
...workspacePackageJson.dependencies,
...workspacePackageJson.devDependencies,
});

const packagesToSymlink = new Set([
...workspacePackages,
...requiredPackages,
]);

createDirectory(appNodeModulesPath);

packagesToSymlink.forEach((p) => {
Expand All @@ -43,19 +88,17 @@ export function ensureNodeModulesSymlink(

function locateNpmPackage(workspaceRoot: string, packageName: string): string {
const pnpmDir = join(workspaceRoot, 'node_modules/.pnpm');
const isPnpm = fs.existsSync(pnpmDir);
if (!isPnpm) {
return join(workspaceRoot, 'node_modules', packageName);
}

let candidates: string[];
if (isScopedPackage(packageName)) {
const { scope, name } = getScopedData(packageName);
const scopedDir = join(pnpmDir, scope);
candidates = fs
.readdirSync(scopedDir)
.filter((f) => f.startsWith(name))
.map((p) => join(scope, p));
.readdirSync(pnpmDir)
.filter(
(f) =>
f.startsWith(`${scope}+${name}`) &&
fs.lstatSync(join(pnpmDir, f)).isDirectory()
);
} else {
candidates = fs
.readdirSync(pnpmDir)
Expand All @@ -76,7 +119,7 @@ function locateNpmPackage(workspaceRoot: string, packageName: string): string {
...packageJson.dependencies,
...packageJson.devDependencies,
};
const version = deps['react-native'];
const version = deps[packageName];
const found = candidates.find((c) => c.includes(version));
if (found) {
return join(pnpmDir, found, 'node_modules', packageName);
Expand Down

0 comments on commit 00e3c99

Please sign in to comment.