Forem

Ramu Narasinga
Ramu Narasinga

Posted on • Edited on • Originally published at thinkthroo.com

[Part 2] How to install npm packages programmatically?

In the previous article, we reviewed some code in Father, an NPM package development tool, related to installing npm packages programmatically and this led us to discover two functions in the installDeps.

installDeps() {
  const { api } = this;
  const npmClient = getNpmClient({ cwd: api.cwd });
  installWithNpmClient({
    npmClient,
  });
  logger.quietExpect.info(`Install dependencies with ${npmClient}`);
}
Enter fullscreen mode Exit fullscreen mode

We will review the two functions listed below:

  • getNpmClient

  • installWithNpmClient

getNpmClient

getNpmClient is imported as shown below

import { getNpmClient, installWithNpmClient, prompts } from '@umijs/utils';
Enter fullscreen mode Exit fullscreen mode

I did search for utils repo in the umijs organisation.

Image description
Since Father codebase is a not a monorepo, @umijs/utils has to be an npm package. You will find this utils package in umi repository.

Image description

Finally, getNpmClient code can be found in utils/npmClient.ts

export const getNpmClient = (opts: { cwd: string }): NpmClient => {
  const tnpmRegistries = ['.alibaba-inc.', '.antgroup-inc.'];
  const tcnpmLockPath = join(opts.cwd, 'node_modules', '.package-lock.json');
  const chokidarPkg = require('chokidar/package.json');

  // detect tnpm/cnpm client
  // all situations:
  //   - npminstall mode + native fs => generate _resolved field in package.json
  //   - npminstall mode + rapid fs => generate .package-lock.json in node_modules
  //   - npm mode + native fs => generate .package-lock.json in node_modules
  //   - npm mode + rapid fs => generate .package-lock.json in node_modules
  // all conditions:
  //   - has _resolved field or .package-lock.json means tnpm/cnpm
  //   - _resolved field or .package-lock.json contains tnpm registry means tnpm
  if (chokidarPkg._resolved) {
    return tnpmRegistries.some((r) => chokidarPkg._resolved.includes(r))
      ? 'tnpm'
      : 'cnpm';
  } else if (existsSync(tcnpmLockPath)) {
    const tcnpmLock = readFileSync(tcnpmLockPath, 'utf-8');

    return tnpmRegistries.some((r) => tcnpmLock.includes(r)) ? 'tnpm' : 'cnpm';
  }

  const chokidarPath = require.resolve('chokidar');
  if (
    chokidarPath.includes('.pnpm') ||
    existsSync(join(opts.cwd, 'node_modules', '.pnpm'))
  ) {
    return 'pnpm';
  }
  if (
    existsSync(join(opts.cwd, 'yarn.lock')) ||
    existsSync(join(opts.cwd, 'node_modules', '.yarn-integrity'))
  ) {
    return 'yarn';
  }
  return 'npm';
};
Enter fullscreen mode Exit fullscreen mode

Detect tnpm/cnpm client

has _resolved field or .package-loc.json means tnpm/cnpm

This comment explains what this below if block is about

if (chokidarPkg._resolved) {
    return tnpmRegistries.some((r) => chokidarPkg._resolved.includes(r))
      ? 'tnpm'
      : 'cnpm';
}
Enter fullscreen mode Exit fullscreen mode

- _resolved field or .package-lock.json contains tnpm registry means tnpm

This comment above explains the else if block below

else if (existsSync(tcnpmLockPath)) {
  const tcnpmLock = readFileSync(tcnpmLockPath, 'utf-8');

  return tnpmRegistries.some((r) => tcnpmLock.includes(r)) ? 'tnpm' : 'cnpm';
}
Enter fullscreen mode Exit fullscreen mode

This is the first time I am learning about tnpm or cnpm. I will write an article about these.

Detect pnpm client

const chokidarPath = require.resolve('chokidar');
  if (
    chokidarPath.includes('.pnpm') ||
    existsSync(join(opts.cwd, 'node_modules', '.pnpm'))
  ) {
    return 'pnpm';
  }
Enter fullscreen mode Exit fullscreen mode

Detect yarn client

if (
    existsSync(join(opts.cwd, 'yarn.lock')) ||
    existsSync(join(opts.cwd, 'node_modules', '.yarn-integrity'))
  ) {
    return 'yarn';
  }
Enter fullscreen mode Exit fullscreen mode

if none of the above conditions are met, this function defaults to “npm”.

installWithNpmClient

In the same packages/utils/npmClient.ts fiile, you will find the installWithNpmClient.

export const installWithNpmClient = ({
  npmClient,
  cwd,
}: {
  npmClient: NpmClient;
  cwd?: string;
}): void => {
  const { sync } = require('../compiled/cross-spawn');
  // pnpm install will not install devDependencies when NODE_ENV === 'production'
  // we should remove NODE_ENV to make sure devDependencies can be installed
  const { NODE_ENV: _, ...env } = process.env;
  const npm = sync(npmClient, [npmClient === 'yarn' ? '' : 'install'], {
    stdio: 'inherit',
    cwd,
    env,
  });
  if (npm.error) {
    throw npm.error;
  }
};
Enter fullscreen mode Exit fullscreen mode

We need to learn more about sync function. sync is required from compiled/cross-spawn. Uncompiled cross-spawn can be found at node-cross-spawn. How do I know? both of these have the common name mentioned — André Cruz in utils/compiled/cross-spawn/package.json and in node-cross-spawn package.json.

Seeing this “compiled” rings a bell for me. I have seen similar compiled folder in Next.js source code.

About me:

Hey, my name is Ramu Narasinga. I study large open-source projects and create content about their codebase architecture and best practices, sharing it through articles, videos.

I am open to work on interesting projects. Send me an email at ramu.narasinga@gmail.com

My Github — https://github.com/ramu-narasinga

My website — https://ramunarasinga.com

My Youtube channel — https://www.youtube.com/@thinkthroo

Learning platform — https://thinkthroo.com

Codebase Architecture — https://app.thinkthroo.com/architecture

Best practices — https://app.thinkthroo.com/best-practices

Production-grade projects — https://app.thinkthroo.com/production-grade-projects

References:

  1. https://github.com/umijs/father/blob/a95a4ead36ecb4788728348fcb45a83507a0fb17/src/commands/generators/jest.ts#L97

  2. https://github.com/umijs/father/blob/master/src/commands/generators/utils.ts#L1

  3. https://github.com/search?q=repo%3Aumijs%2Ffather%20%40umijs%2Futils&type=code

  4. https://github.com/orgs/umijs/repositories?q=utils

  5. https://www.npmjs.com/package/@umijs/utils

  6. https://github.com/umijs/umi/blob/master/package.json#L82

  7. https://github.com/umijs/umi/tree/master/packages/utils

  8. https://github.com/umijs/umi/blob/9c3194d0617276fbecd09d19a8cff606fcaac82d/packages/utils/src/npmClient.ts#L13

  9. https://github.com/umijs/umi/tree/9c3194d0617276fbecd09d19a8cff606fcaac82d/packages/utils/compiled

Top comments (0)