📃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!
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.
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.
-
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.
Let's move on to editing the virtual repository to combine with local and remote ones.
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 || {})],
});
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.
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"
}
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/"
// }
// }
}
I added npmClient
and 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):
❗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
❗Please note:
- this url of virtual repository - 'https://some-art.com/artifactory/api/npm/portfolio-yues-it-npm/' we take from artifactory
- 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
- 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"
},
My examples:
Thanks for reading and feedback!🙂
Top comments (0)