While I'm building my website/blog, there are a few things that I see can be useful to separate and publish as 3rd packages - the biggest motivation is to tear down my codebase and avoid maintaining as much irrelevant code as possible.
So, I decided it's a good chance to learn, create, publish my first NPM package ever, and finally share to motivate and help others π. Let's jump into it.
Requirements
It is coming in 2021, I wanted to publish my NPM package in a modern-ish way so that I can re-apply later and my packages stay relevant as long as possible, so I put down some requirements:
TypeScript: it has to support TypeScript. Using a package without TypeScript support in 2020 always feels not right for me.
Concise documentation.
Release workflow: takes less than 1 min. I don't want to completely automate this as I don't think I'll release that many times, automating seems to overkill a quick simple command.
Auto-upgrade dependencies: stay up-to-date with all dependencies to avoid security issues, I don't want to take care of this.
Prettier code style: standardized, zero configuration.
Call to action. It's always good to put a note to the end of what you created and redirect it back to your primary online presence, I believe.
What the package is about?
To build an automatic crosspost to DEV.to (this post you're reading is automatically cross-posted to DEV.to when I pushed it to my website), I need to convert my Markdown posts into a Markdown variant that renders properly on DEV.to. One of these features is that every wrap (virtual newline in a paragraph to make it readable on code editor) is rendered as a newline character on DEV.to, aka, DEV.to unexpectedly breaks a paragraph into multiple paragraphs. To solve it, I wrote a Remark plugin to replace all wraps by spaces.
module.exports = () => (tree) => {
visit(tree, "text", (text) => {
text.value = text.value.replace(/\n/g, " ");
});
};
The code is as simple as that but is quite re-usable, so I decided to make it an NPM package. (It's my first package, it should be simple right?)
I called it remark-unwrap-texts
.
Create a TypeScript repo
Initialize a Git repo:
mkdir remark-unwrap-texts
cd remark-unwrap-texts
git init
Create a Github repo for it:
gh repo create phuctm97/remark-unwrap-texts --public
Initialize Yarn/NPM:
yarn init
name: "remark-unwrap-texts"
version: "0.0.0"
author: "Minh-Phuc Tran"
license: "MIT"
private: false
Add TypeScript and Prettier (as dev dependencies):
yarn add -D typescript prettier @tsconfig/recommended
@tsconfig/recommended
is a base TypeScript configuration that helps you configure your TypeScript project with minimal code.
Create a tsconfig.json
:
{
"extends": "@tsconfig/recommended/tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"declaration": true
},
"include": ["**/*.ts"],
"exclude": ["node_modules", "dist"]
}
Done β¨! I got a base TypeScript project.
Write the logic
My package logic requires one library and a type definition package.
-
Install the library:
yarn add unist-util-visit
-
Install the type definition as dev dependencies:
yarn add -D @types/mdast
Write the code, with a little nice documentation:
import { Parent, Text } from "mdast";
import visit from "unist-util-visit";
/**
* Unwraps `text` nodes in Markdown.
*
* Is useful when publishing to platforms like DEV.to, Medium, Hashnode, etc.
* These platforms may not support text wraps and generate unexpected newlines.
*/
const plugin = () => (tree: Parent) => {
visit(tree, "text", (text: Text) => {
text.value = text.value.replace(/\n/g, " ");
});
};
export = plugin;
Add build information to package.json
Now I got the code, I need to build it into JavaScript as well as a type declaration file. I update my package.json
to include these:
{
// Other attributes.
"main": "dist/index.js", // for module import/require
"types": "dist/index.d.ts", // for TypeScript support
"files": ["dist/**/*"], // includes only build output in the NPM package
"scripts": {
"build": "tsc",
"prepublish": "yarn build", // Make sure output is up-to-date before publishing
"type:check": "tsc --noEmit"
}
}
Publish the first version
Publishing with yarn
is surprisingly simple:
-
Configure an NPM account to publish to:
yarn login username: "<npm username>" email: "<npm email>"
-
Publish a new version:
yarn publish New version: "0.0.1" password: "<npm password>" ... build ... publish ... Revoked token
-
Yarn automatically update
package.json
with the new version, create a commit and a tag. All you need to do is to push them:
git push && git push --tags
Done β¨! I got my first NPM package ever published.
Add documentation and tools
-
Create a README:
- Explain shortly what the package is about.
- How-to install and use it.
- Badges from shields.io to show the latest NPM version and the repo's license (also helps add a little character to the repo/package).
- A Build with π by @phuctm97 at the end.
Add a license and code of conduct using Github UI, it helps auto-fill the files for you.
-
Update
package.json
to updatedescription
andkeywords
displayed on NPM.
{ // Other attributes. "description": "π Unwraps text nodes in Markdown, is useful when publishing to platforms like DEV.to, Medium, Hashnode, etc.", "keywords": [ "markdown", "remark", "commonmark", "unified", "remark-plugin", "unified-plugin", "plugin", "extension" ] }
yarn publish
again to push the updated documentation to NPM.-
Add
.github/dependabot.yml
to auto-grade dependencies:
version: 2 updates: - package-ecosystem: npm directory: / schedule: interval: weekly
Commit and push β¬οΈ.
Test and release v1
I've almost done, just gotta test the package in my website implementation to make sure it works:
yarn add remark-unwrap-texts
.Delete my previous code and replace by
require('remark-unwrap-texts')
.Bump. Everything works correctly!
Go back to remark-unwrap-texts
:
yarn publish
with version1.0.0
.git push && git push --tags
.
I got my first NPM package released π!
Hope it helps you publish your first NPM package soon, too. For more details in practice, you can checkout the repository and the NPM package.
Top comments (0)