Haha, ok ok…
Another summary of what we have discussed so far:
Schema
(Not sure if this types are correct, but the TypeScript docs are down right know, so bear with me)
type MainSettings = {
url?: string; // Default: undefined
title?: string; // Default: undefined
timezone?: number; // Default: 0
language?: string; // Default: "en"
}
type Package = {
name: string;
active?: boolean; // Default: true
namespaces?: string | string[]; // Default: ["all", "available", "namespaces"],
settings?: object;
}
type Settings = {
name?: string; // Default: undefined
match?: string | string[]; // Default: undefined
mode?: string; // Default: "html"
settings?: MainSettings;
packages: (string | Package)[];
}
type ExportedSettings = Settings | Settings[];
Examples
One site
export default {
settings: {
url: "https://site.com",
title: "Site Title",
timezone: 1,
language: "en",
},
packages: [
{
name: "@frontity/theme",
namespaces: ["theme", "h2r"],
settings: {
theme: { ... },
h2r: { ... },
// Each key corresponds to one namespace.
}
}
]
}
One site with AMP
export default [
{
settings: { ... },
packages: [
{
name: "@frontity/theme",
namespaces: ["theme", "h2r", "comments"],
settings: { ... },
}
]
},
{
mode: "amp",
match: "\/amp\/?$",
settings: { ... },
packages: [
{
name: "@frontity/theme",
namespaces: ["theme", "h2r"],
settings: { ... }
}
]
}
]
More than one site
export default [
{
name: "site-one",
settings: { ... },
packages: [
{
name: "@frontity/theme",
settings: { ... }
}
]
},
{
name: "site-one-amp",
match: "\/amp\/?",
mode: "amp",
settings: { ... },
packages: [
{
name: "@frontity/theme",
settings: { ... }
}
]
},
{
name: "site-two",
settings: { ... },
packages: [
{
name: "@frontity/theme",
settings: { ... }
}
]
},
{
name: "site-two-amp",
match: "\/amp\/?",
mode: "amp",
settings: { ... },
packages: [
{
name: "@frontity/theme",
settings: { ... }
}
]
},
]
I realised that if we use the match
setting for both the site and the mode, we would end with more than one match
matching the current url, and we’d need to set some kind of priority. In order to avoid that, to select some site settings we’ll use the name
field, and to select some mode settings the match
field. We can also use the url
field instead of the name
field.
I don’t have a preference here, but if using one of them would reduce the schema surface, I’d go for that one.
Also, I’d rather build a repetitive schema that looks clearer at first glance, than something more complicated to avoid duplicated settings. As the settings file is a JS file, the developer can always extract duplicated settings and reuse them when necessary:
const mainSettings = {
url: "https://site.com",
title: "Site Title",
timezone: 0,
language: "en"
}
export default [
{
settings: mainSettings,
packages: [ ... ]
},
{
match: "\/amp\/?$",
settings: mainSettings,
packages: [ ... ]
}
]
API
So far, unless something changes, the API remains as discussed before:
getSettings({ name, url })
getPackages({ name, url })
Ideas
The frontity.settings.js
file should be in the root folder of the project, at the same level than package.json
.
The default settings will be defined in a frontity.settings.js
file inside the file-settings
package.
The queries modifying the url
or name
fields shouldn’t affect the settings schema, they should be used in core
, either to override the value in the store, or to be used with getSettings
or getPackages
to retrieve different settings.
The mode
defined in settings is defined only to be used within React, not to decide what settings to use.
I think I covered pretty much everything we discussed. Let me know if I’m missing something.
Yes, I think that’s the last problem we need to solve, at least for now.
In my first designs, it worked because everything was an array and the user can order the items. That means things like “amp” must go before “html” which may not be very intuitive:
This works:
export default = [
{
mode: "amp",
match: "\/amp$",
...
},
{
mode: "html",
// match: ".*" (the default)
...
}
];
This doesn’t, because the first match catches everything:
export default = [
{
mode: "html",
// match: ".*" (the default)
...
},
{
mode: "amp",
match: "\/amp$",
...
}
];
The getPackages()
should return an object with all the packages for each site:
{
"my-site": [ ... ],
"my-blog": [ ... ],
"my-blog-amp": [ ... ],
}
With that we can generate the client bundles and server bundle.
The first version of file-settings
has been implemented. Here is the README info:
Usage
You can install it with npm
:
npm i @frontity/file-settings
Here is a small example of how to use getSettings
:
import { getSettings } from "@frontity/file-settings";
const settings = await getSettings({ name: "example-name", url: "https://example.site" });
API Reference
async getSettings(options) => settings
Used to retrieve the settings from the frontity.settings.js
file.
Parameters
options
: { name?: string; url: string; }
Used to match the right set of settings when there is more than one.
-
options.name
:string (optional)
The name of the set of settings you want to retrieve. When provided,getSettings
won’t useoptions.url
. -
options.url
:string
The url of the site using Frontity. Thematches
field of each set of settings will be tested against this url to determine which set of settings should be used.
Return
settings
: Settings
An object with type Settings
containing a set of settings.
async getPackages() => packages
Used to retrieve a list of names of the packages used in each settings set.
Return
packages
: { [key: string]: string[] }
An object with a key for each set of settings populated with an array of packages names from that set.
If the settings file exports only one set of settings (or mono settings ), packages
will have only one key named default
:
{
default: [ "theme-package", "source-package" ]
}
If the settings file exports various sets of settings (or multi settings ), packages
will have one key per set of settings named like them.
{
"settings-one": [ "theme-one", "source-one" ],
"settings-two": [ "theme-two", "source-one" ]
}
Settings File
The file must be located in the root directory of the project, it must be named frontity.settings.ts
or frontity.settings.js
, and it needs to export a serializable object.
The settings exported can be mono settings (only one):
{
name?: string;
matches?: string[];
mode?: string; // Default: "html"
settings?: {
url?: string;
title?: string;
timezone?: number; // Default: 0
language?: string; // Default; "en"
},
packages: [
string,
{
name: string;
active?: boolean; // Default: true
namespaces?: string[];
settings?: object;
}
]
}
Or multi settings :
// An array of more than one set of settings.
[
{
name: string; // This time the name is mandatory and must be unique.
matches?: string[];
mode?: string; // Default: "html"
settings?: { ... },
packages: [ ... ]
},
{
name: string; // This time the name is mandatory and must be unique.
matches?: string[];
mode?: string; // Default: "html"
settings?: { ... },
packages: [ ... ]
}
]
Typescript
Some TS types are exposed to be used in development. They can be accessed like this:
import { Settings } from “@frontity/file-settings”; const settings: Settings = { … };
The following are probably the only types you will need during development:
Settings<T = Package>
Types for the imported settings object from the settings file. You’ll want to use them on your frontity.settings.ts
file.
Package
Types for each package within a settings object.
I’m going to reverse namespace
and call it excludedNamespaces
. Because it’s the only use it has and makes everything much simpler. The default which is an empty array makes sense this way.
{
name: string;
active?: boolean;
excludedNamespaces?: string[]; // Default []
settings?: object;
}
With that change I think I will also be able to treat settings
just like any other namespace.
Pushing our design principles even further, I’m going to get rid of settings and live only state.
Once we finished the design of Connect, state and namespaces were not the same than in our previous framework.
- Old framework: each package lives in its own namespace.
- New framework: each package has the opportunity to use a shared state for whatever they want, namespaced or not.
Regarding settings, this is the change I want to make
- From: frontity.settings.js can be used to overwrite a subset of the state named settings.
- To: frontity.settings.js can be used to overwrite any part of the state.
These are the changes in code:
- Packages don’t use the
setting
namespace, just their own:
export default {
state: {
theme: {
mainColor: "#FFF",
linkColor: "#CCC",
featuredImage: true,
}
}
}
- Users can overwrite any state using
frontity.settings.js
:
export default {
packages: [
{
name: "my-theme",
active: true,
state: {
theme: {
linkColor: "#AAA",
featuredImage: false,
}
}
]
}
That’s it. Everything gets merged together when the app starts.
The pros for removing settings are:
- Settings are not different from state anymore, it’s just a small subset of it.
- Settings don’t provide any additional feature the state itself doesn’t.
- Settings is an extra concept (and we want to minimize concepts).
- If devs are able to work with state directly Frontity becomes more hackable.
- It’s easier to launch Frontity without settings and introduce them later on, rather than the opposite.
The cons for removing settings are:
- Package providers won’t be able to select what part of the state is a setting and what not. Everything becomes a setting. And therefore, everything becomes hackable.
Interesting things arise. For example, users will be able to add a non-existent urls to their sites:
export default {
packages: [
{
name: "@frontity/wp-source",
state: {
source: {
dataMap: {
"/about": {
type: "page",
id: 0,
isPage: true,
}
},
page: {
0: {
title: "About us",
content: "Yes, this is us!"
}
}
}
}
]
}
As an extra, I’m going to remove the concept of excluded namespaces as well. I think the API to do so (if it is needed at all) needs to be programmatic instead of something backed in the frontity.settings.js
file.
We will keep state.frontity
for the general state, but now everything is explicitly namespaced:
export default {
name: "site-1",
mode: "html",
state: {
frontity: {
url: "https://site.com",
language: "en"
}
},
packages: [...]
}
Actually, the concept of namespaces is abstract to Frontity, so users can overwrite any state they want in any place.
export default {
name: "site-1",
mode: "html",
state: {
frontity: {
url: "https://site.com",
language: "en"
},
source: {
api: "https://wp.site.com", // <- this works but it's not recommended.
},
packages: [...]
}
I’ll try to finish the refactor today.
@development-team if you feel I’m missing something, say it now