Skip to content

Commit

Permalink
docs: add ARCHITECTURE.md (projen#903)
Browse files Browse the repository at this point in the history
Closes projen#647.

Not a comprehensive doc - just focused on file synthesis for v1. I've tried to keep this at a high level while including details that I thought would be most useful, and hopefully won't be outdated 6 months from now. As always, open to feedback. :-)

Even though the sub-headers are questions, the doc is spiritually meant to be just for architecture things, not a FAQ - open to restructuring things / renaming headers to make this more clear if needed.

I've also tried to avoid linking to source code for the most part in case things get refactored / to avoid stale links.

[View rendered version](https://github.com/Chriscbr/projen/blob/docs/architecture/ARCHITECTURE.md)

---
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
  • Loading branch information
Chriscbr authored Jun 23, 2021
1 parent 01ba8ca commit 6cdcafb
Showing 1 changed file with 99 additions and 0 deletions.
99 changes: 99 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Architecture

This document attempts to document the high-level architecture of projen. This
could be useful if you're trying to contribute to projen, trying to debug an
error message, or if you're just curious!

## How are a project's files synthesized?

### Bird's eye view

When `npx projen` is run, the command-line process executes the project's
projenrc file. This is usually a file like `.projenrc.js` or `projenrc.java`.

> The "rc" in the name is a common convention for configuration files - see
https://en.wikipedia.org/wiki/Configuration_file.

projenrc files follow a general structure:

1. they define one or more `Project` instances
2. these projects are configured and customized
3. `project.synth()` is called on the root project

For simplicity, most of this document will just assume there is a single project
unless otherwise specified.

Steps 1 and 2 only serve to initialize an in-memory representation of the
project. projen runs on Node.js; so in the JavaScript runtime, these steps
create a hierarchy of objects (called `Component`'s), each with various fields
specifying the names of files, tasks, options, and so on. The data within each
component provides enough information to uniquely determine the structure and
contents of the files it is responsible for. Components can add other components
to the project, and even make changes to existing components through common
interfaces like `project.tasks`, `project.deps`, or
`project.tryFindObjectFile()`.

Step 3 is the only step that actually performs any changes to files in the
user's project / file system.

### Synthesizing actual files

The `synth()` method of `Project` performs the actual synthesizing (and
updating) of all configuration files managed by projen. This is achieved by
deleting all projen-managed files (if there are any), and then re-synthesizing
them based on the latest configuration specified by the user. In code, this
breaks down as follows (slightly simplified):

1. the project's `preSynthesize()` method is called
2. all components' `preSynthesize()` methods are called
3. all projen-synthesized files are cleaned up
4. all components' `synthesize()` methods are called (most files are generated)
5. all components' `postSynthesize()` methods are called
6. the project's `postSynthesize()` method is called

In the above list, step 3 is critical since it's important that only files that
are managed by projen get cleaned up - we don't want user source code to be
deleted! Moreover, if a file was synthesized by projen at one point in time, but
later a user changes their projenrc configuration so it is no longer necessary,
we want it to be automatically cleaned up.

Rather than manually keeping track of synthesized files with some form of stored
state (which could easily get desynced by tampering from users or other tools),
projen simply looks for files with the _magic string_ that you get by
concatenating `"~~ Generated by "` and `"projen"`, and removes them. See
[cleanup.ts](src/cleanup.ts).

Since any file with this string gets automatically cleaned up, you should not
include this magic string verbatim in source code files. If you are writing your
own projen project type or component, you can simply reference this magic string
via `FileBase.PROJEN_MARKER`.

----

Steps 1, 2, 4, 5, and 6 are more straightforward. `synthesize()` is used to
generate the actual files in the user's file system (including applying
appropriate read/write permissions). `preSynthesize()` and `postSynthesize()`
are complementary methods that can used to enable components to perform
additional logic before and after synthesis. See the source code of `Component`
and `FileBase` for more details.

> Note: in practice, there are many existing components for creating specific
> types of files (such as `JsonFile` and `TextFile`), so we recommend using
> these over hand-making components wherever possible. (Believe in the power of
> abstractions!)
Since `preSynthesize()` is called before any files are cleaned up, it can be
used for e.g. observing any changes made to a generated file, and then adjusting
how the file is re-synthesized based on those changes. (As an example, running
`npm install` or `yarn install` can change the dependencies listed in the
`package.json` file of JavaScript projects. The built-in `NodeProject` uses
`preSynthesize()` to automatically integrate these changes to the `package.json`
file synthesized by projen, instead of overriding them.)

## How can projenrc files be written in multiple languages?

The projen library is transpiled by [jsii](https://github.com/aws/jsii) so that
projenrc files can be written in languages besides JavaScript. Under the hood,
API calls made in projen's Java/Python/etc. libraries communicate with a
JavaScript runtime to deliver the same behavior as if you wrote the code in
JavaScript. For more information, check out [jsii](https://github.com/aws/jsii).

0 comments on commit 6cdcafb

Please sign in to comment.