Components.js
Components.js is a semantic dependency injection framework for TypeScript and JavaScript projects, leveraging JSON-LD or other RDF serializations for declarative component configuration. It allows developers to define and wire software components using unique, globally identifiable URIs, promoting modular and easily reconfigurable applications. The current stable version is 6.4.0, with major releases occurring periodically, introducing breaking changes primarily related to configuration file syntax and API usage. A key differentiator is its reliance on semantic configuration, enabling dynamic component injection without hard-coding dependencies. This makes it suitable for complex applications requiring flexible component orchestration, such as the Comunica query engine.
Common errors
-
ReferenceError: MyClass is not defined (or similar for other component names)
cause The `componentsjs-generator` tool has not been run, or its output path is misconfigured, preventing Components.js from discovering the component metadata.fixEnsure `npm run build:components` (or equivalent) has been executed after compiling TypeScript/JavaScript. Verify that the generator is targeting the correct source files and outputting metadata to the expected location. -
Error: Could not resolve module for component 'urn:my-package:myInstance'
cause Components.js cannot find the module definition. This often happens if `"lsd:module": true` is missing from the `package.json` of the component's package, or if `mainModulePath` in `ComponentsManager.build()` is incorrect.fixAdd `"lsd:module": true` to the `package.json` of the module containing the component. Double-check that `mainModulePath` passed to `ComponentsManager.build()` correctly points to the root of the npm package. -
SyntaxError: Cannot use import statement outside a module
cause Attempting to use ESM `import` syntax in a Node.js environment configured for CommonJS, or in an older Node.js version without proper ESM setup.fixEnsure your project's `package.json` includes `"type": "module"` if you intend to use ESM globally, or rename files using `import` to `.mjs`. For older Node.js, stick to CommonJS `require()` or transpile your code. -
Error: Parameter 'myParameter' has no associated range
cause This error typically indicates that a parameter in your component's configuration (JSON-LD) is not defined correctly according to the current Components.js version's parameter range specification, or an array is not explicitly wrapped in an RDF list (`@list`).fixRefer to the `CHANGELOG` for your Components.js major version (v5 or v6) and update your component configuration to align with the new parameter range and RDF list requirements. Ensure all array values are explicitly defined with `@list`.
Warnings
- breaking Components.js v6.0.0 introduced breaking changes to parameter range definition within JSON-LD configuration files. Previously, `required` and `unique` flags were used; these have been removed in favor of explicit parameter ranges. Arrays must now be explicitly defined using an RDF list (`@list` in JSON-LD). Incorrectly defined parameters will lead to configuration parsing errors.
- breaking Components.js v5.0.0 also introduced significant changes to parameter definition, including the removal of `required` and `unique` flags in favor of parameter ranges, similar to v6. This change also impacted how arrays are handled, requiring explicit RDF list definitions.
- gotcha Configuration context URLs are version-specific and should always refer to the major version range of a package (e.g., `^6.0.0`). Using an exact version or an incorrect range in `@context` URLs within your JSON-LD configuration can lead to modules and components not being found.
- gotcha For Components.js to automatically discover and use your components, your `package.json` must include the `"lsd:module": true` entry. Additionally, the `componentsjs-generator` tool (typically run via a build script) is required to generate the necessary component metadata files from your TypeScript (`.d.ts`) or JavaScript source. Skipping these steps will prevent Components.js from finding your defined components.
Install
-
npm install componentsjs -
yarn add componentsjs -
pnpm add componentsjs
Imports
- ComponentsManager
const ComponentsManager = require('componentsjs');import { ComponentsManager } from 'componentsjs'; - compileConfig
const { compileConfig } = require('componentsjs');import { compileConfig } from 'componentsjs'; - build
await ComponentsManager.build({ mainModulePath: __dirname });
Quickstart
import { ComponentsManager } from 'componentsjs';
import { writeFileSync, readFileSync, mkdirSync } from 'node:fs';
import { resolve } from 'node:path';
// Create a dummy TypeScript class and compile it (in a real project, this would be part of your build process)
const myClassCode = `
export class MyClass {
public readonly name: string;
constructor(name: string) {
this.name = name;
}
}
`;
const tsOutputDirectory = resolve(process.cwd(), 'lib');
mkdirSync(tsOutputDirectory, { recursive: true });
writeFileSync(resolve(tsOutputDirectory, 'my-package.d.ts'), myClassCode);
writeFileSync(resolve(tsOutputDirectory, 'my-package.js'), myClassCode.replace('export class', 'class').replace(/\: string/g, '')); // Basic JS output
// 1. Define package.json (simulating a module)
const packageJson = {
name: 'my-package',
version: '2.3.4',
"lsd:module": true,
main: 'lib/my-package.js',
types: 'lib/my-package.d.ts'
};
writeFileSync(resolve(process.cwd(), 'package.json'), JSON.stringify(packageJson, null, 2));
// 2. Create a configuration file
const configContent = `
{
"@context": [
"https://linkedsoftwependencies.org/bundles/npm/componentsjs/^6.0.0/components/context.jsonld",
"https://linkedsoftwependencies.org/bundles/npm/my-package/^2.0.0/components/context.jsonld"
],
"@id": "urn:my-package:myInstance",
"@type": "MyClass",
"name": "John Doe"
}
`;
const configPath = resolve(process.cwd(), 'config.jsonld');
writeFileSync(configPath, configContent);
async function runComponentsJs() {
// In a real setup, `__dirname` would point to the package root, not this script's directory.
// For this example, we mock `mainModulePath` to the current working directory
// where we've created the dummy package.json and lib directory.
const manager = await ComponentsManager.build({
mainModulePath: process.cwd(),
});
await manager.configRegistry.register(configPath);
const myInstance = await manager.instantiate('urn:my-package:myInstance');
console.log('Instantiated object:', myInstance);
console.log('Name property:', myInstance.name);
if (myInstance.name === 'John Doe') {
console.log('Quickstart successful: Instance created and property accessed.');
} else {
console.error('Quickstart failed: Unexpected instance property.');
}
}
runComponentsJs().catch(console.error);