UNPKG

sketch-plugin-types

Version:

TypeScript type definitions for the Sketch app plugin API (sketch/dom, sketch/ui, sketch/settings, sketch/async, sketch/data-supplier) and the Action API.

172 lines (131 loc) 7.66 kB
# Building with skpm > **Status note (2026-04).** `@skpm/builder` is in maintenance mode: the > latest release is `0.9.5` published **2023-10-10**, the parent repo's > last commit is the same day, and the bundler still targets webpack 4. > The runtime pieces Sketch needs — `sketch-module-web-view`, > `sketch-polyfill-fetch` — are still patched actively, but the build > tooling is effectively frozen. It works; it just isn't evolving. > > For a **greenfield** plugin, consider bypassing skpm entirely and > building directly onto the `.sketchplugin` bundle layout with `tsc` + > a small bundle script (~20 lines) or `esbuild`. See > [`examples/hello-world/`](../examples/hello-world/) for the minimal > template; several production plugins in the wild use this approach. > You keep `sketch-plugin-types` either way. > > If you already have a skpm project, or you want `skpm watch` / `skpm > publish` ergonomics, the rest of this guide still applies. [`skpm`](https://github.com/skpm/skpm) is the standard Sketch plugin toolchain. It scaffolds a project, bundles your code (webpack + babel), installs the plugin into Sketch, watches files on save, and publishes releases. ## Setup Install skpm once, globally: ```sh npm install -g skpm ``` Create a new plugin: ```sh skpm create my-plugin cd my-plugin npm install ``` You get a plugin folder: ``` my-plugin/ src/ manifest.json source manifest my-command.js one file per command assets/ icons and other resources package.json has an "skpm" field pointing at src/manifest.json ``` ## Add TypeScript ```sh npm install --save-dev typescript sketch-plugin-types ``` Rename your source files from `.js` to `.ts`, drop an `env.d.ts` into `src/`: ```ts // src/env.d.ts /// <reference types="sketch-plugin-types" /> import 'sketch-plugin-types/globals'; ``` `src/manifest.json` keeps pointing at the `.js` output (skpm writes `.js` regardless of source extension): ```json { "commands": [ { "script": "my-command.js", "handler": "onRun", "...": "..." } ] } ``` In principle `@skpm/builder` hands `.ts` sources to Babel and they compile without extra config. In practice, on `@skpm/builder@0.9.5` TypeScript is not always picked up, and the failure mode is silent — the `.ts` file is either ignored or emitted as-is and crashes in Sketch. If you hit that, drop a `webpack.skpm.config.js` next to `package.json` and wire `ts-loader` explicitly: ```js // webpack.skpm.config.js module.exports = function (config, _isPluginCommand) { config.module = config.module || {}; config.module.rules = config.module.rules || []; config.module.rules.push({ test: /\.tsx?$/, exclude: /node_modules/, use: [{ loader: 'ts-loader', options: { transpileOnly: true } }], }); config.resolve = config.resolve || {}; config.resolve.extensions = [ '.ts', '.tsx', ...(config.resolve.extensions || ['.js', '.json']), ]; return config; }; ``` ```sh npm install --save-dev ts-loader@^8 ``` `ts-loader@8` is the last line that supports the webpack 4 skpm ships. Do not upgrade to `ts-loader@9` — it requires webpack 5. If you additionally import `webpack-merge`, use `merge()` and not `smart()` — the `smart` helper was removed and a transitive dep still references it under certain npm tree shapes. ## Run it ```sh npm run build # one-off build npm run watch # rebuild on save npm run start # watch + reload Sketch on each build ``` The first build installs the plugin into `~/Library/Application Support/com.bohemiancoding.sketch3/Plugins/` as a symlink, so subsequent rebuilds are visible to Sketch right away. ## See the log ```sh skpm log -f # tail the plugin log ``` ## Publish a release ```sh skpm login # paste a GitHub token once (repo scope) skpm publish patch # bumps version, tags, creates GitHub release ``` skpm publishes to a GitHub release that Sketch can auto-update from if you set `appcast` in the manifest. ## Templates ```sh skpm create my-plugin --template with-datasupplier skpm create my-plugin --template with-webview ``` See [`skpm/skpm`](https://github.com/skpm/skpm) for the full template list. ## Polyfilled core modules `@skpm/builder` bundles small polyfills for `buffer`, `path`, `crypto`, `stream`, `util`, `events`, `url`, and `assert` — enough to let Node-oriented deps run inside a Sketch plugin. Opt in to the types: ```ts // src/env.d.ts /// <reference types="sketch-plugin-types" /> import 'sketch-plugin-types/globals'; import 'sketch-plugin-types/skpm'; ``` Then `import * as path from 'path'`, `const { Buffer } = require('buffer')`, etc. resolve with types. Do not install `@types/node` — skpm's polyfill surface is deliberately narrower than Node's, and `@types/node` will over-promise methods that blow up at runtime. `fs` is intentionally absent — skpm does not polyfill it. Use `NSFileManager` (see [cocoa.md](./cocoa.md)). ## Not polyfilled — shim yourself Sketch runs plugins in a bare JavaScriptCore context. A number of things most JS libraries assume on `globalThis` are *not* there and skpm does not provide them. The ones we see people hit most often: - `structuredClone` — missing. Any library that clones state defensively (e.g. `dagre`, `immer` in certain paths) will throw `ReferenceError: Can't find variable: structuredClone`. Shim with `JSON.parse(JSON.stringify(…))` for JSON-safe graphs, or pull in the [`@ungap/structured-clone`](https://www.npmjs.com/package/@ungap/structured-clone) ponyfill for Date / Map / Set / typed arrays. - `TextEncoder` / `TextDecoder` — missing. Convert through `NSString` + `NSData`: ```ts const NSString = NSClassFromString('NSString'); const data = NSString.stringWithUTF8String_(input).dataUsingEncoding_(4); // NSUTF8StringEncoding ``` Or vendor a small pure-JS implementation (`fast-text-encoding`). - `fetch`**injected** by `@skpm/builder` via webpack's `ProvidePlugin` → [`sketch-polyfill-fetch`](https://github.com/skpm/sketch-polyfill-fetch) (wraps `NSURLSession`). The function works out of the box; no types ship here, so write `declare const fetch: (...args: any[]) => Promise<any>;` or narrow locally. - `Request` / `Response` / `Headers`**not native**. `sketch-polyfill-fetch` returns plain objects with `ok` / `status` / `headers` / `text()` / `json()` but is *not* a WHATWG implementation — `new Response(...)` / `new Headers(...)` / `new Request(...)` all throw. Anything that expects `instanceof Response` will break. - `FormData` — injected by the same `ProvidePlugin`. Usable as a constructor. - `URL` / `URLSearchParams``URL` is polyfilled via the `url` module, but the WHATWG globals are not injected on `globalThis`. Import what you need from `'url'`. - `Promise` — present on modern Sketch, but there is no native microtask queue for long-running commands. If your command exits synchronously the promise resolver never fires. Use `Async.createFiber()` to keep the runtime alive (see [developer.sketch.com](https://developer.sketch.com/reference/api/#async)). - `queueMicrotask` / `setImmediate` / process ticks — missing. Use `setTimeout(fn, 0)`. - `crypto.subtle`, `crypto.getRandomValues` — the skpm `crypto` polyfill only covers `createHash` / `randomBytes`. For WebCrypto, bridge to `NSData` / `CCHmac` via Cocoa or vendor a JS implementation. - `fs` — see above; use `NSFileManager`. If a dependency crashes immediately with `ReferenceError: Can't find variable: X`, that's almost always one of these. Shim at the top of your entry script before the first `import`.