DEV Community

loading...
Cover image for Publish My First NPM TypeScript Package

Publish My First NPM TypeScript Package

phuctm97 profile image Minh-Phuc Tran Originally published at phuctm97.com Updated on ・4 min read

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, " ");
  });
};
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Create a Github repo for it:

gh repo create phuctm97/remark-unwrap-texts --public
Enter fullscreen mode Exit fullscreen mode

Initialize Yarn/NPM:

yarn init
name: "remark-unwrap-texts"
version: "0.0.0"
author: "Minh-Phuc Tran"
license: "MIT"
private: false
Enter fullscreen mode Exit fullscreen mode

Add TypeScript and Prettier (as dev dependencies):

yarn add -D typescript prettier @tsconfig/recommended
Enter fullscreen mode Exit fullscreen mode

@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"]
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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 update description and keywords 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 version 1.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.

Discussion (0)

pic
Editor guide