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
/m/@example/app@1.0.0/styles/core/router?target=browser&format=esmA 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
./globalproduces 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
/m/@example/app@1.0.0/maps/core/router?target=browser&format=esmA 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
/m/@example/app@1.0.0/assets/images/logo.pngAn 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 itspackage.json(beyond.assets). A file that aurl()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 is404 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 are400 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 aLinkheader to its stylesheet. - With
types=true, a module response adds aLinkheader to its declarations, on a service that produces them. - A stylesheet response can carry a
SourceMapheader 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
import { ResourcePath } from '@beyond-js/artifact-api';
const style = ResourcePath.parse('/m/@example/app@1.0.0/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/app@1.0.0/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/app@1.0.0/maps/core/router'