Skip to content

Element libraries: a namespace's components folder holds its .apiext files, and optionally a published contract (parsley.taglib) #78

Open

Description

@hugithordarson

An element library for each namespace: which elements it publishes, what each one is, and what templates call it. It's read by the runtime (to resolve and render tags), by tooling (completion, hover, validation, the element reference), and listed by ng itself, so documentation pages stop assembling their own element lists.

This is the implementation side of undur/apiext-format#6 ("a shortcut is not a property of the element… lands as implementation in ng-objects"), and grows out of #63.

A namespace's folder is its library

Following WO's convention that an element's API file lives with the components:

ng/<namespace>/
	components/
		NGString.apiext        one per element: class, bindings, docs
		TABadge.apiext         a template-only component's too: no class
		TABadge.html           templates, as before
		parsley.taglib         optional: the library's published contract
	tag-aliases.properties    what templates call them: tag = element name

The same layout carries over to WO: src/main/components/ already holds a framework's .apiext files (built into Resources/), and a parsley.taglib there would be built into Resources/parsley.taglib with no build change.

How ng loads them

Each plugin's namespace is loaded as the plugin is initialized, in plugin order: every .apiext in its components folder (in directories and jars, named after the file), each with its class and API, marked published or internal by the taglib if there is one, then the namespace's tags.

  • A later namespace's tag overrides an earlier one's. Plugins are initialized after the plugins they require() (on master, 77cf7cb); unrelated ones keep the classpath order. A plugin can rename the tags of one it builds on, and the application, loaded last, has the last word. Within one namespace, two files that disagree keep the first, with a warning.
  • This is still one global map, so an application's tag also applies inside a framework's own templates. That's what Tag-alias resolution is global at runtime — a framework's templates should resolve tags in their own context, not the application's #63 is about, and it stays open: element replacements should apply everywhere, tag shortcuts only within their library.
  • A published class must exist and be an NGElement, otherwise startup fails rather than the first render. When two namespaces publish the same element name, the first stands, with a warning.
  • declaredElements() lists every element, each with its namespace, class, API and whether it's published; publishedElements() lists what documentation and tooling offer. tagsFor(name) gives an element's tags.
  • Tooling can't run requires(), so the editor orders namespaces by the project's dependency graph: a library's tags override those of the libraries it depends on, and the application's override everyone's. A plugin that requires another compiles against it, so the two orders agree. A jar that depends on another without declaring requires() is the gap. Two libraries with no dependency between them that define the same tag get a warning in the editor.

Finding undeclared elements: NGSearchClassPathForElements

To align with WO, an element nobody publishes is searched for on the classpath by simple name:

  • an index of class names from the classpath; only the candidates matching the requested name are loaded, and they must be an NGElement;
  • two matches are refused with both names, rather than guessed between;
  • in development, each element found this way logs a warning once.

For now both the search and the warning are hardcoded on (a FIXME in NGApplication): the properties that should configure them aren't available when the element manager is constructed.

Done on the branch

  • Readers for .apiext and parsley.taglib in ng-template-parser (ng.appserver.templating.definitions: NGElementAPI, NGTaglib), dependency-free (on master, 09eb1f2).
  • ng-appserver's .apiext files in ng/ng/components/, naming their classes in full (on master, 701cf22 and faeebc9).
  • Loading per namespace in plugin order, with the registry listable.
  • ng-appserver's elements in ng/ng/, replacing NGCorePlugin.elements(); code registration of elements (Elements, NGPlugin.elements(), package probing) is gone.
  • The classpath search, with the dev-mode warning (on master, 693d0f1).
  • ng-testapp's own library in ng/app/ (its templates moved to ng/app/components; two documented components, one template-only, an alias for an element it doesn't declare, and a compatibility alias), tested in-process.
  • The optional taglib contract, tested with and without a taglib.
  • The website's element reference generated from the registry.
  • Plugins initialized in dependency order (requires()), on master.

Before merging

  • Document the layout in ng-objects' docs.
  • Parslips reads ng's element libraries, reusing NGElementAPI from ng-template-parser. The convention (ng/*/components/<Name>.apiext) is checked first; the class-based lookups stay behind it (beside the class, a WO jar's Resources/, next to the .api, the bundled copies), since released ng and wonder-slim ship their files that way. Until then, tags declared only by an app's or library's namespace show as unknown in the editor; the framework's own tags are fine. The same change removes the editor's bundled copies of ng's definitions (Remove the bundled copies of ng's element definitions (apiext/ng) undur/parslips#15).

Later

Activity

  1. changed the title [-]Tag libraries: a parsley.taglib registry of elements and tags[/-] [+]Element libraries: each namespace publishes elements/<Name>.apiext and tag-aliases.properties[/+] on Oct 14, 2026
  2. changed the title [-]Element libraries: each namespace publishes elements/<Name>.apiext and tag-aliases.properties[/-] [+]Element libraries: a namespace's components folder holds its .apiext files, and optionally a published contract (parsley.taglib)[/+] on Oct 14, 2026
  3. hugithordarson commented on Oct 14, 2026

    @hugithordarson
    CollaboratorAuthor

    Parked (October 2026)

    How elements are declared, found and named is central to how the framework works, and the design kept moving while this was worked on. It's parked until it has had time to settle. Nothing is waiting for it: editor support is deferred, no library needs to hide elements yet, and the global-tags question (#63) is separate.

    What landed on master (the parts that settled):

    • Plugins are initialized after the plugins they require() (77cf7cb).
    • An element nobody registers is found on the classpath by name, as in WO (693d0f1), including classes created during development (a180923); a class that exists but can't be loaded is reported (1570144). The servlet case is #87.
    • Readers for .apiext and parsley.taglib, in ng-template-parser (09eb1f2).
    • ng-appserver's .apiext files in ng/ng/components/ (701cf22), naming their classes in full (faeebc9).
    • ng-testapp's and ng-control's templates in their namespaces' components folders (ae72944, 26804f8).
    • The shared development instance stopper (4d8a467).

    What's parked on the taglib branch (one commit): loading each namespace's elements and tags, the optional taglib contract, per-namespace tag alias files, and removing code registration (NGPlugin.elements() deprecated).

    Open questions to settle first, from two reviews of the branch:

    • Does the runtime need the taglib at all? It only marks elements internal, for tooling, and nothing uses it yet. A scope attribute in the .apiext might be the smaller answer.
    • Naming: parsley.taglib holds no tags, while the file that does is a properties file.
    • One element has five states: declared with or without a class, internal, published without an .apiext, or found on the classpath. Fewer would be simpler to explain.
    • Conflict rules: elements are first-wins across namespaces, while tags are last-wins across namespaces and first-wins within one. Tag-alias resolution is global at runtime — a framework's templates should resolve tags in their own context, not the application's #63 suggests element replacements should win from later namespaces too.
    • Loading belongs in the resource layer: NGResourceLoader already knows where a namespace's resources live, so listing a namespace's components folder should go through it, not through the element manager.
    • Where the tag alias file lives, and whether it moves into the components folder with the rest.

    The website's element reference reads ng-appserver's files directly in the meantime.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions