[proposal] feat: support brick template inheritance
- Dominant language
- Dart
- Stars
- 1.1k
- Forks
- 113
- PR merge metrics
- No merged PRs in 30d
Description
# Proposal: Brick Template Inheritance
## Description
As a developer, I want to be able to compose bricks from other bricks so that I can simplify, reuse, and reduce the maintenance cost of bricks.
For example, suppose we are maintaining a Flutter app brick called `my_flutter_app` which builds on the standard `flutter create` template. Currently, we must duplicate and keep up-to-date the entire application (including `iOS`, `android`, `web`, etc.) which are not specific to our brick. It would be a lot simpler and easier to maintain if it were possible to create a `flutter_core` brick and specify that `my_flutter_app` extends `flutter_core`.
## Proposal
I propose adding the ability to have a brick `extend` another `brick`.
```yaml
name: my_flutter_app
description: My opinioned started Flutter application
extends: flutter_core@v1.0.0
```
The above `brick.yaml` specifies that `my_flutter_app` builds on top of the `v1.0.0` of the `flutter_core` brick.
As a result, when generating `my_flutter_app`, `mason` will:
1. Install `v1.0.0` of `flutter_core`
2. Generate the `flutter_core` brick code
3. Generate the `my_flutter_app` brick on top of `flutter_core`
a. Any conflicting files will be overwritten
### brick.yaml extends keyword
The `extends` value must be of the format:
- `@v` when referencing a brick hosted in a registry (coming soon).
`version` can be one of the following:
- `v` - signifying the latest minor version (e.g. `v1` ~> `>= 1.0.0 <2.0.0`>)
- `v.` - signifying the latest patch version (e.g. `v1.1` ~> `>= 1.1.0 < 1.2.0`>)
- `v..` - the exact version (e.g. `v1.2.3` ~> `1.2.3`)
- `:@` when referencing a brick hosted in a git repository.
`ref` can be one of the following:
- A branch name (e.g. `main`)
- A commit hash (e.g. `d45751b`)
- A tag name (e.g. `v1`)
#### Example Uses
```yaml
# extends hosted brick `flutter_core` version: `1.0.0`
name: my_flutter_app
description: My opinioned started Flutter application
extends: flutter_core@v1.0.0
# --------------------------------------------------- #
# extends hosted brick `flutter_core` version: `>=1.0.0 <2.0.0`
name: my_flutter_app
description: My opinioned started Flutter application
extends: flutter_core@v1
# --------------------------------------------------- #
# extends git brick `flutter_core` default branch
name: my_flutter_app
description: My opinioned started Flutter application
extends: https://github.com/felangel/flutter_core
# --------------------------------------------------- #
# extends git brick `flutter_core` release/v1 branch
name: my_flutter_app
description: My opinioned started Flutter application
extends: https://github.com/felangel/flutter_core@release/v1
# --------------------------------------------------- #
# extends git brick `flutter_core` at path `/templates/core` commit d45751b
name: my_flutter_app
description: My opinioned started Flutter application
extends: https://github.com/felangel/flutter_core:templates/core@d45751b
```
### Brick Inheritance Conflict Resolution
Suppose we have:
```yaml
name: sub_brick
extends: super_brick@v1
```
If both `sub_brick` and `super_brick` generate a file with path `P` and contents `C` and `Cβ` respectively, the outcome of generating `sub_brick` will be a file with path `P` and contents: `C'`.
For example, if `super_brick` contains:
```
βββ LICENSE
βββ README.md
βββ android
β βββ app
β β βββ build.gradle
β β βββ src
β βββ app_android.iml
β βββ build.gradle
β βββ gradle
β β βββ wrapper
β βββ gradle.properties
β βββ gradlew
β βββ gradlew.bat
β βββ local.properties
β βββ settings.gradle
βββ ios
β βββ Flutter
β β βββ AppFrameworkInfo.plist
β β βββ Debug.xcconfig
β β βββ Generated.xcconfig
β β βββ Release.xcconfig
β β βββ flutter_export_environment.sh
β βββ Runner
β β βββ AppDelegate.swift
β β βββ Assets.xcassets
β β βββ Base.lproj
β β βββ GeneratedPluginRegistrant.h
β β βββ GeneratedPluginRegistrant.m
β β βββ Info.plist
β β βββ Runner-Bridging-Header.h
β βββ Runner.xcodeproj
β β βββ project.pbxproj
β β βββ project.xcworkspace
β β βββ xcshareddata
β βββ Runner.xcworkspace
β βββ contents.xcworkspacedata
β βββ xcshareddata
β βββ xcuserdata
βββ l10n.yaml
βββ lib
β βββ main.dart
βββ pubspec.yaml
βββ web
βββ favicon.png
βββ icons
β βββ Icon-192.png
β βββ Icon-512.png
β βββ favicon.png
βββ index.html
βββ manifest.json
```
And if `sub_brick` contains:
```
βββ LICENSE
βββ README.md
βββ lib
β βββ counter
β β βββ counter.dart
β β βββ cubit
β β βββ view
β βββ main.dart
βββ pubspec.yaml
```
The result of generating `sub_brick` would be:
```
βββ LICENSE **
βββ README.md **
βββ android
β βββ app
β β βββ build.gradle
β β βββ src
β βββ app_android.iml
β βββ build.gradle
β βββ gradle
β β βββ wrapper
β βββ gradle.properties
β βββ gradlew
β βββ gradlew.bat
β βββ local.properties
β βββ settings.gradle
βββ ios
β βββ Flutter
β β βββ AppFrameworkInfo.plist
β β βββ Debug.xcconfig
β β βββ Generated.xcconfig
β β βββ Release.xcconfig
β β βββ flutter_export_environment.sh
β βββ Runner
β β βββ AppDelegate.swift
β β βββ Assets.xcassets
β β βββ Base.lproj
β β βββ GeneratedPluginRegistrant.h
β β βββ GeneratedPluginRegistrant.m
β β βββ Info.plist
β β βββ Runner-Bridging-Header.h
β βββ Runner.xcodeproj
β β βββ project.pbxproj
β β βββ project.xcworkspace
β β βββ xcshareddata
β βββ Runner.xcworkspace
β βββ contents.xcworkspacedata
β βββ xcshareddata
β βββ xcuserdata
βββ l10n.yaml
βββ lib
β βββ counter **
β β βββ counter.dart **
β β βββ cubit **
β β βββ view **
β βββ main.dart **
βββ pubspec.yaml **
βββ web
βββ favicon.png
βββ icons
β βββ Icon-192.png
β βββ Icon-512.png
β βββ favicon.png
βββ index.html
βββ manifest.json
```
Where files marked with `**` came from `sub_brick`.
### Replaceable Blocks
A file in `sub_brick` can reference the contents of the conflicting file in the `super_brick` via `{{ return 'bar';
```
The resulting `main.dart` would look like:
```dart
void main() {
print('Hello Dash!');
print('goodbye');
}
void foo() => return 'bar';
```
### Variable Resolution
When a brick `B` which requires variables {x, y}, extends brick `A` which requires variables {x, z}, variables {x, y, z} are required when generating brick `B`. The variables can still either be passed via args: `mason make B --x --y --z `, via a configuration file, via commandline prompt.
### Hook Execution
When a brick `B` which has a `pre_gen` and `post_gen` hook extends brick `A` which also has a `pre_gen` and `post_gen` hook, the result of generating brick `B` is:
1. run `pre_gen` A
2. generate brick A
3. run `post_gen` A
4. run `pre_gen` B
5. generate brick B
6. run `post_gen` B
### References / Inspiration
- https://docs.docker.com/engine/reference/builder/#from
- https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsuses
- https://groups.google.com/g/mustachejava/c/2z_73dJIAO4
- https://github.com/mustache/spec/issues/38
- https://gist.github.com/spullara/1854699
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.