RepositoryGuide
Guide to the repository
Section titled βGuide to the repositoryβOverview
Section titled βOverviewβThe following figure gives a brief overview of the repository structure.
starterβββ docs -- Documentationβββ src -- Contains project codeβ βββ apps -- Apps go hereβ βββ packages -- Packages that can be used by appsβ βββ samples -- Example sites, apps and packagesβ βββ types -- TypeScript support filesβ βββ sites -- Additional .html sitesβ βββ index.html -- Main HTML entry pointβββ .editorconfig -- Common text file settings (encoding, line length)βββ .eslint.config.mjs -- ESLint configuration fileβββ .gitignore -- Lists files ignored by gitβββ .npmrc -- pnpm configuration fileβββ .prettierrc -- Prettier configuration fileβββ package.json -- Dependencies of the root package (mostly development tools)βββ pnpm-lock.yaml -- Package manager lockfileβββ pnpm-workspace.yaml -- Workspaces configuration file for pnpm, also includes the dependency catalogβββ tsconfig.json -- Main TypeScript configuration file for code that runs in the browserβββ tsconfig.node.json -- Additional TypeScript configuration file for files running in Nodeβββ typedoc.base.json -- TypeDoc configuration that is used as a base for the TypeDoc configuration in the packagesβββ typedoc.config.cjs -- TypeDoc main configuration fileβββ vite.config.ts -- Vite configuration fileNode scripts
Section titled βNode scriptsβSome frequently used tasks are implemented as scripts in the root package.json file.
They should be invoked via pnpm: pnpm run <SCRIPT_NAME> (run is optional if the script name is unambiguous).
pnpm run dev
Section titled βpnpm run devβLaunches Viteβs (opens in a new tab) local development server.
The main configuration file for vite is vite.config.ts.
pnpm run check-types
Section titled βpnpm run check-typesβRuns the TypeScript compiler (opens in a new tab) to detect problems during development.
pnpm run watch-types
Section titled βpnpm run watch-typesβStarts the TypeScript compiler (opens in a new tab) in watch mode to detect problems during development.
It is recommended to run this script alongside the dev server.
pnpm check-duplicates
Section titled βpnpm check-duplicatesβRuns the check-pnpm-duplicates CLI tool (docs (opens in a new tab)) to check for accidental duplicate packages.
When it encounters a duplicate package (that has not been explicitly allowed), it will fail with an error message.
To configure the tool (for example, to add expected duplicates), edit support/duplicate-packages.yaml.
This script is used because we migrated away from peerDependencies due to some limitations of current pnpm versions.
With the help of this script, we can ensure that we donβt accidentally install the same package multiple times.
The script runs automatically after pnpm install (see prepare script in package.json).
pnpm run test
Section titled βpnpm run testβStarts Vitest (opens in a new tab) to run all automated tests. Vitest will automatically watch all source code files and will rerun tests during development whenever it detects changes.
pnpm run build
Section titled βpnpm run buildβBuilds the project as a static site.
Generated files are output into the dist/www directory.
The main configuration file for vite is vite.config.ts.
pnpm run build-docs
Section titled βpnpm run build-docsβBuilds the projectβs API documentation. Documentation is generated using TypeDoc (opens in a new tab).
TypeDoc is configured in the typedoc.config.cjs (this file contains the βglobalβ TypeDoc configuration).
Most importantly, it configures for which packages API documentation will be built.
The default configuration file contained in this repository will build the documentation for all packages within src/packages/**.
However, you can freely edit the typedoc.config.cjs to your liking.
In the individual packages, a typedoc.json file specifies how the input is converted.
The typedoc.base.json file is used as a base for the typedoc configuration specified in the single packages.
The API documentation is written into dist/docs.
You can serve the API documentation locally by executing:
$ pnpm install -g serve # installs the 'serve' web server globally (needed only once)$ pnpm build-docs$ pnpm serve dist/docspnpm run build-license-report
Section titled βpnpm run build-license-reportβCreate a license report for dependencies used by the project.
The report is written to dist/license-report.html.
The source code for report generation is located in support/create-license-report.ts.
Configuration happens via support/license-config.yaml.
By default, devDependencies are not included in the report. If you need them to be included, you can change the source code in support/create-license-report.ts: Remove the β-Pβ from the following command in the source code: pnpm licenses list --json --long -P.
pnpm run generate-sbom
Section titled βpnpm run generate-sbomβGenerate a CycloneDX (opens in a new tab) SBOM (Software Bill of Materials) for the project.
The generated sbom file is written to dist/sbom.json.
The source code for report generation is located in support/create-cyclonedx-sbom.ts.
Currently only JSON encoding is supported. The generated SBOM lists all components excluding devDependencies.
The script reads the project name (and, if present, the version) from the project rootβs package.json and embeds it into the SBOM.
The current git revision (commit hash of HEAD) is also included.
[!IMPORTANT] This command depends on Trivy (opens in a new tab) and can only be executed if Trivy is installed (opens in a new tab) globally.
pnpm run preview
Section titled βpnpm run previewβStarts a local http server serving the contents of the dist/www directory.
pnpm run clean
Section titled βpnpm run cleanβRemoves files that were created during the build (e.g. the dist directory).
pnpm run lint
Section titled βpnpm run lintβRuns ESLint (opens in a new tab) on all source code files to detect problems.
Simple errors can be fixed automatically by running pnpm run lint --fix.
ESLint is configured via the .eslintrc file.
pnpm run prettier
Section titled βpnpm run prettierβRuns Prettier (opens in a new tab) on all source code files for automated (re-) formatting.
Prettier and ESLint are integrated (see .eslintrc), so prettier rules are also respected when linting.
pnpm audit
Section titled βpnpm auditβChecks for known security issues with the installed packages. (Will also be checked with a nightly github action job)
Miscellaneous tools
Section titled βMiscellaneous toolsβInstalled node tools can be invoked by running pnpm exec <TOOL_AND_OPTIONS> (once again, the exec is optional).
Common workflows
Section titled βCommon workflowsβAdding a dependency
Section titled βAdding a dependencyβTo install a shared build dependency (a vite plugin for example), run pnpm add -D -w <PACKAGE_NAME>.
-D will include the package as a devDependency, and -w will add it to the workspace rootβs package.json.
To add a dependency to a workspace package or app, execute pnpm add PACKAGE_NAME from the package or app directory.
You can also add the dependency manually by editing the package.json file directory.
Keep in mind to execute pnpm install to update the lockfile after youβre done with editing.
Note that we prefer to use catalog: as the version of the package. This way, the version can be managed centrally in the pnpm-workspace.yaml (see Keeping dependency versions in sync).
Updating dependencies
Section titled βUpdating dependenciesβYou can always update your dependencies by simply editing the package.json files directly, or by using pnpmβs catalog feature.
Keep in mind to also execute pnpm install after you updated a package.json file (or the pnpm-workspace.yaml) to install packages and to update your lockfile (pnpm-lock.yaml).
Checking for outdated packages
Section titled βChecking for outdated packagesβUse pnpm outdated -r (opens in a new tab) to show which packages in your workspace are available in newer versions.
Alternatively, you can check npmjs.com, use features of your IDE or use other tools (e.g. Renovate (opens in a new tab)).
Example:
$ pnpm outdated -rβββββββββββββββββββββββββββββββββββ¬βββββββββββ¬βββββββββ¬ββββββββββββββ Package β Current β Latest β Dependents ββββββββββββββββββββββββββββββββββββΌβββββββββββΌβββββββββΌβββββββββββββ€β @types/node (dev) β 18.19.41 β 22.7.8 β starter ββββββββββββββββββββββββββββββββββββΌβββββββββββΌβββββββββΌβββββββββββββ€...Not all packages need to be updated immediately.
For example, the @types/node package may be deliberately kept at a lower version for backwards compatibility reasons.
Using a newer version
Section titled βUsing a newer versionβIn order to update the package version, either update the package.json of a certain package directly (if that package uses a specific version) or update your central catalog in pnpm-workspace.yaml (the default).
The catalog in this repository has been prepared to make updating a package as easy as possible.
For example, updating the openlayers-base-packages can only requires changing a single version number in the pnpm-workspace.yaml:
__versions: # ... - &ol_base_packages_version ^0.7.0
# https://pnpm.io/catalogscatalog: # Trails OpenLayers base packages # https://github.com/open-pioneer/trails-openlayers-base-packages "@open-pioneer/basemap-switcher": *ol_base_packages_version "@open-pioneer/coordinate-viewer": *ol_base_packages_version "@open-pioneer/geolocation": *ol_base_packages_version "@open-pioneer/map-navigation": *ol_base_packages_version "@open-pioneer/map-ui-components": *ol_base_packages_version "@open-pioneer/map": *ol_base_packages_version "@open-pioneer/measurement": *ol_base_packages_version "@open-pioneer/overview-map": *ol_base_packages_version "@open-pioneer/scale-bar": *ol_base_packages_version "@open-pioneer/scale-viewer": *ol_base_packages_version "@open-pioneer/theme": *ol_base_packages_version # ...After changing version numbers, run pnpm install to apply your changes.
Handling version conflicts and duplicate packages
Section titled βHandling version conflicts and duplicate packagesβWhen installing new dependencies or updating existing ones, you may run into version conflicts related to transitive dependencies.
Oftentimes pnpm will be able to select a common version that satisfies all requirements (sometimes it needs a little help by running pnpm dedupe).
For example, if package a requires "react": "^18.0.0 and package b needs "react": "^18.1.0", it will be able to install a shared version somewhere within the range >= 18.1.0 < 19.0.0.
However, if the version ranges are incompatible, pnpm will resort to installing both versions of the package (or generating an error for peer dependencies).
Sometimes duplicate packages are not a problem, but for certain βcentralβ packages (like react), there may only be a single version present in your application.
Although one typically uses peer dependencies to solve this issue, that has proved to be impractical at the moment (see dependencies vs peerDependencies).
We have configured a custom CLI tool to check for duplicate packages after pnpm install, so this error case cannot remain unnoticed (see pnpm check-duplicates).
When encountering a duplicate package, consider taking the following steps:
- Run
pnpm dedupe. This can sometimes resolve the issue. - Investigate why the package is duplicated.
Use
pnpm why -r PACKAGE_NAME(optional: add@SOME_VERSIONafter the package name) to see why that package is present in your dependency tree. Then, decide what to do next. - Alternative 1: Update other packages as well, so all of them use a shared version again. This is only an option if those updates actually exist.
- Alternative 2: Override the version for some packages (using pnpm overrides). This is an option if the newer version is actually compatible to the older one.
- Alternative 3:
Allow the duplicates by adding the package name to the
support/duplicate-packages.yamlfile. Some packages do not cause any problems when duplicated and can be safely listed there. They will only increase your applicationβs bundle size slightly. Central packages likereact,react-dom, chakra packages or any trails packages must not be allowed as duplicates.
Example: tslib is present twice
$ pnpm install# ...β > pnpm check-pnpm-duplicates -c support/duplicate-packages.yamlβ Found unexpected duplicate packages:β - "tslib" # (versions 2.4.0, 2.7.0)β # ...ββ Failed in 940ms at /home/mbeckemeyer/projects/trails/trails-starterβELIFECYCLEβ Command failed with exit code 1.The command tells us that tslib is present in versions 2.4.0 and 2.7.0.
We use pnpm why to investigate:
$ pnpm why -r tslib# ... lots of output ...βββ¬ @open-pioneer/runtime 2.3.0 peer βββ¬ @formatjs/intl 2.10.4 β βββ¬ @formatjs/ecma402-abstract 2.0.0 β β βββ¬ @formatjs/intl-localematcher 0.5.4 β β β βββ tslib 2.7.0# ... βββ¬ @open-pioneer/base-theme 2.3.0 β βββ¬ @open-pioneer/chakra-integration 2.3.0 β βββ¬ @chakra-ui/react 2.8.2 β β βββ¬ @chakra-ui/accordion 2.3.1 β β β βββ¬ @chakra-ui/icon 3.2.0 β β β β βββ¬ @chakra-ui/system 2.6.2 peer β β β β βββ¬ @chakra-ui/react-utils 2.0.12 β β β β β βββ¬ @chakra-ui/utils 2.0.15 β β β β β βββ¬ framesync 6.1.2 β β β β β βββ tslib 2.4.0# ...This tells us that tslib@2.7.0 is used by @formatjs/intl-localematcher and tslib@2.4.0 is used by framesync. Inspecting the package.json of framesync reveals that it uses a fixed version of "tslib": "2.4.0", so it cannot be unified with the newer version used by @formatjs/intl-localematcher.
In this case, we can either update the version used by framesync (likely ok since 2.7.0 should be compatible to 2.4.0), or we can just list the package as an allowed duplicate:
# ...allowed: - "stylis" - "tslib"After making one of those changes, pnpm install will succeed:
$ pnpm i# ...β > pnpm check-pnpm-duplicates -c support/duplicate-packages.yamlβ No unexpected duplicate packages found.ββ Done in 958msDone in 2.3sInteractive updates with pnpm
Section titled βInteractive updates with pnpmβpnpm has a helpful update (opens in a new tab) command to update packages automatically or interactively.
Unfortunately, pnpm update currently does not work in combination with the catalog feature.
For example:
# Updates all dependencies in all packages interactively with a simple CLI wizard.# By default, updates will respect the sematic versioning restrictions in your package.json (e.g. update ^14.0.0 to ^14.1.0 is allowed).# Use --latest to go the latest versions instead.$ pnpm update -r -iBy default, pnpm update will touch both package.json and pnpm-lock.yaml.
If you only want to install newer (matching) packages and update the lockfile, without modifying package.json files, use --no-save:
# Installs newer compatible packages (according to version ranges in package.json files) and updates the lockfile.# Does not modify the package.json files.$ pnpm update -r --no-saveKeeping dependency versions in sync
Section titled βKeeping dependency versions in syncβWeβre using pnpmβs catalog feature (opens in a new tab) to keep dependency versions in our package.json files in sync.
Central management for shared dependencies (most of them) happens in the pnpm-workspace.yaml.
In your package.json, it is usually sufficient to use "catalog:" as your βversionβ.
pnpm will then automatically resolve the correct version from your catalog.
You only need to specify a version manually if you want to deviate from the catalog for some reason.
Explaining a dependency
Section titled βExplaining a dependencyβUse pnpm why (opens in a new tab) to display why a certain package is a dependency (-r to include all packages in the workspace).
For example:
# Explains why the runtime package depedends on @formatjs/fast-memoize$ pnpm why -r --filter runtime @formatjs/fast-memoize# @open-pioneer/runtime@0.1.0 /home/<PROJECT_PATH>/src/packages/framework/runtime## dependencies:# @formatjs/intl 2.6.7# βββ @formatjs/fast-memoize 1.2.8# βββ¬ intl-messageformat 10.3.1# βββ @formatjs/fast-memoize 1.2.8## devDependencies:# @open-pioneer/test-utils link:../test-utils# βββ¬ @formatjs/intl 2.6.7# βββ @formatjs/fast-memoize 1.2.8# βββ¬ intl-messageformat 10.3.1# βββ @formatjs/fast-memoize 1.2.8Concepts
Section titled βConceptsβPackage manager: PNPM
Section titled βPackage manager: PNPMβPlease use pnpm (opens in a new tab) instead of npm to manage dependencies in this repository.
Here is a list of some common commands you are likely to need:
pnpm install: Install local dependencies, for example after versions changed or a new local package has been created.- In a package directory:
pnpm add <DEP>. Adds the dependency to the package and installs it. Use-Dfor devDependencies.pnpm removeremoves a dependency again. pnpm -w <COMMAND>: run the command in the workspace root instead of the local package.pnpm run <SCRIPT>: runs the script from thepackage.json.pnpm exec <COMMAND>: runs the CLI command (should be installed in node_modules), e.g.pnpm exec -w tsc --noEmit.
Monorepo
Section titled βMonorepoβWe use PNPMβs workspace support (opens in a new tab) to manage packages in our repository.
All node packages matching the patterns configured in the pnpm-workspace.yaml file are included in the workspace.
Workspace packages may reference each other.
For example, the following package-a will be able to use package-b:
{ "name": "package-a", "dependencies": { "package-b": "workspace:*" }}pnpm install will resolve dependencies such as these by linking the packages to each other.
For example, package-a/node_modules/package-b will be a link to package-bβs actual location in the workspace.
TypeScript
Section titled βTypeScriptβAs a general rule, most code should be written in TypeScript. The usage of typescript has many advantages. It protects against bugs, improves the developer experience (autocompletion, early detection of problems, etc.) and also ensures that type definitions and documentation for reusable libraries or bundles can be generated with little effort.
However, usage of JavaScript is supported.
UI Component Framework
Section titled βUI Component FrameworkβWe are using Chakra UI (opens in a new tab) as our base framework to develop user interfaces.
In general, chakra components can be directly imported from @chakra-ui/react.
Up from Chakra UI version 3, the Chakra components are more fine-grained and thus chakra provides snippets from some components to allow simpler usage.
These snippets cannot be imported from @chakra-ui/react directly, but need to be added to the project by using a CLI.
However, these snippets are not versioned and thus may be incompatible with the chakra version used in the project.
Thus, trails provides a package @open-pioneer/chakra-snippets that contains the most important Chakra snippets for the Chakra UI Version used in the respective trails version.
It is recommended to use the snippets from @open-pioneer/chakra-snippets instead of the ones from @chakra-ui/react.
For a list of exported snippets, see the packages README.md.
Example:
import { Button } from "@chakra-ui/react";import { Tooltip } from "@open-pioneer/chakra-snippets/tooltip";
function MyComponent() { return ( <Container> <Tooltip showArrow content="Button Tooltip" aria-label="A tooltip" positioning={{ placement: "top" }} > <Button>Button with a tooltip</Button> </Tooltip> </Container> );}Vite (opens in a new tab) is our main build tool and development server. Custom functionalities (such as our specific Open Pioneer Trails framework package support) are developed on top of Vite via plugins.
Vite reads the configuration file vite.config.ts on start, which can be customized to your liking
(see docs (opens in a new tab)).
However, the pioneer and react plugin should not be removed from the configuration.
Supported Browsers
Section titled βSupported BrowsersβThe vite config is preconfigured to support a set of common browsers (βtargetsβ, see docs (opens in a new tab)).
This option influences the features used by the compiled JavaScript and CSS code.
Note that this only changes the set of language features used by the output (e.g. async, class)
and not the set of Browser APIs used (that would require additional polyfills).
By default, vite assumes modern browsers with support for modules. It is possible to support even older browsers using viteβs legacy plugin (opens in a new tab).
Testing
Section titled βTestingβVitest (opens in a new tab) is used to write automated tests.
To create new tests for a source code file, simply add a *.test.ts (or .tsx, .js, etc.) file next to it.
It will then be automatically picked up by vitest.
Use pnpm run test to run the test suite.
Please refer to the official documentation (opens in a new tab) for more information.
Linting and formatting
Section titled βLinting and formattingβWe use Prettier (opens in a new tab) to handle automatic source code formatting.
This keeps code readable with reasonable defaults and also ensures that we donβt waste time with unproductive style discussions.
Prettier is configured by the .prettierrc file and it also respects parts of the .editorconfig file.
It can be integrated into most modern IDEs to keep automatically keep edited files formatted properly.
ESLint (opens in a new tab) runs within the dev server (as a vite plugin) and when pushing to the GitHub repository (within the GitHub actions workflow).
It checks the code against configured rules (see .eslintrc) and fails the build when it detects a code style violation.
ESLint helps to detect minor style issues (e.g. missing semicolons) and outright programming errors (e.g. wrong usage of react hooks).