@ethisyscore/vite-plugin 1.86.0 → 1.88.0
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.
- package/README.md +38 -0
- package/dist/index.cjs +58 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -1
- package/dist/index.d.ts +51 -1
- package/dist/index.js +58 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -24,6 +24,7 @@ match your render mode into `vite.config.ts`.
|
|
|
24
24
|
| `ethisysPlatformReactPlugin` | **PlatformReact** pass — builds one ESM bundle per declared page |
|
|
25
25
|
| `buildPlatformReactPages`, `rewriteAliasedExternalImports`, `virtualPageEntry` | Multi-bundle orchestrator: one isolated Vite sub-build per page, with host-externals dedupe (`PLATFORM_REACT_EXTERNALS`, `PLATFORM_REACT_DEDUPE`) and host-loader import rewriting |
|
|
26
26
|
| `generatePlatformReactManifest`, `computePlatformReactPages` | Build-step codegen — derive `feature.manifest.json` + the `extension.manifest.json` overlay from `routeMeta.json` |
|
|
27
|
+
| `parseExportedNames` | The runtime-named exports of one module's source text — how the codegen resolves a view whose exported name differs from its filename |
|
|
27
28
|
| `parsePlatformReactPages` | Test helper for snapshot-testing PlatformReact page declarations |
|
|
28
29
|
| `ethisysIframeSandboxPlugin`, `buildIframeSandboxPages` | **Tier U** iframe-sandbox pass — cross-origin sandboxed pages with SHA-256 digest emission |
|
|
29
30
|
| `ethisysManifestPlugin` | Legacy manifest-driven pass — auto-generates `index.html` for pages/surfaces that declare a `source` field |
|
|
@@ -44,6 +45,43 @@ Most plugins never call these directly — `cc package` drives the correct pass
|
|
|
44
45
|
for the plugin's declared render mode. Reach for the factories when you maintain
|
|
45
46
|
a bespoke build or need the validators/codegen in tests.
|
|
46
47
|
|
|
48
|
+
### How `generatePlatformReactManifest` resolves a `useView` name
|
|
49
|
+
|
|
50
|
+
Each page's `useView("<module>", "<Name>")` call becomes one entry of that page's
|
|
51
|
+
`views` map, and `build-pages` wires it with a **named** import
|
|
52
|
+
(`import { <Name> } from "<adapter module>"`). `<Name>` is resolved against the
|
|
53
|
+
adapter tree in this order:
|
|
54
|
+
|
|
55
|
+
1. **By filename** — an adapter file whose basename is `<Name>` (e.g.
|
|
56
|
+
`CompanyCostsView` → `src/adapter/finance/CompanyCostsView.tsx`). This is the
|
|
57
|
+
original and still the winning rule, so a plugin that follows the filename
|
|
58
|
+
convention resolves exactly as it always has.
|
|
59
|
+
2. **By exported name** — otherwise, the single adapter file that *exports*
|
|
60
|
+
`<Name>`. An adapter file may legitimately export its component under a name
|
|
61
|
+
other than its filename:
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
// src/adapter/chat/ChatChannelCreateFormView.tsx
|
|
65
|
+
export function ChatChannelCreateFormView(/* … */) { /* … */ }
|
|
66
|
+
export { ChatChannelCreateFormView as ChatChannelCreateForm };
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`useView("chat", "ChatChannelCreateForm")` is correct at runtime, and now
|
|
70
|
+
generates. `export function|const|let|var|class X`, `export { X }`,
|
|
71
|
+
`export { X as Y }` and `export { X } from "…"` all count.
|
|
72
|
+
|
|
73
|
+
`export default …` deliberately does **not** — a default export cannot satisfy
|
|
74
|
+
a named import — and neither do type-only exports (`export type` /
|
|
75
|
+
`export interface`), which are erased at runtime. `export * from "…"` is out of
|
|
76
|
+
reach of the scan, so a view reachable only that way still reports as
|
|
77
|
+
unresolvable rather than being mis-resolved.
|
|
78
|
+
3. **Two files export the same name** → the generator fails, naming every
|
|
79
|
+
candidate. Picking one would compile and then render the wrong component. Give
|
|
80
|
+
the intended file that basename (rule 1 always wins) or rename the view.
|
|
81
|
+
4. **Nothing matches** → the generator fails (unless `strict: false`). This error
|
|
82
|
+
is the point of the pass: `useView` is a manifest lookup, not an import, so an
|
|
83
|
+
unsatisfiable call renders "View not available" at runtime behind a green build.
|
|
84
|
+
|
|
47
85
|
## Build and test
|
|
48
86
|
|
|
49
87
|
```bash
|
package/dist/index.cjs
CHANGED
|
@@ -1639,6 +1639,31 @@ function parsePlatformReactPages(manifestPath, options = {}) {
|
|
|
1639
1639
|
return result;
|
|
1640
1640
|
}
|
|
1641
1641
|
var DEFAULT_SCHEMA = "https://ethisys.dev/schemas/feature-manifest.json";
|
|
1642
|
+
var EXPORT_DECL_RE = /^[ \t]*export\s+(?:async\s+)?(?:function|const|let|var|class)\s+([A-Za-z_$][\w$]*)/gm;
|
|
1643
|
+
var EXPORT_LIST_RE = /^[ \t]*export\s*\{([^}]*)\}/gm;
|
|
1644
|
+
var EXPORT_SPECIFIER_RE = /^([A-Za-z_$][\w$]*)(?:\s+as\s+([A-Za-z_$][\w$]*))?$/;
|
|
1645
|
+
function parseExportedNames(content) {
|
|
1646
|
+
const names = /* @__PURE__ */ new Set();
|
|
1647
|
+
EXPORT_DECL_RE.lastIndex = 0;
|
|
1648
|
+
let m;
|
|
1649
|
+
while ((m = EXPORT_DECL_RE.exec(content)) !== null) {
|
|
1650
|
+
names.add(m[1]);
|
|
1651
|
+
}
|
|
1652
|
+
EXPORT_LIST_RE.lastIndex = 0;
|
|
1653
|
+
while ((m = EXPORT_LIST_RE.exec(content)) !== null) {
|
|
1654
|
+
for (const raw of m[1].split(",")) {
|
|
1655
|
+
const item = raw.trim();
|
|
1656
|
+
if (item.length === 0) continue;
|
|
1657
|
+
if (/^type\s/.test(item)) continue;
|
|
1658
|
+
const spec = EXPORT_SPECIFIER_RE.exec(item);
|
|
1659
|
+
if (spec === null) continue;
|
|
1660
|
+
const exported = spec[2] ?? spec[1];
|
|
1661
|
+
if (exported === "default") continue;
|
|
1662
|
+
names.add(exported);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
return [...names];
|
|
1666
|
+
}
|
|
1642
1667
|
function computePlatformReactPages(input) {
|
|
1643
1668
|
const routePrefix = `/extensions/${input.slug}/`;
|
|
1644
1669
|
const routeRoot = `/extensions/${input.slug}`;
|
|
@@ -1655,6 +1680,7 @@ function computePlatformReactPages(input) {
|
|
|
1655
1680
|
const entries = [];
|
|
1656
1681
|
const missingViews = [];
|
|
1657
1682
|
const unregisteredViews = [];
|
|
1683
|
+
const ambiguousViews = [];
|
|
1658
1684
|
for (const { file, content, moduleSpecifier } of input.pages) {
|
|
1659
1685
|
const name = path.basename(file, ".tsx");
|
|
1660
1686
|
const override = input.pageOverrides?.[file] ?? {};
|
|
@@ -1671,6 +1697,15 @@ function computePlatformReactPages(input) {
|
|
|
1671
1697
|
}
|
|
1672
1698
|
if (input.viewFiles[v] !== void 0) {
|
|
1673
1699
|
views[v] = input.viewFiles[v];
|
|
1700
|
+
continue;
|
|
1701
|
+
}
|
|
1702
|
+
const byExport = input.viewExports?.[v] ?? [];
|
|
1703
|
+
if (byExport.length === 1) {
|
|
1704
|
+
views[v] = byExport[0];
|
|
1705
|
+
} else if (byExport.length > 1) {
|
|
1706
|
+
ambiguousViews.push(
|
|
1707
|
+
`${file}: view "${v}" matches no adapter filename and is exported by ${byExport.length} adapter files, so the binding is ambiguous: ${byExport.join(", ")}. Rename the view, or give the intended file that basename (a filename match always wins)`
|
|
1708
|
+
);
|
|
1674
1709
|
} else {
|
|
1675
1710
|
missingViews.push(`${file}: view "${v}" has no adapter file`);
|
|
1676
1711
|
}
|
|
@@ -1686,7 +1721,7 @@ function computePlatformReactPages(input) {
|
|
|
1686
1721
|
views
|
|
1687
1722
|
});
|
|
1688
1723
|
}
|
|
1689
|
-
return { entries, missingViews, unregisteredViews };
|
|
1724
|
+
return { entries, missingViews, unregisteredViews, ambiguousViews };
|
|
1690
1725
|
}
|
|
1691
1726
|
function walk2(dir) {
|
|
1692
1727
|
const out = [];
|
|
@@ -1713,13 +1748,26 @@ function generatePlatformReactManifest(config) {
|
|
|
1713
1748
|
const pageDirs = (Array.isArray(pagesCfg) ? pagesCfg : [pagesCfg]).map((p) => path.resolve(root, p));
|
|
1714
1749
|
const exclude = new Set(config.paths?.pageExclude ?? []);
|
|
1715
1750
|
const viewFiles = {};
|
|
1751
|
+
const viewExportsByName = /* @__PURE__ */ new Map();
|
|
1716
1752
|
if (fs.existsSync(adapterDir)) {
|
|
1717
1753
|
for (const f of walk2(adapterDir)) {
|
|
1718
1754
|
if (f.endsWith(".tsx")) {
|
|
1719
|
-
|
|
1755
|
+
const rel = slash2(path.relative(root, f));
|
|
1756
|
+
viewFiles[path.basename(f, ".tsx")] = rel;
|
|
1757
|
+
for (const name of parseExportedNames(fs.readFileSync(f, "utf8"))) {
|
|
1758
|
+
let paths = viewExportsByName.get(name);
|
|
1759
|
+
if (paths === void 0) {
|
|
1760
|
+
paths = /* @__PURE__ */ new Set();
|
|
1761
|
+
viewExportsByName.set(name, paths);
|
|
1762
|
+
}
|
|
1763
|
+
paths.add(rel);
|
|
1764
|
+
}
|
|
1720
1765
|
}
|
|
1721
1766
|
}
|
|
1722
1767
|
}
|
|
1768
|
+
const viewExports = Object.fromEntries(
|
|
1769
|
+
[...viewExportsByName].map(([name, paths]) => [name, [...paths].sort()])
|
|
1770
|
+
);
|
|
1723
1771
|
const seen = /* @__PURE__ */ new Map();
|
|
1724
1772
|
const pages = [];
|
|
1725
1773
|
for (const dir of pageDirs) {
|
|
@@ -1743,13 +1791,14 @@ function generatePlatformReactManifest(config) {
|
|
|
1743
1791
|
});
|
|
1744
1792
|
}
|
|
1745
1793
|
}
|
|
1746
|
-
const { entries, missingViews, unregisteredViews } = computePlatformReactPages({
|
|
1794
|
+
const { entries, missingViews, unregisteredViews, ambiguousViews } = computePlatformReactPages({
|
|
1747
1795
|
module,
|
|
1748
1796
|
moduleKeys: config.moduleKeys,
|
|
1749
1797
|
slug,
|
|
1750
1798
|
routeMeta,
|
|
1751
1799
|
pages,
|
|
1752
1800
|
viewFiles,
|
|
1801
|
+
viewExports,
|
|
1753
1802
|
pageOverrides: config.pageOverrides
|
|
1754
1803
|
});
|
|
1755
1804
|
const schema = config.manifest.schema ?? DEFAULT_SCHEMA;
|
|
@@ -1795,13 +1844,15 @@ function generatePlatformReactManifest(config) {
|
|
|
1795
1844
|
pagesWithoutView,
|
|
1796
1845
|
missingViews,
|
|
1797
1846
|
unregisteredViews,
|
|
1847
|
+
ambiguousViews,
|
|
1798
1848
|
featureManifestPath,
|
|
1799
1849
|
overlayPath
|
|
1800
1850
|
};
|
|
1801
|
-
|
|
1851
|
+
const diagnostics = [...unregisteredViews, ...ambiguousViews, ...missingViews];
|
|
1852
|
+
if ((config.strict ?? true) && diagnostics.length > 0) {
|
|
1802
1853
|
throw new Error(
|
|
1803
|
-
`generate-manifest: ${
|
|
1804
|
-
` +
|
|
1854
|
+
`generate-manifest: ${diagnostics.length} unresolvable useView call(s). Each renders "View not available" at runtime behind a green build:
|
|
1855
|
+
` + diagnostics.map((d) => ` - ${d}`).join("\n")
|
|
1805
1856
|
);
|
|
1806
1857
|
}
|
|
1807
1858
|
return result;
|
|
@@ -2166,6 +2217,7 @@ exports.findUnscopedSelectors = findUnscopedSelectors;
|
|
|
2166
2217
|
exports.formatUnscopedSelectorError = formatUnscopedSelectorError;
|
|
2167
2218
|
exports.generatePlatformReactManifest = generatePlatformReactManifest;
|
|
2168
2219
|
exports.isScopableCssId = isScopableCssId;
|
|
2220
|
+
exports.parseExportedNames = parseExportedNames;
|
|
2169
2221
|
exports.parsePlatformReactPages = parsePlatformReactPages;
|
|
2170
2222
|
exports.rewriteAliasedExternalImports = rewriteAliasedExternalImports;
|
|
2171
2223
|
exports.scopePluginCss = scopePluginCss;
|