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.