Outputs, styles and assets

The four resource families of a package version (modules, styles, maps and assets), how an import selects the JavaScript or the stylesheet of a public module, the shared stylesheet of a package, and how companions are advertised.

  • Availability: Experimental
  • Evidence: Read from source
  • Reference

Four families under one identity

Every package version has four sibling families. They share the identity prefix, so a registry, a package, a version or a subpath is never valid in one family and invalid in another.

Family Path Media type Options
Module /m/[<registry>/]<package>@<version>/modules/<subpath> application/javascript Yes
Style /m/[<registry>/]<package>@<version>/styles/<subpath> text/css Yes
Map /m/[<registry>/]<package>@<version>/maps/<subpath> application/json (source map, revision 3) Yes
Asset /m/[<registry>/]<package>@<version>/assets/<path> The media type of the file No

The family segment is the one that follows <package>@<version>. A registry or a module subpath that happens to be spelled styles or assets is read as before.

Modules

A successful module response carries JavaScript, a strong ETag and the Cache-Control of the service. See Caching and releases for both headers.

One request returns one public module. The CDN never merges public modules into one file and never splits one into several: the public module is the unit of delivery, and its imports of other public modules stay bare references that the resolution maps to URLs.

Styles

Text
/m/@example/[email protected]/styles/core/router?target=browser&format=esm

A style is addressed by the subpath of a public module, ~root included. It is either the stylesheet of that module or a style module, which is a public module whose output is CSS. It takes the same options as the module request, with the same validation.

Styles are part of the reachable closure of an application. Preparation traces them as items of their modules. A stylesheet is never dropped and never injected into JavaScript: it is a separate output, linked or adopted by whoever delivers the module.

A url() in a stylesheet, or a source import of a static file, is written as ../assets/<path in the package>, relative to where this contract serves the module or its stylesheet. One output therefore works on every origin.

Selecting an output

A public module can have several outputs. Source code chooses one with its import specifier:

Specifier Selects
pkg/sub The JavaScript of the public module ./sub. This is the default of every import.
pkg/sub.css The stylesheet of ./sub: the style module, or the stylesheet the module produces. When the package publishes a literal ./sub.css subpath, as npm packages that export CSS files do, that subpath is selected.
pkg/sub.js, pkg/sub.mjs The JavaScript of ./sub.js when the package publishes it, otherwise of ./sub
Any other extension Nothing special: it is part of the subpath

The rule is checked when the importing module is compiled or analyzed, and a wrong selection fails there, never at delivery:

Diagnostic When
OUTPUT_NOT_FOUND A style-only module imported without .css (the message names the specifier to write), or .css of a module that produces no stylesheet
OUTPUT_AMBIGUOUS The specifier names two different public modules, such as a literal ./sub.css and a module ./sub with its own stylesheet. Nothing is guessed.
STYLE_BINDING_UNSUPPORTED A stylesheet asked for a value: import sheet from 'pkg/sub.css', a named import of it, or with { type: 'css' }

A selected stylesheet is a style relation, not an import of code. It is removed from the compiled JavaScript, import 'pkg/sub.css' does not load the code of ./sub, and the preparation inventory lists it as the style item of its module. A resolution document maps a specifier to a /styles/ address only when the specifier ends with .css.

Who applies a stylesheet

Nothing applies CSS because of an extension: a browser, SystemJS and a Node or Deno host never do. Whoever delivers the module applies its stylesheets explicitly:

  • A document links the stylesheets of the modules it loads outside any widget. The shell of a released application links the eager ones in its head and the lazy ones after the entry has loaded.
  • A widget adopts, inside its own shadow root, its stylesheet and those of the public modules it imports, through the runtime's styles registry. A nested widget owns its root. See Widgets and view frameworks.
  • Node.js and Deno load no stylesheet at all.

The shared stylesheet of a package

A package can publish one stylesheet that all of its widgets share: the ./global style module. Publishing it is the whole declaration; there is no other flag.

  • Every widget module of that package depends on it implicitly: preparing a single widget prepares the sheet too.
  • Each widget adopts it inside its own root, before its own sheets, and a widget of another package does not.
  • A package without ./global produces no request for one.
  • On a development server, an update of the sheet replaces it in every widget of that package and in no other; a broken sheet keeps the last valid one.

This is different from selecting a stylesheet with .css: the shared sheet is adopted by the widgets of its own package, while .css selects the stylesheet of any public module you name.

Source maps

Text
/m/@example/[email protected]/maps/core/router?target=browser&format=esm

A map is the external source map of the JavaScript that the same options select. Request it with the options of the module it belongs to.

A generated map never names a path of the machine that built it. Every sources entry is written under a virtual root, beyond://<package>@<version>/<path in the package>, for code and stylesheets in both formats; what was generated for the unit is named under ~generated/. Outputs are reproducible: the Packages repository records the same sources, extracted in two different directories, yielding byte-identical outputs, maps and digests. Development maps are the exception and keep names relative to the module directory.

Whether maps exist for a release is a setting of the application, sourcemaps:

Value Effect
restricted (default) Maps are retained and served only to a member: 401 without access and 403 for a guest. The SourceMap header is sent only to a requester the map would be served to.
public Maps are exposed with the release
none No maps are retained; the release is prepared with sourcemap=none

Promoting a release that exposes public maps requires an explicit acknowledgement. See Releases, promotion and rollback.

Assets

Text
/m/@example/[email protected]/assets/images/logo.png

An asset belongs to the package, not to one of its modules. Its path is the file inside the package, with its slashes and its extension.

  • A package declares assets in a module manifest (assets, relative to the module directory) or in its package.json (beyond.assets). A file that a url() or a source import references is inventoried for a release as well. A file that merely exists in the archive is not an asset, and an asset the service does not hold is 404 OUTPUT_NOT_AVAILABLE.
  • An asset request takes no options. Any query is 400 OPTION_INVALID.
  • Empty segments, . and .. segments, encoded slashes, backslashes and control characters are 400 IDENTITY_INVALID, so one asset has one path and a request never leaves the package.

How companions are advertised

A service advertises a companion resource only when the request asks for it and the service serves it:

  • With css=true, a module response adds a Link header to its stylesheet.
  • With types=true, a module response adds a Link header to its declarations, on a service that produces them.
  • A stylesheet response can carry a SourceMap header that points at its external map.

A service that cannot serve the companion rejects the option with OPTION_UNSUPPORTED instead of advertising a resource it does not have. Never build a companion URL by guessing: use the header, or the inventory of the release.

In code

JavaScript
import { ResourcePath } from '@beyond-js/artifact-api';

const style = ResourcePath.parse('/m/@example/[email protected]/styles/core/router');
style.kind; // 'style'
style.media; // 'text/css'
style.options(query); // the same Options as the module request

const asset = ResourcePath.parse('/m/@example/[email protected]/assets/images/logo.png');
asset.path; // 'images/logo.png'
asset.options(query); // undefined; any query throws OPTION_INVALID

ResourcePath.format({ kind: 'map', identity: style.identity }); // '/m/@example/[email protected]/maps/core/router'