How to Create an NPM Packages using Rollup.js + Lerna.js + Jfrog Artifactory

eugene_yues_17

Eugene YuIT

Posted on November 13, 2024

How to Create an NPM Packages using Rollup.js + Lerna.js + Jfrog Artifactory

📃In this article we will figure it out, how to create your own npm package using the following technologies:

  • Rollup.js is a JavaScript module bundler that compiles small pieces of code into larger, more complex applications. It is particularly well-suited for bundling ES6 modules and is known for its efficient tree-shaking capabilities, which eliminate unused code from the final bundle, resulting in smaller file sizes.

  • Lerna.js is a popular tool for managing JavaScript projects with multiple packages, often referred to as monorepos. It simplifies the process of versioning, publishing, and managing dependencies across various packages within a single repository.

  • JFrog Artifactory is a universal artifact repository manager that enables organizations to store, manage, and distribute software packages and artifacts across the entire development lifecycle. It supports a wide range of package formats, including Docker, Maven, npm, PyPI, and more, making it a versatile solution for DevOps and CI/CD pipelines.


JFrog Artifactory Settings

If you don't have artifactory, you can use the free trial or the free open source version. But the free open source is a cut down version that doesn't have any pre-built settings for the npm repository. Let's do it step by step!

  1. Let's create a new virtual npm repository. It's necessary to install our npm package from external access (public repository).
    The JFrog virtual npm repository is a consolidated view of multiple npm repositories, allowing users to access and manage npm packages from various sources in a single location. It acts as a proxy for local and remote npm repositories, enabling seamless integration of public and private packages. This setup simplifies dependency management, enhances performance through caching, and provides a unified interface for package retrieval. Additionally, virtual repositories support security and access control, ensuring that only authorized users can access specific packages.
    Click to create virtual repo
    Choose npm
    Input settings

  2. Add a new local repository. It's necessary to publish our npm package to private repository.
    The JFrog local npm repository is a dedicated storage space within JFrog Artifactory for hosting and managing your organization's private npm packages. It allows teams to publish, version, and manage their own npm packages securely and efficiently. By using a local repository, developers can ensure that their packages are readily available, control access permissions, and maintain a consistent versioning system. Local npm repositories also facilitate faster builds and deployments by providing quick access to internal packages, ultimately enhancing the overall development workflow.
    Click to create local repo
    Input settings

  3. Add a new remote repository. It's necessary for to save cache from external registry: https://registry.npmjs.org or https://registry.yarnpkg.com/
    The JFrog remote repository is a type of repository in JFrog Artifactory that acts as a proxy for a remote artifact repository, such as npm registry or Docker Hub. It allows users to access and cache artifacts from these external sources without directly interacting with them.

    • Caching: Artifacts are cached locally upon first access, improving performance for subsequent requests. Access Control: Provides security and access management for remote artifacts.
    • Aggregation: Can aggregate multiple remote repositories into a single endpoint, simplifying dependency management.
    • Metadata Management: Supports metadata for better organization and search capabilities. Click to create remote repo Input name Input registry url
  4. Let's move on to editing the virtual repository to combine with local and remote ones.
    Edit virtual repo
    Select repo
    Set default repo
    Remote repo for cache


NPM Packages using Rollup.js + Lerna.js

For our NPM packages example we use Rollup.js + Lerna.js (link to my example npm library). As for the usage of these libraries, we can look at the official documentation:

❗But I'd like to show you the details of the settings ⚙.

Rollup settings

After install Rollup.js, i have set up the basic rollup configuration:

import {defineConfig} from 'rollup';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import {terser} from 'rollup-plugin-terser';
import url from '@rollup/plugin-url';

import * as fs from 'fs';
import path from 'path';

const PACKAGE_NAME = process.cwd();
const packageJson = JSON.parse(fs.readFileSync(path.join(PACKAGE_NAME, 'package.json'), 'utf-8'));

const includePaths = ['**/*.woff', '**/*.woff2', '**/*.svg', '**/*.png'];

export default defineConfig({
  input: 'src/index.ts',
  output: [
    {
      file: packageJson.main,
      format: 'cjs',
      sourcemap: false,
      name: packageJson.name,
    },
    {
      file: packageJson.module,
      format: 'es',
      sourcemap: false,
      name: packageJson.name,
    },
  ],
  plugins: [
    resolve(),
    commonjs(),
    typescript({tsconfig: './tsconfig.json'}),
    terser(),
    url({
      fileName: '[name][extname]',
      include: includePaths,
      limit: 0,
    }),
  ],
  external: [...Object.keys(packageJson.peerDependencies || {})],
});
Enter fullscreen mode Exit fullscreen mode

From this configuration we see the following settings:

  • packageJson is needed to get settings from the package.json file (for which the build is in progress)
  • includePaths is needed to support the paths to our images, fonts and ect. We can also save the file name via props fileName, otherwise the file will be an auto-generated name.
    Auto-generated name
    Save file name

  • external option is used to specify which modules should be treated as external dependencies. ❗When you mark a module as external, Rollup will not include it in the output bundle. Instead, it will assume that the module will be available in the environment where the bundle is executed (e.g., in your project where you will install this npm package). In here we includes peerDependencies from our packages.
    Define peerDependencies in package.json, you can specify the libraries that are expected to be installed by the consumer of your package.
    For example, we added peer dependencies in the package ui

  "peerDependencies": {
    "react": "^18.x",
    "react-dom": "^18.x"
  }
Enter fullscreen mode Exit fullscreen mode

These peer dependencies ensures the package ui can be installed with the 18.x major version of the package react and react-dom only and these React libraries must be installed from your project (e.g. i installed them in my client project portfolio-yues-it-vite)

⁉In other words when using peerDependencies with the external option:

  • external ensures when you run the Rollup build command, it will create a bundle that does not include react and react-dom
  • peerDependencies ensures that these dependencies will be provided by the consumer of your library (i.e. in my case it's portfolio-yues-it-vite) and limits react and react-dom versions to only major version 18.x

Also you can see other official rollup plugins here.

Lerna settings

After init Lerna.js we can see lerna.json:

{
  "$schema": "node_modules/lerna/schemas/lerna-schema.json",
  "version": "independent",
  "packages": ["packages/*"],
  "npmClient": "yarn"
  // "command": {
  //   "publish": {
  //     "registry": "https://some-art.com/artifactory/api/npm/portfolio-yues-it-npm/"
  //   }
  // }
}
Enter fullscreen mode Exit fullscreen mode

I added npmClientand command for testing lerna publish via yarn, but it didn't work for me and i published via yarn workspaces foreach no-private (we will it below ⬇).
An example of a project structure looks like this (❗Please note that in the settings you need to specify the prefix of the name of our root project):
Project structure

❗Before to run npx lerna commands you need to set up ssh access to git via terminal (this is necessary for local publishing from a git repository) and test the connection for github: ssh -T git@github.com.

To publish to git, you need to add settings to .yarnrc.yml:

enableStrictSsl: false

httpTimeout: 12000000

nodeLinker: node-modules

npmAlwaysAuth: false

npmPublishRegistry: 'https://some-art.com/artifactory/api/npm/portfolio-yues-it-npm/'

npmRegistryServer: 'https://some-art.com/artifactory/api/npm/portfolio-yues-it-npm/'

npmAuthToken: 'some token from a virtual repository jfrog (Repositories -> Virtual -> Set me up -> Configure -> Generate token)'

yarnPath: .yarn/releases/yarn-berry.cjs

Enter fullscreen mode Exit fullscreen mode

Please note:

  • this url of virtual repository - 'https://some-art.com/artifactory/api/npm/portfolio-yues-it-npm/' we take from artifactory Set up repo-click button Configure and generate token
  • in addition, if you have a problem with public to JFROG Artifactory (when the yarn uses the Glogal settings), because the yarn doesn't use the local project settings from file .yarnrc.yml -> ❗you can see corepack for isolation yarn configs for different projects.

Once lerna and git are configured we can:

  • install a npm package for a specific lerna package via: yarn workspace @portfolio-yues-it-npm/utils add date-fns
  • version in git and publish to artifactory via:
    • run build via: npx lerna run build
    • then: yarn install
    • make commit changes
    • run versioning via: npx lerna version --no-private (📝--no-private is needed to publish packages other than privates ones)
    • select the necessary version: Patch / Minor / Major (after that will be push to your git repository)
    • make publish to artifactory via yarn: yarn workspaces foreach --all --no-private npm publish (⁉when publishing via: npx lerna publish --no-private, sometimes there is a problem after publishing with installing packages, i.e. with slash decoding (instead of '/' to '%2f'), because of which it doesn't find the path to the package and there is the error)

You can see more of my commands in README


How to use npm packages

After setting jfrog and npm project (lerna + rollup) we can install this package.
But before we must:

  • write virtual url registry of our artifactory in .yarnrc.yml
enableStrictSsl: false

httpTimeout: 600000

nodeLinker: node-modules

npmRegistryServer: 'https://some-art.com/artifactory/api/npm/portfolio-yues-it-npm/'

yarnPath: .yarn/releases/yarn-4.5.1.cjs

Enter fullscreen mode Exit fullscreen mode
  • than install package via:
    • yarn add @portfolio-yues-it-npm/ui
    • yarn add @portfolio-yues-it-npm/utils
    • yarn add @portfolio-yues-it-npm/icons

If you want to test your npm package without publishing, just provide that path (file:../portfolio-yues-it-npm/packages/ui) in dependencies and run yarn install

  "dependencies": {
    "@portfolio-yues-it-npm/ui": "file:../portfolio-yues-it-npm/packages/ui",
    "react": "^18.3.1",
    "react-dom": "^18.3.1"
  },
Enter fullscreen mode Exit fullscreen mode

My examples:

Thanks for reading and feedback!🙂

💖 💪 🙅 🚩
eugene_yues_17
Eugene YuIT

Posted on November 13, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related