How to use ES modules in your plugin

If you are looking to extend Inkdrop with plugins that use remark or related packages, you might encounter a common hiccup: these packages often come as ES modules (short for ECMAScript modules) which can't be directly consumed by CommonJS modules due to Electron's current limitations. Attempting to dynamically import an ES module from a CommonJS module using import() can cause Inkdrop to crash. As a workaround, you have to use a bundler like Rollup to include those modules into your plugin.

Example: Use Rollup

As a case study, consider the math plugin which integrates remark-math. The solution adopted for this plugin was to utilize Rollup for bundling:

Step-by-step Rollup Setup

First, run the following command to install Rollup along with plugins for Babel and Node resolution:

npm i -D rollup @rollup/plugin-babel @rollup/plugin-node-resolve

If your plugin contains React components, you also want to install the Babel preset for React.

npm i -D @babel/preset-react

The next step is to configure Rollup. Create a file named rollup.config.js and populate it as follows:

import { nodeResolve } from '@rollup/plugin-node-resolve'
import babel from '@rollup/plugin-babel'
import packageJson from './package.json'
const deps = Object.keys(packageJson.dependencies || {})

export default [
  {
    input: 'src/index.js',
    output: {
      dir: 'lib',
      format: 'cjs',
      strict: true,
      sourcemap: true,
      exports: 'auto'
    },
    external: ['react', 'codemirror', 'inkdrop', ...deps],
    plugins: [
      nodeResolve(),
      babel({
        presets: ['@babel/preset-react']
      })
    ]
  }
]

This configuration tells Rollup to bundle src/index.js and output to the lib directory. Any dependencies listed in your package.json will be treated as external, and won't be bundled.

Now, you can safely use ES module import statements in your plugin code:

import remarkMath from 'remark-math'

Modify your package.json to include build and development scripts:

  "scripts": {
    "build": "rm -rf lib && rollup -c --bundleConfigAsCjs",
    "watch": "rollup -c --watch --bundleConfigAsCjs"
  },

Now, running npm run build will bundle your plugin, and npm run watch will do so in watch mode, rebuilding whenever your source files change.


A Note on Performance

Remember that adding more modules or dependencies can potentially slow down the app's launch time. To ensure your plugin remains performant:

Lazily Load React Components

Use React.lazy to defer loading of components until they're required:

const ReactMath = lazy(() => import('./react-math'))

This prevents the entire component tree from being loaded and parsed at startup, and can significantly improve the perceived performance of your plugin.

Can you help us improve the docs? 🙏

The source of these docs is here on GitHub. If you see a way these docs can be improved, please fork us!