Making React Native Canvas Charts Package

Introduction

In the previous post, Canvas Charts in React Native, a simple wrapper was made around a WebView to make using canvas-based charts in React Native more convenient. This post contains a few notes describing the process of taking that simple wrapper and publishing it as a package, available on npm as @dpwiese/react-native-canvas-charts.

Making the Package

Before starting, the npm Docs are worth a read, in this case Creating Node.js modules and Creating and publishing scoped public packages. The steps to make a (scoped) package won’t be covered in detail here, but an overview is:

  1. Create package.json with npm init --scope=@dpwiese (my scope name)
  2. Configure package.json with the packages main file and build instructions
  3. Build and publish the package

Compilation

I had written the wrapper in TypeScript so the build step was required to generate consumption-ready JavaScript. I wasn’t sure which compiler I wanted to use, but some good information on this was available in the TypeScript docs Using Babel with TypeScript. Babel is the default compiler for React Native, as described in the React Native docs JavaScript Syntax Transformers, so I opted to use that over the TypeScript compiler.

The TypeScript compiler is still used for type checking and generating type definitions. The TypeScript docs Creating .d.ts Files from .js files explain very well how to generate the type definition files. This basically just requires in tsconfig.json the "emitDeclarationOnly": true compiler flag be set. However, setting this flag conflicts with the noEmit flag, which should be passed when type checking. For this reason I opted not to set these flags in the configuration file, but rather pass them when invoking the compiler command. See package.json for how the build commands are defined, and tsconfig.json and babel.config.js for the TypeScript and Babel compiler configurations, respectively.

Basic Workflow

With package.json created and Babel and TypeScript configured, the basic workflow when writing and updating this package is as follows.

# 1. Make changes to the package source

# 2. Build the package
% npm run build

# 3. Create a tarball for testing the package locally
% npm pack

# 4. Install the package tarball to the project where it will be included and tested
% npm install ./dpwiese-react-native-canvas-charts-0.0.0.tgz

# 5. Bump the version in package.json and rebuild

# 6. Login and publish the package
% npm login
% npm publish

This workflow was very easy to follow while developing and testing the package.

Build Setup

Given this package is just a simple wrapper around a WebView to making plotting with canvas-based libraries more convenient, I had anticipated adding support for several different libraries. As such, I wanted to be able to import various components and types specific to each particular charting library. For example:

import { Chart, SetData } from "@dpwiese/react-native-canvas-charts/ChartJs";
import { SetData, UPlot } from "@dpwiese/react-native-canvas-charts/UPlot";

I figured it would be trivial to configure the package this way, and perhaps it is, although it wasn’t as obvious I expected it would be. It seemed babel-plugin-module-resolver could be used for what I was trying to achieve, but it wasn’t immediately obvious how to do this, especially given my seemingly very simple use case. I looked at subpath exports as well, but again it still didn’t seem to be an obvious and convenient solution. Questions like Import from subfolder of npm package on Stack Overflow that seemed to be looking for a similar solution didn’t have very helpful answers.

I found the post Publishing flat npm packages for easier import paths & smaller consumer bundle sizes that seemed to offer a reasonable solution which I opted to follow instead. I won’t discuss the pros and cons of this approach in this post - it was suitable for now. It was very easy to do, and within minutes I was able to use the package as I’d intended.

This approach basically required package.json be copied to dist when building, and packing and publishing be done from dist as well. To ensure I didn’t accidentally publish the package from the root directory, the prepublishOnly pre-commit script was used to prevent this, and a separate publish script added to handle publishing from dist.

"scripts": {
  "build": "rm -rf dist && babel src --out-dir dist --extensions '.ts,.tsx' --copy-files && tsc --project tsconfig.json --emitDeclarationOnly && cp -rf package.json dist && cp -rf README.md dist",
  "pack": "cd dist && npm pack && cd ..",
  "lint": "eslint src --ext .ts,.tsx --fix",
  "tc": "tsc --project tsconfig.json --noEmit",
  "publish": "cd dist && npm publish --ignore-scripts && cd ..",
  "prepublishOnly": "echo \"Error: Don't run 'npm publish' in root. Use 'npm run publish' instead.\" && exit 1"
},

I’m sure there are many drawbacks to what I’ve done, and a lot I still don’t understand about ES modules, Babel configuration and compilation, and more. But for now it’s an acceptable result.

Finally my basic workflow was updated as follows:

# 1. Make changes to the package source

# 2. Typecheck and lint
% npm run tc
% npm run lint

# 3. Build the package
% npm run build

# 4. Create a tarball for testing the package locally with custom script
% npm run pack

# 5. Install the package tarball to the project where it will be included and tested
% npm install ./dpwiese-react-native-canvas-charts-0.0.0.tgz

# 6. Bump the version in package.json and rebuild

# 7. Login and publish the package with custom script
% npm login
% npm run publish

Using the Package

Installing the package was as easy as running the following commands:

% npm i --save react-native-webview @dpwiese/react-native-canvas-charts
% cd ios && pod install

Note the need to install react-native-webview as a dependency. While this is already a dependency of @dpwiese/react-native-canvas-charts, I haven’t yet figured out how to make autolinking work for dependencies which themselves have native dependencies, as is the case here. The workaround for now is to require react-native-webview be installed along with @dpwiese/react-native-canvas-charts which isn’t so bad.

This seemed like a problem many others would have already encountered and thus have a straightforward solution. I only found resources like the Stack Overflow post React-Native autolink a dependency of a dependency and How do I add a react-native library containing native code as a dependency in my library? which weren’t much help.

After installing the package, it can be used as shown below. See the package’s README.md for more information.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import { Chart, SetData } from "@dpwiese/react-native-canvas-charts/ChartJs";
import { useRef } from "react";
import { chartConfig } from "./chartConfig";

export default () => {
  const setDataRef = useRef<SetData>();

  // Update the charted data with newData
  setDataRef.current.setData(newData);

  return (<Chart config={chartConfig} ref={setDataRef}/>);
}