Package
Package Reference
Section titled āPackage ReferenceāIntroduction
Section titled āIntroductionāThis document specifies the structural requirements for a Trails package in the source directory.
This specification applies to both apps and ānormalā packages. Apps are simply packages that expose a web component: they may use all package features such as services etc.
Application Packages
Section titled āApplication PackagesāAn application package is a package that provides an app. Application packages can use all features documented below (like any other package). Some features are only available in application packages (e.g. overriding i18n messages).
File structure
Section titled āFile structureāsrc/packages/<PACKAGE_NAME>āāā build.config.mjs # Required build system configuration fileāāā package.json # Required package metadata for (p-)npmpackage.json
Section titled āpackage.jsonāA valid package.json (opens in a new tab) file.
It must contain at least a valid package name.
If the package defines services, then it must also have a services module (typically called services.js or services.ts) from which those services are exported.
The build system will automatically import the required service classes from the specified file.
{ "name": "sample-package"}build.config.mjs
Section titled ābuild.config.mjsāA JavaScript file that exports a configuration object for the package. This configuration file is read and interpreted by the build system.
All configuration properties are optional. In its most basic form, it should define an empty object:
import { defineBuildConfig } from "@open-pioneer/build-support";
export default defineBuildConfig({});Reference
Section titled āReferenceāinterface BuildConfig { entryPoints?: string | string[]; styles?: string; i18n?: string[]; services?: Record<string, ServiceConfig>; servicesModule?: string; ui?: UiConfig; properties?: Record<string, unknown>; propertiesMeta?: Record<string, PropertiesMeta>; overrides?: Record<string, PackageOverridesConfig>; publishConfig?: PublishConfig;}entryPoints
Section titled āentryPointsāZero or more TypeScript (or JavaScript) entry point modules. An entry point module is a module that can be imported from other packages. Modules not listed here should be considered internal to the package.
This option is required when building a package with build-pioneer-package and optional otherwise.
Example:
export default defineBuildConfig({ entryPoints: ["index"]});The array can also be left empty if your package does not provide any importable entities.
NOTE: There is no need to list the servicesModule in here, that module automatically becomes an entry point if the package defines any services.
NOTE: Entry point modules are currently not enforced during development, meaning that you can import arbitrary modules from other packages when using the vite dev server. This is however considered a bad practice and may be enforced in a later version.
A path to a .css file within the package.
The file will automatically be linked into the application, and the styles will be loaded when the application runs.
Note that the .css file may include @imports to other .css files.
Example:
export default defineBuildConfig({ styles: "./styles.css"});An array of locales supported by the package or application.
When a package declares support for a given locale <LOC>, then a file named ./i18n/<LOC>.yaml must exist.
See I18N-Format for more details about the format of i18n files.
Example:
export default defineBuildConfig({ // ./i18n/de.yaml and ./i18n/en.yaml must exist i18n: ["de", "en"]});messages: content: header: "i18n example"messages: content: header: "i18n Beispiel"Services and UI components will automatically receive appropriate intl objects from the framework for the current application locale:
-
The service constructorās
optionsparameter containsoptions.currentIntl, a reactive object that provides the currentintlfor the service.-
Be aware that
options.currentIntlhas to be watched for changes, as the application locales can be changed at runtime.- for example:
watchValue(() => currentIntl.value,(intl) => {console.log(intl.formatMessage({ id: "greetingService.greeting" }));// your code to react to intl changes}); -
If your project uses
options.intl, consider switching tooptions.currentIntlinstead, as the former is deprecated and will be removed in a future version.
-
-
The react hook
useIntl()(see below) provides the sameintlobject.
See I18N Format for more details.
NOTE: The
i18nvalue has an additional meaning in application packages: The defined languages will be the languages supported by the application, and they must either be defined in all packages or must be added manually in the application (viaoverridesin alang.yamlfile).The first language in
i18nbecomes the applicationās fallback language when no other language can be applied for a given user.
services
Section titled āservicesāDeclares services that are provided by the package. Services will be included in the application and will automatically start when they are needed.
The name of a service must match the exported class name from the packageās service module.
export interface ServiceConfig { provides?: string | (string | ProvidesConfig)[]; references?: Record<string, string | ReferenceConfig>;}Example:
export default defineBuildConfig({ services: { // The framework will `import { LogService } from "packageName/services";`, // by default, so a matching export must exist (see .js file below). LogService: { provides: "logging.LogService" } }});Make sure to export a class under the same name (LogService in this case):
export class LogService { // implementation...}
// or:export { LogService } from "./LogService";service.provides
Section titled āservice.providesāInterfaces provided by the service. They can be configured as a single string (a single interface name), or an array of entries. Every array entry can specify a string (the interface name) or an object with advanced properties.
Example:
export default defineBuildConfig({ services: { ServiceA: { provides: "example.InterfaceA" }, ServiceB: { provides: [ "example.InterfaceB", { name: "example.InterfaceC" } ] } }});service.references
Section titled āservice.referencesāInterfaces referenced by the service, specified as an object of (referenceName, referenceConfig) entries.
These will be automatically injected by the framework into the serviceās constructor.
The framework will generate an error if a reference cannot be provided.
Example:
export default defineBuildConfig({ services: { ServiceA: { references: { // Injects the ExampleService as `example` into the service's constructor. example: "example.ExampleService" } } }});servicesModule
Section titled āservicesModuleāThe name of the module that exports the packageās service classes.
This value is ./services by default, meaning that ./services.ts or ./services.js will be picked up automatically.
Example:
Read services from a different file:
export default defineBuildConfig({ services: { Foo: { // ... } }, servicesModule: "./my-services-module.ts"});export class Foo { // ...}Contains metadata about UI Components provided by the package.
export interface UiConfig { references?: (string | ReferenceConfig)[];}ui.references
Section titled āui.referencesāLists interfaces required by UI Components.
UI Components can only use services that have previously been referenced in the build.config.mjs.
Example:
export default defineBuildConfig({ ui: { references: ["example.interface.Name"] }});properties
Section titled āpropertiesāA record of (propertyName, value) pairs. All valid JSON values are allowed as property values.
Properties are accessible for all services and UI components in the package.
Default property values defined here may be overwritten by the application.
Example:
export default defineBuildConfig({ properties: { foo: "bar", nested: { value: "baz" } }});propertiesMeta
Section titled āpropertiesMetaāContains additional metadata about properties for advanced use cases.
interface PropertyMetaConfig { required?: boolean;}propertiesMeta.required
Section titled āpropertiesMeta.requiredāSet this value to true to force the application to override this property to a non-null value.
Example:
export default defineBuildConfig({ properties: { foo: null }, propertiesMeta: { foo: { // Application will not start if the developer forgets // to initialize `foo` required: true } }});Note: You cannot configure
required: truefor nested object properties at the moment.
overrides
Section titled āoverridesāAn application package may override certain entities defined in its packages.
export interface PackageOverridesConfig { services?: Record<string, ServiceOverridesConfig>;}
export interface ServiceOverridesConfig { enabled?: boolean;}Currently, it only provides the power to completely remove the implementation of a service, for example:
import { defineBuildConfig } from "@open-pioneer/build-support";
export default defineBuildConfig({ overrides: { // (1) "sample-package": { services: { // (2) GreeterImpl: { // (3) enabled: false } } } }});- (1) The key in the
overridesobject is a package name. - (2) The key in the
servicesobject is a service name within that package. - (3) Disables the service (the default is always
true).
The snippet above completely removes to service called GreeterImpl from the package sample-package.
If that service provided any interfaces required by the rest of the application, the app would now be responsible
to provide alternative implementations, making this an expert feature that should not be overused.
NOTE: It is forbidden to use
overridesfrom a normal (i.e. not āapplicationā) package.
publishConfig
Section titled āpublishConfigāAdditional options interpreted by the build-pioneer-package (opens in a new tab) CLI when a package is built for publishing.
export interface PublishConfig { assets?: string | string[]; types?: boolean; sourceMaps?: boolean; strict?: boolean; validation?: ValidationOptions;}publishConfig.assets
Section titled āpublishConfig.assetsāA set of micromatch patterns (opens in a new tab) defining asset files.
Matching files will be copied into the packageās dist directory (using the same file name) and will therefore be available to the published package.
By default, all files in assets/** will be included.
NOTE: File names starting with
.are always ignored for security reasons.
NOTE: Directories cannot match by themselves, you must configure a pattern that matches the individual files (e.g.
assets/**instead ofassets/).
Example:
import { defineBuildConfig } from "@open-pioneer/build-support";
export default defineBuildConfig({ publishConfig: { assets: ["assets/**", "fonts/*.woff2"] }});publishConfig.types
Section titled āpublishConfig.typesāWhether to generate TypeScript declaration files (.d.ts) for the package under compilation.
true by default if a TypeScript file is detected in the package, false otherwise.
Generating d.ts files requires a tsconfig.json in the project (or, at least, in the package).
publishConfig.sourceMaps
Section titled āpublishConfig.sourceMapsāEnables or disables generation of source maps (opens in a new tab). Defaults to true.
Disable this option to keep your source code private.
publishConfig.strict
Section titled āpublishConfig.strictāEnables or disables strict validation. Defaults to true.
Strict validation makes style issues and other warnings (e.g. missing README, undeclared dependencies etc.) fatal.
Disabling strict temporarily is helpful when starting to prepare a package for separate compilation:
all errors will be shown as warnings instead of aborting at the first error.
publishConfig.validation
Section titled āpublishConfig.validationāFine-tuning options for package validation.
For example, this option can be used to make CHANGELOG and LICENSE optional (they are required by default).
export interface ValidationOptions { requireLicense?: boolean; requireReadme?: boolean; requireChangelog?: boolean;}Service definition
Section titled āService definitionāRead Services to see how services can be defined.
React Integration
Section titled āReact IntegrationāuseService Hook
Section titled āuseService HookāRetrieves a service providing the given interface.
The dependency on that interface must have been declared in the build.config.mjs (see ui.references).
import { useService } from "open-pioneer:react-hooks";
function ExampleComponent() { const service = useService("example.interface.Name"); return <div>{service.sayHello()}</div>;}useServices Hook
Section titled āuseServices HookāRetrieves all services providing the given interface as an array.
The dependency must have been declared in the build.config.mjs (see ui.references).
import { useServices } from "open-pioneer:react-hooks";
function ExampleComponent() { const services = useServices("example.interface.Name"); const messages = services.map((service) => service.sayHello()).join(" - "); return <div>{messages}</div>;}useProperties Hook
Section titled āuseProperties HookāReturns the properties of the calling componentās package.
Note that properties can be customized by the application, so values
may not be equal to their definition in the build.config.mjs.
import { useProperties } from "open-pioneer:react-hooks";
function ExampleComponent() { const properties = useProperties(); return <div>{properties.greeting}</div>;}export default defineBuildConfig({ properties: { greeting: "Hello World" }});useIntl Hook
Section titled āuseIntl HookāReturns the intl object for the calling componentās package.
The intl object is configured for the current application locale (messages, date and number formatting, etc.).
import { useIntl } from "open-pioneer:react-hooks";
function ExampleComponent() { // Uses the content.header value defined in the section "i18n" at the top of the document const intl = useIntl(); return <h1>{intl.formatMessage({ id: "content.header" })}</h1>;}See I18N Format for more details.
Advanced service references
Section titled āAdvanced service referencesāThe references object in a service configuration block and the references array in the ui configuration block both accept a ReferenceConfig object for advanced use cases:
export interface ReferenceConfig { name: string; qualifier?: string; all?: boolean;}referenceConfig.name
Section titled āreferenceConfig.nameāThe interface name required by the reference. This attribute is mandatory.
referenceConfig.qualifier
Section titled āreferenceConfig.qualifierāThe exact interface qualifier required by the reference. This attribute allows disambiguation when there are multiple services that provide the same interface. Note that ambiguous references (references that do not exactly match a single service) are always an error.
referenceConfig.all
Section titled āreferenceConfig.allāA boolean value that indicates that the reference requires all implementations of an interface.
The service (or the UI) can use the services as an array.
This attribute cannot be used together with qualifier.
Examples
Section titled āExamplesāReferencing single instance
Section titled āReferencing single instanceāA service that requires a reference to a single other service.
There can only be a single service providing "example.ExampleService", otherwise the system will throw an error because the reference needs additional disambiguation.
export default defineBuildConfig({ services: { ServiceA: { references: { example: "example.ExampleService" } } }});Referencing a single instance with qualifier
Section titled āReferencing a single instance with qualifierāWhen there are multiple services providing the same interface, the qualifier can be used to pick a specific one.
The qualifier must match the value in the referenced serviceās provides section.
export default defineBuildConfig({ services: { // Reference with qualifier, guaranteed to obtain the `ServiceB` instance below ServiceA: { references: { example: { name: "example.ExampleService", qualifier: "exampleQualifier" } } },
// Provides with qualifier ServiceB: { provides: [ { name: "example.ExampleService", qualifier: "exampleQualifier" } ] } }});Referencing all services providing a specific interface
Section titled āReferencing all services providing a specific interfaceāAll services providing an interface can be referenced by specifying all: true in your build.config.mjs:
export default defineBuildConfig({ services: { ActionServiceImpl: { provides: ["extension-app.ActionService"], references: { // Gathers all services that provide "extension-app.ActionProvider" as an array. providers: { name: "extension-app.ActionProvider", all: true } } }, LoggingActionProvider: { provides: ["extension-app.ActionProvider"] }, MultiActionProvider: { provides: ["extension-app.ActionProvider"] }, OpenWindowActionProvider: { provides: ["extension-app.ActionProvider"] } }});Referencing a service from the UI
Section titled āReferencing a service from the UIāAfter declaring a reference in the build.config.mjs, the service can be used in a React component:
export default defineBuildConfig({ ui: { references: ["some.interface.Name"] }});import { useService } from "open-pioneer:react-hooks";
function ExampleComponent() { const service = useService("some.interface.Name"); return <div>{service.sayHello()}</div>;}Referencing a service from the UI with qualifier
Section titled āReferencing a service from the UI with qualifierāDeclare the qualifier attribute both in your build.config.mjs and the useService call:
export default defineBuildConfig({ ui: { references: [ { name: "some.interface.Name", qualifier: "foo" } ] }});import { useService } from "open-pioneer:react-hooks";
function ExampleComponent() { const service = useService("some.interface.Name", { qualifier: "foo" }); return <div>{service.sayHello()}</div>;}Referencing all services that provide an interface from the UI
Section titled āReferencing all services that provide an interface from the UIāGather all services that provide a certain interface name:
export default defineBuildConfig({ ui: { references: [ { name: "some.interface.Name", all: true } ] }});The services are gathered and available as an array in the referencing service or UI:
import { useServices } from "open-pioneer:react-hooks";
function ExampleComponent() { const services = useServices("some.interface.Name"); // an array const messages = services.map((service) => service.sayHello()).join(" - "); return <div>{messages}</div>;}See also
Section titled āSee alsoāThe type declaration file (opens in a new tab) of the @open-pioneer/build-support package contains additional documentation for the defineBuildConfig(...) function.