useblocks 1.1.85-dev.2 → 1.1.85-dev.3
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/iframe/.vite/manifest.json +4 -4
- package/iframe/js/{discussHandlers.BtCUuj58.js → discussHandlers.BxlCsZ-4.js} +2 -2
- package/iframe/js/iframe-eblock.body.js +1 -1
- package/iframe/js/iframe-eblock.js +1 -1
- package/iframe/js/iframe.body.js +2 -2
- package/iframe/js/iframe.js +1 -1
- package/node_modules/useblocks-ui-kit/package.json +1 -1
- package/package.json +2 -2
- package/runtime/react/index.js +1 -1
- package/static/core/{ef3477dd.js → 14a79b09.js} +1 -1
- package/static/core/{6c7df338.js → 199dc589.js} +1 -1
- package/static/core/{ca873214.js → 230f23ec.js} +1 -1
- package/static/core/{181088a9.js → 30f16e98.js} +1 -1
- package/static/core/{6fdfa46d.js → 34730be9.js} +1 -1
- package/static/core/{9b7f52a5.js → 364dcebf.js} +1 -1
- package/static/core/{43affb86.js → 4916b3a1.js} +1 -1
- package/static/core/{c789db17.js → 4f790fdc.js} +1 -1
- package/static/core/{d74b0853.js → 520ec797.js} +3 -3
- package/static/core/{2639f789.js → 57c114f0.js} +1 -1
- package/static/core/{cab426fa.js → 5bf994b4.js} +1 -1
- package/static/core/{13563d2c.js → 6541580a.js} +1 -1
- package/static/core/{a72f1f53.js → 79431a89.js} +1 -1
- package/static/core/{b502ca96.js → 8fb0fe48.js} +1 -1
- package/static/core/{fad0a613.js → 9a70cc1e.js} +1 -1
- package/static/core/{6589e3b8.js → a15ab67d.js} +1 -1
- package/static/core/{576452f9.js → adf6e008.js} +1 -1
- package/static/core/{f0f90842.js → b743a76d.js} +1 -1
- package/static/core/{c6d342f2.js → b97bd382.js} +6 -6
- package/static/core/{92e10a15.js → ddb5a35d.js} +1 -1
- package/static/core/{70b2c337.js → dfddbd68.js} +1 -1
- package/static/core/{6eeae572.js → f3eda1c7.js} +1 -1
- package/static/core/{5f0c0e3a.js → ffb36e93.js} +1 -1
- package/static/umd-runtime/runtime.js +1 -1
- package/{useblocks-core.4865197d.js → useblocks-core.168aeeb1.js} +8 -8
- package/useblocks-esm.js +6 -6
- package/{useblocks-po.4865197d.js → useblocks-po.168aeeb1.js} +1 -1
- package/useblocks.js +6 -6
- package/vite/index.cjs +99 -3
- package/vite/index.js +99 -3
- package/webpack/index.cjs +93 -0
- package/webpack/index.js +93 -0
- /package/iframe/{iframe-eblock.4865197d.html → iframe-eblock.168aeeb1.html} +0 -0
- /package/iframe/{iframe.4865197d.html → iframe.168aeeb1.html} +0 -0
package/vite/index.cjs
CHANGED
|
@@ -749,6 +749,81 @@ function checkHostReactVersion(projectRoot) {
|
|
|
749
749
|
};
|
|
750
750
|
}
|
|
751
751
|
//#endregion
|
|
752
|
+
//#region src/vite/pinVendoredReact.ts
|
|
753
|
+
/**
|
|
754
|
+
* Pinned React: inside the package `react` always means the copy the package ships.
|
|
755
|
+
*
|
|
756
|
+
* The product never imports `react` by its bare name, but the vendored tree does — antd and its
|
|
757
|
+
* relatives, and the `runtime/react` subpath itself, which is a re-export of `import … from 'react'`.
|
|
758
|
+
* Those bare names are exactly what a host build is free to redirect, and four mechanisms do it:
|
|
759
|
+
* `resolve.dedupe`, Module Federation `shared: { react: { singleton: true } }`, webpack
|
|
760
|
+
* `resolve.alias` and `resolve.modules`. None of them is sabotage — they are the recommended cure for
|
|
761
|
+
* duplicate React — but they silently hand the editor whichever React the host happens to have. At
|
|
762
|
+
* campaigns that was React 18 against an editor built for 19.
|
|
763
|
+
*
|
|
764
|
+
* So imports are pinned by their importer: a request coming from a file inside the package resolves
|
|
765
|
+
* to the vendored copy, and nothing else is touched. Aliases run before any plugin, so the order of
|
|
766
|
+
* the host's own configuration does not matter. `sharedReact` is the one way to opt out — then the
|
|
767
|
+
* editor runs on the host's React on purpose.
|
|
768
|
+
*/
|
|
769
|
+
/** Bare names the vendored tree imports directly; the same set `sharedReact` sends to the host. */
|
|
770
|
+
var PINNED_BARE_NAMES = [
|
|
771
|
+
"react",
|
|
772
|
+
"react-dom",
|
|
773
|
+
"react-dom/client",
|
|
774
|
+
"react/jsx-runtime",
|
|
775
|
+
"react/jsx-dev-runtime"
|
|
776
|
+
];
|
|
777
|
+
function normalize(value) {
|
|
778
|
+
return value.replace(/\\/g, "/");
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Real path, with symlinks resolved. With pnpm the package is a link into `.pnpm/…`, and the
|
|
782
|
+
* importer arrives already resolved: comparing the visible path against it never matches, and the
|
|
783
|
+
* pinning would quietly do nothing. The measurement notes in DELIVERY_BOUNDARIES.md warn about the
|
|
784
|
+
* same trap from the other side.
|
|
785
|
+
*/
|
|
786
|
+
function realPath(value) {
|
|
787
|
+
try {
|
|
788
|
+
return fs.default.realpathSync(value);
|
|
789
|
+
} catch {
|
|
790
|
+
return value;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
/** Is the importing file part of the published package (its own files or its vendored tree). */
|
|
794
|
+
function isInsidePackage(importer, packageRoot) {
|
|
795
|
+
const root = normalize(realPath(packageRoot)).replace(/\/$/, "");
|
|
796
|
+
return normalize(realPath(importer)).startsWith(`${root}/`);
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Where the package's own copy of that module lives. Resolved from the package manifest, so pnpm's
|
|
800
|
+
* `.pnpm/…` layout and a plain `node_modules` tree both land on the vendored files.
|
|
801
|
+
*/
|
|
802
|
+
function resolveVendoredModule(packageRoot, specifier) {
|
|
803
|
+
try {
|
|
804
|
+
return (0, module$1.createRequire)(path.default.join(packageRoot, "package.json")).resolve(specifier);
|
|
805
|
+
} catch {
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Aliases that pin React for the package's own graph. Empty when the package ships no copy: then
|
|
811
|
+
* there is nothing to pin to, and redirecting would break the build instead of protecting it.
|
|
812
|
+
*/
|
|
813
|
+
function buildPinnedReactAliases(packageRoot, onPin) {
|
|
814
|
+
if (resolveVendoredModule(packageRoot, "react") === void 0) return [];
|
|
815
|
+
return PINNED_BARE_NAMES.map((name) => ({
|
|
816
|
+
find: new RegExp(`^${name.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&")}$`, "u"),
|
|
817
|
+
replacement: name,
|
|
818
|
+
customResolver: (source, importer) => {
|
|
819
|
+
if (!importer || !isInsidePackage(importer, packageRoot)) return null;
|
|
820
|
+
const vendored = resolveVendoredModule(packageRoot, source);
|
|
821
|
+
if (vendored && onPin) onPin(source);
|
|
822
|
+
return vendored ?? null;
|
|
823
|
+
}
|
|
824
|
+
}));
|
|
825
|
+
}
|
|
826
|
+
//#endregion
|
|
752
827
|
//#region src/shared/reactDuplicateDetector.ts
|
|
753
828
|
/**
|
|
754
829
|
* Детектор дублированных runtime-копий React/antd в Vite-prebundle integrator'а.
|
|
@@ -883,6 +958,25 @@ var APP_CORE_OPTIMIZED_DEP_RE = createAppCoreOptimizedDepRe(BUNDLER_DEFAULT_APP_
|
|
|
883
958
|
var DEFAULT_OUTPUT_FOLDER = `/${BUNDLER_DEFAULT_APP_NAME}`;
|
|
884
959
|
var DEFAULT_MODULES_PATH = "node_modules";
|
|
885
960
|
var DEFAULT_STARTUP_PREFETCH_MODE = "idle";
|
|
961
|
+
/**
|
|
962
|
+
* Как разрешать React в графе партнёра.
|
|
963
|
+
*
|
|
964
|
+
* По умолчанию — пришиваем свою копию: импорт из файла внутри пакета уходит в неё, чужие импорты не
|
|
965
|
+
* трогаются. `sharedReact` — обратный случай: интегратор просит редактор жить на его React, и тогда
|
|
966
|
+
* наши подпути уводятся к хосту.
|
|
967
|
+
*/
|
|
968
|
+
function reactResolveConfig(root, options) {
|
|
969
|
+
if (options.sharedReact) return { resolve: { alias: buildSharedReactAliases(BUNDLER_DEFAULT_PACKAGE_NAME) } };
|
|
970
|
+
const packageRoot = resolvePublishedPackageRoot(BUNDLER_DEFAULT_PACKAGE_NAME, options.modulesPath || DEFAULT_MODULES_PATH, root ? path.default.resolve(root) : process.cwd());
|
|
971
|
+
const pinned = /* @__PURE__ */ new Set();
|
|
972
|
+
const alias = buildPinnedReactAliases(packageRoot, (source) => {
|
|
973
|
+
if (!pinned.has(source)) {
|
|
974
|
+
pinned.add(source);
|
|
975
|
+
console.log(`[${PLUGIN_NAME}] pinnedReact: "${source}" внутри пакета → своя копия`);
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
return alias.length > 0 ? { resolve: { alias } } : void 0;
|
|
979
|
+
}
|
|
886
980
|
function outputFolderFsSegment(folder, appName) {
|
|
887
981
|
return (folder != null && folder !== "" ? String(folder) : `/${appName}`).replace(/^\/+/, "") || appName;
|
|
888
982
|
}
|
|
@@ -991,6 +1085,9 @@ var ViteServePlugin = (options) => {
|
|
|
991
1085
|
name: `${PLUGIN_NAME}-serve`,
|
|
992
1086
|
enforce: "pre",
|
|
993
1087
|
apply: "serve",
|
|
1088
|
+
config(config) {
|
|
1089
|
+
return reactResolveConfig(config.root, options);
|
|
1090
|
+
},
|
|
994
1091
|
configResolved(config) {
|
|
995
1092
|
projectRoot = config.root;
|
|
996
1093
|
destPath = path.default.normalize(path.default.join(projectRoot, mountSeg));
|
|
@@ -1144,9 +1241,8 @@ var ViteBuildPlugin = (options) => {
|
|
|
1144
1241
|
name: `${PLUGIN_NAME}-build`,
|
|
1145
1242
|
enforce: "pre",
|
|
1146
1243
|
apply: "build",
|
|
1147
|
-
config() {
|
|
1148
|
-
|
|
1149
|
-
return { resolve: { alias: buildSharedReactAliases(BUNDLER_DEFAULT_PACKAGE_NAME) } };
|
|
1244
|
+
config(config) {
|
|
1245
|
+
return reactResolveConfig(config.root, options);
|
|
1150
1246
|
},
|
|
1151
1247
|
resolveId(source, importer) {
|
|
1152
1248
|
if (!options.sharedReact || !importer || !resolvedConfig || !isSharedReactSpecifier(source)) return null;
|
package/vite/index.js
CHANGED
|
@@ -720,6 +720,81 @@ function checkHostReactVersion(projectRoot) {
|
|
|
720
720
|
};
|
|
721
721
|
}
|
|
722
722
|
//#endregion
|
|
723
|
+
//#region src/vite/pinVendoredReact.ts
|
|
724
|
+
/**
|
|
725
|
+
* Pinned React: inside the package `react` always means the copy the package ships.
|
|
726
|
+
*
|
|
727
|
+
* The product never imports `react` by its bare name, but the vendored tree does — antd and its
|
|
728
|
+
* relatives, and the `runtime/react` subpath itself, which is a re-export of `import … from 'react'`.
|
|
729
|
+
* Those bare names are exactly what a host build is free to redirect, and four mechanisms do it:
|
|
730
|
+
* `resolve.dedupe`, Module Federation `shared: { react: { singleton: true } }`, webpack
|
|
731
|
+
* `resolve.alias` and `resolve.modules`. None of them is sabotage — they are the recommended cure for
|
|
732
|
+
* duplicate React — but they silently hand the editor whichever React the host happens to have. At
|
|
733
|
+
* campaigns that was React 18 against an editor built for 19.
|
|
734
|
+
*
|
|
735
|
+
* So imports are pinned by their importer: a request coming from a file inside the package resolves
|
|
736
|
+
* to the vendored copy, and nothing else is touched. Aliases run before any plugin, so the order of
|
|
737
|
+
* the host's own configuration does not matter. `sharedReact` is the one way to opt out — then the
|
|
738
|
+
* editor runs on the host's React on purpose.
|
|
739
|
+
*/
|
|
740
|
+
/** Bare names the vendored tree imports directly; the same set `sharedReact` sends to the host. */
|
|
741
|
+
var PINNED_BARE_NAMES = [
|
|
742
|
+
"react",
|
|
743
|
+
"react-dom",
|
|
744
|
+
"react-dom/client",
|
|
745
|
+
"react/jsx-runtime",
|
|
746
|
+
"react/jsx-dev-runtime"
|
|
747
|
+
];
|
|
748
|
+
function normalize(value) {
|
|
749
|
+
return value.replace(/\\/g, "/");
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Real path, with symlinks resolved. With pnpm the package is a link into `.pnpm/…`, and the
|
|
753
|
+
* importer arrives already resolved: comparing the visible path against it never matches, and the
|
|
754
|
+
* pinning would quietly do nothing. The measurement notes in DELIVERY_BOUNDARIES.md warn about the
|
|
755
|
+
* same trap from the other side.
|
|
756
|
+
*/
|
|
757
|
+
function realPath(value) {
|
|
758
|
+
try {
|
|
759
|
+
return fs.realpathSync(value);
|
|
760
|
+
} catch {
|
|
761
|
+
return value;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
/** Is the importing file part of the published package (its own files or its vendored tree). */
|
|
765
|
+
function isInsidePackage(importer, packageRoot) {
|
|
766
|
+
const root = normalize(realPath(packageRoot)).replace(/\/$/, "");
|
|
767
|
+
return normalize(realPath(importer)).startsWith(`${root}/`);
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Where the package's own copy of that module lives. Resolved from the package manifest, so pnpm's
|
|
771
|
+
* `.pnpm/…` layout and a plain `node_modules` tree both land on the vendored files.
|
|
772
|
+
*/
|
|
773
|
+
function resolveVendoredModule(packageRoot, specifier) {
|
|
774
|
+
try {
|
|
775
|
+
return createRequire(path.join(packageRoot, "package.json")).resolve(specifier);
|
|
776
|
+
} catch {
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Aliases that pin React for the package's own graph. Empty when the package ships no copy: then
|
|
782
|
+
* there is nothing to pin to, and redirecting would break the build instead of protecting it.
|
|
783
|
+
*/
|
|
784
|
+
function buildPinnedReactAliases(packageRoot, onPin) {
|
|
785
|
+
if (resolveVendoredModule(packageRoot, "react") === void 0) return [];
|
|
786
|
+
return PINNED_BARE_NAMES.map((name) => ({
|
|
787
|
+
find: new RegExp(`^${name.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&")}$`, "u"),
|
|
788
|
+
replacement: name,
|
|
789
|
+
customResolver: (source, importer) => {
|
|
790
|
+
if (!importer || !isInsidePackage(importer, packageRoot)) return null;
|
|
791
|
+
const vendored = resolveVendoredModule(packageRoot, source);
|
|
792
|
+
if (vendored && onPin) onPin(source);
|
|
793
|
+
return vendored ?? null;
|
|
794
|
+
}
|
|
795
|
+
}));
|
|
796
|
+
}
|
|
797
|
+
//#endregion
|
|
723
798
|
//#region src/shared/reactDuplicateDetector.ts
|
|
724
799
|
/**
|
|
725
800
|
* Детектор дублированных runtime-копий React/antd в Vite-prebundle integrator'а.
|
|
@@ -854,6 +929,25 @@ var APP_CORE_OPTIMIZED_DEP_RE = createAppCoreOptimizedDepRe(BUNDLER_DEFAULT_APP_
|
|
|
854
929
|
var DEFAULT_OUTPUT_FOLDER = `/${BUNDLER_DEFAULT_APP_NAME}`;
|
|
855
930
|
var DEFAULT_MODULES_PATH = "node_modules";
|
|
856
931
|
var DEFAULT_STARTUP_PREFETCH_MODE = "idle";
|
|
932
|
+
/**
|
|
933
|
+
* Как разрешать React в графе партнёра.
|
|
934
|
+
*
|
|
935
|
+
* По умолчанию — пришиваем свою копию: импорт из файла внутри пакета уходит в неё, чужие импорты не
|
|
936
|
+
* трогаются. `sharedReact` — обратный случай: интегратор просит редактор жить на его React, и тогда
|
|
937
|
+
* наши подпути уводятся к хосту.
|
|
938
|
+
*/
|
|
939
|
+
function reactResolveConfig(root, options) {
|
|
940
|
+
if (options.sharedReact) return { resolve: { alias: buildSharedReactAliases(BUNDLER_DEFAULT_PACKAGE_NAME) } };
|
|
941
|
+
const packageRoot = resolvePublishedPackageRoot(BUNDLER_DEFAULT_PACKAGE_NAME, options.modulesPath || DEFAULT_MODULES_PATH, root ? path.resolve(root) : process.cwd());
|
|
942
|
+
const pinned = /* @__PURE__ */ new Set();
|
|
943
|
+
const alias = buildPinnedReactAliases(packageRoot, (source) => {
|
|
944
|
+
if (!pinned.has(source)) {
|
|
945
|
+
pinned.add(source);
|
|
946
|
+
console.log(`[${PLUGIN_NAME}] pinnedReact: "${source}" внутри пакета → своя копия`);
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
return alias.length > 0 ? { resolve: { alias } } : void 0;
|
|
950
|
+
}
|
|
857
951
|
function outputFolderFsSegment(folder, appName) {
|
|
858
952
|
return (folder != null && folder !== "" ? String(folder) : `/${appName}`).replace(/^\/+/, "") || appName;
|
|
859
953
|
}
|
|
@@ -962,6 +1056,9 @@ var ViteServePlugin = (options) => {
|
|
|
962
1056
|
name: `${PLUGIN_NAME}-serve`,
|
|
963
1057
|
enforce: "pre",
|
|
964
1058
|
apply: "serve",
|
|
1059
|
+
config(config) {
|
|
1060
|
+
return reactResolveConfig(config.root, options);
|
|
1061
|
+
},
|
|
965
1062
|
configResolved(config) {
|
|
966
1063
|
projectRoot = config.root;
|
|
967
1064
|
destPath = path.normalize(path.join(projectRoot, mountSeg));
|
|
@@ -1115,9 +1212,8 @@ var ViteBuildPlugin = (options) => {
|
|
|
1115
1212
|
name: `${PLUGIN_NAME}-build`,
|
|
1116
1213
|
enforce: "pre",
|
|
1117
1214
|
apply: "build",
|
|
1118
|
-
config() {
|
|
1119
|
-
|
|
1120
|
-
return { resolve: { alias: buildSharedReactAliases(BUNDLER_DEFAULT_PACKAGE_NAME) } };
|
|
1215
|
+
config(config) {
|
|
1216
|
+
return reactResolveConfig(config.root, options);
|
|
1121
1217
|
},
|
|
1122
1218
|
resolveId(source, importer) {
|
|
1123
1219
|
if (!options.sharedReact || !importer || !resolvedConfig || !isSharedReactSpecifier(source)) return null;
|
package/webpack/index.cjs
CHANGED
|
@@ -549,6 +549,81 @@ function applySharedReactAliases(compiler, mainPackage, logger) {
|
|
|
549
549
|
logger.info(`sharedReact: ${mapped} module(s) mapped onto the host React${version.version ? ` ${version.version}` : ""}`);
|
|
550
550
|
}
|
|
551
551
|
//#endregion
|
|
552
|
+
//#region src/vite/pinVendoredReact.ts
|
|
553
|
+
/**
|
|
554
|
+
* Pinned React: inside the package `react` always means the copy the package ships.
|
|
555
|
+
*
|
|
556
|
+
* The product never imports `react` by its bare name, but the vendored tree does — antd and its
|
|
557
|
+
* relatives, and the `runtime/react` subpath itself, which is a re-export of `import … from 'react'`.
|
|
558
|
+
* Those bare names are exactly what a host build is free to redirect, and four mechanisms do it:
|
|
559
|
+
* `resolve.dedupe`, Module Federation `shared: { react: { singleton: true } }`, webpack
|
|
560
|
+
* `resolve.alias` and `resolve.modules`. None of them is sabotage — they are the recommended cure for
|
|
561
|
+
* duplicate React — but they silently hand the editor whichever React the host happens to have. At
|
|
562
|
+
* campaigns that was React 18 against an editor built for 19.
|
|
563
|
+
*
|
|
564
|
+
* So imports are pinned by their importer: a request coming from a file inside the package resolves
|
|
565
|
+
* to the vendored copy, and nothing else is touched. Aliases run before any plugin, so the order of
|
|
566
|
+
* the host's own configuration does not matter. `sharedReact` is the one way to opt out — then the
|
|
567
|
+
* editor runs on the host's React on purpose.
|
|
568
|
+
*/
|
|
569
|
+
/** Bare names the vendored tree imports directly; the same set `sharedReact` sends to the host. */
|
|
570
|
+
var PINNED_BARE_NAMES = [
|
|
571
|
+
"react",
|
|
572
|
+
"react-dom",
|
|
573
|
+
"react-dom/client",
|
|
574
|
+
"react/jsx-runtime",
|
|
575
|
+
"react/jsx-dev-runtime"
|
|
576
|
+
];
|
|
577
|
+
function normalize(value) {
|
|
578
|
+
return value.replace(/\\/g, "/");
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Real path, with symlinks resolved. With pnpm the package is a link into `.pnpm/…`, and the
|
|
582
|
+
* importer arrives already resolved: comparing the visible path against it never matches, and the
|
|
583
|
+
* pinning would quietly do nothing. The measurement notes in DELIVERY_BOUNDARIES.md warn about the
|
|
584
|
+
* same trap from the other side.
|
|
585
|
+
*/
|
|
586
|
+
function realPath(value) {
|
|
587
|
+
try {
|
|
588
|
+
return fs.default.realpathSync(value);
|
|
589
|
+
} catch {
|
|
590
|
+
return value;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
/** Is the importing file part of the published package (its own files or its vendored tree). */
|
|
594
|
+
function isInsidePackage(importer, packageRoot) {
|
|
595
|
+
const root = normalize(realPath(packageRoot)).replace(/\/$/, "");
|
|
596
|
+
return normalize(realPath(importer)).startsWith(`${root}/`);
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Where the package's own copy of that module lives. Resolved from the package manifest, so pnpm's
|
|
600
|
+
* `.pnpm/…` layout and a plain `node_modules` tree both land on the vendored files.
|
|
601
|
+
*/
|
|
602
|
+
function resolveVendoredModule(packageRoot, specifier) {
|
|
603
|
+
try {
|
|
604
|
+
return (0, module$1.createRequire)(path.default.join(packageRoot, "package.json")).resolve(specifier);
|
|
605
|
+
} catch {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Aliases that pin React for the package's own graph. Empty when the package ships no copy: then
|
|
611
|
+
* there is nothing to pin to, and redirecting would break the build instead of protecting it.
|
|
612
|
+
*/
|
|
613
|
+
function buildPinnedReactAliases(packageRoot, onPin) {
|
|
614
|
+
if (resolveVendoredModule(packageRoot, "react") === void 0) return [];
|
|
615
|
+
return PINNED_BARE_NAMES.map((name) => ({
|
|
616
|
+
find: new RegExp(`^${name.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&")}$`, "u"),
|
|
617
|
+
replacement: name,
|
|
618
|
+
customResolver: (source, importer) => {
|
|
619
|
+
if (!importer || !isInsidePackage(importer, packageRoot)) return null;
|
|
620
|
+
const vendored = resolveVendoredModule(packageRoot, source);
|
|
621
|
+
if (vendored && onPin) onPin(source);
|
|
622
|
+
return vendored ?? null;
|
|
623
|
+
}
|
|
624
|
+
}));
|
|
625
|
+
}
|
|
626
|
+
//#endregion
|
|
552
627
|
//#region src/shared/resolvePublishedPackage.ts
|
|
553
628
|
/**
|
|
554
629
|
* Resolve the on-disk root of the published main package (handles pnpm hoisting / symlinks).
|
|
@@ -587,6 +662,23 @@ function assertCoreChunkInCompilationAssets(assetNames, appName, outputFolder) {
|
|
|
587
662
|
})) throw new Error(`[host-plugin] No "${appName}-core.*.js" asset was emitted under "${folderPrefix || "(output)"}" after copy. The published package may be incomplete or IGNORED_PATTERNS filtered the core chunk.`);
|
|
588
663
|
}
|
|
589
664
|
//#endregion
|
|
665
|
+
//#region src/webpack/pinVendoredReact.ts
|
|
666
|
+
function applyPinnedReact(compiler, mainPackage, modulesPath, logger) {
|
|
667
|
+
const packageRoot = resolvePublishedPackageRoot(mainPackage, modulesPath, compiler.context);
|
|
668
|
+
if (buildPinnedReactAliases(packageRoot).length === 0) return;
|
|
669
|
+
const { NormalModuleReplacementPlugin } = compiler.webpack ?? {};
|
|
670
|
+
if (!NormalModuleReplacementPlugin) {
|
|
671
|
+
logger.warn("pinnedReact: webpack.NormalModuleReplacementPlugin is unavailable — React stays on the host resolve");
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
new NormalModuleReplacementPlugin(/^react(-dom)?(\/.*)?$/u, (resource) => {
|
|
675
|
+
if (!resource.context || !isInsidePackage(resource.context, packageRoot)) return;
|
|
676
|
+
const vendored = resolveVendoredModule(packageRoot, resource.request);
|
|
677
|
+
if (vendored) resource.request = vendored;
|
|
678
|
+
}).apply(compiler);
|
|
679
|
+
logger.info("pinnedReact: the editor runs on the React shipped inside the package");
|
|
680
|
+
}
|
|
681
|
+
//#endregion
|
|
590
682
|
//#region src/shared/branding.ts
|
|
591
683
|
/**
|
|
592
684
|
* Чтение `process.env.REACT_APP_*` — как в основном приложении.
|
|
@@ -738,6 +830,7 @@ var WebpackPlugin = class {
|
|
|
738
830
|
logger.info(`Host plugin active (package=${BUNDLER_DEFAULT_PACKAGE_NAME}, namespace=${ENVIRONMENT_VARIABLE}, publicPath=${normalizedPublicPath}, strict=${this._strict})`);
|
|
739
831
|
if (isPackageExternalized(compiler.options.externals, "useblocks")) logger.warn(`${BUNDLER_DEFAULT_PACKAGE_NAME} is listed in webpack externals — the product facade may be absent from the bundle and host bootstrap cannot be injected.`);
|
|
740
832
|
if (this._sharedReact) applySharedReactAliases(compiler, BUNDLER_DEFAULT_PACKAGE_NAME, logger);
|
|
833
|
+
else applyPinnedReact(compiler, BUNDLER_DEFAULT_PACKAGE_NAME, this._modulesPath, logger);
|
|
741
834
|
compiler.hooks.afterCompile?.tap(PLUGIN_NAME, (compilation) => {
|
|
742
835
|
if (!compilation.modules || !compilation.moduleGraph) return;
|
|
743
836
|
const edges = [];
|
package/webpack/index.js
CHANGED
|
@@ -525,6 +525,81 @@ function applySharedReactAliases(compiler, mainPackage, logger) {
|
|
|
525
525
|
logger.info(`sharedReact: ${mapped} module(s) mapped onto the host React${version.version ? ` ${version.version}` : ""}`);
|
|
526
526
|
}
|
|
527
527
|
//#endregion
|
|
528
|
+
//#region src/vite/pinVendoredReact.ts
|
|
529
|
+
/**
|
|
530
|
+
* Pinned React: inside the package `react` always means the copy the package ships.
|
|
531
|
+
*
|
|
532
|
+
* The product never imports `react` by its bare name, but the vendored tree does — antd and its
|
|
533
|
+
* relatives, and the `runtime/react` subpath itself, which is a re-export of `import … from 'react'`.
|
|
534
|
+
* Those bare names are exactly what a host build is free to redirect, and four mechanisms do it:
|
|
535
|
+
* `resolve.dedupe`, Module Federation `shared: { react: { singleton: true } }`, webpack
|
|
536
|
+
* `resolve.alias` and `resolve.modules`. None of them is sabotage — they are the recommended cure for
|
|
537
|
+
* duplicate React — but they silently hand the editor whichever React the host happens to have. At
|
|
538
|
+
* campaigns that was React 18 against an editor built for 19.
|
|
539
|
+
*
|
|
540
|
+
* So imports are pinned by their importer: a request coming from a file inside the package resolves
|
|
541
|
+
* to the vendored copy, and nothing else is touched. Aliases run before any plugin, so the order of
|
|
542
|
+
* the host's own configuration does not matter. `sharedReact` is the one way to opt out — then the
|
|
543
|
+
* editor runs on the host's React on purpose.
|
|
544
|
+
*/
|
|
545
|
+
/** Bare names the vendored tree imports directly; the same set `sharedReact` sends to the host. */
|
|
546
|
+
var PINNED_BARE_NAMES = [
|
|
547
|
+
"react",
|
|
548
|
+
"react-dom",
|
|
549
|
+
"react-dom/client",
|
|
550
|
+
"react/jsx-runtime",
|
|
551
|
+
"react/jsx-dev-runtime"
|
|
552
|
+
];
|
|
553
|
+
function normalize(value) {
|
|
554
|
+
return value.replace(/\\/g, "/");
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Real path, with symlinks resolved. With pnpm the package is a link into `.pnpm/…`, and the
|
|
558
|
+
* importer arrives already resolved: comparing the visible path against it never matches, and the
|
|
559
|
+
* pinning would quietly do nothing. The measurement notes in DELIVERY_BOUNDARIES.md warn about the
|
|
560
|
+
* same trap from the other side.
|
|
561
|
+
*/
|
|
562
|
+
function realPath(value) {
|
|
563
|
+
try {
|
|
564
|
+
return fs.realpathSync(value);
|
|
565
|
+
} catch {
|
|
566
|
+
return value;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
/** Is the importing file part of the published package (its own files or its vendored tree). */
|
|
570
|
+
function isInsidePackage(importer, packageRoot) {
|
|
571
|
+
const root = normalize(realPath(packageRoot)).replace(/\/$/, "");
|
|
572
|
+
return normalize(realPath(importer)).startsWith(`${root}/`);
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Where the package's own copy of that module lives. Resolved from the package manifest, so pnpm's
|
|
576
|
+
* `.pnpm/…` layout and a plain `node_modules` tree both land on the vendored files.
|
|
577
|
+
*/
|
|
578
|
+
function resolveVendoredModule(packageRoot, specifier) {
|
|
579
|
+
try {
|
|
580
|
+
return createRequire(path.join(packageRoot, "package.json")).resolve(specifier);
|
|
581
|
+
} catch {
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Aliases that pin React for the package's own graph. Empty when the package ships no copy: then
|
|
587
|
+
* there is nothing to pin to, and redirecting would break the build instead of protecting it.
|
|
588
|
+
*/
|
|
589
|
+
function buildPinnedReactAliases(packageRoot, onPin) {
|
|
590
|
+
if (resolveVendoredModule(packageRoot, "react") === void 0) return [];
|
|
591
|
+
return PINNED_BARE_NAMES.map((name) => ({
|
|
592
|
+
find: new RegExp(`^${name.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&")}$`, "u"),
|
|
593
|
+
replacement: name,
|
|
594
|
+
customResolver: (source, importer) => {
|
|
595
|
+
if (!importer || !isInsidePackage(importer, packageRoot)) return null;
|
|
596
|
+
const vendored = resolveVendoredModule(packageRoot, source);
|
|
597
|
+
if (vendored && onPin) onPin(source);
|
|
598
|
+
return vendored ?? null;
|
|
599
|
+
}
|
|
600
|
+
}));
|
|
601
|
+
}
|
|
602
|
+
//#endregion
|
|
528
603
|
//#region src/shared/resolvePublishedPackage.ts
|
|
529
604
|
/**
|
|
530
605
|
* Resolve the on-disk root of the published main package (handles pnpm hoisting / symlinks).
|
|
@@ -563,6 +638,23 @@ function assertCoreChunkInCompilationAssets(assetNames, appName, outputFolder) {
|
|
|
563
638
|
})) throw new Error(`[host-plugin] No "${appName}-core.*.js" asset was emitted under "${folderPrefix || "(output)"}" after copy. The published package may be incomplete or IGNORED_PATTERNS filtered the core chunk.`);
|
|
564
639
|
}
|
|
565
640
|
//#endregion
|
|
641
|
+
//#region src/webpack/pinVendoredReact.ts
|
|
642
|
+
function applyPinnedReact(compiler, mainPackage, modulesPath, logger) {
|
|
643
|
+
const packageRoot = resolvePublishedPackageRoot(mainPackage, modulesPath, compiler.context);
|
|
644
|
+
if (buildPinnedReactAliases(packageRoot).length === 0) return;
|
|
645
|
+
const { NormalModuleReplacementPlugin } = compiler.webpack ?? {};
|
|
646
|
+
if (!NormalModuleReplacementPlugin) {
|
|
647
|
+
logger.warn("pinnedReact: webpack.NormalModuleReplacementPlugin is unavailable — React stays on the host resolve");
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
new NormalModuleReplacementPlugin(/^react(-dom)?(\/.*)?$/u, (resource) => {
|
|
651
|
+
if (!resource.context || !isInsidePackage(resource.context, packageRoot)) return;
|
|
652
|
+
const vendored = resolveVendoredModule(packageRoot, resource.request);
|
|
653
|
+
if (vendored) resource.request = vendored;
|
|
654
|
+
}).apply(compiler);
|
|
655
|
+
logger.info("pinnedReact: the editor runs on the React shipped inside the package");
|
|
656
|
+
}
|
|
657
|
+
//#endregion
|
|
566
658
|
//#region src/shared/branding.ts
|
|
567
659
|
/**
|
|
568
660
|
* Чтение `process.env.REACT_APP_*` — как в основном приложении.
|
|
@@ -714,6 +806,7 @@ var WebpackPlugin = class {
|
|
|
714
806
|
logger.info(`Host plugin active (package=${BUNDLER_DEFAULT_PACKAGE_NAME}, namespace=${ENVIRONMENT_VARIABLE}, publicPath=${normalizedPublicPath}, strict=${this._strict})`);
|
|
715
807
|
if (isPackageExternalized(compiler.options.externals, "useblocks")) logger.warn(`${BUNDLER_DEFAULT_PACKAGE_NAME} is listed in webpack externals — the product facade may be absent from the bundle and host bootstrap cannot be injected.`);
|
|
716
808
|
if (this._sharedReact) applySharedReactAliases(compiler, BUNDLER_DEFAULT_PACKAGE_NAME, logger);
|
|
809
|
+
else applyPinnedReact(compiler, BUNDLER_DEFAULT_PACKAGE_NAME, this._modulesPath, logger);
|
|
717
810
|
compiler.hooks.afterCompile?.tap(PLUGIN_NAME, (compilation) => {
|
|
718
811
|
if (!compilation.modules || !compilation.moduleGraph) return;
|
|
719
812
|
const edges = [];
|
|
File without changes
|
|
File without changes
|