npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

quicklook-kit

v0.7.0

Published

Build Quick Look previews for macOS and Windows with Vite-based web frameworks

Readme

quicklook-kit

License Version Build

Build Quick Look previews for macOS and Windows with Vite-based web frameworks, no Swift or C# required

Your preview is a web page. quicklook-kit builds it with your framework and wraps it in a small native host: a Quick Look extension on macOS, a QuickLook plugin on Windows. You never touch Swift, C# or Xcode.

Built-in support for SvelteKit, Astro, SolidStart, Nuxt and Analog. Any other framework that builds with Vite and writes a static index.html works through an integration.

Requirements

  • Node.js 22.17 or later, which runs the quicklook CLI
  • For macOS: macOS 14 or later, with Xcode and XcodeGen installed
  • For Windows: the .NET SDK, on any OS. Testing the plugin needs Windows with QuickLook and the Microsoft Edge WebView2 Runtime, which ships with Windows 11

Installation

npm install --save-dev quicklook-kit

To start from scratch, run pnpm create quicklook instead.

Setup

1. Configure

Create quicklook.config.ts in your project root:

import { defineConfig } from 'quicklook-kit';

export default defineConfig({
	name: 'Quick Look Example',
	macos: {
		bundleId: 'com.example.QuickLookExample',
		types: [
			{
				id: 'com.example.QuickLookExample.example',
				description: 'Example File',
				extensions: ['example'],
				conformsTo: ['public.plain-text'],
			},
		],
	},
	windows: {
		id: 'QuickLook.Plugin.Example',
		extensions: ['example'],
	},
	framework: 'sveltekit',
});

Each platform group is optional, but you need at least one: leave out windows to build for macOS only, or macos for Windows only.

A new type conforms to public.plain-text for text formats, or public.data for binary ones. To preview a type macOS already knows about, use a plain string like 'public.json' in place of an object.

The config can also be quicklook.config.js, .json, .yaml or .toml, or a .quicklookrc. The keys are the same in every format.

| Key | Default | | | -------------- | ------------ | ---------------------------------------------------------------------------------- | | name | required | App name, shown in Finder and System Settings | | macos | none | macOS options, see below | | windows | none | Windows options, see below | | framework | required | 'sveltekit', 'astro', 'solid-start', 'nuxt', 'analog', or an integration | | integrations | [] | See Integrations | | csp | locked down | Directives here replace the defaults of the same name | | maxFileSize | 10_000_000 | Larger files get Quick Look's generic preview | | public | {} | Values the page can read at build time | | settings | {} | Settings the user edits in the app, see Settings | | devFile | none | A file the dev server previews until you drop another, relative to the project |

Under macos:

| Key | Default | | | ---------------- | -------- | ----------------------------------------- | | bundleId | required | The extension gets <bundleId>.Extension | | types | required | File types to preview | | icon | none | Path to an .icns file for the app | | minimumVersion | '14.0' | Minimum macOS version |

Under windows:

| Key | Default | | | ------------ | -------- | ---------------------------------------------------------------------------------- | | id | required | Plugin name, starting with QuickLook.Plugin., e.g. QuickLook.Plugin.Example | | extensions | required | File extensions to preview, without the dot. They win over QuickLook's own viewers |

Upgrading from a version without platform groups? The old top-level bundleId, icon, macOS and types now live under macos, with macOS renamed to minimumVersion.

A framework can take options as a [name, options] pair, e.g. ['sveltekit', { outDir: 'dist' }] when your adapter writes somewhere other than the default.

Each framework needs a static, client-side build:

| Framework | Requirement | | ---------- | ------------------------------------------------------ | | Analog | @analogjs/vite-plugin-angular | | Astro | the default output: 'static' | | Nuxt | ssr: false | | SolidStart | ssr: false, Nitro's static preset prerendering / | | SvelteKit | @sveltejs/adapter-static |

2. Add the Vite plugin

import { quicklook } from 'quicklook-kit/vite';

// in the framework's Vite config
plugins: [quicklook()];

For TypeScript, add the types of the virtual module, either in a .d.ts file:

/// <reference types="quicklook-kit/virtual" />

or in tsconfig.json under compilerOptions.types as "quicklook-kit/virtual".

3. Read the file

import { browserFile, config, nativeFile } from 'virtual:quicklook';

const file = nativeFile(); // the previewed file, undefined in the browser
config.public; // values from quicklook.config.*

| Field | Type | | | ----------- | ------------ | ------------------------------ | | name | string | "notes.example" | | extension | string | "example", lowercase, no dot | | bytes | Uint8Array | the raw file contents |

Decode bytes however your format needs: new TextDecoder().decode(file.bytes) for text, or a parser for binary formats.

In the dev server, nativeFile() returns devFile if you set one, and undefined otherwise. Pass a dropped or picked File to browserFile() to get the same shape.

4. Add scripts

{
	"scripts": {
		"dev": "quicklook dev",
		"build": "quicklook build",
		"register": "quicklook build && quicklook register",
		"unregister": "quicklook unregister"
	}
}

CLI

