How I made my first open source React package

Sharjeel Ahmad
Sharjeel Ahmad
Open Source NPM Package for React

Recently I was looking for a react package that would enable syntax highlighted for code blocks in blog posts. I found some decent modules on npm registry. However for one reason or another, I decided to not use those modules. For some modules, the package size was too large for my liking. One of the modules came quite close but it didn't have nice integration within React. So I didn't want to use it either.

Finally I decided to make my own package to do just one job: highlight syntax for code in blog posts.

My objectives were these:

  1. Ease of use
  2. Easy to import within React projects
  3. Tiny module size
  4. Making it available for anyone to use. This made me release it as open source npm package

Here is the step by step process I followed from start to finish. I will also give some tips along the way to explain why I did certain things in a particular way.

Identifying an existing solution to syntax highlighting

Initially I explored if there was an existing vanilla JavaScript solution to snytax highligting. After a quick search online, I found PrismJS. According to official website, Prism is a lightweight and extensible syntax highlighter. In fact, current release is only 5.3 kB in size when minified and zipped. So far so good, so I decided to give it a go.

Choosing a build tool

Next step was to think about how others could use my package from npm. For broad compatibility, I decided to export CommonJS, UMD and ES modules for this package.

Now it required me to find a build tool that would do this for me. Previously I have been using Webpack for most of my projects. However I wanted to find a tool that would be more suitable for open source projects while allowing multiple exports.

I settled on using Rollup for my project considering following points:

  • Rollup supports ES modules and tree shaking out of box. You can still use CommonJS modules with the help of a plugin
  • Creating different build configurations for UMD and CommonJS is a breeze
  • Rollup supports external packages. This means that I can choose to skip bundling of peer dependencies in production build of module

In the end, my build configuration for Rollup looked like this:

import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import babel from 'rollup-plugin-babel';
import pkg from './package.json';
import postcss from 'rollup-plugin-postcss';
import { terser } from 'rollup-plugin-terser';

const commonPlugins = [
  resolve(),
  postcss({}),
  babel({
    exclude: ['node_modules/**'],
    presets: ['@babel/preset-env', '@babel/preset-react']
  }),
  commonjs()
];

const getUmdConfig = isProduction => ({
  input: 'src/index.js',
  output: {
    name: 'ReactPrismJS',
    file: isProduction ? pkg.browser.replace('.js', '.min.js') : pkg.browser,
    format: 'umd',
    globals: {
      react: 'React',
      'react-dom': 'ReactDOM'
    }
  },
  external: ['react', 'react-dom'],
  plugins: [...commonPlugins, isProduction && terser({})]
});

export default [
  {
    ...getUmdConfig()
  },
  {
    ...getUmdConfig(true)
  },
  {
    input: 'src/index.js',
    output: [
      { file: pkg.main, format: 'cjs' },
      { file: pkg.module, format: 'es' }
    ],
    external: ['react', 'react-dom'],
    plugins: [...commonPlugins]
  }
];

A couple of things to note here are:

  • Terser plugin is used to minify bundle for production UMD build
  • React and React DOM are considered global modules and will be skipped during build

As you can see this setup does not seem complicated and was enough for my use case.

Choosing Code Styling and Formatting

Since It was going to an open source software, I wanted to set the code styling guidelines for other developers. I included eslint and prettier in my package for styling and formatting respectively. My eslintrc configuration looked like this:

module.exports = {
  extends: ['standard', 'standard-react', 'plugin:import/errors', 'plugin:import/warnings', 'plugin:prettier/recommended'],
  settings: {
    react: {
      pragma: 'React',
      version: '16.8.6'
    }
  },
  parser: 'babel-eslint'
}

And prettierrc was as simple as a couple of lines:

{
  "printWidth": 100,
  "singleQuote": true
}

Deciding commit linter

Another aspect of writing open source modules is to consider commit styles for code maintainers and contributors. This is mainly for consistency and to promote good commit messages that explain the purpose of a commit. As we will see later on, good commit messages also help with release versioning.

Commitizen is a quite popular tool to make it easier for developers to follow a convention for commit messages. I chose conventional commitlint types for my package with one small change:

  1. I like the commit message to be capitalized for ease of reading (e.g Update readme). To achieve this, I used cz-customizable and created my own configuration for it.
  2. I added a commit message hook using husky. This is done to make sure commit messages are consistent regardless if you use Commitizen tool or you are simply using git commit. With husky, you can run a linter on commit message to make sure contributors are committing changes based on guidelines.

To check the exact configuration, checkout the source code at Github.

Choosing a Licence

This was the easy part. I wanted to make my package available for anyone to use and modify. For this, I chose MIT licence and placed the licence terms in LICENCE.md file at root of repository.

Writing Code of Conduct

When you release open source software, it is a good practice to remind everyone about code of conduct. I used a template available at Contributor Covenant and added it to my package. If you are looking for a template, I recommend to have a look at their website.

Choosing a CI/CD Tool

