@ethisyscore/vite-plugin 1.31.0 → 1.33.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/dist/index.cjs +330 -247
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +42 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +332 -249
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -607,6 +607,221 @@ function titleCase(s) {
|
|
|
607
607
|
function escapeRegExp(s) {
|
|
608
608
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
609
609
|
}
|
|
610
|
+
var PLATFORM_REACT_EXTERNALS = [
|
|
611
|
+
"react",
|
|
612
|
+
"react/jsx-runtime",
|
|
613
|
+
"react-dom",
|
|
614
|
+
"@mui/material",
|
|
615
|
+
"@mui/icons-material",
|
|
616
|
+
"@emotion/react",
|
|
617
|
+
"@emotion/styled",
|
|
618
|
+
"date-fns",
|
|
619
|
+
"@ethisyscore/components-react",
|
|
620
|
+
"@ethisyscore/extension-runtime",
|
|
621
|
+
"@ethisyscore/extension-runtime/plugin"
|
|
622
|
+
];
|
|
623
|
+
var PLATFORM_REACT_DEDUPE = [
|
|
624
|
+
"@tanstack/react-query",
|
|
625
|
+
"react",
|
|
626
|
+
"react-dom",
|
|
627
|
+
"@ethisyscore/extension-runtime"
|
|
628
|
+
];
|
|
629
|
+
var DEFAULT_OUT_DIR = "dist";
|
|
630
|
+
var DEFAULT_OUTPUT_PREFIX = "platform-react";
|
|
631
|
+
var DEFAULT_DEFINE_PLUGIN_PAGE = "@/app/definePluginPage";
|
|
632
|
+
var DEFAULT_SRC_DIR = "src";
|
|
633
|
+
var DEFAULT_ALIAS_PREFIX = "@";
|
|
634
|
+
var SUMMARY_FILENAME = "platform-react-pages.json";
|
|
635
|
+
var DIGEST_MAP_FILENAME = "platform-react-digest-map.json";
|
|
636
|
+
function rewriteAliasedExternalImports() {
|
|
637
|
+
return {
|
|
638
|
+
name: "rewrite-aliased-external-imports",
|
|
639
|
+
renderChunk(code) {
|
|
640
|
+
let counter = 0;
|
|
641
|
+
let changed = false;
|
|
642
|
+
const out = code.replace(
|
|
643
|
+
// Optional default binding (`import Foo, { … }`) then the named braces.
|
|
644
|
+
/\bimport\s+(?:([\w$]+)\s*,\s*)?\{([^{}]*)\}\s*from\s*("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')\s*;?/g,
|
|
645
|
+
(full, def, specs, mod) => {
|
|
646
|
+
if (!/\sas\s/.test(specs)) return full;
|
|
647
|
+
changed = true;
|
|
648
|
+
const ns = `__ns_imp_${counter++}`;
|
|
649
|
+
const decls = specs.split(",").map((s) => s.trim()).filter(Boolean).map((s) => {
|
|
650
|
+
const [imported, local] = s.split(/\s+as\s+/).map((t) => t.trim());
|
|
651
|
+
return `${local ?? imported} = ${ns}.${imported}`;
|
|
652
|
+
}).join(", ");
|
|
653
|
+
const defPart = def ? `import ${def} from ${mod}; ` : "";
|
|
654
|
+
return `${defPart}import * as ${ns} from ${mod}; const ${decls};`;
|
|
655
|
+
}
|
|
656
|
+
);
|
|
657
|
+
return changed ? { code: out, map: null } : null;
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
function virtualPageEntry(page, config) {
|
|
662
|
+
const VIRTUAL_ID = `\0virtual:surface/${page.id}`;
|
|
663
|
+
const views = page.views ?? {};
|
|
664
|
+
const names = Object.keys(views);
|
|
665
|
+
const viewImports = names.map((n) => `import { ${n} } from ${JSON.stringify(config.toAlias(views[n]))};`).join("\n");
|
|
666
|
+
const src = `import Page from ${JSON.stringify(config.toAlias(page.moduleSpecifier))};
|
|
667
|
+
${viewImports}
|
|
668
|
+
import { definePluginPage } from ${JSON.stringify(config.definePluginPageSpecifier)};
|
|
669
|
+
export default definePluginPage(Page, { ${names.join(", ")} });
|
|
670
|
+
`;
|
|
671
|
+
return {
|
|
672
|
+
id: VIRTUAL_ID,
|
|
673
|
+
plugin: {
|
|
674
|
+
name: `virtual-surface-${page.id}`,
|
|
675
|
+
resolveId: (id) => id === VIRTUAL_ID ? VIRTUAL_ID : null,
|
|
676
|
+
load: (id) => id === VIRTUAL_ID ? src : null
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
function defaultWriteFile(path$1, contents) {
|
|
681
|
+
fs.mkdirSync(path.dirname(path$1), { recursive: true });
|
|
682
|
+
fs.writeFileSync(path$1, contents);
|
|
683
|
+
}
|
|
684
|
+
function readManifest(options, root) {
|
|
685
|
+
if (options.manifest !== void 0) {
|
|
686
|
+
return options.manifest;
|
|
687
|
+
}
|
|
688
|
+
const rel = options.manifestPath ?? "feature.manifest.json";
|
|
689
|
+
const abs = path.isAbsolute(rel) ? rel : path.resolve(root, rel);
|
|
690
|
+
if (!fs.existsSync(abs)) {
|
|
691
|
+
return null;
|
|
692
|
+
}
|
|
693
|
+
const raw = fs.readFileSync(abs, "utf-8");
|
|
694
|
+
try {
|
|
695
|
+
return JSON.parse(raw);
|
|
696
|
+
} catch (e) {
|
|
697
|
+
throw new Error(
|
|
698
|
+
`[ethisys-platform-react] Failed to parse manifest at "${abs}": ${e.message}`
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
function makeDefaultBuild(viteBuild) {
|
|
703
|
+
return async (ctx) => {
|
|
704
|
+
const build = viteBuild ?? (await import('vite')).build;
|
|
705
|
+
await build({
|
|
706
|
+
root: ctx.root,
|
|
707
|
+
configFile: false,
|
|
708
|
+
plugins: [ctx.surfacePlugin, ...ctx.plugins, rewriteAliasedExternalImports()],
|
|
709
|
+
resolve: {
|
|
710
|
+
dedupe: [...ctx.dedupe],
|
|
711
|
+
alias: ctx.alias
|
|
712
|
+
},
|
|
713
|
+
build: {
|
|
714
|
+
target: ctx.target,
|
|
715
|
+
outDir: ctx.outDir,
|
|
716
|
+
emptyOutDir: false,
|
|
717
|
+
assetsDir: "",
|
|
718
|
+
minify: ctx.minify,
|
|
719
|
+
sourcemap: ctx.sourcemap,
|
|
720
|
+
rollupOptions: {
|
|
721
|
+
input: { [ctx.pageId]: ctx.entryId },
|
|
722
|
+
preserveEntrySignatures: "strict",
|
|
723
|
+
external: [...ctx.external],
|
|
724
|
+
output: {
|
|
725
|
+
format: "es",
|
|
726
|
+
inlineDynamicImports: true,
|
|
727
|
+
entryFileNames: `${ctx.outputPrefix}/[name].js`,
|
|
728
|
+
chunkFileNames: `${ctx.outputPrefix}/[name]-[hash].js`,
|
|
729
|
+
assetFileNames: `${ctx.outputPrefix}/[name][extname]`
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
},
|
|
733
|
+
logLevel: "warn"
|
|
734
|
+
});
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
async function buildPlatformReactPages(options = {}) {
|
|
738
|
+
const root = options.root ?? process.cwd();
|
|
739
|
+
const logger = options.logger ?? console;
|
|
740
|
+
const manifest = readManifest(options, root);
|
|
741
|
+
const declared = manifest?.ui?.platformReactPages;
|
|
742
|
+
if (!Array.isArray(declared) || declared.length === 0) {
|
|
743
|
+
logger.warn("[build-pages] no platformReactPages declared \u2014 nothing to build");
|
|
744
|
+
return { pages: [], digests: [], summaryPath: null, digestMapPath: null };
|
|
745
|
+
}
|
|
746
|
+
const seen = /* @__PURE__ */ new Set();
|
|
747
|
+
const pages = declared.map((raw, i) => {
|
|
748
|
+
const decl = raw;
|
|
749
|
+
if (typeof decl?.id !== "string" || decl.id.length === 0) {
|
|
750
|
+
throw new Error(`[build-pages] ui.platformReactPages[${i}].id is required (non-empty string).`);
|
|
751
|
+
}
|
|
752
|
+
if (seen.has(decl.id)) {
|
|
753
|
+
throw new Error(`[build-pages] Duplicate page id "${decl.id}".`);
|
|
754
|
+
}
|
|
755
|
+
seen.add(decl.id);
|
|
756
|
+
if (typeof decl.moduleSpecifier !== "string" || decl.moduleSpecifier.length === 0) {
|
|
757
|
+
throw new Error(`[build-pages] surface "${decl.id}" has no moduleSpecifier in the manifest.`);
|
|
758
|
+
}
|
|
759
|
+
return decl;
|
|
760
|
+
});
|
|
761
|
+
const outDir = options.outDir ?? DEFAULT_OUT_DIR;
|
|
762
|
+
const outDirAbs = path.isAbsolute(outDir) ? outDir : path.resolve(root, outDir);
|
|
763
|
+
const outputPrefix = (options.outputPrefix ?? DEFAULT_OUTPUT_PREFIX).replace(/^\/+|\/+$/g, "");
|
|
764
|
+
const outputPrefixDir = path.resolve(outDirAbs, outputPrefix);
|
|
765
|
+
const external = options.external ?? PLATFORM_REACT_EXTERNALS;
|
|
766
|
+
const dedupe = options.dedupe ?? PLATFORM_REACT_DEDUPE;
|
|
767
|
+
const alias = options.alias ?? [];
|
|
768
|
+
const plugins = options.plugins ?? [];
|
|
769
|
+
const definePluginPageSpecifier = options.definePluginPageSpecifier ?? DEFAULT_DEFINE_PLUGIN_PAGE;
|
|
770
|
+
const srcDir = options.srcDir ?? DEFAULT_SRC_DIR;
|
|
771
|
+
const aliasPrefix = options.aliasPrefix ?? DEFAULT_ALIAS_PREFIX;
|
|
772
|
+
const srcRe = new RegExp(`^${escapeRegExp(srcDir)}/`);
|
|
773
|
+
const toAlias = (p) => `${aliasPrefix}/${p.replace(srcRe, "")}`;
|
|
774
|
+
const write = options.writeFile ?? defaultWriteFile;
|
|
775
|
+
const read = options.readFile ?? ((p) => fs.readFileSync(p, "utf-8"));
|
|
776
|
+
const runBuild = options.build ?? makeDefaultBuild(options.viteBuild);
|
|
777
|
+
if (options.clean ?? true) {
|
|
778
|
+
if (fs.existsSync(outDirAbs)) {
|
|
779
|
+
fs.rmSync(outDirAbs, { recursive: true, force: true });
|
|
780
|
+
}
|
|
781
|
+
fs.mkdirSync(outputPrefixDir, { recursive: true });
|
|
782
|
+
}
|
|
783
|
+
for (const page of pages) {
|
|
784
|
+
const { id: entryId, plugin: surfacePlugin } = virtualPageEntry(page, {
|
|
785
|
+
definePluginPageSpecifier,
|
|
786
|
+
toAlias
|
|
787
|
+
});
|
|
788
|
+
logger.log(`[build-pages] ${page.id} \u2190 ${page.moduleSpecifier}`);
|
|
789
|
+
await runBuild({
|
|
790
|
+
pageId: page.id,
|
|
791
|
+
entryId,
|
|
792
|
+
surfacePlugin,
|
|
793
|
+
root,
|
|
794
|
+
outDir: outDirAbs,
|
|
795
|
+
outputPrefix,
|
|
796
|
+
external,
|
|
797
|
+
dedupe,
|
|
798
|
+
alias,
|
|
799
|
+
plugins,
|
|
800
|
+
target: options.target ?? "es2022",
|
|
801
|
+
minify: options.minify ?? false,
|
|
802
|
+
sourcemap: options.sourcemap ?? true
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
const summaryPages = pages.map((p) => ({
|
|
806
|
+
id: p.id,
|
|
807
|
+
exportName: p.exportName ?? "default",
|
|
808
|
+
title: p.title ?? null,
|
|
809
|
+
bundlePath: `${outputPrefix}/${p.id}.js`,
|
|
810
|
+
source: p.moduleSpecifier
|
|
811
|
+
}));
|
|
812
|
+
const summaryPath = path.resolve(outputPrefixDir, SUMMARY_FILENAME);
|
|
813
|
+
write(summaryPath, JSON.stringify({ pages: summaryPages }, null, 2));
|
|
814
|
+
const digests = pages.map((p) => `${outputPrefix}/${p.id}.js`).sort().map((relPath) => {
|
|
815
|
+
const code = read(path.resolve(outDirAbs, relPath));
|
|
816
|
+
return { path: relPath, sha256: crypto.createHash("sha256").update(code, "utf8").digest("hex") };
|
|
817
|
+
});
|
|
818
|
+
const digestMapPath = path.resolve(outputPrefixDir, DIGEST_MAP_FILENAME);
|
|
819
|
+
write(digestMapPath, JSON.stringify({ digests }, null, 2));
|
|
820
|
+
logger.log(
|
|
821
|
+
`[build-pages] emitted ${pages.length} page(s) + summary + digest map (${digests.length} digests)`
|
|
822
|
+
);
|
|
823
|
+
return { pages: summaryPages, digests, summaryPath, digestMapPath };
|
|
824
|
+
}
|
|
610
825
|
|
|
611
826
|
// src/platform-react/page-bundles.ts
|
|
612
827
|
var ID_REGEX = /^[a-z0-9]+(?:[-_][a-z0-9]+)*$/i;
|
|
@@ -640,6 +855,7 @@ function ethisysPlatformReactPlugin(options = {}) {
|
|
|
640
855
|
let resolvedRoot;
|
|
641
856
|
let manifestAbsPath;
|
|
642
857
|
let pages = [];
|
|
858
|
+
let overlay = null;
|
|
643
859
|
function resolveRoot() {
|
|
644
860
|
if (resolvedRoot) {
|
|
645
861
|
return resolvedRoot;
|
|
@@ -664,54 +880,94 @@ function ethisysPlatformReactPlugin(options = {}) {
|
|
|
664
880
|
}
|
|
665
881
|
function validate() {
|
|
666
882
|
pages = [];
|
|
883
|
+
overlay = null;
|
|
667
884
|
const manifest = readManifest3();
|
|
668
885
|
if (manifest === null) {
|
|
669
886
|
return;
|
|
670
887
|
}
|
|
671
888
|
const declared = manifest.ui?.platformReactPages;
|
|
672
|
-
if (
|
|
673
|
-
|
|
889
|
+
if (Array.isArray(declared) && declared.length > 0) {
|
|
890
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
891
|
+
for (let i = 0; i < declared.length; i++) {
|
|
892
|
+
const decl = declared[i];
|
|
893
|
+
const where = `ui.platformReactPages[${i}]`;
|
|
894
|
+
if (typeof decl?.id !== "string" || decl.id.length === 0) {
|
|
895
|
+
throw new Error(
|
|
896
|
+
`[ethisys-platform-react] ${where}.id is required (non-empty string).`
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
if (!ID_REGEX.test(decl.id)) {
|
|
900
|
+
throw new Error(
|
|
901
|
+
`[ethisys-platform-react] ${where}.id "${decl.id}" must match [a-z0-9_-]+ (URL-safe; case-insensitive).`
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
if (seenIds.has(decl.id)) {
|
|
905
|
+
throw new Error(
|
|
906
|
+
`[ethisys-platform-react] Duplicate page id "${decl.id}".`
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
seenIds.add(decl.id);
|
|
910
|
+
if (typeof decl.moduleSpecifier !== "string" || decl.moduleSpecifier.length === 0) {
|
|
911
|
+
throw new Error(
|
|
912
|
+
`[ethisys-platform-react] ${where}.moduleSpecifier is required (non-empty relative path).`
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
assertHostOriginRelativePath2(decl.moduleSpecifier, `${where}.moduleSpecifier`);
|
|
916
|
+
const absPath = path.resolve(resolvedRoot, decl.moduleSpecifier);
|
|
917
|
+
if (!fs.existsSync(absPath)) {
|
|
918
|
+
throw new Error(
|
|
919
|
+
`[ethisys-platform-react] ${where}.moduleSpecifier "${decl.moduleSpecifier}" does not exist on disk (resolved: ${absPath}).`
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
const exportName = typeof decl.exportName === "string" && decl.exportName.length > 0 ? decl.exportName : "default";
|
|
923
|
+
pages.push({
|
|
924
|
+
id: decl.id,
|
|
925
|
+
exportName,
|
|
926
|
+
title: typeof decl.title === "string" && decl.title.length > 0 ? decl.title : null,
|
|
927
|
+
moduleSpecifierRel: decl.moduleSpecifier,
|
|
928
|
+
moduleSpecifierAbs: absPath
|
|
929
|
+
});
|
|
930
|
+
}
|
|
674
931
|
}
|
|
675
|
-
const
|
|
676
|
-
|
|
677
|
-
const
|
|
678
|
-
|
|
679
|
-
if (typeof decl?.id !== "string" || decl.id.length === 0) {
|
|
932
|
+
const overlayDecl = manifest.ui?.platformReactOverlay;
|
|
933
|
+
if (overlayDecl !== void 0 && overlayDecl !== null) {
|
|
934
|
+
const where = "ui.platformReactOverlay";
|
|
935
|
+
if (typeof overlayDecl.id !== "string" || overlayDecl.id.length === 0) {
|
|
680
936
|
throw new Error(
|
|
681
937
|
`[ethisys-platform-react] ${where}.id is required (non-empty string).`
|
|
682
938
|
);
|
|
683
939
|
}
|
|
684
|
-
if (!ID_REGEX.test(
|
|
940
|
+
if (!ID_REGEX.test(overlayDecl.id)) {
|
|
685
941
|
throw new Error(
|
|
686
|
-
`[ethisys-platform-react] ${where}.id "${
|
|
942
|
+
`[ethisys-platform-react] ${where}.id "${overlayDecl.id}" must match [a-z0-9_-]+ (URL-safe; case-insensitive).`
|
|
687
943
|
);
|
|
688
944
|
}
|
|
689
|
-
if (
|
|
945
|
+
if (typeof overlayDecl.moduleSpecifier !== "string" || overlayDecl.moduleSpecifier.length === 0) {
|
|
690
946
|
throw new Error(
|
|
691
|
-
`[ethisys-platform-react]
|
|
947
|
+
`[ethisys-platform-react] ${where}.moduleSpecifier is required (non-empty relative path).`
|
|
692
948
|
);
|
|
693
949
|
}
|
|
694
|
-
|
|
695
|
-
|
|
950
|
+
assertHostOriginRelativePath2(overlayDecl.moduleSpecifier, `${where}.moduleSpecifier`);
|
|
951
|
+
const absPath = path.resolve(resolvedRoot, overlayDecl.moduleSpecifier);
|
|
952
|
+
if (!fs.existsSync(absPath)) {
|
|
696
953
|
throw new Error(
|
|
697
|
-
`[ethisys-platform-react] ${where}.moduleSpecifier
|
|
954
|
+
`[ethisys-platform-react] ${where}.moduleSpecifier "${overlayDecl.moduleSpecifier}" does not exist on disk (resolved: ${absPath}).`
|
|
698
955
|
);
|
|
699
956
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
if (!fs.existsSync(absPath)) {
|
|
957
|
+
const collidingPage = pages.find((p) => p.id === overlayDecl.id);
|
|
958
|
+
if (collidingPage !== void 0) {
|
|
703
959
|
throw new Error(
|
|
704
|
-
`[ethisys-platform-react]
|
|
960
|
+
`[ethisys-platform-react] ui.platformReactOverlay.id "${overlayDecl.id}" collides with a declared page id \u2014 ids must be unique across pages and the overlay.`
|
|
705
961
|
);
|
|
706
962
|
}
|
|
707
|
-
const exportName = typeof
|
|
708
|
-
|
|
709
|
-
id:
|
|
963
|
+
const exportName = typeof overlayDecl.exportName === "string" && overlayDecl.exportName.length > 0 ? overlayDecl.exportName : "default";
|
|
964
|
+
overlay = {
|
|
965
|
+
id: overlayDecl.id,
|
|
710
966
|
exportName,
|
|
711
|
-
title: typeof
|
|
712
|
-
moduleSpecifierRel:
|
|
967
|
+
title: typeof overlayDecl.title === "string" && overlayDecl.title.length > 0 ? overlayDecl.title : null,
|
|
968
|
+
moduleSpecifierRel: overlayDecl.moduleSpecifier,
|
|
713
969
|
moduleSpecifierAbs: absPath
|
|
714
|
-
}
|
|
970
|
+
};
|
|
715
971
|
}
|
|
716
972
|
}
|
|
717
973
|
let isBuild = false;
|
|
@@ -731,6 +987,15 @@ function ethisysPlatformReactPlugin(options = {}) {
|
|
|
731
987
|
}
|
|
732
988
|
return {
|
|
733
989
|
build: {
|
|
990
|
+
// CRITICAL: emit NON-MINIFIED ESM. The host loads each bundle by
|
|
991
|
+
// fetching it as text and REGEX-rewriting its bare-specifier imports
|
|
992
|
+
// (`import { x } from "react"` → `const { x } = registry["react"]`) —
|
|
993
|
+
// the host's rewriter (hostModuleRegistry) assumes non-minified ESM,
|
|
994
|
+
// so a minified bundle leaves `react`/`react-dom`/MUI unrewritten and
|
|
995
|
+
// they resolve to `undefined`/`null` at mount (the overlay/page then
|
|
996
|
+
// throws on the first hook). Vite minifies production builds by
|
|
997
|
+
// default, so this MUST be forced off for every PlatformReact surface.
|
|
998
|
+
minify: false,
|
|
734
999
|
rollupOptions: {
|
|
735
1000
|
// CRITICAL: single-file ESM output per page. The host loads each
|
|
736
1001
|
// page via `import(url)` at mount time and reads the declared
|
|
@@ -744,10 +1009,20 @@ function ethisysPlatformReactPlugin(options = {}) {
|
|
|
744
1009
|
chunkFileNames: `${outputPrefix}[name]-[hash].js`,
|
|
745
1010
|
assetFileNames: `${outputPrefix}[name][extname]`
|
|
746
1011
|
},
|
|
747
|
-
//
|
|
748
|
-
//
|
|
749
|
-
//
|
|
750
|
-
|
|
1012
|
+
// Externalise every host-realm specifier so the host's module-registry
|
|
1013
|
+
// rewriter resolves them to the live host singletons at runtime — react,
|
|
1014
|
+
// react-dom, MUI, emotion, components-react, extension-runtime (the shared
|
|
1015
|
+
// PLATFORM_REACT_EXTERNALS contract) PLUS host-ui-externals. This is
|
|
1016
|
+
// CRITICAL: if react is NOT externalised it gets bundled, the surface then
|
|
1017
|
+
// runs against a SECOND React instance, and hooks throw (invalid-hook /
|
|
1018
|
+
// "reading useEffect of null"). Must match the programmatic build-pages
|
|
1019
|
+
// externals so config-hook plugins (cc-scaffolded) behave identically.
|
|
1020
|
+
external: [...PLATFORM_REACT_EXTERNALS, "@ethisyscore/host-ui-externals"],
|
|
1021
|
+
// Nothing inside the build imports the entry module, so the bundler's
|
|
1022
|
+
// default entry tree-shaking would drop the declared `exportName`
|
|
1023
|
+
// (e.g. the overlay's `Overlay`). Pin entry signatures so the host
|
|
1024
|
+
// loader can read `bundle[exportName]` at mount time.
|
|
1025
|
+
preserveEntrySignatures: "strict"
|
|
751
1026
|
}
|
|
752
1027
|
}
|
|
753
1028
|
};
|
|
@@ -770,13 +1045,16 @@ function ethisysPlatformReactPlugin(options = {}) {
|
|
|
770
1045
|
return;
|
|
771
1046
|
}
|
|
772
1047
|
validate();
|
|
773
|
-
if (pages.length === 0) {
|
|
1048
|
+
if (pages.length === 0 && overlay === null) {
|
|
774
1049
|
return;
|
|
775
1050
|
}
|
|
776
1051
|
const input = {};
|
|
777
1052
|
for (const page of pages) {
|
|
778
1053
|
input[page.id] = slash2(page.moduleSpecifierAbs);
|
|
779
1054
|
}
|
|
1055
|
+
if (overlay !== null) {
|
|
1056
|
+
input[overlay.id] = slash2(overlay.moduleSpecifierAbs);
|
|
1057
|
+
}
|
|
780
1058
|
const writable = config;
|
|
781
1059
|
writable.build ??= {};
|
|
782
1060
|
writable.build.rollupOptions ??= {};
|
|
@@ -806,7 +1084,7 @@ function ethisysPlatformReactPlugin(options = {}) {
|
|
|
806
1084
|
* having to re-parse the manifest.
|
|
807
1085
|
*/
|
|
808
1086
|
generateBundle(_options, bundle) {
|
|
809
|
-
if (pages.length === 0) {
|
|
1087
|
+
if (pages.length === 0 && overlay === null) {
|
|
810
1088
|
return;
|
|
811
1089
|
}
|
|
812
1090
|
const digests = [];
|
|
@@ -828,6 +1106,15 @@ function ethisysPlatformReactPlugin(options = {}) {
|
|
|
828
1106
|
source: p.moduleSpecifierRel
|
|
829
1107
|
}))
|
|
830
1108
|
};
|
|
1109
|
+
if (overlay !== null) {
|
|
1110
|
+
summary.overlay = {
|
|
1111
|
+
id: overlay.id,
|
|
1112
|
+
exportName: overlay.exportName,
|
|
1113
|
+
title: overlay.title,
|
|
1114
|
+
bundlePath: `${outputPrefix}${overlay.id}.js`,
|
|
1115
|
+
source: overlay.moduleSpecifierRel
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
831
1118
|
this.emitFile({
|
|
832
1119
|
type: "asset",
|
|
833
1120
|
fileName: `${outputPrefix}platform-react-pages.json`,
|
|
@@ -929,221 +1216,6 @@ function parsePlatformReactPages(manifestPath, options = {}) {
|
|
|
929
1216
|
}
|
|
930
1217
|
return result;
|
|
931
1218
|
}
|
|
932
|
-
var PLATFORM_REACT_EXTERNALS = [
|
|
933
|
-
"react",
|
|
934
|
-
"react/jsx-runtime",
|
|
935
|
-
"react-dom",
|
|
936
|
-
"@mui/material",
|
|
937
|
-
"@mui/icons-material",
|
|
938
|
-
"@emotion/react",
|
|
939
|
-
"@emotion/styled",
|
|
940
|
-
"date-fns",
|
|
941
|
-
"@ethisyscore/components-react",
|
|
942
|
-
"@ethisyscore/extension-runtime",
|
|
943
|
-
"@ethisyscore/extension-runtime/plugin"
|
|
944
|
-
];
|
|
945
|
-
var PLATFORM_REACT_DEDUPE = [
|
|
946
|
-
"@tanstack/react-query",
|
|
947
|
-
"react",
|
|
948
|
-
"react-dom",
|
|
949
|
-
"@ethisyscore/extension-runtime"
|
|
950
|
-
];
|
|
951
|
-
var DEFAULT_OUT_DIR = "dist";
|
|
952
|
-
var DEFAULT_OUTPUT_PREFIX = "platform-react";
|
|
953
|
-
var DEFAULT_DEFINE_PLUGIN_PAGE = "@/app/definePluginPage";
|
|
954
|
-
var DEFAULT_SRC_DIR = "src";
|
|
955
|
-
var DEFAULT_ALIAS_PREFIX = "@";
|
|
956
|
-
var SUMMARY_FILENAME = "platform-react-pages.json";
|
|
957
|
-
var DIGEST_MAP_FILENAME = "platform-react-digest-map.json";
|
|
958
|
-
function rewriteAliasedExternalImports() {
|
|
959
|
-
return {
|
|
960
|
-
name: "rewrite-aliased-external-imports",
|
|
961
|
-
renderChunk(code) {
|
|
962
|
-
let counter = 0;
|
|
963
|
-
let changed = false;
|
|
964
|
-
const out = code.replace(
|
|
965
|
-
// Optional default binding (`import Foo, { … }`) then the named braces.
|
|
966
|
-
/\bimport\s+(?:([\w$]+)\s*,\s*)?\{([^{}]*)\}\s*from\s*("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')\s*;?/g,
|
|
967
|
-
(full, def, specs, mod) => {
|
|
968
|
-
if (!/\sas\s/.test(specs)) return full;
|
|
969
|
-
changed = true;
|
|
970
|
-
const ns = `__ns_imp_${counter++}`;
|
|
971
|
-
const decls = specs.split(",").map((s) => s.trim()).filter(Boolean).map((s) => {
|
|
972
|
-
const [imported, local] = s.split(/\s+as\s+/).map((t) => t.trim());
|
|
973
|
-
return `${local ?? imported} = ${ns}.${imported}`;
|
|
974
|
-
}).join(", ");
|
|
975
|
-
const defPart = def ? `import ${def} from ${mod}; ` : "";
|
|
976
|
-
return `${defPart}import * as ${ns} from ${mod}; const ${decls};`;
|
|
977
|
-
}
|
|
978
|
-
);
|
|
979
|
-
return changed ? { code: out, map: null } : null;
|
|
980
|
-
}
|
|
981
|
-
};
|
|
982
|
-
}
|
|
983
|
-
function virtualPageEntry(page, config) {
|
|
984
|
-
const VIRTUAL_ID = `\0virtual:surface/${page.id}`;
|
|
985
|
-
const views = page.views ?? {};
|
|
986
|
-
const names = Object.keys(views);
|
|
987
|
-
const viewImports = names.map((n) => `import { ${n} } from ${JSON.stringify(config.toAlias(views[n]))};`).join("\n");
|
|
988
|
-
const src = `import Page from ${JSON.stringify(config.toAlias(page.moduleSpecifier))};
|
|
989
|
-
${viewImports}
|
|
990
|
-
import { definePluginPage } from ${JSON.stringify(config.definePluginPageSpecifier)};
|
|
991
|
-
export default definePluginPage(Page, { ${names.join(", ")} });
|
|
992
|
-
`;
|
|
993
|
-
return {
|
|
994
|
-
id: VIRTUAL_ID,
|
|
995
|
-
plugin: {
|
|
996
|
-
name: `virtual-surface-${page.id}`,
|
|
997
|
-
resolveId: (id) => id === VIRTUAL_ID ? VIRTUAL_ID : null,
|
|
998
|
-
load: (id) => id === VIRTUAL_ID ? src : null
|
|
999
|
-
}
|
|
1000
|
-
};
|
|
1001
|
-
}
|
|
1002
|
-
function defaultWriteFile(path$1, contents) {
|
|
1003
|
-
fs.mkdirSync(path.dirname(path$1), { recursive: true });
|
|
1004
|
-
fs.writeFileSync(path$1, contents);
|
|
1005
|
-
}
|
|
1006
|
-
function readManifest(options, root) {
|
|
1007
|
-
if (options.manifest !== void 0) {
|
|
1008
|
-
return options.manifest;
|
|
1009
|
-
}
|
|
1010
|
-
const rel = options.manifestPath ?? "feature.manifest.json";
|
|
1011
|
-
const abs = path.isAbsolute(rel) ? rel : path.resolve(root, rel);
|
|
1012
|
-
if (!fs.existsSync(abs)) {
|
|
1013
|
-
return null;
|
|
1014
|
-
}
|
|
1015
|
-
const raw = fs.readFileSync(abs, "utf-8");
|
|
1016
|
-
try {
|
|
1017
|
-
return JSON.parse(raw);
|
|
1018
|
-
} catch (e) {
|
|
1019
|
-
throw new Error(
|
|
1020
|
-
`[ethisys-platform-react] Failed to parse manifest at "${abs}": ${e.message}`
|
|
1021
|
-
);
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
function makeDefaultBuild(viteBuild) {
|
|
1025
|
-
return async (ctx) => {
|
|
1026
|
-
const build = viteBuild ?? (await import('vite')).build;
|
|
1027
|
-
await build({
|
|
1028
|
-
root: ctx.root,
|
|
1029
|
-
configFile: false,
|
|
1030
|
-
plugins: [ctx.surfacePlugin, ...ctx.plugins, rewriteAliasedExternalImports()],
|
|
1031
|
-
resolve: {
|
|
1032
|
-
dedupe: [...ctx.dedupe],
|
|
1033
|
-
alias: ctx.alias
|
|
1034
|
-
},
|
|
1035
|
-
build: {
|
|
1036
|
-
target: ctx.target,
|
|
1037
|
-
outDir: ctx.outDir,
|
|
1038
|
-
emptyOutDir: false,
|
|
1039
|
-
assetsDir: "",
|
|
1040
|
-
minify: ctx.minify,
|
|
1041
|
-
sourcemap: ctx.sourcemap,
|
|
1042
|
-
rollupOptions: {
|
|
1043
|
-
input: { [ctx.pageId]: ctx.entryId },
|
|
1044
|
-
preserveEntrySignatures: "strict",
|
|
1045
|
-
external: [...ctx.external],
|
|
1046
|
-
output: {
|
|
1047
|
-
format: "es",
|
|
1048
|
-
inlineDynamicImports: true,
|
|
1049
|
-
entryFileNames: `${ctx.outputPrefix}/[name].js`,
|
|
1050
|
-
chunkFileNames: `${ctx.outputPrefix}/[name]-[hash].js`,
|
|
1051
|
-
assetFileNames: `${ctx.outputPrefix}/[name][extname]`
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
},
|
|
1055
|
-
logLevel: "warn"
|
|
1056
|
-
});
|
|
1057
|
-
};
|
|
1058
|
-
}
|
|
1059
|
-
async function buildPlatformReactPages(options = {}) {
|
|
1060
|
-
const root = options.root ?? process.cwd();
|
|
1061
|
-
const logger = options.logger ?? console;
|
|
1062
|
-
const manifest = readManifest(options, root);
|
|
1063
|
-
const declared = manifest?.ui?.platformReactPages;
|
|
1064
|
-
if (!Array.isArray(declared) || declared.length === 0) {
|
|
1065
|
-
logger.warn("[build-pages] no platformReactPages declared \u2014 nothing to build");
|
|
1066
|
-
return { pages: [], digests: [], summaryPath: null, digestMapPath: null };
|
|
1067
|
-
}
|
|
1068
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1069
|
-
const pages = declared.map((raw, i) => {
|
|
1070
|
-
const decl = raw;
|
|
1071
|
-
if (typeof decl?.id !== "string" || decl.id.length === 0) {
|
|
1072
|
-
throw new Error(`[build-pages] ui.platformReactPages[${i}].id is required (non-empty string).`);
|
|
1073
|
-
}
|
|
1074
|
-
if (seen.has(decl.id)) {
|
|
1075
|
-
throw new Error(`[build-pages] Duplicate page id "${decl.id}".`);
|
|
1076
|
-
}
|
|
1077
|
-
seen.add(decl.id);
|
|
1078
|
-
if (typeof decl.moduleSpecifier !== "string" || decl.moduleSpecifier.length === 0) {
|
|
1079
|
-
throw new Error(`[build-pages] surface "${decl.id}" has no moduleSpecifier in the manifest.`);
|
|
1080
|
-
}
|
|
1081
|
-
return decl;
|
|
1082
|
-
});
|
|
1083
|
-
const outDir = options.outDir ?? DEFAULT_OUT_DIR;
|
|
1084
|
-
const outDirAbs = path.isAbsolute(outDir) ? outDir : path.resolve(root, outDir);
|
|
1085
|
-
const outputPrefix = (options.outputPrefix ?? DEFAULT_OUTPUT_PREFIX).replace(/^\/+|\/+$/g, "");
|
|
1086
|
-
const outputPrefixDir = path.resolve(outDirAbs, outputPrefix);
|
|
1087
|
-
const external = options.external ?? PLATFORM_REACT_EXTERNALS;
|
|
1088
|
-
const dedupe = options.dedupe ?? PLATFORM_REACT_DEDUPE;
|
|
1089
|
-
const alias = options.alias ?? [];
|
|
1090
|
-
const plugins = options.plugins ?? [];
|
|
1091
|
-
const definePluginPageSpecifier = options.definePluginPageSpecifier ?? DEFAULT_DEFINE_PLUGIN_PAGE;
|
|
1092
|
-
const srcDir = options.srcDir ?? DEFAULT_SRC_DIR;
|
|
1093
|
-
const aliasPrefix = options.aliasPrefix ?? DEFAULT_ALIAS_PREFIX;
|
|
1094
|
-
const srcRe = new RegExp(`^${escapeRegExp(srcDir)}/`);
|
|
1095
|
-
const toAlias = (p) => `${aliasPrefix}/${p.replace(srcRe, "")}`;
|
|
1096
|
-
const write = options.writeFile ?? defaultWriteFile;
|
|
1097
|
-
const read = options.readFile ?? ((p) => fs.readFileSync(p, "utf-8"));
|
|
1098
|
-
const runBuild = options.build ?? makeDefaultBuild(options.viteBuild);
|
|
1099
|
-
if (options.clean ?? true) {
|
|
1100
|
-
if (fs.existsSync(outDirAbs)) {
|
|
1101
|
-
fs.rmSync(outDirAbs, { recursive: true, force: true });
|
|
1102
|
-
}
|
|
1103
|
-
fs.mkdirSync(outputPrefixDir, { recursive: true });
|
|
1104
|
-
}
|
|
1105
|
-
for (const page of pages) {
|
|
1106
|
-
const { id: entryId, plugin: surfacePlugin } = virtualPageEntry(page, {
|
|
1107
|
-
definePluginPageSpecifier,
|
|
1108
|
-
toAlias
|
|
1109
|
-
});
|
|
1110
|
-
logger.log(`[build-pages] ${page.id} \u2190 ${page.moduleSpecifier}`);
|
|
1111
|
-
await runBuild({
|
|
1112
|
-
pageId: page.id,
|
|
1113
|
-
entryId,
|
|
1114
|
-
surfacePlugin,
|
|
1115
|
-
root,
|
|
1116
|
-
outDir: outDirAbs,
|
|
1117
|
-
outputPrefix,
|
|
1118
|
-
external,
|
|
1119
|
-
dedupe,
|
|
1120
|
-
alias,
|
|
1121
|
-
plugins,
|
|
1122
|
-
target: options.target ?? "es2022",
|
|
1123
|
-
minify: options.minify ?? false,
|
|
1124
|
-
sourcemap: options.sourcemap ?? true
|
|
1125
|
-
});
|
|
1126
|
-
}
|
|
1127
|
-
const summaryPages = pages.map((p) => ({
|
|
1128
|
-
id: p.id,
|
|
1129
|
-
exportName: p.exportName ?? "default",
|
|
1130
|
-
title: p.title ?? null,
|
|
1131
|
-
bundlePath: `${outputPrefix}/${p.id}.js`,
|
|
1132
|
-
source: p.moduleSpecifier
|
|
1133
|
-
}));
|
|
1134
|
-
const summaryPath = path.resolve(outputPrefixDir, SUMMARY_FILENAME);
|
|
1135
|
-
write(summaryPath, JSON.stringify({ pages: summaryPages }, null, 2));
|
|
1136
|
-
const digests = pages.map((p) => `${outputPrefix}/${p.id}.js`).sort().map((relPath) => {
|
|
1137
|
-
const code = read(path.resolve(outDirAbs, relPath));
|
|
1138
|
-
return { path: relPath, sha256: crypto.createHash("sha256").update(code, "utf8").digest("hex") };
|
|
1139
|
-
});
|
|
1140
|
-
const digestMapPath = path.resolve(outputPrefixDir, DIGEST_MAP_FILENAME);
|
|
1141
|
-
write(digestMapPath, JSON.stringify({ digests }, null, 2));
|
|
1142
|
-
logger.log(
|
|
1143
|
-
`[build-pages] emitted ${pages.length} page(s) + summary + digest map (${digests.length} digests)`
|
|
1144
|
-
);
|
|
1145
|
-
return { pages: summaryPages, digests, summaryPath, digestMapPath };
|
|
1146
|
-
}
|
|
1147
1219
|
var DEFAULT_SCHEMA = "https://ethisys.dev/schemas/feature-manifest.json";
|
|
1148
1220
|
function computePlatformReactPages(input) {
|
|
1149
1221
|
const routePrefix = `/extensions/${input.slug}/`;
|
|
@@ -1231,16 +1303,26 @@ function generatePlatformReactManifest(config) {
|
|
|
1231
1303
|
});
|
|
1232
1304
|
const schema = config.manifest.schema ?? DEFAULT_SCHEMA;
|
|
1233
1305
|
const renderMode = config.manifest.renderMode ?? "platform-react";
|
|
1306
|
+
const overlayEntry = config.overlay;
|
|
1234
1307
|
const featureManifest = {
|
|
1235
1308
|
$schema: schema,
|
|
1236
1309
|
id: config.manifest.id,
|
|
1237
1310
|
name: config.manifest.name,
|
|
1238
1311
|
version: config.manifest.version,
|
|
1239
1312
|
renderMode,
|
|
1240
|
-
ui: {
|
|
1313
|
+
ui: {
|
|
1314
|
+
platformReactPages: entries,
|
|
1315
|
+
...overlayEntry !== void 0 ? { platformReactOverlay: overlayEntry } : {}
|
|
1316
|
+
}
|
|
1241
1317
|
};
|
|
1242
1318
|
const featureManifestPath = path.resolve(root, featureManifestRel);
|
|
1243
1319
|
fs.writeFileSync(featureManifestPath, JSON.stringify(featureManifest, null, 2) + "\n");
|
|
1320
|
+
const overlayWireEntry = overlayEntry !== void 0 ? {
|
|
1321
|
+
id: overlayEntry.id,
|
|
1322
|
+
moduleSpecifier: overlayEntry.moduleSpecifier,
|
|
1323
|
+
exportName: overlayEntry.exportName,
|
|
1324
|
+
title: overlayEntry.title
|
|
1325
|
+
} : void 0;
|
|
1244
1326
|
const overlay = {
|
|
1245
1327
|
$schema: schema,
|
|
1246
1328
|
ui: {
|
|
@@ -1250,7 +1332,8 @@ function generatePlatformReactManifest(config) {
|
|
|
1250
1332
|
exportName,
|
|
1251
1333
|
...route !== void 0 ? { route } : {},
|
|
1252
1334
|
title
|
|
1253
|
-
}))
|
|
1335
|
+
})),
|
|
1336
|
+
...overlayWireEntry !== void 0 ? { platformReactOverlay: overlayWireEntry } : {}
|
|
1254
1337
|
}
|
|
1255
1338
|
};
|
|
1256
1339
|
const overlayPath = path.resolve(root, overlayRel);
|