Getting Started

This guide will walk you through creating a Duplicis plugin. If you’d prefer a head start, grab a plugin template instead.

Scaffolding

Duplicis plugins are npm packages. While you could create plugins completely by hand; This guide will assume you have a few things installed:

# 1. create project
mkdir -p my-plugin/src
touch    my-plugin/src/index.js
cd       my-plugin

# 2. initialize package
npm init -y
git init # optional

# 3. install dependencies
npm i -D @duplicis/types @duplicis/config

Your project should now look like this:

my-plugin/
├── src/
│   └── index.js
└── package.json

You’ll also probably want to set up typing for IntelliSense and pick a build system.

Builds

Plugins must be browser compatible ES modules. @duplicis/config provides ready made configs for the most common build tools.

Any runtime dependencies your plugin uses must be marked as external!

Rollup
import { defineConfig } from '@duplicis/config/rollup'

export default defineConfig({
  input: './src/index.js',
  output: {
    file: './dist/index.js',
    format: 'es',
  },
  /* ... */
})
Vite
import duplicis from '@duplicis/config/vite'
import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    lib: {
      entry: './src/index.js',
      format: ['es'],
    },
  },
  plugins: [duplicis()],
  /* ... */
})

Dependencies

Plugins can depend on other plugins and patches, which are loaded and ordered automatically by Duplicis.

Installation

Install dependencies via npm and then exclude them from your built output:

 {
   "name": "my-plugin",
   "version": "0.0.0",
+  "dependencies": {
+    "duplicis-plugin-xyz": "~0.0.0"
+  },
   ...
 }
 export default {
+  external: ['duplicis-plugin-xyz'],
   ...
 }

Skipping external causes Uncaught SyntaxError: redeclaration of ... at runtime since the dependency ends up bundled twice.

Usage

import PluginXyz, { AppMixin } from 'duplicis-plugin-xyz'
import { Plugin } from '@duplicis/core'

export class MyPlugin extends Plugin {
  async onLoad() {
    // calling a method on another plugin
    PluginXyz.instance.xyz()
    // calling a method added by a patch
    MyAppMixin.instance.method()
  }
}

// extending an existing patch from another plugin
export const MyAppMixin = MyPlugin.patch('app', AppMixin, (AppMixin) => {
  return class MyAppMixin extends AppMixin {
    override method(...args: any[]) {
      console.log('I run before!', args)
      const result = super.method(...args)
      console.log('I run after!', result)
      return result
    }
  }
})

Typing

If you’re not using TypeScript, use a jsconfig.json instead.

Duplicis uses two tsconfig environments to keep browser and Node.js types separate:

  • tsconfig.app.json — runtime code (client & browser-compatible)
  • tsconfig.node.json — build tooling (NodeJS, vite, tests, etc.)
// tsconfig.app.json
{
  "extends": ["@duplicis/config/tsconfig.app.json"],
  "include": ["./src/**/*"]
}
// tsconfig.node.json
{
  "extends": ["@duplicis/config/tsconfig.node.json"],
  "include": "./vite*.ts"
}

Finally, import the Duplicis client types in a declaration file so they’re globally available:

echo "import '@duplicis/types/client'" >> src/client.d.ts

What’s next?