A quick reference guide to setting up a npm opensource package to easily manage with as little manual maintenance as possible.
I like opensource alot and I constantly have new ideas. BUT... sometimes those ideas are stifled just thinking about the time it takes to set one up and then maintain it. This document is hoping to make that process easy and efficient.
I got most of my information and inspiration from Patrick Lee Scott's article on hackernoon. The article is a little outdated so I wanted to write some more notes for my own sake.
Kind of obvious:
Clone the new repo from github and make sure to initialize npm to generate the package.json
.
git clone <repo-name>
cd ./<repo-name>
npm init
Now, create a .travis.yml
file in your root directory with the following contents:
# tell travis what language you want to use
language: node_js
# these are the versions of node to run the tests on
node_js:
- node
- 12
- 10
# use `npm` cache to speed up the builds
cache: npm
# skip version branches (ie. v1.1.1)
branches:
except:
- '/^v\d+\.\d+\.\d+$/'
# install deps
install:
- npm install
# declare two jobs, "Test" & "Release"
jobs:
include:
# Test, Lint, and Report Coverage
- stage: "Test"
script:
- npm run test
- npm run lint
- npm run codecov
- npm run build
# build and release only on non-forked, master branch
- stage: "Release"
if: branch == master && !fork
node_js: 12
script:
- npm run build
- npx semantic-release
Most of that is explained by the comments. A couple things to note:
"Test"
job. We have to override it with ours"Release"
stage will build the app (this can be any type of build script), and then call semantic-release
to potentially release a new version (or on that in the next section)Example output for those jobs would look like this:
You will notice that there are three "Test"
jobs because we told travis to test using node versions node
(which equals the default), 12
, & 8
. There is only one "Release"
job which ran because this was on the master
branch.
semantic-release determines when and what to deploy. My favorite part is it keeps your npm and github release versions in line with each other.
# install package globally
npm i -g semantic-release-cli
# from your repo's root run
semantic-release-cli setup
# this will ask you some questions
? What is your npm registry? https://registry.npmjs.org/
? What is your npm username? djhouseknecht
? What is your npm password? [hidden]
? What is your GitHub username? djhouseknecht
? What is your GitHub password? [hidden]
? What is your GitHub two-factor authentication code? [hidden]
? What CI are you using? Travis CI
? Do you want a `.travis.yml` file with semantic-release setup? Yes
This takes care of authentication between npm, github, and travis, and should make a couple modifications to your package.json
and create a .travis.yml
file (we will come back to both of these files). For now, just make sure this was added to the "scripts"
object.
"scripts": {
"semantic-release": "semantic-release",
//...
},
semantic-release
needs there to be standard commit messages so it needs helps. Which is our next step.
Optional: You may want to publish a placeholder version (ie.
0.0.1
) to npm before you start usingsemantic-release
. I did not do this once and my first placeholder version was1.0.0
which isn't ideal.
commitizen is a tool to standardize your commit messages. It is configurable so we are going to be using the package cz-conventional-changelog to help.
# install packages
npm i --save-dev commitizen cz-conventional-changelog
Add these lines to your package.json
{
//...
scripts: {
"commit": "git-cz",
// ...
},
//...
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
}
Remember to use
npm run commit
instead ofgit commit -m ""
to leveragecommitizen
Here is some helpful info on commit message from angular.js' DEVELOPER.md
PRO TIP: You’ll still get Pull Requests that don’t follow these rules – in those cases, select “Squash and Merge” instead of the default “Create Merge Commit”. You’ll be able to enter a new commit message that will trigger a new release.
I also use typescript where possible. So, here is a good setup.
TypeScript v3.7.x is not backward compatible with v3.6.x and lower, so if you need backward compatibility (like for an Angular <= 8 app) then install ts
~3.6.5
(see here).
Installing v3.6.x is optional. There are some serious breaking changes between version 3.6.x and 3.7.x. If you are targeting Angular apps version 8 and below, you will want to use this version.
npm i --save-dev typescript
NOTE:
A new stable version of Node was released last Wednesday, and with it came the newest version of npm. This update included a lot of big fixes, but the most visible change is that ‘install –save’ now prepends a caret (^) instead of a tilde (~).
If you went with typescript v3.x, then you have to go into out package.json
and manually edit the typescript dependency to the patch version — ie. tilde (~
). Otherwise, you are good to go (later you will need to tell Dependabot not to try to bump ts).
"devDependencies": {
"typescript": "~3.6.5",
// ...
},
Create ./tsconfig.json with some good starting options:
{
"include": [
"src/**/*"
],
"exclude": [],
"compilerOptions": {
/* Basic Options */
"target": "es5",
"module": "commonjs",
"baseUrl": "./",
"outDir": "./build",
"sourceMap": true,
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"lib": [
"es2015"
],
"declaration": true,
}
}
In package.json
, add this to "scripts"
:
{
"scripts": {
"build": "tsc --project .",
// ...
}
// ...
}
Let's also add build
to our .gitignore
so we aren't pushing built files to github. Our travis build pipeline will take care of getting built source code to the npm registry.
.gitignore should look like
node_modules
build
You can use webpack or rollup.js to build if you want. There are tons of resources out there for setup. I am just compiling the typescript for now.
We will test it out to make sure you get compiled js in the ./build
directory after we add some source code and tests in the next section.
I like jest for testing my JS/TS libraries. It has some convient code coverage tools. I'm not going to go into depth, but here is some basic jest setup.
npm i --save-dev jest @types/jest ts-jest @babel/core @babel/preset-env babel-jest
We need babel to transpile our ES6 JavaScript (if we have any) and ts-jest to compile out TypeScript files.
src/index.ts
export function helloWorld () {
return 'Hello World';
}
test/index.spec.ts
import { helloWorld } from '../src';
describe('Index', () => {
test('should say Hello World', () => {
expect(helloWorld()).toBe('Hello World');
});
});
jest.config.js
module.exports = {
testEnvironment: 'node',
// tells jest where are files are located
roots: [
'<rootDir>/src',
'<rootDir>/test'
],
// tells jest what file path to match for tests
testMatch: [
'<rootDir>/test/**/*.spec.(ts|js)'
],
modulePaths: [
'src',
'/node_modules/'
],
// this will load js and ts files
transform: {
'^.+\\.jsx?$': 'babel-jest',
'^.+\\.tsx?$': 'ts-jest'
},
// we want to collect coverage for later
collectCoverage: true,
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!**/node_modules/**',
'!**/types/**'
],
coverageReporters: [
'lcov', 'text'
],
coverageThreshold: {
global: {
branches: 100,
functions: 100,
lines: 100,
statements: 100
}
},
coverageDirectory: './coverage'
};
babel.config.js
module.exports = {
// tells babel to use this preset
presets: [
'@babel/preset-env'
]
};
In package.json add this script:
"scripts": {
"test": "jest"
// other scripts
}
I like to add coverage
to my .gitignore
. Travis and Codecov will take care of showing our coverage results.
.gitignore should look like this now:
node_modules
build
coverage
See if it works.
npm test
You should get this output:
Now that we have files, let's make sure our build works (from Setting up TypeScript)
npm run build
Check to see if there is output in the ./build
directory. Should look like:
Install codecov
npm i --save-dev codecov
In package.json add this script:
"scripts": {
"codecov": "codecov",
// other scripts
}
I like to use tslint for linting. Install needed devDependencies and create a tslint.json
file
npm i --save-dev tslint
touch tslint.json
Checkout tslint.json for some good rules. Copy and paste that into your tslint.json
file.
In package.json add this script:
"scripts": {
"lint": "tslint --project . --config tslint.json"
// other scripts
}
Also, creating an .editorconfig helps standardize things. Checkout the .editorconfig for some basic config.
I use husky, but there is pre-commit which is pretty good too. This will ensure that your code passes tests and linting before you push and/or commit. Otherwise travis may be the one to find
Install the dependency:
npm i --save-dev husky
In package.json add this config:
"husky": {
"hooks": {
"pre-commit": "npm run lint && npm run test",
"pre-push": "npm run lint && npm run test"
}
},
You don't need both pre-commit
and pre-push
. Both can get annoying if you have a slow test process.
Note: I was not able to get typedoc to work with typescript v3.6.x
Let's use typedoc to set up some nice documentation of our APIs. First, we will install it:
npm install --save-dev typedoc
We will create a typedoc.config.js
file to specify a few config options. We are only going to set some basic configuration, but checkout typedoc's configuration options for lots more options.
// typedoc.config.js
module.exports = {
inputFiles: [
'./src'
],
mode: 'modules',
out: 'docs',
excludePrivate: true
}
We are going to build html
files into a docs/
directory. We will then deploy them to GitHub Pages! You can also compile typedoc to Markdown using the typedoc-plugin-markdown
.
Then we will add a doc
script to our package.json
to execute the generation of the files:
"scripts": {
"doc": "typedoc --options typedoc.config.js && touch ./docs/.nojekyll"
// other scripts
}
Now, run npm run doc
and go check the docs/
folder. There should be an output like this:
We set the mode: 'module'
so there is not a docs/modules
directory with all the modules. The index.html
file has our README.md
generated to html (and some other nice typedoc styling/navigation).
To deploy to Git Pages, we needed to add the docs/.nojekyll
file. Github using Jekyll to compile our site files, and it will not publish files that start with an underscore _
(see here).
Once you have that pushed up to your repo, you can go to: your github repo > Settings (the tab to the far right) > scroll down to GitHub Pages and set the Source to master branch /docs folder:
Then navigate to the URL it gives you, in our case: https://djhouseknecht.github.io/setting-up-an-opensource-npm-project/index.html
You may need to wait a few minutes for the docs to actually be published
The final product, is the README
with links to our source code documentation:
Click on the "index"
link:
You could even update your husky
hooks to build docs before every commit to ensure your docs stay up to date (another options is to use your travis build to push build docs and push to master. Maybe I will add that). In package.json
:
{
"husky": {
"hooks": {
"pre-commit": "npm run lint && npm run test && npm run doc && git add ./docs", // <- update this
"pre-push": "npm run lint && npm run test"
}
},
}
A changelog helps you and your users quickly see what changed between versions of your package. This is a little bit of a manual process (I haven' taken the time to write scripts to do this for me). semantic-release
and commitizen
document each version in github releases really well (based on commit message).
I like to use Keep a Changelog formatting because it links to github version differences. This is great for comparing the difference in the code. There is a great example on that website so I won't re-write it all.
We will look at the example for my rxjs-util-classes repo. The main points are:
Added, Changed, Deprecated, Removed, Fixed, Security, Breaking Changes
Create a CHANGELOG.md
file at the root, paste the following as a starting point, and adjust based on your repo:
# Changelog
_this is just an example changelog. make sure to change it based on your repo_
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
# [Unreleased]
# [v1.1.1]
### Added
* CHANGELOG
### Changed
* Documentation
* Patch release on doc change ([from this comment](https://github.com/semantic-release/semantic-release/issues/192#issuecomment-333328071))
# [v1.1.0]
### Added
* Observable, Behavior, and Subject maps
* Documentation
# [v1.0.0]
### Added
* Initial release
* README
[Unreleased]: https://github.com/djhouseknecht/rxjs-util-classes/compare/v1.1.1...HEAD
[v1.1.1]: https://github.com/djhouseknecht/rxjs-util-classes/compare/v1.1.0...v1.1.1
[v1.1.0]: https://github.com/djhouseknecht/rxjs-util-classes/compare/v1.0.0...v1.1.0
[v1.0.0]: https://github.com/djhouseknecht/rxjs-util-classes/releases/tag/v1.0.0
Check out my rxjs-util-classes CHANGELOG for a full sample.
I tried dependabot for this. It is crazy easy to setup for javascript libraries so just check it out.
If you are using typescript v3.6.x, you need to tell dependabot not to try to bump typescript to v3.7.x. Create this:
.dependabot/config.yml
version: 1
update_configs:
- package_manager: "javascript"
directory: "/"
update_schedule: "live"
ignored_updates:
# this will tell Dependabot not to try bumping these versions
- match:
# ignore typescript minor version
# this is for compatibility with Angular apps 4 >= and <= 9
# only add these if you went with ts v3.x
# (you can also add more dependencies you don't want to bump)
dependency_name: "typescript"
version_requirement: "~3.6.5"
Note this file is only really needed if you need extra configuration over dependabot
Now let's add some fancy badges!
build: passing
iconPut this in your README and alter it with your username and repo name
[![codecov](https://codecov.io/gh/username/repo-name/branch/master/graph/badge.svg)](https://codecov.io/gh/username/repo-name)
Put this in your README and alter it with your repo name
[![npm version](https://badge.fury.io/js/repo-name.svg)](https://badge.fury.io/js/repo-name)
Put this in your README and alter it with your username and repo name
[![dependabot-status](https://flat.badgen.net/dependabot/username/repo-name/?icon=dependabot)][dependabot]
Put this in your README
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
Put this in your README
[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
My project rxjs-util-classes uses this setup. Go check out the repo and its travis build to see some examples of this in action.
Generated using TypeDoc