
Security News
vlt Launches "reproduce": A New Tool Challenging the Limits of Package Provenance
vlt's new "reproduce" tool verifies npm packages against their source code, outperforming traditional provenance adoption in the JavaScript ecosystem.
@celement/cli
Advanced tools
Pluggable CLI tool that collects metadata about your web components and transforms it into whatever you like.
This is a lightweight CLI tool that parses your JavaScript/TypeScript files and collects metadata about your Web Components via plugins, which can then be used again via plugins to transform it into whatever output you require such as JSON or Markdown.
Metadata includes the properties, methods, events, cssprops, cssparts, slots and more about each component. This tool works completely off plugins so there's not much underlying logic out of the box. Pick, use and create what you need. See below on how to create your own plugin.
The following are some plugins available out of the box:
$: npm install @celement/cli -D
First create a celement.config.ts
file at the root your project directory and include some plugins...
// FILE: celement.config.ts
// Hover over each plugin here in your editor to see what options are available.
import {
litPlugin,
jsonPlugin,
markdownPlugin,
vscodeHtmlDataPlugin,
} from '@celement/cli';
export default [
litPlugin(),
jsonPlugin(),
markdownPlugin(),
vscodeHmlDataPlugin(),
];
Next simply run the analyze
command...
$: celement analyze src/**/*.ts
For more information call celement analyze -h
to see what arguments are available.
Here's an example of how you can document a component when using the litPlugin
...
/**
* Description about my component here.
*
* @tagname my-element
* @slot Used to pass in additional content inside (default slot).
* @slot another-slot - Used to pass content into another part.
* @csspart root - The component's root element.
* @cssprop --my-component-bg - The background color of the component.
* @example
* ```html
* <my-component></my-component>
* ```
* @example
* ```html
* <!-- Hidden. -->
* <my-component hidden></my-component>
* ```
*/
export class MyElement extends LitElement {
/**
* Whether the component is hidden.
*/
@property({ type: Boolean }) hidden = false;
/**
* The size of the component.
*
* @deprecated - Use `size` instead.
*/
@property({ attribute: 'size' }) sizing: 'small' | 'big' = 'small';
/**
* The current size of the component - example of a `readonly` property.
*/
get currentSize(): 'small' | 'big' {
return this.size;
}
/**
* Call this method to show the component.
*/
onShow() {
// ...
}
/**
* `protected` and `private` methods will not be included in `ComponentMeta`.
*
* You can also hide metadata by adding the following tag...
*
* @internal - For private use... don't touch!
*/
protected internalMethod() {
// ...
}
}
You might've noticed that some information such as events were missing, and there may
potentially be other information you'd like to include in the final output. In these cases it'd be
best to create your own plugin and extract the information you need. Depending on what you're
gathering the postbuild
and postlink
plugin lifecycle steps are generally the best time to do
this. See the next section for more information on how you can go about achieving this with
custom plugins.
/**
* Don't be scared by the interface, you can achieve simple things really easily. All
* lifecycle steps are completely optional. The complexity is really in the `discover` and
* `build` steps (Phase 2) where some basic knowledge of how the TS compiler works is needed,
* otherwise you're good to go!
*
* Take advantage of existing plugins such as `litPlugin` to do the hard work, so you
* can create your own plugins that focus on the simple stuff.
*/
export interface Plugin<ComponentRootNodeType extends Node = Node> {
/**
* The name of the plugin.
*/
name: string;
// *** PHASE 1 ***
/**
* Optional - Called when initializing the plugin, receives the TypeScript `Program` as an
* argument.
*/
init?(program: Program): Promise<void>;
// *** PHASE 2 ***
/**
* Optional - Called to discover any component root nodes inside the given `sourceFile`, these
* nodes are collected and passed to the `build` function to turn it into a `ComponentMeta`.
* Discovered nodes are not shared between plugins.
*/
discover?(sourceFile: SourceFile): Promise<ComponentRootNodeType[]>;
build?(node: ComponentRootNodeType): Promise<ComponentMeta>;
/**
* Optional - Called immediately after ALL plugins complete their `discover` and `build` steps.
* It's a chance for you to query/add/update/delete any component metadata.
*/
postbuild?(
components: ComponentMeta[],
sourceFiles: SourceFile[],
): Promise<ComponentMeta[]>;
// *** PHASE 3 ***
/**
* Optional - Links/merges heritage (mixins/subclasses/interfaces) metadata with its respective
* component metadata. It's important to note that there's a base `link` process that'll do this
* out of the box which will run before any plugin `link`.
*/
link?(
component: ComponentMeta,
heritage: HeritageMeta,
): Promise<ComponentMeta>;
/**
* Optional - Called immediately after ALL plugins complete their `link` step. Similar to
* `postbuild`, it's a chance for you to query/add/update/delete any component metadata.
*/
postlink?(
components: ComponentMeta[],
sourceFiles: SourceFile[],
): Promise<ComponentMeta[]>;
// *** PHASE 4 ***
/**
* Optional - Receives the final component metadata collection and transforms it. This step also
* receives an `fs` argument that is a collection of filesystem utilties that basically extends
* the `fs-extra` library with a few Windows friendly path resolver functions.
*/
transform?(components: ComponentMeta[], fs: PluginFs): Promise<void>;
// *** PHASE 5 ***
/**
* Optional - Called when destroying the plugin.
*/
destroy?(): Promise<void>;
}
Assume you're registering your component's in a separate .ts
file so that when someone
imports my-library/button/my-button.ts
it'll register the MyButton
custom element in the
Window registry under the tag name my-button
.
// my-button.ts
// Dependencies.
import '../theme/my-theme.js';
import { MyButton } from './MyButton';
window.customElements.define('my-button', MyButton);
This plugin will discover component dependencies by looking at the import declarations at
the top of said file, and seeing if they reference other component registration files. In the
example above, the imports listed directly under the comment // Dependencies
will be discovered.
// FILE: celement.config.ts
import {
litPlugin,
markdownPlugin,
Plugin,
ComponentMeta,
} from '@celement/cli';
import { escapeQuotes, isUndefined } from '@celement/cli/dist/utils';
import { SourceFile, isImportDeclaration } from 'typescript';
export default [litPlugin(), dependencyDiscoveryPlugin(), markdownPlugin()];
function dependencyDiscoveryPlugin(): Plugin {
return {
name: 'deps-discovery',
async postbuild(components, sourceFiles) {
// Loop through each source file.
sourceFiles.forEach(sourceFile => {
const path = sourceFile.fileName;
// Loop through each component.
components.forEach(component => {
// Look for a component definition file by the name `{tag-name}.ts`.
const definitionFile = `${component.tagName!}.ts`;
// If current source file is definition file.
if (path.endsWith(definitionFile)) {
// Find dependencies (implementation below).
const deps = findDependencies(components, sourceFile);
// Append dependencies to metadata.
component.dependencies.push(...deps);
// For each dependency, add current component as dependent.
deps.forEach(dep => {
// If dependent doesn't exist yet.
const notFound = !dep.dependents.some(
c => c.tagName === component.tagName,
);
if (notFound) dep.dependents.push(component);
});
}
});
});
return components;
},
};
}
function findDependencies(
components: ComponentMeta[],
sourceFile: SourceFile,
): ComponentMeta[] {
const deps: ComponentMeta[] = [];
// For each node in the source file.
sourceFile.forEachChild(node => {
// If node is a import declaration.
if (isImportDeclaration(node)) {
// Get the module path for the current import declaration.
const importPath = escapeQuotes(node.moduleSpecifier.getText());
if (importPath.startsWith('../.js')) {
// Check if module path includes another component's tag name.
const dep = components.find(c => importPath.includes(c.tagName!));
if (!isUndefined(dep)) deps.push(dep);
}
}
});
return deps;
}
Here's an example of running Prettier on the markdown files generated by
the markdownPlugin
...
// FILE: celement.config.ts
import { litPlugin, markdownPlugin } from '@celement/cli';
import prettier from 'prettier';
export default [
litPlugin(),
markdownPlugin({
async transformContent(_, content) {
return prettier.format(content);
},
}),
];
FAQs
Pluggable CLI tool that collects metadata about your web components and transforms it into whatever you like.
We found that @celement/cli demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
vlt's new "reproduce" tool verifies npm packages against their source code, outperforming traditional provenance adoption in the JavaScript ecosystem.
Research
Security News
Socket researchers uncovered a malicious PyPI package exploiting Deezer’s API to enable coordinated music piracy through API abuse and C2 server control.
Research
The Socket Research Team discovered a malicious npm package, '@ton-wallet/create', stealing cryptocurrency wallet keys from developers and users in the TON ecosystem.