Is your feature request related to a problem? Please describe.
I've been working through configuring Front Matter for my site and my configuration (without adding the data types for my data files) has been getting long. I'm currently at ~750 lines and adding the data schemas will add another 500+, plus additional short codes etc.
I'm also a hugo theme developer and maintainer with multiple themes, but no way to provide my users defaults for Front Matter when using my themes.
Describe the solution you'd like
I would like an extensibility model similar to markdownlint or vale, which would allow me to package up Front Matter configuration items (data types, snippets, sorting options, content types, etc) for my users to take advantage of and extend or override as needed.
Where the configurations are merged in the order that they are specified, with any new items from local.frontmatter.json added to the settings in the remote config, and items in this config added to that merged config.
Any subkeys with the same name would replace existing keys, so if I had this config in local.frontmatter.json:
"frontMatter.content.snippets":{"Hint":{"description":"Add a colored callout hint/alert - info, warning, or danger.","body":["{{< hint [[type]] >}}","[[&selection]]","{{< /hint >}}"],"fields":[{"name":"type","title":"Type: How the hint should be styled.","type":"choice","choices":["info","warning","danger"],"default":""},{"name":"selection","title":"Body","type":"string","default":"FM_SELECTED_TEXT"}]},}
and this one in frontmatter.json at the project root:
"frontMatter.content.snippets":{"Hint":{"description":"Add a colored callout hint/alert - info, warning, or danger.","body":["{{< hint [[type]] >}}","[[&selection]]","{{< /hint >}}"],"fields":[{"name":"type","title":"Type: How the hint should be styled.","type":"choice","choices":["info","warning","danger","example","important"],"default":""},{"name":"selection","title":"Body","type":"string","default":"FM_SELECTED_TEXT"}]},}
The second definition of Hint would be used, making the valid choices include example and important as well as info, warning, and danger.
This would allow me to supercharge the usability of my themes for my users and make maintenance a little easier on myself at the same time.
Describe alternatives you've considered
I could resolve this with custom scripting (probably rising to the level of a toolkit or small app), but that would be another thing to require my users to install/track/manage, which is always extra friction.
I can see a potential for publishing Front Matter configuration setting packages that Front Matter could be aware of, but that seems like much more effort and would require a lot of UI and workflow updates and be difficult/costly to maintain (though possibly high value for users).
Additional context
I'm coming at this from both a "I have to maintain a very big complex configuration for myself" and "I would like to make delightful UX/DevX for users of my themes" which are separate but related use cases that could both benefit from a feature like this.
I'm fairly ignorant of typescript and VS Code development, but I would be happy to test this and help pitch in however you think helpful, if this feature is something you think worth bringing into the project. And if not, I'm also willing to write some scripts and document those as a workaround/prototype.
**Is your feature request related to a problem? Please describe.**
I've been working through configuring Front Matter for my site and my configuration (without adding the data types for my data files) has been getting long. I'm currently at ~750 lines and adding the data schemas will add another 500+, plus additional short codes etc.
I'm also a hugo theme developer and maintainer with multiple themes, but no way to provide my users defaults for Front Matter when using my themes.
**Describe the solution you'd like**
I would like an extensibility model similar to [markdownlint][01] or [vale][02], which would allow me to package up Front Matter configuration items (data types, snippets, sorting options, content types, etc) for my users to take advantage of and extend or override as needed.
In the Front Matter config, something like:
```json
{
"frontMatter.extends": [
"https://platen.io/data/frontmatter/base.frontmatter.json",
"./local.frontmatter.json"
]
}
```
Where the configurations are merged in the order that they are specified, with any new items from `local.frontmatter.json` added to the settings in the remote config, and items in this config added to that merged config.
Any subkeys with the same name would replace existing keys, so if I had this config in `local.frontmatter.json`:
```json
"frontMatter.content.snippets": {
"Hint": {
"description": "Add a colored callout hint/alert - info, warning, or danger.",
"body": [
"{{< hint [[type]] >}}",
"[[&selection]]",
"{{< /hint >}}"
],
"fields": [
{
"name": "type",
"title": "Type: How the hint should be styled.",
"type": "choice",
"choices": [
"info",
"warning",
"danger"
],
"default": ""
},
{
"name": "selection",
"title": "Body",
"type": "string",
"default": "FM_SELECTED_TEXT"
}
]
},
}
```
and this one in `frontmatter.json` at the project root:
```json
"frontMatter.content.snippets": {
"Hint": {
"description": "Add a colored callout hint/alert - info, warning, or danger.",
"body": [
"{{< hint [[type]] >}}",
"[[&selection]]",
"{{< /hint >}}"
],
"fields": [
{
"name": "type",
"title": "Type: How the hint should be styled.",
"type": "choice",
"choices": [
"info",
"warning",
"danger",
"example",
"important"
],
"default": ""
},
{
"name": "selection",
"title": "Body",
"type": "string",
"default": "FM_SELECTED_TEXT"
}
]
},
}
```
The second definition of `Hint` would be used, making the valid choices include `example` and `important` as well as `info`, `warning`, and `danger`.
This would allow me to supercharge the usability of my themes for my users _and_ make maintenance a little easier on myself at the same time.
**Describe alternatives you've considered**
I _could_ resolve this with custom scripting (probably rising to the level of a toolkit or small app), but that would be another thing to require my users to install/track/manage, which is always extra friction.
I can see a potential for publishing Front Matter configuration setting packages that Front Matter could be aware of, but that seems like much more effort and would require a lot of UI and workflow updates and be difficult/costly to maintain (though possibly high value for users).
**Additional context**
I'm coming at this from both a "I have to maintain a very big complex configuration for myself" and "I would like to make delightful UX/DevX for users of my themes" which are separate but related use cases that could both benefit from a feature like this.
I'm fairly ignorant of typescript and VS Code development, but I would be happy to test this and help pitch in however you think helpful, if this feature is something you think worth bringing into the project. And if not, I'm also willing to write some scripts and document those as a workaround/prototype.
[01]: https://github.com/DavidAnson/markdownlint/blob/main/README.md#optionsconfig
[02]: https://vale.sh/docs/topics/packages/
<!-- github-sync-id:1368725914 -->
In connection to this, maybe it would also be a good feature to add a packages.json parameter that can contain the configuration or a path to the configuration (ie "extends @davidsneighbour/frontmatter-config")
In connection to this, maybe it would also be a good feature to add a packages.json parameter that can contain the configuration or a path to the configuration (ie "extends @davidsneighbour/frontmatter-config")
<!-- github-comment-id:1242876408 -->
You can override the config using the settings.json file from VS Code. Wouldn't this confuse the user too much?
Although the base gets loaded first, then the local frontmatter.json, and the settings.json file, it might still be simple enough to understand.
Online vs offline
I see you entered an URL; this assumes you are working online which is not always the case. It might not be an issue for the initial configuration, but once you have done the configuration. It might be the author is doing some offline changes and does not have access to the base file.
Ideas I can think of:
Make the base part of the project
During the initialization of a new front matter project, fets the base content and stores it locally.
If offline, you can show a message the base could not be fetched.
This concept could work, but initializing based on a base file would mean the project was already initialized.
Versioning
Is the base file something you'll version? What if you introduce something new or remove something? All of a sudden, the experience for the user will be different. By having a local file, they keep working with the same version. Of course, it might be harder to update.
Merges/overrides
If you configure settings in the base and apply new settings in the local version. Are they merging with the base settings or overriding settings?
Like with VS Code settings, if you update a setting in the current project, it overrides what is defined globally.
For this base concept, on some settings, it would be useful, I think to merge instead of override. Although it could cause some conflicts.
package.json
@davidsneighbour, not all projects will have a package.json file. Not sure if this would bring any additional value. Having a base property on the root of the frontmatter.json object might just do the trick, but this comes back to previous highlighted things.
I like the idea but got a couple of concerns.
## Yet another place to configure Front Matter
You can override the config using the `settings.json` file from VS Code. Wouldn't this confuse the user too much?
Although the base gets loaded first, then the local `frontmatter.json`, and the `settings.json` file, it might still be simple enough to understand.
## Online vs offline
I see you entered an URL; this assumes you are working online which is not always the case. It might not be an issue for the initial configuration, but once you have done the configuration. It might be the author is doing some offline changes and does not have access to the base file.
Ideas I can think of:
- Make the base part of the project
- During the initialization of a new front matter project, fets the base content and stores it locally.
- If offline, you can show a message the base could not be fetched.
- This concept could work, but initializing based on a base file would mean the project was already initialized.
### Versioning
Is the base file something you'll version? What if you introduce something new or remove something? All of a sudden, the experience for the user will be different. By having a local file, they keep working with the same version. Of course, it might be harder to update.
### Merges/overrides
If you configure settings in the base and apply new settings in the local version. Are they merging with the base settings or overriding settings?
Like with VS Code settings, if you update a setting in the current project, it overrides what is defined globally.
For this base concept, on some settings, it would be useful, I think to merge instead of override. Although it could cause some conflicts.
## package.json
@davidsneighbour, not all projects will have a `package.json` file. Not sure if this would bring any additional value. Having a `base` property on the root of the `frontmatter.json` object might just do the trick, but this comes back to previous highlighted things.
<!-- github-comment-id:1243471293 -->
I think this is different enough to be worth doing as (all?) SSGs support theming, which usually includes some specific opinions about metadata, snippets, data structures, etc.
Without a way to extend, everyone has to manually copy the right settings into the right keys in their configuration file and try to keep them up to date. This is okay for a single theme/module, but if you're using several together, that becomes more and more complex to manage.
I would expect that this is something theme authors (or enthusiasts) provide, rather than a best practice for configuring Front Matter for a single project.
Specifically for frontmatter.js vs ~/.vscode/settings.json vs .vscode/settings.json vs extended configurations, my understanding/thoughts are:
The current order for how settings are applied is ~/.vscode/settings.json -> frontmatter.js -> .vscode/settings.json. I'm not sure this is accurate, but that's what I'm getting from the docs in my current read.
~/.vscode/settings.json is loaded first as the user's settings, which makes the effective config:
{"frontMatter.content.snippets":{"Baz":{// user implementation
}}}
frontmatter.js is the global configuration. If it does extend any configs, they are applied in the order they're specified before the rest of frontmatter.js is.
So in this example, the config from loading frontmatter would have loaded the Foo and Bar snippets would first have combined config from Foo and Barbefore applying this one. You would end up with (omitting the extends key for brevity):
{"frontMatter.content.snippets":{"Baz":{// user implementation
},"Foo":{// global implementation
},"Bar":{// ext 2 implementation
}}}
Finally, the team settings in .vscode/settings.json get applied, leaving us with:
{"frontMatter.content.snippets":{"Baz":{// user implementation
},"Foo":{// team implementation
},"Bar":{// ext 2 implementation
}},"frontMatter.content.sorting":{{// project implementation
}}}
On offline vs online
I would expect first initialization to give me the same default configuration anyone not using extends (or whatever, just using that key name for now) gets. I can see room for an enhancement, like "do you want to extend an existing configuration? Paste the URL to it here" or similar, then handle fetching/etc. I don't think I would expect that to be in the initial implementation.
Maybe a way to handle this would be to cache the remote configuration blob (maybe in .frontmatter/extends or similar?) as part of the first-fetch process. I think it would be reasonable to have a warning in the main frontmatter.js file for un-fetched entries in extends, noting that you have to reload the extension (or run a command, maybe).
On Versioning
Thinking on this, here's how I might handle it as an extendable config author:
I publish the configuration from main to platen.io/frontmatter/base.frontmatter.json every time my site rebuilds.
I publish major versions to their own file, like platen.io/frontmatter/base.v1.frontmatter.json and whenever I need to make a breaking change (if ever), I keep the last release of that major version around.
I document and clarify all of this for my users. I make it clear that you should use a versioned file, not the unversioned one, if you want stability.
The other (similar, but not requiring me to publish them anywhere but GitHub) option is to use tags to reference versioned files. I'm less likely to do that personally but that wouldn't require any specific architecture.
I think letting users know about new versions etc is neat but out of scope for this, especially in the first iterations.
On Merging and Overriding
I think for clarity it's probably useful to distinguish what we mean by each.
For merging, I mean that if a given value for a key (even deeply nested) does not exist, it is added.
For overriding, I mean that if a given value for a key conflicts with another, the "latest" value wins, replacing the earlier one.
I think if a first pass on this feature was merge-only, that would probably make enormous sense. I see some possibility for configurable override, but after thinking through how you might do that I'm finding myself firmly in the "punt that for later, if it ever seems like it actually matters it can be looked at again" camp.
I think this also means that from a "what do I do to get the canonicalized config settings" the answer is the same at each step:
Start from the settings I already have
Does this config declare any extends? If it does, merge them into the existing settings in the order they're specified.
Merge this config into the existing settings
Return the merged config
Additional Considerations
I think there's a possibility for some more delightful UX around this (where did this setting come from?) but like overrides and update notifications etc, I think it's a later concern compared to being able to extend the config at all. Presumably, documenting the settings provided by an extendable config is the job of that config's author (like me), and I doubt any user is going to accidentally end up extending a config vs following directions from a theme author.
A few thoughts:
## On yet another place to configure
I think this is different enough to be worth doing as (all?) SSGs support theming, which usually includes some specific opinions about metadata, snippets, data structures, etc.
Without a way to extend, everyone has to manually copy the right settings into the right keys in their configuration file and try to keep them up to date. This is _okay_ for a single theme/module, but if you're using several together, that becomes more and more complex to manage.
I would expect that this is something theme authors (or enthusiasts) provide, rather than a best practice for configuring Front Matter for a single project.
Specifically for `frontmatter.js` vs `~/.vscode/settings.json` vs `.vscode/settings.json` vs extended configurations, my understanding/thoughts are:
1. The current order for how settings are applied is `~/.vscode/settings.json` -> `frontmatter.js` -> `.vscode/settings.json`. I'm not _sure_ this is accurate, but that's what I'm getting from the docs in my current read.
1. Given the following configurations:
```jsonc
// ext1.frontmatter.json
{
"frontMatter.content.snippets": {
"Foo": {
// ext1 implementation
}
}
}
```
```jsonc
// ext2.frontmatter.json
{
"frontMatter.content.snippets": {
"Bar": {
// ext 2 implementation
}
}
}
```
```jsonc
// frontmatter.json
{
"frontMatter.config.extends": [
"path/to/ext1.frontmatter.json",
"path/to/ext2.frontmatter.json"
],
"frontMatter.content.snippets": {
"Foo": {
// global implementation
}
}
}
```
```jsonc
// ~/.vscode/settings.json
{
"frontMatter.content.snippets": {
"Baz": {
// user implementation
}
}
}
```
```jsonc
// .vscode/settings.json
{
"frontMatter.content.sorting": {
{
// project implementation
}
}
}
```
1. `~/.vscode/settings.json` is loaded first as the user's settings, which makes the effective config:
```jsonc
{
"frontMatter.content.snippets": {
"Baz": {
// user implementation
}
}
}
```
1. `frontmatter.js` is the _global_ configuration. If it does `extend` any configs, they are applied in the order they're specified _before_ the rest of `frontmatter.js` is.
So in this example, the config from loading `frontmatter` would have loaded the `Foo` and `Bar` snippets would first have combined config from `Foo` and `Bar` _before_ applying this one. You would end up with (omitting the extends key for brevity):
```jsonc
{
"frontMatter.content.snippets": {
"Baz": {
// user implementation
},
"Foo": {
// global implementation
},
"Bar": {
// ext 2 implementation
}
}
}
```
1. Finally, the _team_ settings in `.vscode/settings.json` get applied, leaving us with:
```jsonc
{
"frontMatter.content.snippets": {
"Baz": {
// user implementation
},
"Foo": {
// team implementation
},
"Bar": {
// ext 2 implementation
}
},
"frontMatter.content.sorting": {
{
// project implementation
}
}
}
```
## On offline vs online
I would expect first initialization to give me the same default configuration anyone not using `extends` (or whatever, just using that key name for now) gets. I can see room for an enhancement, like "do you want to extend an existing configuration? Paste the URL to it here" or similar, then handle fetching/etc. I don't think I would expect that to be in the initial implementation.
Maybe a way to handle this would be to cache the remote configuration blob (maybe in `.frontmatter/extends` or similar?) as part of the first-fetch process. I think it would be reasonable to have a warning in the main `frontmatter.js` file for un-fetched entries in `extends`, noting that you have to reload the extension (or run a command, maybe).
## On Versioning
Thinking on this, here's how I might handle it as an extendable config author:
1. I publish the configuration from `main` to `platen.io/frontmatter/base.frontmatter.json` every time my site rebuilds.
2. I publish major versions to their own file, like `platen.io/frontmatter/base.v1.frontmatter.json` and whenever I need to make a breaking change (if ever), I keep the last release of that major version around.
3. I document and clarify all of this for my users. I make it clear that you should use a versioned file, not the unversioned one, if you want stability.
The other (similar, but not requiring me to publish them anywhere but GitHub) option is to use tags to reference versioned files. I'm less likely to do that _personally_ but that wouldn't require any specific architecture.
I think letting users know about new versions etc is neat but out of scope for this, especially in the first iterations.
## On Merging and Overriding
I think for clarity it's probably useful to distinguish what we mean by each.
For **merging**, I mean that if a given value for a key (even deeply nested) does not exist, it is added.
For **overriding**, I mean that if a given value for a key conflicts with another, the "latest" value wins, replacing the earlier one.
I think if a first pass on this feature was merge-only, that would probably make enormous sense. I see some _possibility_ for configurable override, but after thinking through how you might do that I'm finding myself firmly in the "punt that for later, if it ever seems like it actually matters it can be looked at again" camp.
I think this also means that from a "what do I do to get the canonicalized config settings" the answer is the same at each step:
1. Start from the settings I already have
2. Does this config declare any `extends`? If it does, merge them into the existing settings in the order they're specified.
3. Merge this config into the existing settings
4. Return the merged config
## Additional Considerations
I think there's a possibility for some more delightful UX around this (where did this setting come from?) but like overrides and update notifications etc, I think it's a later concern compared to being able to extend the config at all. Presumably, documenting the settings provided by an extendable config is the job of that config's author (like me), and I doubt any user is going to accidentally end up extending a config vs following directions from a theme author.
<!-- github-comment-id:1243987465 -->
Thank you for the reply @michaeltlombardi this clarifies things a lot.
Specifically for frontmatter.js vs ~/.vscode/settings.json vs .vscode/settings.json vs extended configurations, my understanding/thoughts are:
For the configuration part, first, the frontmatter.json files get used, then any settings you might have in Visual Studio Code, which for this kind of extension typically happens on the project settings.
Maybe a way to handle this would be to cache the remote configuration blob (maybe in .frontmatter/extends or similar?)
This makes sense, and we could introduce show a notification once the remote file changes with a message saying: Do you wish to update?
I publish major versions to their own file, like platen.io/frontmatter/base.v1.frontmatter.json and whenever I need to make a breaking change (if ever), I keep the last release of that major version around.
Wouldn't this mean that the user needs to update the reference in their frontmatter.json file?
Merging and overriding
For merging I was thinking like the example you gave above:
If the setting does not exist, it will be added
If the setting exists on the remote/frontmatter.json, it will get overridden when it also exists locally
But, if the setting is an array (and only for a couple), both arrays are merged. For example, this would work for the snippets, where you have snippets coming from the remote file, and extended with the snippets locally.
Reason I mentioned that it only works for a couple, is for instance with the different modes you can enable for Front Matter. This is a setting that should not be extended but overwritten.
That last part makes me think.
Internally we need to map which settings are meant to be extended, and which to be overridden (might be the default)
Should the user somehow be able to decide on: whether this setting needs to be overridden or extended? What if they want to only use their snippets?
Thank you for the reply @michaeltlombardi this clarifies things a lot.
> Specifically for frontmatter.js vs ~/.vscode/settings.json vs .vscode/settings.json vs extended configurations, my understanding/thoughts are:
For the configuration part, first, the `frontmatter.json` files get used, then any settings you might have in Visual Studio Code, which for this kind of extension typically happens on the project settings.
> Maybe a way to handle this would be to cache the remote configuration blob (maybe in .frontmatter/extends or similar?)
This makes sense, and we could introduce show a notification once the remote file changes with a message saying: Do you wish to update?
> I publish major versions to their own file, like `platen.io/frontmatter/base.v1.frontmatter.json` and whenever I need to make a breaking change (if ever), I keep the last release of that major version around.
Wouldn't this mean that the user needs to update the reference in their `frontmatter.json` file?
> Merging and overriding
For **merging** I was thinking like the example you gave above:
- If the setting does not exist, it will be added
- If the setting exists on the `remote/frontmatter.json`, it will get overridden when it also exists locally
- But, if the setting is an array (and only for a couple), both arrays are merged. For example, this would work for the snippets, where you have snippets coming from the remote file, and extended with the snippets locally.
- Reason I mentioned that it only works for a couple, is for instance with the different modes you can enable for Front Matter. This is a setting that should not be extended but overwritten.
That last part makes me think.
1. Internally we need to map which settings are meant to be extended, and which to be overridden (might be the default)
2. Should the user somehow be able to decide on: whether this setting needs to be overridden or extended? What if they want to only use their snippets?
<!-- github-comment-id:1249240996 -->
For the configuration part, first, the frontmatter.json files get used, then any settings you might have in Visual Studio Code, which for this kind of extension typically happens on the project settings.
Ah, that makes sense!
This makes sense, and we could introduce show a notification once the remote file changes with a message saying: Do you wish to update?
I think that's reasonable to do, even if it's just a quick hash comparison or something.
Wouldn't this mean that the user needs to update the reference in their frontmatter.json file?
Oops! I was missing a sentence. So in this hypothetical example, I publish the same file to two locations:
platen.io/frontmatter/base.v1.frontmatter.json - Pinned version, you can trust (as much as you trust any dev not to lie about breaking changes) that any updates here won't ruin your usage.
platen.io/frontmatter/base.frontmatter.json - Always latest version, don't use this one if you're not willing to play with fire.
And then when I need to make a breaking change, I would continue to publish the old base.v1.frontmatter.json but also add base.v2.frontmatter.json and now base.frontmatter.json points to v2.
That last part makes me think.
Internally we need to map which settings are meant to be extended, and which to be overridden (might be the default)
Should the user somehow be able to decide on: whether this setting needs to be overridden or extended? What if they want to only use their snippets?
Agree with the flow you outlined on merging, I think that makes plenty of sense. For configuration, I think this could be iterative - step one, follow default logic as defined by the updated schema for extensible/overridable.
In this case, to support picking and choosing what people can import, you would be stuck as a (module? package? extension?) developer writing small extensible JSON files, maybe with a meta file - so snippets.frontmatter.json and metadata.frontmatter.json etc.
With that in place, we could consider supporting a full.frontmatter.json that uses the extends keyword to pull in snippets and metadata, requiring end users to just list full if they want everything. One downside/eventuality there though is that it can require recursively finding and using extension definitions - though that also makes composing this sort of thing easier in the long run, it's also complexity, especially for an experimental prototype.
Once we have some info to look at / see how that works in practice, I think I definitely see some value in being able to define a per-extension settings config, but the shape of that seems like a lot to chew on - probably useful, but definitely needing lengthy thought. Maybe I just want the snippets, which is easy enough, but maybe I want only a specific snippet, or I want all but a specific snippet, or I want the snippets as-is but I want to override one of them, etc.
That definitely gets hairy quick, so my thought is to kinda noodle through those things alongside the minimal/basic implementation iterations with an understanding for extension config authors that this is still being worked out.
> For the configuration part, first, the frontmatter.json files get used, then any settings you might have in Visual Studio Code, which for this kind of extension typically happens on the project settings.
Ah, that makes sense!
> This makes sense, and we could introduce show a notification once the remote file changes with a message saying: Do you wish to update?
I think that's reasonable to do, even if it's just a quick hash comparison or something.
> Wouldn't this mean that the user needs to update the reference in their frontmatter.json file?
Oops! I was missing a sentence. So in this hypothetical example, I publish the _same_ file to two locations:
- `platen.io/frontmatter/base.v1.frontmatter.json` - Pinned version, you can trust (as much as you trust any dev not to lie about breaking changes) that any updates here won't ruin your usage.
- `platen.io/frontmatter/base.frontmatter.json` - Always latest version, don't use this one if you're not willing to play with fire.
And then when I need to make a breaking change, I would continue to publish the old `base.v1.frontmatter.json` but _also_ add `base.v2.frontmatter.json` and now `base.frontmatter.json` points to v2.
> That last part makes me think.
>
> 1. Internally we need to map which settings are meant to be extended, and which to be overridden (might be the default)
> 1. Should the user somehow be able to decide on: whether this setting needs to be overridden or extended? What if they want to only use their snippets?
Agree with the flow you outlined on merging, I think that makes plenty of sense. For configuration, I think this could be iterative - step one, follow default logic as defined by the updated schema for extensible/overridable.
In this case, to support picking and choosing what people can import, you would be stuck as a (module? package? extension?) developer writing small extensible JSON files, maybe with a meta file - so `snippets.frontmatter.json` and `metadata.frontmatter.json` etc.
With that in place, we could consider supporting a `full.frontmatter.json` that uses the extends keyword to pull in `snippets` and `metadata`, requiring end users to just list `full` if they want everything. One downside/eventuality there though is that it can require recursively finding and using extension definitions - though that also makes composing this sort of thing easier in the long run, it's _also_ complexity, especially for an experimental prototype.
Once we have some info to look at / see how that works in practice, I think I definitely see some value in being able to define a per-extension settings config, but the shape of that seems like a lot to chew on - probably useful, but definitely needing lengthy thought. Maybe I just want the snippets, which is easy enough, but maybe I want only a specific snippet, or I want all but a specific snippet, or I want the snippets as-is but I want to override one of them, etc.
That definitely gets hairy quick, so my thought is to kinda noodle through those things alongside the minimal/basic implementation iterations with an understanding for extension config authors that this is still being worked out.
<!-- github-comment-id:1249570291 -->
Info: I'll keep the https://beta.frontmatter.codes/demo/frontmatter.extends.json file available for testing.
The order in how configuration gets applied
Loads and applies external configuration files in the order how they are defined
Loads the splitted configuration files (the ones in the .frontmatter/config folder)
Loads the frontmatter.json file and overrides or merges with the other settings.
@davidsneighbour @michaeltlombardi first external config support functionality got added and is up for its first tests.
## How it works
In the `frontmatter.json` file, you can add the `frontMatter.extends` setting, and a list of URLs/file paths to load needs to be passed.
Here is an example:
```json
"frontMatter.extends": [
"https://beta.frontmatter.codes/demo/frontmatter.extends.json",
"./config/frontmatter.config.json"
],
```
> **Info**: I'll keep the `https://beta.frontmatter.codes/demo/frontmatter.extends.json` file available for testing.
## The order in how configuration gets applied
- Loads and applies external configuration files in the order how they are defined
- Loads the splitted configuration files (the ones in the `.frontmatter/config` folder)
- Loads the `frontmatter.json` file and overrides or merges with the other settings.
<!-- github-comment-id:1363784382 -->
@estruyf I've been wrapping a big refactor for my site/theme, which prepped me for this. My main question right now is whether I need to compose the big JSON blob as an external reference or if I can publish them in the split-out layout I have now.
In other words, can I publish a frontmatter.json to https://platen.io/schemas/frontmatter/config.json
And then have the split-out pieces, like snippets, in their own blobs:
https://platen.io/schemas/frontmatter/content/snippets/button.json
```jsonc
{
"title": "Button",
"description": "Add a button linking to another page or external site.",
"body": [
"",
"{ &class }"
],
"fields": [
{
"name": "selection",
"title": "Button Text: This displays on the button itself.",
"type": "string",
"single": true,
"default": "FM_SELECTED_TEXT"
},
{
"name": "url",
"title": "Button Url: Specify the URL this button should lead to.",
"type": "string",
"single": true,
"default": ""
},
{
"name": "class",
"title": "Class List: Specify any classes to add to the button in a space-separated list. Each class must have a period (`.`) prefix.",
"type": "string",
"single": true,
"default": ""
}
]
}
```
I wouldn't mind if I had to add a key to the main importable config like:
But otherwise it looks like I'll have to compose the full config file. I can figure that out - likely my approach will simply be to make a series of "small" config that contains only one snippet or whatever and compose those upwards - but that's the limitation I am seeing at a glance right now.
That would look like:
// button.json defines its snippet
{"frontMatter.content.snippets":{"platen-buttons":{"title":"button"// rest of the implementation
}}}// snippets.json only includes the others as configs it's extending
{"frontMatter.extends":["https://platen.io/frontmatter/content/snippets/button.json","https://platen.io/frontmatter.content/snippets/details.json"// others
]}// main theme extend includes the composed configs _and_ has some other settings
{"frontMatter.extends":["https://platen.io/frontmatter/content/snippets.json""https://platen.io/frontmatter/content/sorting.json""https://platen.io/frontmatter/data/types.json"],"frontMatter.framework.id":"hugo"}
And then my users could either pull the composite config or any component piece of it they want to use, and I can add scripts/etc to help with that.
Speaking of scripts, that's the only other limitation I think I see right now - this works for anything that is defined only in the JSON blobs, but doesn't have a way to identify/package custom scripts. I don't think that's a deal breaker, especially not for the initial feature.
@estruyf I've been wrapping a big refactor for my site/theme, which prepped me for this. My main question right now is whether I need to compose the big JSON blob as an external reference or if I can publish them in the split-out layout I have now.
In other words, can I publish a `frontmatter.json` to `https://platen.io/schemas/frontmatter/config.json`
```json
{
"$schema": "https://beta.frontmatter.codes/frontmatter.schema.json",
"frontMatter.framework.id": "hugo",
"frontMatter.content.publicFolder": "static",
"frontMatter.content.sorting": [
{
"id": "byWeight",
"title": "By Weight",
"name": "weight",
"order": "asc",
"type": "number"
}
]
}
```
And then have the split-out pieces, like snippets, in their own blobs:
<details>
<summary><code>https://platen.io/schemas/frontmatter/content/snippets/button.json</code></summary>
```jsonc
{
"title": "Button",
"description": "Add a button linking to another page or external site.",
"body": [
"![button:[[&selection]]]([[&url]])",
"{ [[&class]] }"
],
"fields": [
{
"name": "selection",
"title": "Button Text: This displays on the button itself.",
"type": "string",
"single": true,
"default": "FM_SELECTED_TEXT"
},
{
"name": "url",
"title": "Button Url: Specify the URL this button should lead to.",
"type": "string",
"single": true,
"default": ""
},
{
"name": "class",
"title": "Class List: Specify any classes to add to the button in a space-separated list. Each class must have a period (`.`) prefix.",
"type": "string",
"single": true,
"default": ""
}
]
}
```
</details>
I wouldn't mind if I had to add a key to the main importable config like:
```jsonc
"frontMatter.content.snippets": {
"foo": {
"$ref": "https://platen.io/schemas/frontmatter/content/snippets/button.json"
}
}
```
But otherwise it _looks like_ I'll have to compose the full config file. I can figure that out - likely my approach will simply be to make a series of "small" config that contains only one snippet or whatever and compose those upwards - but that's the limitation I am seeing at a glance right now.
That would look like:
```jsonc
// button.json defines its snippet
{
"frontMatter.content.snippets": {
"platen-buttons": {
"title": "button"
// rest of the implementation
}
}
}
// snippets.json only includes the others as configs it's extending
{
"frontMatter.extends": [
"https://platen.io/frontmatter/content/snippets/button.json",
"https://platen.io/frontmatter.content/snippets/details.json"
// others
]
}
// main theme extend includes the composed configs _and_ has some other settings
{
"frontMatter.extends": [
"https://platen.io/frontmatter/content/snippets.json"
"https://platen.io/frontmatter/content/sorting.json"
"https://platen.io/frontmatter/data/types.json"
],
"frontMatter.framework.id": "hugo"
}
```
And then my users could either pull the composite config or any component piece of it they want to use, and I can add scripts/etc to help with that.
Speaking of scripts, that's the only other limitation I think I see right now - this works for anything that is defined _only_ in the JSON blobs, but doesn't have a way to identify/package custom scripts. I don't think that's a deal breaker, especially not for the initial feature.
<!-- github-comment-id:1375799920 -->
That is correct; I also wanted to make sure a never-ending loop could happen. This might be the case if something needs to be configured correctly.
Splitting is possible, but you can only do this for now, as you explained.
If this gets well adopted, it would be an excellent next step to put the further effort in it to allow you to insert REFs.
That is correct; I also wanted to make sure a never-ending loop could happen. This might be the case if something needs to be configured correctly.
Splitting is possible, but you can only do this for now, as you explained.
If this gets well adopted, it would be an excellent next step to put the further effort in it to allow you to insert REFs.
<!-- github-comment-id:1375812626 -->
I'm rewriting my Front Matter configs this week, I'll get them published as extensible configs and share those findings in here when there's something to show for it. I'm very excited because this also dovetails with the JSON schematization I've been doing for site configuration. All going well, this should mean that I'm able to give my users highly functional UIs for managing their content metadata, configuration, and snippets without much work on their end. Once I'm able to figure out a coherent way to do custom scripts from an extended config, that will also really help smooth out their authoring experience.
I'm rewriting my Front Matter configs this week, I'll get them published as extensible configs and share those findings in here when there's something to show for it. I'm very excited because this also dovetails with the JSON schematization I've been doing for site configuration. All going well, this _should_ mean that I'm able to give my users highly functional UIs for managing their content metadata, configuration, and snippets without much work on their end. Once I'm able to figure out a coherent way to do custom scripts from an extended config, that will also really help smooth out their authoring experience.
<!-- github-comment-id:1375833980 -->
The implementations seems hard-coded to only support remote URLs with an https:// prefix, meaning I can't use hugo serve to locally test whether that part is working (I'll do a commit and push so I can see it on netlify later this week).
It doesn't look like the composable model is working for local extendable configs.
{"$schema":"https://beta.frontmatter.codes/frontmatter.schema.json","frontMatter.content.snippets":{"platen-button-link":{"title":"Button","description":"Add a button linking to another page or external site.","body":["![button:[[&selection]]]([[&url]])","{ [[&class]] }"],"fields":[{"name":"selection","title":"Button Text: This displays on the button itself.","type":"string","single":true,"default":"FM_SELECTED_TEXT"},{"name":"url","title":"Button Url: Specify the URL this button should lead to.","type":"string","single":true,"default":""},{"name":"class","title":"Class List: Specify any classes to add to the button in a space-separated list. Each class must have a period (`.`) prefix.","type":"string","single":true,"default":""}]}}}
.frontmatter/config/content/snippets/param.json
{"title":"Hugo Parameter","description":"Insert a hugo parameter's value.","body":"{{< param \"[[name]]\" />}}","fields":[{"name":"name","title":"Parameter Name","description":"Specify the name of a page or site parameter. It will prefer the page value over the site value. To access deeply nested parameters, use the dot syntax, like `my.nested.param`.","type":"string","single":true,"required":true}]}
Front Matter doesn't seem to find the snippet from extends:
Diagnostics Output (Nested Extends)
{"$schema":"https://beta.frontmatter.codes/frontmatter.schema.json","frontMatter.extends":["./modules/platen/assets/frontmatter/content/snippets/buttons.json"],"frontMatter.content.snippets":{"param":{"title":"Hugo Parameter","description":"Insert a hugo parameter's value.","body":"{{< param \"[[name]]\" />}}","fields":[{"name":"name","title":"Parameter Name","description":"Specify the name of a page or site parameter. It will prefer the page value over the site value. To access deeply nested parameters, use the dot syntax, like `my.nested.param`.","type":"string","single":true,"required":true}],"sourcePath":"c:/code/personal/platen/.frontmatter/config/content/snippets/param.json"}}}
If I swap from the config with the nested config to the direct config:
{"$schema":"https://beta.frontmatter.codes/frontmatter.schema.json","frontMatter.extends":["./modules/platen/assets/frontmatter/content/snippets/buttons/image.json"],"frontMatter.content.snippets":{"platen-button-link":{"title":"Button","description":"Add a button linking to another page or external site.","body":["![button:[[&selection]]]([[&url]])","{ [[&class]] }"],"fields":[{"name":"selection","title":"Button Text: This displays on the button itself.","type":"string","single":true,"default":"FM_SELECTED_TEXT"},{"name":"url","title":"Button Url: Specify the URL this button should lead to.","type":"string","single":true,"default":""},{"name":"class","title":"Class List: Specify any classes to add to the button in a space-separated list. Each class must have a period (`.`) prefix.","type":"string","single":true,"default":""}]},"param":{"title":"Hugo Parameter","description":"Insert a hugo parameter's value.","body":"{{< param \"[[name]]\" />}}","fields":[{"name":"name","title":"Parameter Name","description":"Specify the name of a page or site parameter. It will prefer the page value over the site value. To access deeply nested parameters, use the dot syntax, like `my.nested.param`.","type":"string","single":true,"required":true}],"sourcePath":"c:/code/personal/platen/.frontmatter/config/content/snippets/param.json"}}}
I also tried having buttons.json use ./modules/platen/assets/frontmatter/content/snippets/buttons/image.json and [[workspace]]/modules/platen/assets/frontmatter/content/snippets/buttons/image.json instead of ./buttons/image.json, no luck. I suspect this is because extendConfig() isn't doing a recursive check and merge for extensions, at least as far as I can tell.
@estruyf a few notes:
1. The implementations seems [hard-coded][01] to only support remote URLs with an `https://` prefix, meaning I can't use `hugo serve` to locally test whether that part is working (I'll do a commit and push so I can see it on netlify later this week).
2. It doesn't look like the composable model is working for local extendable configs.
Given:
<details>
<summary><code>frontmatter.json</code></summary>
```jsonc
{
"$schema": "https://beta.frontmatter.codes/frontmatter.schema.json",
"frontMatter.extends": [
"./modules/platen/assets/frontmatter/content/snippets/buttons.json"
],
}
```
</details>
<details>
<summary><code>modules/platen/assets/frontmatter/content/snippets/buttons.json</code></summary>
```jsonc
{
"$schema": "https://beta.frontmatter.codes/frontmatter.schema.json",
"frontMatter.extends": [
"./buttons/image.json"
]
}
```
</details>
<details>
<summary><code>modules/platen/assets/frontmatter/content/snippets/buttons/image.json</code></summary>
```jsonc
{
"$schema": "https://beta.frontmatter.codes/frontmatter.schema.json",
"frontMatter.content.snippets": {
"platen-button-link": {
"title": "Button",
"description": "Add a button linking to another page or external site.",
"body": [
"![button:[[&selection]]]([[&url]])",
"{ [[&class]] }"
],
"fields": [
{
"name": "selection",
"title": "Button Text: This displays on the button itself.",
"type": "string",
"single": true,
"default": "FM_SELECTED_TEXT"
},
{
"name": "url",
"title": "Button Url: Specify the URL this button should lead to.",
"type": "string",
"single": true,
"default": ""
},
{
"name": "class",
"title": "Class List: Specify any classes to add to the button in a space-separated list. Each class must have a period (`.`) prefix.",
"type": "string",
"single": true,
"default": ""
}
]
}
}
}
```
</details>
<details>
<summary><code>.frontmatter/config/content/snippets/param.json</code></summary>
```jsonc
{
"title": "Hugo Parameter",
"description": "Insert a hugo parameter's value.",
"body": "{{< param \"[[name]]\" />}}",
"fields": [
{
"name": "name",
"title": "Parameter Name",
"description": "Specify the name of a page or site parameter. It will prefer the page value over the site value. To access deeply nested parameters, use the dot syntax, like `my.nested.param`.",
"type": "string",
"single": true,
"required": true
}
]
}
```
</details>
Front Matter doesn't seem to find the snippet from extends:
<details>
<summary><code>Diagnostics Output (Nested Extends)</code></summary>
```jsonc
{
"$schema": "https://beta.frontmatter.codes/frontmatter.schema.json",
"frontMatter.extends": [
"./modules/platen/assets/frontmatter/content/snippets/buttons.json"
],
"frontMatter.content.snippets": {
"param": {
"title": "Hugo Parameter",
"description": "Insert a hugo parameter's value.",
"body": "{{< param \"[[name]]\" />}}",
"fields": [
{
"name": "name",
"title": "Parameter Name",
"description": "Specify the name of a page or site parameter. It will prefer the page value over the site value. To access deeply nested parameters, use the dot syntax, like `my.nested.param`.",
"type": "string",
"single": true,
"required": true
}
],
"sourcePath": "c:/code/personal/platen/.frontmatter/config/content/snippets/param.json"
}
}
}
```
</details>
If I swap from the config with the nested config to the direct config:
<details>
<summary><code>frontmatter.json</code></summary>
```jsonc
{
"$schema": "https://beta.frontmatter.codes/frontmatter.schema.json",
"frontMatter.extends": [
"./modules/platen/assets/frontmatter/content/snippets/buttons/image.json"
]
}
```
</details>
It does show up:
<details>
<summary><code>Diagnostics Output (Direct Extends)</code></summary>
```jsonc
{
"$schema": "https://beta.frontmatter.codes/frontmatter.schema.json",
"frontMatter.extends": [
"./modules/platen/assets/frontmatter/content/snippets/buttons/image.json"
],
"frontMatter.content.snippets": {
"platen-button-link": {
"title": "Button",
"description": "Add a button linking to another page or external site.",
"body": [
"![button:[[&selection]]]([[&url]])",
"{ [[&class]] }"
],
"fields": [
{
"name": "selection",
"title": "Button Text: This displays on the button itself.",
"type": "string",
"single": true,
"default": "FM_SELECTED_TEXT"
},
{
"name": "url",
"title": "Button Url: Specify the URL this button should lead to.",
"type": "string",
"single": true,
"default": ""
},
{
"name": "class",
"title": "Class List: Specify any classes to add to the button in a space-separated list. Each class must have a period (`.`) prefix.",
"type": "string",
"single": true,
"default": ""
}
]
},
"param": {
"title": "Hugo Parameter",
"description": "Insert a hugo parameter's value.",
"body": "{{< param \"[[name]]\" />}}",
"fields": [
{
"name": "name",
"title": "Parameter Name",
"description": "Specify the name of a page or site parameter. It will prefer the page value over the site value. To access deeply nested parameters, use the dot syntax, like `my.nested.param`.",
"type": "string",
"single": true,
"required": true
}
],
"sourcePath": "c:/code/personal/platen/.frontmatter/config/content/snippets/param.json"
}
}
}
```
</details>
I also tried having `buttons.json` use `./modules/platen/assets/frontmatter/content/snippets/buttons/image.json` and `[[workspace]]/modules/platen/assets/frontmatter/content/snippets/buttons/image.json` instead of `./buttons/image.json`, no luck. I suspect this is because [`extendConfig()`][02] isn't doing a recursive check and merge for extensions, at least as far as I can tell.
[01]: https://github.com/estruyf/vscode-front-matter/commit/cfa805add99c43f7eb4643102b053794936a9f13#diff-ba42116277e5d1456dbf3712cc53a63073e41a6bae7551c643ec63c06e2fab9aR739
[02]: https://github.com/estruyf/vscode-front-matter/commit/cfa805add99c43f7eb4643102b053794936a9f13#diff-ba42116277e5d1456dbf3712cc53a63073e41a6bae7551c643ec63c06e2fab9aR520
<!-- github-comment-id:1377353789 -->
http can be made available, I just wanted to be "safer" 😊
The frontMatter.extends setting only works on the root file, not for sub-config files. I did this on purpose because otherwise, it could get messy/tricky to understand where which of the configs are coming from.
1. `http` can be made available, I just wanted to be "safer" 😊
2. The `frontMatter.extends` setting only works on the root file, not for sub-config files. I did this on purpose because otherwise, it could get messy/tricky to understand where which of the configs are coming from.
<!-- github-comment-id:1379917899 -->
Is fair, it's only a problem for me while developing a Front Matter config to publish for others. Maybe, if it does get added, it's an opt-in value to allow fetching from non-secured sources?
This makes sense but does mean I'll have to compose the values in the config somehow and from a maintenance perspective that means at least one of:
Duplicated definitions (so users can just grab the component(s) they want from my published stuff and not have to take the entirety of it)
Very large files (the problem I moved to split configs to solve)
Composing the various smaller files into larger files during a build process (which is the road I'm about to travel I think)
1. Is fair, it's only a problem for me while developing a Front Matter config to publish for others. Maybe, if it does get added, it's an opt-in value to allow fetching from non-secured sources?
2. This makes sense but does mean I'll have to compose the values in the config somehow and from a maintenance perspective that means at least one of:
- Duplicated definitions (so users can just grab the component(s) they want from my published stuff and not have to take the entirety of it)
- Very large files (the problem I moved to split configs to solve)
- Composing the various smaller files into larger files during a build process (which is the road I'm about to travel I think)
<!-- github-comment-id:1380153394 -->
Pulling together the composed configs without being able to somehow specify I wanted them to recurse was a little bit of work but I got there in the end. I need to have a longer think about whether/how to pull in other information and how to approach scripts.
@estruyf got a very rough prototype up and functional (just tested!) locally:
```jsonc
{
"$schema": "https://beta.frontmatter.codes/frontmatter.schema.json",
"frontMatter.extends": [
"https://deploy-preview-44--platen.netlify.app/frontmatter/platen.json",
"https://deploy-preview-44--platen.netlify.app/frontmatter/toroidal.json",
"https://deploy-preview-44--platen.netlify.app/frontmatter/schematize.json"
],
"frontMatter.site.baseURL": "https://platen.io"
}
```
Pulling together the composed configs without being able to somehow specify I wanted them to recurse was a little bit of work but I got there in the end. I need to have a longer think about whether/how to pull in other information and how to approach scripts.
<!-- github-comment-id:1382200064 -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Is your feature request related to a problem? Please describe.
I've been working through configuring Front Matter for my site and my configuration (without adding the data types for my data files) has been getting long. I'm currently at ~750 lines and adding the data schemas will add another 500+, plus additional short codes etc.
I'm also a hugo theme developer and maintainer with multiple themes, but no way to provide my users defaults for Front Matter when using my themes.
Describe the solution you'd like
I would like an extensibility model similar to markdownlint or vale, which would allow me to package up Front Matter configuration items (data types, snippets, sorting options, content types, etc) for my users to take advantage of and extend or override as needed.
In the Front Matter config, something like:
Where the configurations are merged in the order that they are specified, with any new items from
local.frontmatter.jsonadded to the settings in the remote config, and items in this config added to that merged config.Any subkeys with the same name would replace existing keys, so if I had this config in
local.frontmatter.json:and this one in
frontmatter.jsonat the project root:The second definition of
Hintwould be used, making the valid choices includeexampleandimportantas well asinfo,warning, anddanger.This would allow me to supercharge the usability of my themes for my users and make maintenance a little easier on myself at the same time.
Describe alternatives you've considered
I could resolve this with custom scripting (probably rising to the level of a toolkit or small app), but that would be another thing to require my users to install/track/manage, which is always extra friction.
I can see a potential for publishing Front Matter configuration setting packages that Front Matter could be aware of, but that seems like much more effort and would require a lot of UI and workflow updates and be difficult/costly to maintain (though possibly high value for users).
Additional context
I'm coming at this from both a "I have to maintain a very big complex configuration for myself" and "I would like to make delightful UX/DevX for users of my themes" which are separate but related use cases that could both benefit from a feature like this.
I'm fairly ignorant of typescript and VS Code development, but I would be happy to test this and help pitch in however you think helpful, if this feature is something you think worth bringing into the project. And if not, I'm also willing to write some scripts and document those as a workaround/prototype.
In connection to this, maybe it would also be a good feature to add a packages.json parameter that can contain the configuration or a path to the configuration (ie "extends @davidsneighbour/frontmatter-config")
I like the idea but got a couple of concerns.
Yet another place to configure Front Matter
You can override the config using the
settings.jsonfile from VS Code. Wouldn't this confuse the user too much?Although the base gets loaded first, then the local
frontmatter.json, and thesettings.jsonfile, it might still be simple enough to understand.Online vs offline
I see you entered an URL; this assumes you are working online which is not always the case. It might not be an issue for the initial configuration, but once you have done the configuration. It might be the author is doing some offline changes and does not have access to the base file.
Ideas I can think of:
Versioning
Is the base file something you'll version? What if you introduce something new or remove something? All of a sudden, the experience for the user will be different. By having a local file, they keep working with the same version. Of course, it might be harder to update.
Merges/overrides
If you configure settings in the base and apply new settings in the local version. Are they merging with the base settings or overriding settings?
Like with VS Code settings, if you update a setting in the current project, it overrides what is defined globally.
For this base concept, on some settings, it would be useful, I think to merge instead of override. Although it could cause some conflicts.
package.json
@davidsneighbour, not all projects will have a
package.jsonfile. Not sure if this would bring any additional value. Having abaseproperty on the root of thefrontmatter.jsonobject might just do the trick, but this comes back to previous highlighted things.@estruyf I completely blacked that out. Front Matter is not a node package, so discard my idea. :)
@davidsneighbour, no worries, always good to share your ideas 💚
A few thoughts:
On yet another place to configure
I think this is different enough to be worth doing as (all?) SSGs support theming, which usually includes some specific opinions about metadata, snippets, data structures, etc.
Without a way to extend, everyone has to manually copy the right settings into the right keys in their configuration file and try to keep them up to date. This is okay for a single theme/module, but if you're using several together, that becomes more and more complex to manage.
I would expect that this is something theme authors (or enthusiasts) provide, rather than a best practice for configuring Front Matter for a single project.
Specifically for
frontmatter.jsvs~/.vscode/settings.jsonvs.vscode/settings.jsonvs extended configurations, my understanding/thoughts are:The current order for how settings are applied is
~/.vscode/settings.json->frontmatter.js->.vscode/settings.json. I'm not sure this is accurate, but that's what I'm getting from the docs in my current read.Given the following configurations:
~/.vscode/settings.jsonis loaded first as the user's settings, which makes the effective config:frontmatter.jsis the global configuration. If it doesextendany configs, they are applied in the order they're specified before the rest offrontmatter.jsis.So in this example, the config from loading
frontmatterwould have loaded theFooandBarsnippets would first have combined config fromFooandBarbefore applying this one. You would end up with (omitting the extends key for brevity):Finally, the team settings in
.vscode/settings.jsonget applied, leaving us with:On offline vs online
I would expect first initialization to give me the same default configuration anyone not using
extends(or whatever, just using that key name for now) gets. I can see room for an enhancement, like "do you want to extend an existing configuration? Paste the URL to it here" or similar, then handle fetching/etc. I don't think I would expect that to be in the initial implementation.Maybe a way to handle this would be to cache the remote configuration blob (maybe in
.frontmatter/extendsor similar?) as part of the first-fetch process. I think it would be reasonable to have a warning in the mainfrontmatter.jsfile for un-fetched entries inextends, noting that you have to reload the extension (or run a command, maybe).On Versioning
Thinking on this, here's how I might handle it as an extendable config author:
maintoplaten.io/frontmatter/base.frontmatter.jsonevery time my site rebuilds.platen.io/frontmatter/base.v1.frontmatter.jsonand whenever I need to make a breaking change (if ever), I keep the last release of that major version around.The other (similar, but not requiring me to publish them anywhere but GitHub) option is to use tags to reference versioned files. I'm less likely to do that personally but that wouldn't require any specific architecture.
I think letting users know about new versions etc is neat but out of scope for this, especially in the first iterations.
On Merging and Overriding
I think for clarity it's probably useful to distinguish what we mean by each.
For merging, I mean that if a given value for a key (even deeply nested) does not exist, it is added.
For overriding, I mean that if a given value for a key conflicts with another, the "latest" value wins, replacing the earlier one.
I think if a first pass on this feature was merge-only, that would probably make enormous sense. I see some possibility for configurable override, but after thinking through how you might do that I'm finding myself firmly in the "punt that for later, if it ever seems like it actually matters it can be looked at again" camp.
I think this also means that from a "what do I do to get the canonicalized config settings" the answer is the same at each step:
extends? If it does, merge them into the existing settings in the order they're specified.Additional Considerations
I think there's a possibility for some more delightful UX around this (where did this setting come from?) but like overrides and update notifications etc, I think it's a later concern compared to being able to extend the config at all. Presumably, documenting the settings provided by an extendable config is the job of that config's author (like me), and I doubt any user is going to accidentally end up extending a config vs following directions from a theme author.
Thank you for the reply @michaeltlombardi this clarifies things a lot.
For the configuration part, first, the
frontmatter.jsonfiles get used, then any settings you might have in Visual Studio Code, which for this kind of extension typically happens on the project settings.This makes sense, and we could introduce show a notification once the remote file changes with a message saying: Do you wish to update?
Wouldn't this mean that the user needs to update the reference in their
frontmatter.jsonfile?For merging I was thinking like the example you gave above:
remote/frontmatter.json, it will get overridden when it also exists locallyThat last part makes me think.
Ah, that makes sense!
I think that's reasonable to do, even if it's just a quick hash comparison or something.
Oops! I was missing a sentence. So in this hypothetical example, I publish the same file to two locations:
platen.io/frontmatter/base.v1.frontmatter.json- Pinned version, you can trust (as much as you trust any dev not to lie about breaking changes) that any updates here won't ruin your usage.platen.io/frontmatter/base.frontmatter.json- Always latest version, don't use this one if you're not willing to play with fire.And then when I need to make a breaking change, I would continue to publish the old
base.v1.frontmatter.jsonbut also addbase.v2.frontmatter.jsonand nowbase.frontmatter.jsonpoints to v2.Agree with the flow you outlined on merging, I think that makes plenty of sense. For configuration, I think this could be iterative - step one, follow default logic as defined by the updated schema for extensible/overridable.
In this case, to support picking and choosing what people can import, you would be stuck as a (module? package? extension?) developer writing small extensible JSON files, maybe with a meta file - so
snippets.frontmatter.jsonandmetadata.frontmatter.jsonetc.With that in place, we could consider supporting a
full.frontmatter.jsonthat uses the extends keyword to pull insnippetsandmetadata, requiring end users to just listfullif they want everything. One downside/eventuality there though is that it can require recursively finding and using extension definitions - though that also makes composing this sort of thing easier in the long run, it's also complexity, especially for an experimental prototype.Once we have some info to look at / see how that works in practice, I think I definitely see some value in being able to define a per-extension settings config, but the shape of that seems like a lot to chew on - probably useful, but definitely needing lengthy thought. Maybe I just want the snippets, which is easy enough, but maybe I want only a specific snippet, or I want all but a specific snippet, or I want the snippets as-is but I want to override one of them, etc.
That definitely gets hairy quick, so my thought is to kinda noodle through those things alongside the minimal/basic implementation iterations with an understanding for extension config authors that this is still being worked out.
@davidsneighbour @michaeltlombardi first external config support functionality got added and is up for its first tests.
How it works
In the
frontmatter.jsonfile, you can add thefrontMatter.extendssetting, and a list of URLs/file paths to load needs to be passed.Here is an example:
The order in how configuration gets applied
.frontmatter/configfolder)frontmatter.jsonfile and overrides or merges with the other settings.@davidsneighbour @michaeltlombardi have you been able to try it out? Any feedback or things you are missing?
@estruyf I've been wrapping a big refactor for my site/theme, which prepped me for this. My main question right now is whether I need to compose the big JSON blob as an external reference or if I can publish them in the split-out layout I have now.
In other words, can I publish a
frontmatter.jsontohttps://platen.io/schemas/frontmatter/config.jsonAnd then have the split-out pieces, like snippets, in their own blobs:
```jsonc { "title": "Button", "description": "Add a button linking to another page or external site.", "body": [ "", "{ &class }" ], "fields": [ { "name": "selection", "title": "Button Text: This displays on the button itself.", "type": "string", "single": true, "default": "FM_SELECTED_TEXT" }, { "name": "url", "title": "Button Url: Specify the URL this button should lead to.", "type": "string", "single": true, "default": "" }, { "name": "class", "title": "Class List: Specify any classes to add to the button in a space-separated list. Each class must have a period (`.`) prefix.", "type": "string", "single": true, "default": "" } ] } ```https://platen.io/schemas/frontmatter/content/snippets/button.jsonI wouldn't mind if I had to add a key to the main importable config like:
But otherwise it looks like I'll have to compose the full config file. I can figure that out - likely my approach will simply be to make a series of "small" config that contains only one snippet or whatever and compose those upwards - but that's the limitation I am seeing at a glance right now.
That would look like:
And then my users could either pull the composite config or any component piece of it they want to use, and I can add scripts/etc to help with that.
Speaking of scripts, that's the only other limitation I think I see right now - this works for anything that is defined only in the JSON blobs, but doesn't have a way to identify/package custom scripts. I don't think that's a deal breaker, especially not for the initial feature.
That is correct; I also wanted to make sure a never-ending loop could happen. This might be the case if something needs to be configured correctly.
Splitting is possible, but you can only do this for now, as you explained.
If this gets well adopted, it would be an excellent next step to put the further effort in it to allow you to insert REFs.
I'm rewriting my Front Matter configs this week, I'll get them published as extensible configs and share those findings in here when there's something to show for it. I'm very excited because this also dovetails with the JSON schematization I've been doing for site configuration. All going well, this should mean that I'm able to give my users highly functional UIs for managing their content metadata, configuration, and snippets without much work on their end. Once I'm able to figure out a coherent way to do custom scripts from an extended config, that will also really help smooth out their authoring experience.
Looking forward to hearing more about it! Thanks @michaeltlombardi.
@estruyf a few notes:
https://prefix, meaning I can't usehugo serveto locally test whether that part is working (I'll do a commit and push so I can see it on netlify later this week).Given:
frontmatter.jsonmodules/platen/assets/frontmatter/content/snippets/buttons.jsonmodules/platen/assets/frontmatter/content/snippets/buttons/image.json.frontmatter/config/content/snippets/param.jsonFront Matter doesn't seem to find the snippet from extends:
Diagnostics Output (Nested Extends)If I swap from the config with the nested config to the direct config:
frontmatter.jsonIt does show up:
Diagnostics Output (Direct Extends)I also tried having
buttons.jsonuse./modules/platen/assets/frontmatter/content/snippets/buttons/image.jsonand[[workspace]]/modules/platen/assets/frontmatter/content/snippets/buttons/image.jsoninstead of./buttons/image.json, no luck. I suspect this is becauseextendConfig()isn't doing a recursive check and merge for extensions, at least as far as I can tell.httpcan be made available, I just wanted to be "safer" 😊frontMatter.extendssetting only works on the root file, not for sub-config files. I did this on purpose because otherwise, it could get messy/tricky to understand where which of the configs are coming from.@estruyf got a very rough prototype up and functional (just tested!) locally:
Pulling together the composed configs without being able to somehow specify I wanted them to recurse was a little bit of work but I got there in the end. I need to have a longer think about whether/how to pull in other information and how to approach scripts.