Creating extensions - freelensapp/freelens GitHub Wiki

HOWTO: Creating an Extension for the Freelens Application

This guide explains how to create a new extension for the Freelens application using the freelens-example-extension repository as a template.


Prerequisites

  • Basic knowledge of JavaScript/TypeScript and Node.js
  • Documentation for an API of the original Lens 5.5.4 from our repository or an original site.
  • Git
  • Node.js installed — Node.js version must match the version used by the main Freelens application
  • A Node.js version manager such as NVM, asdf, or mise-en-place is highly recommended for managing Node.js versions.
  • pnpm installed
  • Freelens application installed (see Freelens documentation)

1. Fork or Clone the Example Extension

Create a new project based on the example extension:

git clone https://github.com/freelensapp/freelens-example-extension.git my-freelens-extension
cd my-freelens-extension

2. Rename and Update Metadata

  • Rename the directory (my-freelens-extension) as desired.
  • Update package.json:
    • Set "name", "description", "author", etc.
    • Update "repository" and "homepage" if public.
    • Required fields:
      • "main": Entry point for the main process (should be "out/main/index.js").
      • "renderer": Entry point for the renderer/UI (should be "out/renderer/index.js").
      • "engines": Must include a "freelens" field specifying the compatible version and "node" matching the main app, for example:
        "engines": {
          "freelens": "^1.4.0",
          "node": ">= 22.15.0"
        }
  • Dependency management:
    • All dependencies must be listed under "devDependencies". Extensions cannot load Node.js modules at runtime.
    • All modules are transpiled and bundled at build time; no runtime node_modules are allowed.
    • Externalized modules:
      Modules listed in externalizeDepsPlugin and pluginExternal in your Vite config are provided by the Freelens main app.
      • These modules must be listed as devDependencies and their versions must match the main app.
      • They are not bundled with your extension.

3. Directory and Runtime Conventions

  • main/ and common/ directories:
    • Files here are executed in the Node.js process (the main process).
    • Do not use JSX runtime in these files: only *.ts files are allowed.
    • Avoid importing UI libraries or anything that requires a browser environment.
  • renderer/ (and renderer subdirectories in common/):
    • Files here are executed in the renderer (browser) process.
    • These files can use JSX and UI libraries: you can use *.tsx files.
    • Do not require/import Node.js modules in these files (e.g., fs, os, custom node-only libraries).
    • Modules can be transpiled and bundled, but must be compatible with browser environments.

4. Bundling Assets with Your Extension

Extensions are technically classes loaded by the Electron main application and its renderer (Node.js and browser contexts).
All assets needed by your extension (icons, images, style files, markdown, YAML, and text files) must be bundled into the built main/index.js and renderer/index.js files.

  • How to bundle assets:

    • Use import statements for all assets you need, e.g.:
      import icon from './icon.svg';
      import styles from './MyComponent.module.scss';
      import helpText from './help.md?raw';
      import config from './config.yaml?raw';
    • The bundler (we use Vite for extensions) will include these assets directly in your JS bundles as data URIs or strings.
    • Do not rely on loading external files at runtime—everything must be available inside your built JS files.
  • Why?

    • Extensions can be distributed as a single package and will work regardless of the host filesystem or packaging.
    • This prevents missing asset errors and simplifies deployment.

5. CSS Naming and Usage

To avoid CSS class name conflicts with the main application or other extensions:

  • Always use unique prefixes for CSS class names in global styles.
    Example:
    .myext-button { ... }
    .myext-panel { ... }
  • Prefer CSS Modules for isolation.
    • Name your style files as *.module.scss or *.module.css.
    • Import them in your components:
      import styles from './MyComponent.module.scss';
      import stylesInline from './MyComponent.module.scss?inline';
      // usage: <><style>{stylesInline}</style><div className={styles.button}></>
    • This ensures class names are locally scoped and avoids conflicts automatically.
  • In CSS modules, you can use class names from the main application. Use :global(ClassName) syntax in your SCSS file.
  • You should not rely on or override global Freelens styles unless explicitly intended. You can use styles defined by components exposed in the Extensions API.
  • You must explicitly import style files. The bundler does not automatically load any CSS or SCSS files—you must import each style file you need.
  • Avoid Windtail styles. They are used by the main application and should not be added a second time by extension.

6. APIs and Stores Registration

  • New APIs and Stores:
    If your extension defines new API endpoints or data stores, do not explicitly register them in your extension code.
    • The Freelens main application automatically discovers and registers all new APIs and Stores exported by your extension.
    • Simply export your API handlers or store objects as modules—the main app will handle registration.
  • Do not use manual registration hooks for APIs or Stores in your extension.

7. Use Linters and Formatters

