@appshell/webpack-plugin 1.0.0-alpha.23 → 1.0.0-alpha.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,6 +11,6 @@ export { default as outdated } from './outdated';
11
11
  export { activate, publish } from './publish';
12
12
  export type { PublishOptions, PublishResult } from './publish';
13
13
  export { default as sync } from './sync';
14
- export type { AppshellComposition, AppshellConfig, AppshellConfigRemote, AppshellIndex, AppshellManifest, AppshellRemote, AppshellTemplate, ComparisonResult, ComparisonResults, ComparisonTarget, Metadata, ModuleFederationPluginOptions, PackageSpec, ResolvedRemote, Schema, SharedConfig, SharedModuleSpec, } from './types';
14
+ export type { AppshellComposition, AppshellConfig, AppshellConfigRemote, AppshellIndex, AppshellManifest, AppshellRemote, AppshellTemplate, AppshellTokenUsage, ComparisonResult, ComparisonResults, ComparisonTarget, Metadata, ModuleFederationPluginOptions, PackageSpec, ResolvedRemote, Schema, SharedConfig, SharedObject, SharedModuleSpec, } from './types';
15
15
  export * as utils from './utils';
16
16
  export * as validators from './validators';
@@ -1,4 +1,6 @@
1
+ import type { AppshellIndex, AppshellRemote, Metadata } from '@appshell/runtime';
1
2
  import { JSONSchema4, JSONSchema6, JSONSchema7 } from 'json-schema';
3
+ export type { AppshellIndex, AppshellRemote, Metadata } from '@appshell/runtime';
2
4
  export type Schema = JSONSchema4 | JSONSchema6 | JSONSchema7;
