Forem

Ramu Narasinga
Ramu Narasinga

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

[Part 1] How to install npm packages programmatically?

In this article, we review how Father, an NPM package development tool is installs npm dependencies programmatically.

Image description

How did I find this code snippet?

I wrote an article about Father and in its README.md, I found that it supports micro generators that adds commonly used engineering capabilities to projects, such as setting up Jest for testing. This is similar to the CLI tool I am building, to generate code for authentication or S3 upload in Next.js.

I started searching for the micro generators code and found a folder named commands/generators.

Image description

Common pattern in the generators

You will see that there is a common pattern in the way these generators are defined.

generators/eslint.ts

eslint.ts has the below code

import { GeneratorType } from '@umijs/core';
import { logger } from '../../utils';
import fg from 'fast-glob';
import { writeFileSync } from 'fs';
import { join } from 'path';
import { IApi } from '../../types';
import { GeneratorHelper } from './utils';

export default (api: IApi) => {
  api.describe({
    key: 'generator:eslint',
  });

  api.registerGenerator({
    key: 'eslint',
    name: 'Enable ESLint',
    description: 'Setup ESLint Configuration',
    type: GeneratorType.enable,
    checkEnable: () => {
      ...
    },
    disabledDescription:
      'ESLint has already enabled. You can remove .eslintrc, then run this again to re-setup.',
    fn: async () => {
      ....
    },
  });
};
Enter fullscreen mode Exit fullscreen mode

generators/jest.ts

jest.ts has the below definition:

import { GeneratorType } from '@umijs/core';
import { logger } from '../../utils';
import { existsSync, writeFileSync } from 'fs';
import { join } from 'path';
import { IApi } from '../../types';
import { GeneratorHelper, promptsExitWhenCancel } from './utils';

export default (api: IApi) => {
  api.describe({
    key: 'generator:jest',
  });

  api.registerGenerator({
    key: 'jest',
    name: 'Enable Jest',
    description: 'Setup Jest Configuration',
    type: GeneratorType.enable,
    checkEnable: () => {
      ...
    },
    disabledDescription:
      'Jest has already enabled. You can remove jest.config.{ts,js}, then run this again to re-setup.',
    fn: async () => {
      ...
    },
  });
};
Enter fullscreen mode Exit fullscreen mode

Do you see the common pattern in these two definitions above? there is

  • api.describe

This accepts an object that has the below properties:

  1. key
  • api.registerGenerator

This accepts an object that has the below properties:

  1. key

  2. name

  3. description

  4. type

  5. checkEnable

  6. disabledDescription

  7. fn

h.installDeps()

At line 97 in generators/jest.ts, you find this below code snippet

h.installDeps();
Enter fullscreen mode Exit fullscreen mode

What is h here? At line 27, you will see h is initialized as shown below:

const h = new GeneratorHelper(api);
Enter fullscreen mode Exit fullscreen mode

To see how the installDeps is defined, we need to review GeneratorHelper

GeneratorHelper

GeneratorHelper has the below shown functions at the time of writing this article

import { getNpmClient, installWithNpmClient, prompts } from '@umijs/utils';
import { writeFileSync } from 'fs';
import { IApi } from '../../types';
import { logger } from '../../utils';

export class GeneratorHelper {
  constructor(readonly api: IApi) {}

  addDevDeps(deps: Record<string, string>) {
    ...
  }

  addScript(name: string, cmd: string) {
    ...
  }

  private addScriptToPkg(name: string, cmd: string) {
    ...
  }

  installDeps() {
    ...  
  }
}
Enter fullscreen mode Exit fullscreen mode

installDeps

installDeps is defined as shown below in GeneratorHelper.

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

There are two functions that we need to learn about to understand how Father installs npm deps programmatically.

  • getNpmClient

  • installWithNpmClient

This will be discussed in part 2, i.e., in the next article.

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)