| Command | Description | | ---------------------- | ----------------------------------------------------------------------------- | | quicklook dev | Starts the framework's dev server | | quicklook build:web | Builds the page only, into .quicklook/web | | quicklook build | Builds every configured platform into .quicklook/build/<platform> | | quicklook register | Installs the build on the current OS: the app on macOS, the plugin on Windows | | quicklook unregister | Removes it again |

build takes --platform macos or --platform windows to build just one. The Windows plugin builds on any OS; the macOS app only on a Mac, so elsewhere build skips it. Run build and register again after every change.

On macOS, register copies the app to /Applications and registers its extension. Select a file in Finder and press Space. If the preview doesn't show up, check pluginkit -mAv | grep <your macos.bundleId>, and make sure no other app claims the same file type.

On Windows, register copies the plugin to %APPDATA%\pooi.moe\QuickLook\QuickLook.Plugin\<windows.id>. Quit and restart QuickLook, then select a file in Explorer and press Space.

Add .quicklook to your .gitignore.

Things to know

  • Nothing can be loaded from the network. The native host serves your build from disk with a strict Content Security Policy, on both platforms. Import images, fonts and CSS normally and they get bundled. The dev server only reports violations in the console, without blocking them.
  • Inline scripts are allowed by hash. Frameworks that bootstrap with inline <script> tags keep working; the hashes are added to the CSP at build time.
  • Test in Safari and Edge. macOS previews run in WebKit, like Safari; Windows previews in WebView2, like Edge.
  • The light/dark theme follows the system. Use prefers-color-scheme in CSS.
  • The macOS app is signed ad hoc. Other people have to allow it in System Settings → Privacy & Security on first launch. Distributing it without that step requires an Apple Developer account and notarization.
  • Windows users install the .qlplugin from .quicklook/build/windows by previewing it in QuickLook and clicking the install button.

Releasing

Projects made with create quicklook include a release workflow. Push a tag like v1.0.0 and it builds every configured platform and attaches <App>-v1.0.0-macos.zip and <App>-v1.0.0-windows.qlplugin to a GitHub release.

If you publish a Homebrew cask, its URL needs the -macos suffix once you move to a release with platform groups.

Settings

settings declares options the user can change. On macOS, the app shows a control for each entry, picked by its type:

settings: {
  theme: {
    type: "string",
    default: "light",
    enum: [{ value: "light", description: "Light" }, "dark"],
    radio: true,
  },
  wrap: { type: "boolean", default: true, description: "Wrap **long** lines" },
  fontSize: { type: "integer", default: 13, minimum: 9, maximum: 32 },
  editor: {
    type: "object",
    properties: { tabSize: { type: "integer", default: 2, enum: [2, 4, 8] } },
  },
},

| Type | Control | | ------------------------------------------- | ------------------------------------------------------------------------- | | boolean | Checkbox | | enum (on string, integer or number) | Picker, or radio buttons with radio: true | | string | Text field | | integer, number | Number field, clamped to minimum/maximum; integer rejects fractions | | object | A group with a heading, for nested properties |

Every entry except object needs a default. title defaults to the key in words (fontSize becomes "Font Size"), description is inline Markdown, and order sorts entries. enum entries are plain values or { value, description }. A schema with an unknown type or field, a field on the wrong type, a key containing ., or a default that doesn't fit its type, enum or bounds fails the build with the key path, e.g. settings.editor.tabSize: default 0 is below minimum 1.

The page reads the values, nested like the schema, with settings():

import { settings } from 'virtual:quicklook';

const { wrap, editor } = settings();

In the dev server, settings() returns the defaults.

Windows has no settings window. Users set values in %APPDATA%\pooi.moe\QuickLook\<windows.id>.config, with nested keys joined by dots, and restart QuickLook. Values that don't fit the schema fall back to the default.

<?xml version="1.0" encoding="utf-8"?>
<Settings>
  <theme>dark</theme>
  <editor.tabSize>4</editor.tabSize>
</Settings>

Integrations

An integration is an object with a name and any of these hooks. They run in order: the framework first, then each entry in integrations.

import type { Integration } from 'quicklook-kit';

export default (options: { key: string }): Integration => ({
	name: 'my-integration',
	hooks: {
		// Adjust the resolved config.
		'config:resolved'(config) {},

		// Post-process the framework's static output before it's bundled.
		async 'build:done'({ outDir, config }) {},

		// Edit the generated XcodeGen spec: Info.plist keys, entitlements, targets. macOS only.
		'xcode:spec'(spec, config) {
			spec.targets.QuickLookExampleExtension.info.properties.MyKey = options.key;
		},

		// Add Vite plugins wherever quicklook() is used.
		vite: () => [],
	},
});

Add it as an object, or by name so JSON and YAML configs can use it too. A name is imported from the project: a package or a relative path, whose default export is the integration or a function returning it.

{
	"integrations": ["quicklook-some-package", ["./my-integration.ts", { "key": "value" }]]
}

A framework is an integration with a build field:

framework: {
	name: 'my-framework',
	build: {
		dev: ['vite', 'dev'],
		command: ['vite', 'build'],
		outDir: 'dist'
	}
}

The commands run with the project's own binaries.

License

This work is licensed under The MIT License.