3
5
  export type ConfigValidator = {
4
6
  validate: <T>(...config: T[]) => void;
@@ -68,29 +70,34 @@ export type AppshellTemplate<TMetadata = Metadata> = {
68
70
  remotes?: Record<string, AppshellConfigRemote<TMetadata>>;
69
71
  module: ModuleFederationPluginOptions;
70
72
  vars?: Record<string, unknown>;
73
+ tokens?: Record<string, AppshellTokenUsage>;
71
74
  overrides?: AppshellOverrides;
72
75
  };
73
76
  /** Appshell manifest types */
74
- export type AppshellRemote<TMetadata = Metadata> = {
75
- id: string;
76
- manifestUrl: string;
77
- remoteEntryUrl: string;
78
- scope: string;
79
- module: string;
80
- shareScope?: string;
81
- metadata: TMetadata;
82
- };
83
77
  export type AppshellOverrides = {
84
78
  vars: Record<string, Record<string, string | number | undefined>>;
85
79
  };
80
+ /**
81
+ * Which design tokens a package's own output reaches for. Observed from the emitted
82
+ * assets rather than declared: the CSS already says it, and a hand-kept list is a copy
83
+ * that drifts the first time someone adds a token and forgets the yaml.
84
+ *
85
+ * `required` is a reference with no fallback — the package has no plan B. `optional` is
86
+ * `var(--appshell-x, something)`, which degrades on its own. That split is read off what
87
+ * the author wrote rather than asked of them.
88
+ */
89
+ export type AppshellTokenUsage = {
90
+ required: string[];
91
+ optional: string[];
92
+ };
86
93
  export type AppshellManifest<TMetadata = Metadata> = {
87
94
  remotes: Record<string, AppshellRemote<TMetadata>>;
88
95
  modules: Record<string, ModuleFederationPluginOptions>;
89
96
  vars: Record<string, Record<string, string | number | undefined>>;
97
+ /** Keyed by federation scope, so a merged manifest still says which package needs what. */
98
+ tokens?: Record<string, AppshellTokenUsage>;
90
99
  overrides?: AppshellOverrides;
91
100
  };
92
- export type AppshellIndex = Record<string, string>;
93
- export type Metadata = Record<string, unknown>;
94
101
  /** An `AppshellRemote` the registry already resolved, so the browser needs no manifest fetch. */
95
102
  export type ResolvedRemote<TMetadata = Metadata> = AppshellRemote<TMetadata>;
96
103
  /**
@@ -0,0 +1,32 @@
1
+ /**
2
+ * A package asked for vars that were never delivered.
3
+ *
4
+ * Almost always one of three things: the package was loaded outside `remoteLoader`
5
+ * (a test, a storybook, a direct import), the host and the package resolved separate
6
+ * copies of `@appshell/runtime` because one of them failed to declare it as a
7
+ * singleton, or the scope compiled into the package is not the scope the registry
8
+ * knows it by.
9
+ */
10
+ export declare class MissingVarsError extends Error {
11
+ readonly scope: string;
12
+ constructor(scope: string, known: string[]);
13
+ }
14
+ /**
15
+ * Something tried to replace vars that were already delivered for a scope.
16
+ *
17
+ * The first write wins and is final. Within one page load a scope has exactly one
18
+ * vars object — both resolvers read it from the same `composition.vars[scope]` — so a
19
+ * differing second write is a bug or a package reaching for a scope that is not its own,
20
+ * and neither should be applied quietly.
21
+ */
22
+ export declare class VarsConflictError extends Error {
23
+ readonly scope: string;
24
+ constructor(scope: string);
25
+ }
26
+ /**
27
+ * The package was built without `AppshellPlugin`, so nothing substituted the scope the
28
+ * `@appshell/runtime/vars` accessor needs to know which vars are its own.
29
+ */
30
+ export declare class MissingScopeError extends Error {
31
+ constructor();
32
+ }
@@ -0,0 +1,4 @@
1
+ export { MissingScopeError, MissingVarsError, VarsConflictError } from './errors';
2
+ export { hasVars, readVars, resetVars, setVars } from './store';
3
+ export type { Vars } from './types';
4
+ export type { AppshellIndex, AppshellRemote, Metadata } from './wire';
@@ -0,0 +1,25 @@
1
+ import type { Vars } from './types';
2
+ /**
3
+ * Delivers a scope's vars. Called by `@appshell/loader` immediately before the remote
4
+ * is loaded, so they are in place before the package's modules evaluate.
5
+ *
6
+ * The first write for a scope wins and is frozen. Re-delivering the identical vars is a
7
+ * no-op — the same remote can be mounted more than once — but replacing them throws.
8
+ * That is what keeps one package from overwriting another's: by the time any package
9
+ * evaluates, the host has already written for it.
10
+ *
11
+ * It does not make a scope's vars *private*. Any code on the page can still read another
12
+ * scope by name, and nothing short of a separate realm would change that.
13
+ */
14
+ export declare const setVars: (scope: string, vars: Vars) => void;
15
+ /**
16
+ * Reads a scope's vars, throwing rather than handing back an empty object — a package
17
+ * that silently renders with no configuration is the failure this replaced.
18
+ *
19
+ * Prefer `getVars()` from `@appshell/runtime/vars`, which supplies the scope for you.
20
+ */
21
+ export declare const readVars: <TVars extends Vars = Vars>(scope: string) => TVars;
22
+ /** Whether a scope has been delivered, for callers that want to branch instead of catch. */
23
+ export declare const hasVars: (scope: string) => boolean;
24
+ /** Test seam. Not part of the contract a package should build on. */
25
+ export declare const resetVars: () => void;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * A package's runtime configuration, already merged by the registry — a package's
3
+ * declared vars, then the application's `overrides.vars`.
4
+ *
5
+ * Structurally identical to `AppshellComposition['vars'][scope]` in `@appshell/config`,
6
+ * but declared here rather than imported. This package is a shared singleton loaded
7
+ * into every micro-frontend on the page, so it carries no dependencies at all.
8
+ */
9
+ export type Vars = Record<string, string | number | undefined>;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * What the browser is told about a remote.
3
+ *
4
+ * These live here rather than in `@appshell/config` because config is build tooling — it
5
+ * carries yaml, lodash and axios — and a package that only wants to type a remote should
6
+ * not install a compiler to get it. This package is already the one every micro-frontend
7
+ * on the page shares, and it has no dependencies of its own to pass on.
8
+ *
9
+ * `AppshellManifest` deliberately stays in `@appshell/config`. It is a build artifact, and
10
+ * its `modules` field is Module Federation plugin options — build-time webpack
11
+ * configuration the browser never sees and this package should never drag in.
12
+ */
13
+ /** Arbitrary, application-defined description of a remote. Appshell never reads it. */
14
+ export type Metadata = Record<string, unknown>;
15
+ /** A remote the registry has already resolved, so the browser needs no manifest fetch. */
16
+ export type AppshellRemote<TMetadata = Metadata> = {
17
+ id: string;
18
+ manifestUrl: string;
19
+ remoteEntryUrl: string;
20
+ scope: string;
21
+ module: string;
22
+ shareScope?: string;
23
+ metadata: TMetadata;
24
+ };
25
+ /** Remote key to manifest url. */
26
+ export type AppshellIndex = Record<string, string>;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The token contract: the complete, fixed vocabulary a package may consume and an
3
+ * Application must supply.
4
+ *
5
+ * Small and stable is the property that matters. A package authors against these names
6
+ * with fallbacks (`var(--appshell-primary, #0af)`) and renders standalone; an Application
7
+ * supplies the values and every package it composes moves together.
8
+ */
9
+ export type Mode = 'light' | 'dark';
10
+ /** Colour roles an Application's *base* supplies — everything not tied to the accent. */
11
+ export declare const BASE_COLOR_ROLES: readonly ["surface", "on-surface", "surface-raised", "on-surface-raised", "text-muted", "border", "danger", "on-danger", "warning", "on-warning", "success", "on-success"];
12
+ /** Colour roles an Application's *accent* supplies. */
13
+ export declare const ACCENT_COLOR_ROLES: readonly ["primary", "on-primary", "secondary", "on-secondary"];
14
+ /**
15
+ * Derived rather than authored. Hover and active are computed from their accent with
16
+ * `color-mix`, and the focus ring is picked per base+accent so it stays visible. A theme
17
+ * may override any of them; none has to be supplied.
18
+ */
19
+ export declare const DERIVED_COLOR_ROLES: readonly ["primary-hover", "primary-active", "secondary-hover", "secondary-active", "focus-ring"];
20
+ /**
21
+ * Named for their role, not their size. A numeric scale would reintroduce exactly the
22
+ * divergence this contract exists to prevent — one author mapping `h1` to `2xl` and
23
+ * another to `xl` is how headings stop matching across a composed page.
24
+ */
25
+ export declare const TYPE_ROLES: readonly ["font-body", "font-mono", "font-size-h1", "font-size-h2", "font-size-h3", "font-size-h4", "font-size-h5", "font-size-h6", "font-size-body", "font-size-small", "line-height-tight", "line-height-normal"];
26
+ /** Genuinely a scale. Nobody wants `--appshell-space-card-padding`. */
27
+ export declare const DIMENSION_ROLES: readonly ["space-xs", "space-sm", "space-md", "space-lg", "space-xl", "radius-sm", "radius-md", "radius-lg"];
28
+ export type BaseColorRole = (typeof BASE_COLOR_ROLES)[number];
29
+ export type AccentColorRole = (typeof ACCENT_COLOR_ROLES)[number];
30
+ export type DerivedColorRole = (typeof DERIVED_COLOR_ROLES)[number];
31
+ export type TypeRole = (typeof TYPE_ROLES)[number];
32
+ export type DimensionRole = (typeof DIMENSION_ROLES)[number];
33
+ export type TokenRole = BaseColorRole | AccentColorRole | DerivedColorRole | TypeRole | DimensionRole;
34
+ export declare const TOKEN_ROLES: readonly TokenRole[];
35
+ export type BaseTokens = Record<BaseColorRole, string>;
36
+ export type AccentTokens = Record<AccentColorRole, string>;
37
+ export type Theme = Record<TokenRole, string>;
38
+ /** The custom property a role is published as. */
39
+ export declare const cssVar: (role: TokenRole) => string;
40
+ /**
41
+ * Pairs the registry checks for contrast. Because every role that carries text has an
42
+ * `on-` partner, a theme cannot express illegible text without failing this list — which
43
+ * is the failure that actually reaches users, and the one a CSS parser alone never catches.
44
+ */
45
+ export declare const TEXT_PAIRS: readonly (readonly [TokenRole, TokenRole])[];
46
+ /** Muted text has no `on-` partner; it is read against both surfaces. */
47
+ export declare const MUTED_AGAINST: readonly TokenRole[];
48
+ /** Non-text, so 3:1 rather than 4.5:1 — WCAG 1.4.11. */
49
+ export declare const NON_TEXT_PAIRS: readonly (readonly [TokenRole, TokenRole])[];
50
+ export declare const AA_TEXT = 4.5;
51
+ export declare const AA_NON_TEXT = 3;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Contrast checking for a theme.
3
+ *
4
+ * Syntax validation on its own earns little: it catches `--appshell-primary: bananas` and
5
+ * misses `--appshell-on-primary: #fff` on `--appshell-primary: #fff`, which is valid CSS
6
+ * and invisible text. Checking the `on-` pairs catches the failure that reaches users, and
7
+ * subsumes syntax validation for free — a value that will not parse cannot be measured.
8
+ */
9
+ import { type Theme, type TokenRole } from './contract';
10
+ type Rgb = [number, number, number];
11
+ /** Returns undefined rather than throwing — an unmeasurable value is reported, not fatal. */
12
+ export declare const parseColor: (value: string) => Rgb | undefined;
13
+ /** WCAG 2.1 contrast ratio, 1–21. */
14
+ export declare const contrastRatio: (a: Rgb, b: Rgb) => number;
15
+ /**
16
+ * A colour as `#rrggbb`.
17
+ *
18
+ * `<meta name="theme-color">` takes a CSS colour, but browser support for `oklch` there
19
+ * is not something to rely on for a hint the page cannot detect the failure of — a
20
+ * value the browser does not understand is simply ignored, silently.
21
+ */
22
+ export declare const toHex: (value: string) => string | undefined;
23
+ export type ContrastFinding = {
24
+ roles: [TokenRole, TokenRole];
25
+ ratio?: number;
26
+ required: number;
27
+ reason: 'below-threshold' | 'unparseable';
28
+ };
29
+ /**
30
+ * Every finding for a theme. Empty means it passes.
31
+ *
32
+ * `color-mix` values are skipped rather than failed: hover and active are derived from
33
+ * roles that are themselves checked, and resolving them needs a browser.
34
+ */
35
+ export declare const validateTheme: (theme: Theme) => ContrastFinding[];
36
+ export declare const describeFinding: ({ roles, ratio, required, reason }: ContrastFinding) => string;
37
+ export {};
@@ -0,0 +1,4 @@
1
+ export * from './contract';
2
+ export { contrastRatio, describeFinding, parseColor, toHex, validateTheme, type ContrastFinding, } from './contrast';
3
+ export { ACCENTS, BASES, FOCUS_RINGS } from './presets';
4
+ export { composeTheme, DEFAULT_TYPE_AND_DIMENSIONS, pinnedMode, toCss, type ColorScheme, type ThemeSelection, } from './theme';
@@ -0,0 +1,4 @@
1
+ import type { AccentTokens, BaseTokens, Mode } from './contract';
2
+ export declare const BASES: Record<string, Record<Mode, BaseTokens>>;
3
+ export declare const ACCENTS: Record<string, AccentTokens>;
4
+ export declare const FOCUS_RINGS: Record<string, Record<string, Record<Mode, string>>>;
@@ -0,0 +1,30 @@
1
+ import { type AccentTokens, type BaseTokens, type Mode, type Theme } from './contract';
2
+ /**
3
+ * `system` follows the viewer's preference. Pinning a scheme is for an application that
4
+ * ships one look on purpose, and it is honoured all the way down: the palette stops
5
+ * varying, the root is stamped so the tokens cannot be swapped underneath it, and the
6
+ * browser is told which scheme to render its own scrollbars and form controls in.
7
+ */
8
+ export type ColorScheme = 'system' | 'light' | 'dark';
9
+ export type ThemeSelection = {
10
+ /** A base preset name, or the base's own token values. */
11
+ base: string | Record<Mode, BaseTokens>;
12
+ /** An accent preset name, or the accent's own token values. */
13
+ accent: string | AccentTokens;
14
+ /** Defaults to `system`. */
15
+ colorScheme?: ColorScheme;
16
+ /** Overrides applied last, so an Application can adjust a preset without forking it. */
17
+ overrides?: Partial<Theme>;
18
+ };
19
+ /** The mode a pinned scheme resolves to, or undefined when it follows the viewer. */
20
+ export declare const pinnedMode: (selection: ThemeSelection) => Mode | undefined;
21
+ /** Type and dimensions do not vary by mode, and no preset currently changes them. */
22
+ export declare const DEFAULT_TYPE_AND_DIMENSIONS: Record<string, string>;
23
+ /** The full token map for one mode. */
24
+ export declare const composeTheme: (selection: ThemeSelection, mode: Mode) => Theme;
25
+ /**
26
+ * Three states, not two: an explicit choice in either direction, and the system default
27
+ * when nothing is stamped on the root. A theme that only handled `prefers-color-scheme`
28
+ * would give a viewer no way to override it.
29
+ */
30
+ export declare const toCss: (selection: ThemeSelection) => string;
@@ -24,6 +24,29 @@ export default class AppshellPlugin {
24
24
  static findModuleFederationPlugin(webpackConfig: WebpackOptionsNormalized): ModuleFederationPluginInstance | undefined;
25
25
  static createTemplate(config: AppshellConfig, plugin: ModuleFederationPluginInstance): AppshellTemplate;
26
26
  static validate(template: AppshellTemplate): boolean;
27
+ /**
28
+ * Which tokens this package's output actually reaches for.
29
+ *
30
+ * Read from the emitted assets rather than declared. The CSS already states it, and a
31
+ * hand-kept list is a second copy that goes stale the first time somebody adds a token
32
+ * and forgets to update it. This cannot drift, because it *is* the usage.
33
+ *
34
+ * A role referenced both with and without a fallback counts as required: one place in
35
+ * the package has nothing to fall back to.
36
+ *
37
+ * The blind spot is a reference built at runtime from a constructed string, which no
38
+ * static scan sees — the same limit Tailwind has with dynamic class names. It fails
39
+ * toward under-reporting, never toward inventing a requirement.
40
+ *
41
+ * Takes file contents rather than webpack assets: by `afterEmit` the compilation has
42
+ * swapped its sources for `SizeOnlySource`, which knows a length and nothing else.
43
+ * The files are on disk by then, which is what the hook means.
44
+ */
45
+ static tokenUsage(sources?: Record<string, string>): {
46
+ required: string[];
47
+ optional: string[];
48
+ unknown: string[];
49
+ };
27
50
  /**
28
51
  * Whether a request is shared, and shared as a singleton. `shared` has four shapes —
29
52
  * an object, an array of names, an array of objects, or a mix — and a bare name shares
@@ -5,3 +5,4 @@ export type { AppshellManifest } from '@appshell/config';
5
5
  export { default as AppshellPlugin } from './AppshellPlugin';
6
6
  export { DEV_HINT_FILE, DEV_HINT_VERSION, devServerOrigin, writeDevHint } from './devHint';
7
7
  export type { DevHint } from './devHint';
8
+ export { appshellShared, type AppshellSharedOptions } from './shared';
@@ -0,0 +1,36 @@
1
+ import type { SharedObject } from '@appshell/config';
2
+ export type AppshellSharedOptions = {
3
+ /** Include the React bindings and React itself. */
4
+ react?: boolean;
5
+ /**
6
+ * Usually your package.json `dependencies`. Anything named here is pinned to the range
7
+ * you depend on; anything absent is left for module federation to infer, which it does
8
+ * from the installed package.
9
+ */
10
+ dependencies?: Record<string, string>;
11
+ /** Merged last, so a package can still say something the preset does not. */
12
+ extra?: SharedObject;
13
+ };
14
+ /**
15
+ * The `shared` block an Appshell package needs, so it is not written out by hand in every
16
+ * webpack config and wrong in one of them.
17
+ *
18
+ * It exists because the alternative failed in practice: the examples in this repo declared
19
+ * `@appshell/react` as a singleton in three configs and omitted it in a fourth, which is
20
+ * silent until a root package calls `useRemote()` and gets `undefined`.
21
+ *
22
+ * `AppshellPlugin` cannot inject this itself — `ModuleFederationPlugin` reads its own
23
+ * options during its `apply`, which webpack has already run by the time it reaches ours.
24
+ * Spread into your own config, it sidesteps plugin ordering entirely:
25
+ *
26
+ * ```js
27
+ * const { appshellShared } = require('@appshell/webpack-plugin');
28
+ * const { dependencies } = require('./package.json');
29
+ *
30
+ * new ModuleFederationPlugin({
31
+ * shared: appshellShared({ react: true, dependencies }),
32
+ * });
33
+ * ```
34
+ */
35
+ export declare const appshellShared: ({ react, dependencies, extra, }?: AppshellSharedOptions) => SharedObject;
36
+ export default appshellShared;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appshell/webpack-plugin",
3
- "version": "1.0.0-alpha.23",
3
+ "version": "1.0.0-alpha.25",
4
4
  "description": "Webpack plugin used to generate a global Appshell configuration for micro-frontends built with Module Federation",
5
5
  "main": "dist/main.js",
6
6
  "types": "dist/types/webpack-plugin/src/index.d.ts",
@@ -32,5 +32,8 @@
32
32
  "plugin"
33
33
  ],
34
34
  "license": "MIT",
35
- "gitHead": "c6a783bb4602f305b98b2cc618a5a20e74538fca"
35
+ "gitHead": "7d90eb91be3cb50aae5aaa680063878dfcaafea2",
36
+ "dependencies": {
37
+ "@appshell/tokens": "^1.0.0-alpha.25"
38
+ }
36
39
  }