There are plently of popular and lesser known options available for continous integration. For my use case, I looked for a CI/CD tool that would satisfy following objectives:

  • Free to use for open source
  • Easy to configure
  • Has good user interface

I tried Travis CI, Circle CI and Pipelines from Azure DevOps. In the end, I settled on Azure DevOps as it has a generous quota for open source projects. Secondly its user interface is one of the best I have seen for a CI/CD tool.

Changelog and Release Management

Based on my previous knowledge, I knew I wanted to use Semantic Versioning for my module. The reason was to achieve a consistent way of tagging releases and assigning version numbers accordingly. Semantic Versioning makes this process seamless. When following semantic versioning, you simply need to look at three factors for every release:

  1. It is a MAJOR version when you are making breaking changes
  2. It is a MINOR version when you are adding fixes and features with backward compatibility
  3. It is a PATCH when you make backwards compatible bug fixes

I used a tool called semantic-release to automate this process for me. Basically you can add Semantic Release step in your build pipeline to analyze all commit messages and publish new releases if required. Here you can see that having good commit messages makes it easier for you to release software with correct version numbers.

Writing Contribution Guidelines

To figure this out, I looked at the most popular React packages and analyzed what type of documentation I needed in my module. Here is a minimum set of things I found other packages did mention in contribution guidelines:

  • Describe ground rules how others should interact and contribute
  • Describe how you expect bugs, feature requests and questions to be raised
  • Describe how others can develop and send pull requests
  • Anything else you would like someone using your software to know

You can have a look at Github source to see finished contribution guidelines.

Choosing a code coverage tool

Disclaimer: I have not completed this step but I thought I should mention my findings.

I liked Codecov.io as it has a free tier for open sources projects and integrates well with CI. I may add it to my module at some point in future.

Moving on to implementation

Requirements for my react module were quite simple as mentioned below:

Input: Code block as a string

Output: Tokenized HTML styled with necessary CSS to support syntax highlighting

The easiest way to do this was to create "highlighted HTML" with PrismJS and inject it into our React component using dangerouslySetInnerHTML. However I didn't prefer this way as it is not quite the React way of doing things.

PrismJS provides a helper function to covert your code string to tokens. These tokens are created with span elements and can be styled with the help of CSS.

const ReactPrism = ({ children, language }) => {
  const tokens = Prism.tokenize(children, Prism.languages[language]);
  return <Renderer tokens={tokens} language={language} />;
};

Once we have an array of tokens we can pass these to a renderer to create JSX for us.

export const Renderer = ({ tokens, language }) => {
  return (
    <pre className={`language-${language}`}>
      <code className={`language-${language}`}>
        <RenderTokens tokens={tokens} />
      </code>
    </pre>
  );
};

Here is the final implementation for rendering tokens. You may notice that I used recursive rendering strategy because some tokens may have further array like structure within. This depends on the input code and is not always the case. However we do need to handle this nicely if needed so I used recursion as a solution.

export const RenderTokens = ({ tokens }) => {
  return (
    <>
      {tokens.map((item, ii) => {
        const { content, type } = item || {};
        if (!content || !type) return <React.Fragment key={ii}>{item}</React.Fragment>;
        if (Array.isArray(content)) {
          return <RenderTokens key={ii} tokens={content} />;
        }
        return (
          <span key={ii} className={`token ${type}`}>
            {`${content}`}
          </span>
        );
      })}
    </>
  );
};

Writing Documentation

This is probably one of the most important steps when publishing an open source package. I like it when readme instructions are clear on usage and limitations of any given package. I essentially did the same for my own module. I added a README.md at the root level with following information:

  • What is it
  • How to use it
  • Limitations
  • Props documention

Testing

I did some basic testing for this module using this blog. If you are reading this post, you have seen this package in action. When you are developing a module, you can still install it as a dependency using npm install. However instead of using module name, you should use relative path to package.json on your disk.

Publishing to npm

As I mentioned earlier, I installed semantic-release package to perform this step. Here is the configuration for Azure Pipelines:

- script:
   npm run semantic-release
  displayName: 'semantic release'
  env:
    GH_TOKEN: $(GH_TOKEN)
    NPM_TOKEN: $(NPM_TOKEN)

That's it

That is all what was needed to release my first open source npm package. I really enjoyed the entire process from start to finish. You may notice that doing this for the first time might take extra efforts to figure out documentation, build tools and release management. Hopefully this guide will help you plan and release your own module faster.

PS: I would love to know your feedback.

Feel free to contact me on LinkedIn for any feedback or questions.

Let's learn more insights like this

We are committed to sharing useful content, tips and insights for the ecommerce industry. Sign up below to get more valuable content straight to your email.

Sharjeel Ahmad

Sharjeel Ahmad

  • LinkedIn

I am the founder and front-end development expert at Versant Digital. I am passionate about all things web and clean, professional looking websites.

I started this blog to share stories about React, Redux, JAMStack and stuff developers love about Front-end development.

Read more about these topics: