Table of content:
Introduction This style guide contains information about file, class and variable naming conventions, code practices, file structure and rules about the usage of TS types.
The following naming conventions were agreed upon in the set-up phase of the project and should always be followed.
Folder names should be in camelCase.
.ts files that
- Exports a single class should be in PascalCase
- Exports a single interface should be in PascalCase and begin with an uppercase "I".
- Ex. interface called IPoint should be in a file called IPoint.ts to indicate that it exports an interface.
- Does not export a single class or interface should be in camelCase
.svelte files that
- Is a component should be in PascalCase
- Is a route should be in camelCase
.json files should be in PascalCase
Class names should be in PascalCase Interface names should be in PascalCase and begin with an uppercase "I" to indicate that it is an interface.
- Variable names should be in camelCase.
- Private properties should begin with _ (underscore) or #:
- _ (underscore) represents a variable using the native TypeScript access modifier private, which is a compile-time construct that prevents private properties from being accessed outside of the class. After compilation the private property is simply removed.
- # represents a JavaScript private field and is a runtime feature that enforces privacy, making the field inaccessible outside the class even during runtime.
- Variable names should be written in full.
siteCtxshould be siteContexttestBtnshould be testButton
Functions and members should be in camelCase.
- CSS classes and id's should be in kebab-case
- CSS variables loaded using the GlobalCssSchemesLoader should be in kebab-case with an initial --
- Example: --test-css-variable-name
- Use const instead of let whenever possible.
- Use async/await instead of Promises whenever possible.
- Use explicit types as it makes the code more readable and makes it easier to identify type related errors.
- Use private properties to encapsulate implementation details that are not relevant to other components.
- If a function cant be described based on its name it is too big and should be split.
File structure specifics:
- Group related files in the same directory.
- Mark function parameters and object properties as optional (?) if they can be omitted.
- Mark properties as readonly if it should not be changed after being created.
- Always explicitely specify types as TypeScript might not always be able to infer it.
- Use null and undefined carefully.
- DO NOT use the any type as it omits all type checking. In extreme cases one can use the unknown type if necessary.
- DO NOT use @ts-ignore or @ts-expect-error as they disable TypeScript checks
For a more in-depth and complete style one can visit the Google TypeScript Style Guide.
Use JSDoc comments for classes, methods and functions. JSDoc comments are useful as they can provide a lot of information. An example of a JSDoc comment can be seen in the following code block:
/**
* Function for concatenating two strings.
*
* @param a - The first input string
* @param b - The second input string
* @returns The concatenation of `a` and `b`
*/
function concat(a: string, b: string): string {
return a + b;
}
For more information on JSDoc comments, visit @use JSDoc
- Avoid obvious comments i.e. code that is self-explanatory.
- Write comments for complex code that explains what it does.
The following folder structure is meant to be a guideline of where one should put specific files and folders.
.
├── examples
│ └── <-- Working examples of projects -->
├── presentation
│ └── <-- Example pictures of the application -->
├── src
│ ├── lib
│ │ ├── interfaces
│ │ │ └── <-- Folders and typescript files for interfaces -->
│ │ ├── classes
│ │ │ └── <-- Folders and typescript files for object classes -->
│ │ ├── globalState
│ │ │ └── <-- Folders and files related to the global state of the application -->
│ │ └── components
│ │ ├── <-- Folders and files for Svelte components -->
│ │ └── samplesImplementations
│ │ └── <-- An example of how one would implement and use a specific component -->
│ │
│ ├── routes
│ │ └── <-- Files for routing and global layout -->
│ └── tests
│ └── <-- Unit test files here.
Folder structure should copy the project structure starting from src,
and put tests in their corresponing folders ->
├── static
└── tests
└── <-- End-to-end test files here.
Folder structure should copy the project structure starting from src,
and put tests in their corresponing folders -->
- There should be a one to one relation between files tested and test files.
- When writing end-to-end tests the test names should explain the test.
- When writing unit tests, the describe and it functions should be used in a way such that they form a sentence.
An example of a unit test:
describe('Circle class', => {
describe('area is calculated when', => {
it('sets the radius', => { ... });
it('sets the diameter', => { ... });
it('sets the circumference', => { ... });
});
});
The CSS loader was created to support dynamic loading of color schemes and userdefined colorschemes.
The different media schemes are stored using a JSON file. The root of this file contains a default mediascheme and a list of mediaschemes:
const MediaSchemes = z
.object({
default: RequiredMediaScheme.required(),
schemes: z.array(MediaScheme),
})
.strict();
- Default scheme: Requires all defined CSS variables to have a default fallback value to prevent the situation where an elements does not have styling. This scheme automatically also functions as the "light mode" styling.
- List of mediaschemes: Contains specifications of some CSS variables that overwrites the standard values in case the mediafeature matches the browser. Example: A mediascheme has mediafeature "prefers-color-scheme: dark". This feature is matched when the browser is set to dark mode.
To add support for additional mediafeatures one has to add a new mediafeature object to the schemes array located in the root of the JSON file.
The MediaScheme definition looks like the following:
const MediaScheme = z
.object({
mediaFeature: z.string(),
color: ColorVariables.partial().optional(),
fontSize: FontSizeVariables.partial().optional(),
border: BorderVariables.partial().optional(),
})
.strict();
As seen in the above definition of MediaScheme one is not required to specify any color, fontSize or border, meaning that one only has to specify the variables specifically needed for that scheme.
An example of a new media scheme can be seen below:
{
"mediaFeature": "prefers-reduced-motion",
"color": {},
"fontSize": {},
"border": {}
}
A list of media features can be seen on the MDN Web Docs: Using media queries.
The allowed CSS variables are listed in the CSSVariables.ts file. To add support for new CSS variables these have to be added to the corresponding list.
The process of adding new CSS variables are the following:
- Add the name and type of the variable to the correct scheme in the CSSVariables.ts file. Example: Adding a color called --css-color-name of the type ColorAttribute.
export const ColorVariables = z.object({
...
"--css-color-name": ColorAttribute,
}).strict();
- Add a default value in the default scheme in the JSON file.
"color": {
...
"--navigationbar-text-color": ["display-p3", 1, 1, 1],
}
- (Optional) Further specify the CSS variable in other media schemes e.g. add a darkmode version.
The color attribute is based on the CSS color() function. The color function takes four parameters and a fifth optional parameter. The syntax is defined as following:
color(colorspace value-one value-two value-three / optional-alpha-value);
The colorspace value should be one of the colorspaces listed in the SupportedGamuts enum in the ColorAttribute.ts file and the four numerical values should be within the range 0-1.
Now that the implementation specifics have been discussed we can take a look at how one would define a color in the JSON file. An example of ColorAttribute is seen in the following:
"--console-scrollbar-thumb-color": [
"display-p3",
0.22745098039,
0.27450980392,
0.30588235294
],
For more information on the color() function see MDN Web Docs: color()
The specified CSS variables are extremely easy to apply in the GUI as they are simply added as CSS properties in the root file.
Examples of applying the CSS variable --background-color are:
.side-panel {
...
background-color: var(--background-color);
}
<div style="background-color: var(--background-color);">...</div>
A more advanced implementation switching between two CSS variables:
<script>
let buttonActive: string = "var(--button-active)";
let buttonInActive: string = "var(--button-inactive)";
</script>
<button
style="background-color: { currentTab == thisTab
? buttonActive
: buttonInActive}"
></button>