penpot/docs/technical-guide/developer/architecture/common.md
2025-05-13 08:09:59 +02:00

3.8 KiB

title desc
Common code Learn about architecture, data models, and development environments. See Penpot's technical guide for developers. Dive into common code.

Common code

In penpot, we take advantage of using the same language in frontend and backend, to have a bunch of shared code.

Sometimes, we use conditional compilation, for small chunks of code that are different in a Clojure+Java or ClojureScript+JS environments. We use the #? construct, like this, for example:

(defn ordered-set?
  [o]
  #?(:cljs (instance? lks/LinkedSet o)
     :clj (instance? LinkedSet o)))
  ▾ common/src/app/common/
    ▸ geom/
    ▸ pages/
    ▸ path/
    ▸ types/
      ...

Some of the modules need some refactoring, to organize them more cleanly.

Data model and business logic

  • geom contains functions to manage 2D geometric entities.
    • point defines the 2D Point type and many geometric transformations.
    • matrix defines the 2D transformation matrix type and its operations.
    • shapes manages shapes as a collection of points with a bounding rectangle.
  • path contains functions to manage SVG paths, transform them and also convert other types of shapes into paths.
  • pages contains the definition of the Penpot data model and the conceptual business logic (transformations of the model entities, independent of the user interface or data storage).
    • spec has the definitions of data structures of files and shapes, and also of the transformation operations in changes module. Uses Clojure spec to define the structure and validators.
    • init defines the default content of files, pages and shapes.
    • helpers are some functions to help manipulating the data structures.
    • migrations is in charge to manage the evolution of the data model structure over time. It contains a function that gets a file data content, identifies its version, and applies the needed migrations. Much like the SQL database migrations scripts.
    • changes and changes_builder define a set of transactional operations, that receive a file data content, and perform a semantic operation following the business logic (add a page or a shape, change a shape attribute, modify some file asset, etc.).
  • types we are currently in process of refactoring pages module, to organize it in a way more compliant of Abstract Data Types paradigm. We are approaching the process incrementally, rewriting one module each time, as needed.

Utilities

The main ones are:

  • data basic data structures and utility functions that could be added to Clojure standard library.
  • math some mathematic functions that could also be standard.
  • file_builder functions to parse the content of a .penpot exported file and build a File data structure from it.
  • logging functions to generate traces for debugging and usage analysis.
  • text an adapter layer over the DraftJS editor that we use to edit text shapes in workspace.
  • transit functions to encode/decode Clojure objects into transit, a format similar to JSON but more powerful.
  • uuid functions to generate Universally Unique Identifiers (UUID), used over all Penpot models to have identifiers for objects that are practically ensured to be unique, without having a central control.