preamble
- Hey, it's me.Immerse
- Series of articles first appeared on【Immerse】For more information, please visit this website
- Reprint Note: Reprinted with original source and copyright notice!
1. understand the structure of NPM packages
1.1 Documentation: the core of the package
file is the central configuration of an NPM package, defining all aspects of the package, from basic metadata to complex release configurations.
{
"name": "my-awesome-package",
"version": "1.0.0",
"description": "An amazing package!",
"main": "./dist/",
"module": "./dist/",
"types": "./dist/",
"files": ["dist"],
"scripts": {
"build": "tsup src/ --format cjs,esm --dts",
"test": "jest"
},
"keywords": ["awesome", "package"],
"author": "Your Name <you@>",
"license": "MIT",
"dependencies": {
"lodash": "^4.17.21"
},
"devDependencies": {
"typescript": "^4.5.5",
"tsup": "^5.11.13",
"jest": "^27.4.7"
}
}
Let's parse some key fields in detail:
-
name
cap (a poem)version
: These two fields form the unique identifier of the package in the NPM registry. -
main
,module
cap (a poem)types
: These specify entry points for different module systems and TypeScript support. -
files
: This array specifies which files and directories should be included when publishing the package. -
scripts
: These are command shortcuts for common tasks such as building and testing.
1.2 Understanding Package Entry Points
The modern JavaScript ecosystem supports multiple module formats. Your package should adapt to different environments by providing multiple entry points.
- main: The main entry point, usually used for CommonJS (CJS) modules.
- module: Entry point for ECMAScript (ESM) modules.
- browser: Entry point for the browser environment.
- types: The entry point for TypeScript type declarations.
The following is an example of a package structure:
my-awesome-package/
├── src/
│ ├──
│ └──
├── dist/
│ ├── (CJSconstruct (sth abstract))
│ ├─── (ESMconstruct (sth abstract))
│ ├── (TypeScriptherald)
│ └── (浏览器特定construct (sth abstract))
├──
└──
correspondingConfiguration:
{
"name": "my-awesome-package",
"version": "1.0.0",
"main": "./dist/",
"module": "./dist/",
"browser": "./dist/",
"types": "./dist/",
"exports": {
".": {
"require": "./dist/",
"import": "./dist/",
"types": "./dist/"
}
}
}
2. In-depth understanding of the module format
2.1 CommonJS (CJS)
CommonJS is the traditional module format. It usesrequire()
Importing, using thePerform the export.
//
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
= {
add,
subtract,
};
//
const mathUtils = require('./mathUtils');
((5, 3)); // exports: 8
2.2 ECMAScript Module (ESM)
ESM is the modern standard for JavaScript modules, using theimport
cap (a poem)export
Statements.
//
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
//
import { add, subtract } from './';
(add(5, 3)); // exports: 8
2.3 Universal Module Definition (UMD)
UMD is a mode that allows modules to work in multiple environments (CommonJS, AMD, global variables).
(function (root, factory) {
if (typeof define === 'function' && ) {
// AMD
define(['exports'], factory);
} else if (typeof exports === 'object' && typeof !== 'string') {
// CommonJS
factory(exports);
} else {
// Browser Global Variables
factory(( = {}));
}
})(typeof self !== 'undefined' ? self : this, function (exports) {
= function (a, b) {
return a + b;
};
= function (a, b) {
return a - b;
};
});
3. Advanced package optimization techniques
3.1 Tree Shaking and Side Effects
Tree shaking is a technique used by modern packaging tools to eliminate dead code. To make your package tree shaking-ready:
- Using ES Modules
- Avoiding side effects
- exist
hit the nail on the head
"sideEffects"
field
{
"name": "my-utils",
"version": "1.0.0",
"sideEffects": false
}
If certain files do have side effects:
{
"name": "my-utils",
"version": "1.0.0",
"sideEffects": ["./src/", "*.css"]
}
3.2 Code Segmentation and Dynamic Import
For large packages, consider using code splitting to allow users to import only the parts they need:
//
export function heavyFunction() {
// ... Some computationally intensive operations
}
//
async function doHeavyWork() {
const { heavyFunction } = await import('./');
heavyFunction();
}
3.3 Conditional export
Use conditional export to provide different entry points for different environments or import conditions:
{
"name": "my-package",
"exports": {
".": {
"import": "./dist/",
"require": "./dist/",
"browser": "./dist/"
},
"./utils": {
"import": "./dist/",
"require": "./dist/"
}
}
}
4. Versioning and publishing
4.1 Semantic Version Control (SemVer)
Semanticized versions use a three-part version number: primary version number . Secondary version number . Revision number
- Master version number: when making incompatible API changes
- Sub-version number: when adding functionality in a backward-compatible manner
- Revision number: for backward compatible bug fixes
npm version patch -m "version updated to %s - fix spelling errors in documentation"
npm version minor -m "Version updated to %s - Add new utility functions"
npm version major -m "Version updated to %s - Changed API structure"
4.2 Pre-release versions
For pre-release versions, use tags with hyphens:
-
latest
: Latest online version -
alpha
:: Internal test version -
beta
:: Public beta version -
rc
:: Release candidates- Tips: It is possible to add these identifiers to the version number, as well as to add additional versions: e.g.:
1.0.0-alpha.0
cap (a poem)1.0.0-beta.1
cap (a poem)1.0.0-rc.1
- Tips: It is possible to add these identifiers to the version number, as well as to add additional versions: e.g.:
npm version prerelease --preid=alpha
# 1.0.0 -> 1.0.1-alpha.0
npm version prerelease --preid=beta
# 1.0.1-alpha.0 -> 1.0.1-beta.0
npm version prerelease --preid=rc
# 1.0.1-beta.0 -> 1.0.1-rc.0
4.3 Publishing with labels
Use tags to publish different versions or pre-release versions:
npm publish --tag next
npm publish --tag beta
Users can install specific versions:
npm install my-package@next
npm install my-package@beta
5. Continuous integration and deployment (CI/CD)
5.1 Automated Publishing with GitHub Actions
Create a.github/workflows/
Documentation:
name: publishing kit
on:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
registry-url: ''
- run: npm ci
- run: npm test
- run: npm run build
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
publish-gpr:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
registry-url: ''
- run: npm ci
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}
This workflow will automatically publish your packages to NPM and GitHub Packages as you create new versions.
5.2 Automated version updates
You can automate version updates in a CI/CD pipeline. Here is an example of using GitHub Action:
name: new version
on:
push:
branches:
- main
jobs:
bump-version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-node@v2
with:
node-version: '14'
- name: new version
run: |
git config --local "action@"
git config --local "GitHub Action"
npm version patch -m "new version到 %s [skip ci]"
- name: Push Changes
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ }}
This action will automatically update the package revision number each time a change is pushed to the master branch.
6. Package development best practices
6.1 Documentation
Good documentation is critical for package adoption. Consider using a tool like JSDoc for inline documentation:
/**
* Adds two numbers together.
* @param {number} a - The first number.
* @param {number} b - The second number.
* @returns {number} a and b - the sum.
*/
function add(a, b) {
return a + b; }
}
6.2 Testing
Use a framework like Jest for comprehensive testing:
//
export function add(a, b) {
return a + b;
}
//
import { add } from './math';
test('1 + 2 should be equal to 3', () => {
expect(add(1, 2)).toBe(3);
});
6.3 Code Checking and Formatting
Use ESLint for code inspection and Prettier for code formatting. Here is an example.
:
= {
env: {
browser: true,
es2021: true,
node: true,
},
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 12,
sourceType: 'module',
},
plugins: ['@typescript-eslint'],
rules: {
// Add custom rules here
},
};
and a.prettierrc
Documentation:
{
"singleQuote": true,
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"printWidth": 100
}