Getting Started

This guide will walk you through creating a Duplicis plugin from scratch. 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 instaled:

# 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 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.

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()],
  /* ... */
})

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

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 build 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?