Using linters and code formatters is recommended to ensure code quality and consistency.

  • Biome and Prettier can be run directly as package scripts:
    • To check with Biome:
      pnpm biome:check
    • To check with Prettier:
      pnpm prettier:check
  • You can also use Trunk Check to run multiple checks in one command:
    pnpm trunk:check
    Trunk Check checks only modified files reported by git or all files only if --all option is added to the command.
    
    

8. Validate Dependencies with Knip

It is recommended to validate your dependencies using the Knip tool to detect unused or missing dependencies:

  • After installing and building your extension, run:
    pnpm knip:development
    pnpm build
    pnpm knip:production
  • This helps ensure that your devDependencies are accurate and that your extension does not include unnecessary code.

9. Handling Build Outputs and Debugging

  • During development:
    The build process produces separate files for each module (preserved modules) in the out/ directory, making debugging easier.
  • For distribution:
    When preparing your extension for release, you should produce a single bundled file for the main process and one for the renderer.
    • Set VITE_PRESERVE_MODULES="false" before building.
    • Example:
      VITE_PRESERVE_MODULES=false pnpm build
    • The output will be a single index.js file for each file referenced in package.json.

10. GitHub Actions & Renovate Bot Configuration

  • Workflows for GitHub Actions (CI/CD) and Renovate Bot configuration files are used and maintained in the freelensapp organization.
  • If you are hosting your extension within the freelensapp organization or want to reuse their automation:
    • You may use or copy the shared workflow and Renovate configuration files.
  • If your extension is hosted in a different repository or organization:
    • These workflow and configuration files are optional.
    • You may add your own CI, Renovate, or automation as appropriate for your project.
    • The absence of these files does not affect extension compatibility with Freelens.

11. Error Handling & Debugging

Extensions that throw errors during initialization will not work correctly.
It is crucial to verify that your extension does not emit any error messages immediately after enabling it.

  • Where to check for errors:

    • Main context (Node.js process):
      • Check the terminal output where you started the Freelens application.
      • Any errors from your extension’s main process code will appear here.
    • Renderer context (browser process):
      • Open the Developer Tools (Console tab) in Freelens.
      • Any errors from your extension’s renderer code will appear here.
  • Best practice:

    • After enabling your extension, check both the terminal and the browser’s developer console for error messages.
    • Fix any errors before distributing your extension.

12. Project Structure Overview

  • src/ – Your extension source code (with main/, renderer/, common/ as subdirectories as appropriate)
  • out/ – Output directory for built (bundled) code
  • integration/ – Integration tests for your extension
  • package.json – Project metadata, scripts, and devDependencies

Note: There is no extension.json manifest. All configuration is handled via package.json and/or code.


13. Install Dependencies

Install all dependencies using pnpm:

pnpm install

14. Implement Your Extension Logic

  • Edit the entry file in src/main/ for backend logic, in src/renderer/ for UI, and share logic in src/common/ as needed.
  • Follow the runtime rules:
    • Only use Node.js APIs in main/ and non-renderer common/ modules.
    • Only use browser-friendly code (including JSX if needed) in renderer/ and common/renderer/.
    • Do not cross the boundary: never import Node.js modules into renderer code.
  • Use unique CSS class names or CSS modules for style isolation.
  • Explicitly import all required style files in your components or entry files.
  • Bundle all asset files (icons, text, yaml, markdown, etc.) by importing them directly so they are included in your build.
  • Export APIs and Stores for automatic registration, do not register them manually.

15. Build the Extension

  • Build the extension:

pnpm build

- Pack to tgz file:
```bash
pnpm pack

16. Run Integration Tests (Optional)

Integration tests are located in the integration/ directory. You can copy them to Freelens main application source to run as extensions can use them directly.


17. Test Locally in Freelens

  1. In the Freelens app, go to Extensions settings and select "Add Local Extension."
  2. Choose the built tgz file.
  3. Wait until extension is enabled.
  4. Restart Freelens.
  5. Verify your extension works as expected.
  6. Check both the terminal and developer console for errors immediately after enabling your extension. Resolve any errors before distribution.

18. Package and Distribute (Optional)

  • Push your extension to a public repository (like GitHub).
  • Optionally, publish to npm if supported by Freelens.
  • Ensure your README.md includes usage and installation instructions.

19. Maintain and Update

  • Monitor changes in the Freelens API and update your extension as needed.
  • Keep devDependencies up to date, especially for externalized modules and Node.js version.
  • Use integration tests and code checks (linters/formatters) to ensure new changes do not break your extension.
  • Regularly validate dependencies with Knip.

References


Support

If you need help:

  • Browse Freelens community discussions.
  • Open an issue in your extension’s repository or in the main Freelens repository if it’s a general extension problem.

Happy coding!

⚠️ **GitHub.com Fallback** ⚠️