forked from projen/projen
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: add ARCHITECTURE.md (projen#903)
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
Showing
1 changed file
with
99 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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). |