openxiangda 1.0.1 → 1.0.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/package.json +1 -1
- package/packages/sdk/dist/build/index.cjs +68 -0
- package/packages/sdk/dist/build/index.cjs.map +1 -1
- package/packages/sdk/dist/build/index.d.mts +9 -1
- package/packages/sdk/dist/build/index.d.ts +9 -1
- package/packages/sdk/dist/build/index.mjs +68 -0
- package/packages/sdk/dist/build/index.mjs.map +1 -1
- package/packages/sdk/dist/components/index.cjs +40 -13
- package/packages/sdk/dist/components/index.cjs.map +1 -1
- package/packages/sdk/dist/components/index.mjs +40 -13
- package/packages/sdk/dist/components/index.mjs.map +1 -1
- package/packages/sdk/dist/runtime/index.cjs +100 -25
- package/packages/sdk/dist/runtime/index.cjs.map +1 -1
- package/packages/sdk/dist/runtime/index.mjs +100 -25
- package/packages/sdk/dist/runtime/index.mjs.map +1 -1
- package/packages/sdk/src/build-source/scripts/build-forms.mjs +155 -36
- package/packages/sdk/src/build-source/scripts/build-pages.mjs +27 -4
- package/packages/sdk/src/build-source/scripts/utils/namespace-css.mjs +21 -5
- package/packages/sdk/src/build-source/scripts/utils/namespace-css.test.ts +29 -0
- package/packages/sdk/src/build-source/scripts/utils/tailwind-config.mjs +51 -1
- package/packages/sdk/src/build-source/scripts/utils/tailwind-config.test.ts +31 -2
- package/templates/sy-lowcode-app-workspace/postcss.config.cjs +22 -4
|
@@ -30,6 +30,7 @@ const runtimeDistDir = path.join(rootDir, "dist/form-runtime");
|
|
|
30
30
|
const tmpDir = path.join(rootDir, ".tmp");
|
|
31
31
|
const runtimeVersionPlaceholder = "__SY_FORM_RUNTIME_VERSION__";
|
|
32
32
|
const runtimeCacheFileName = "build-cache.json";
|
|
33
|
+
const portalContainerResolverGlobal = "__OPENXIANGDA_GET_PORTAL_CONTAINER__";
|
|
33
34
|
|
|
34
35
|
const runtimePackages = [
|
|
35
36
|
"react",
|
|
@@ -164,7 +165,6 @@ function parseArgs(argv) {
|
|
|
164
165
|
}
|
|
165
166
|
if (arg === "--force") {
|
|
166
167
|
result.force = true;
|
|
167
|
-
result.runtimeCache = false;
|
|
168
168
|
continue;
|
|
169
169
|
}
|
|
170
170
|
if (arg === "--dry-run") {
|
|
@@ -196,7 +196,7 @@ build-forms - 构建表单页面
|
|
|
196
196
|
选项:
|
|
197
197
|
--form <name> 只构建指定表单(src/forms/ 下的目录名)
|
|
198
198
|
--only <list> 只构建指定模块,如 forms/customer,pages/dashboard
|
|
199
|
-
--force
|
|
199
|
+
--force 忽略增量缓存,强制重建表单产物
|
|
200
200
|
--dry-run 只打印增量计划,不构建
|
|
201
201
|
--clean-cache 删除 .openxiangda/build-cache.json
|
|
202
202
|
--no-runtime-cache 强制重建共享 runtime
|
|
@@ -242,7 +242,13 @@ import '../src/index.css';
|
|
|
242
242
|
|
|
243
243
|
const runtimeVersion = '${runtimeVersionPlaceholder}';
|
|
244
244
|
const roots = new WeakMap();
|
|
245
|
+
const portalContainerCleanups = new WeakMap();
|
|
245
246
|
const { StandardFormPage, defineFormSchema } = SyFormComponentsModule;
|
|
247
|
+
const NAMESPACE_ROOT_CLASS = 'sy-app-workspace';
|
|
248
|
+
const RUNTIME_PORTAL_ATTR = 'data-sy-runtime-portal';
|
|
249
|
+
const PORTAL_CONTAINER_STACK_GLOBAL = '__OPENXIANGDA_PORTAL_CONTAINER_STACK__';
|
|
250
|
+
const PORTAL_CONTAINER_RESOLVER_GLOBAL = '${portalContainerResolverGlobal}';
|
|
251
|
+
const portalContainers = new WeakMap();
|
|
246
252
|
|
|
247
253
|
function getStyleContainer(el) {
|
|
248
254
|
const rootNode = el.getRootNode?.();
|
|
@@ -251,58 +257,143 @@ function getStyleContainer(el) {
|
|
|
251
257
|
: document.head;
|
|
252
258
|
}
|
|
253
259
|
|
|
254
|
-
function
|
|
255
|
-
return
|
|
260
|
+
function isShadowRoot(rootNode) {
|
|
261
|
+
return typeof ShadowRoot !== 'undefined' && rootNode instanceof ShadowRoot;
|
|
256
262
|
}
|
|
257
263
|
|
|
258
|
-
function
|
|
259
|
-
|
|
260
|
-
|
|
264
|
+
function getRuntimeRoot(el, triggerNode) {
|
|
265
|
+
if (isShadowRoot(el.getRootNode?.())) return el;
|
|
266
|
+
const triggerRoot = triggerNode?.closest?.('.' + NAMESPACE_ROOT_CLASS);
|
|
267
|
+
return (
|
|
268
|
+
triggerRoot ||
|
|
269
|
+
el.closest?.('.' + NAMESPACE_ROOT_CLASS) ||
|
|
270
|
+
el.querySelector?.('.' + NAMESPACE_ROOT_CLASS) ||
|
|
271
|
+
el
|
|
272
|
+
);
|
|
261
273
|
}
|
|
262
274
|
|
|
263
|
-
function
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
275
|
+
function getOverlayRoot(el, portalContainer, triggerNode) {
|
|
276
|
+
return portalContainer?.isConnected ? portalContainer : getRuntimeRoot(el, triggerNode);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function getPopupContainer(el, portalContainer) {
|
|
280
|
+
return (triggerNode) => getOverlayRoot(el, portalContainer, triggerNode);
|
|
269
281
|
}
|
|
270
282
|
|
|
271
283
|
function getTargetContainer(el) {
|
|
272
|
-
return () => (
|
|
284
|
+
return () => getRuntimeRoot(el);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function createAntdConfig(el, portalContainer) {
|
|
288
|
+
return {
|
|
289
|
+
locale: zhCN,
|
|
290
|
+
prefixCls: 'sy-ant',
|
|
291
|
+
iconPrefixCls: 'sy-anticon',
|
|
292
|
+
theme: antdTheme,
|
|
293
|
+
getPopupContainer: getPopupContainer(el, portalContainer),
|
|
294
|
+
getTargetContainer: getTargetContainer(el),
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function createPortalContainer(el) {
|
|
299
|
+
const rootNode = el.getRootNode?.();
|
|
300
|
+
const parent = isShadowRoot(rootNode) ? rootNode : el.ownerDocument.body;
|
|
301
|
+
const portalContainer = el.ownerDocument.createElement('div');
|
|
302
|
+
portalContainer.setAttribute(RUNTIME_PORTAL_ATTR, '');
|
|
303
|
+
portalContainer.classList.add(NAMESPACE_ROOT_CLASS);
|
|
304
|
+
parent.appendChild(portalContainer);
|
|
305
|
+
return portalContainer;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function installRuntimePortalContainer(el) {
|
|
309
|
+
const portalContainer = createPortalContainer(el);
|
|
310
|
+
const stack = Array.isArray(globalThis[PORTAL_CONTAINER_STACK_GLOBAL])
|
|
311
|
+
? globalThis[PORTAL_CONTAINER_STACK_GLOBAL]
|
|
312
|
+
: [];
|
|
313
|
+
stack.push(portalContainer);
|
|
314
|
+
globalThis[PORTAL_CONTAINER_STACK_GLOBAL] = stack;
|
|
315
|
+
globalThis[PORTAL_CONTAINER_RESOLVER_GLOBAL] = () => {
|
|
316
|
+
for (let index = stack.length - 1; index >= 0; index -= 1) {
|
|
317
|
+
const candidate = stack[index];
|
|
318
|
+
if (candidate?.isConnected) {
|
|
319
|
+
return candidate;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return (
|
|
323
|
+
document.querySelector('[' + RUNTIME_PORTAL_ATTR + ']') ||
|
|
324
|
+
document.querySelector('.' + NAMESPACE_ROOT_CLASS) ||
|
|
325
|
+
document.body
|
|
326
|
+
);
|
|
327
|
+
};
|
|
328
|
+
return {
|
|
329
|
+
container: portalContainer,
|
|
330
|
+
release: () => {
|
|
331
|
+
const position = stack.lastIndexOf(portalContainer);
|
|
332
|
+
if (position >= 0) stack.splice(position, 1);
|
|
333
|
+
portalContainer.remove();
|
|
334
|
+
},
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function installAntdStaticHolder(el, portalContainer) {
|
|
339
|
+
ConfigProvider.config({
|
|
340
|
+
prefixCls: 'sy-ant',
|
|
341
|
+
iconPrefixCls: 'sy-anticon',
|
|
342
|
+
theme: antdTheme,
|
|
343
|
+
holderRender: (children) => {
|
|
344
|
+
if (!el.isConnected) {
|
|
345
|
+
return (
|
|
346
|
+
<ConfigProvider prefixCls="sy-ant" iconPrefixCls="sy-anticon" theme={antdTheme}>
|
|
347
|
+
{children}
|
|
348
|
+
</ConfigProvider>
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
return (
|
|
352
|
+
<StyleProvider hashPriority="high" container={getStyleContainer(el)}>
|
|
353
|
+
<ConfigProvider {...createAntdConfig(el, portalContainer)}>{children}</ConfigProvider>
|
|
354
|
+
</StyleProvider>
|
|
355
|
+
);
|
|
356
|
+
},
|
|
357
|
+
});
|
|
273
358
|
}
|
|
274
359
|
|
|
275
360
|
function renderStandardForm(el, schemaInput, context = {}) {
|
|
276
361
|
let root = roots.get(el);
|
|
362
|
+
el.classList.add(NAMESPACE_ROOT_CLASS);
|
|
363
|
+
let portalContainer = portalContainers.get(el);
|
|
364
|
+
if (!portalContainer?.isConnected) {
|
|
365
|
+
const cleanup = portalContainerCleanups.get(el);
|
|
366
|
+
if (cleanup) cleanup();
|
|
367
|
+
const portalHandle = installRuntimePortalContainer(el);
|
|
368
|
+
portalContainer = portalHandle.container;
|
|
369
|
+
portalContainers.set(el, portalContainer);
|
|
370
|
+
portalContainerCleanups.set(el, portalHandle.release);
|
|
371
|
+
}
|
|
372
|
+
installAntdStaticHolder(el, portalContainer);
|
|
277
373
|
if (!root) {
|
|
278
|
-
if (!isInShadowRoot(el)) el.classList.add('sy-app-workspace');
|
|
279
374
|
root = createRoot(el);
|
|
280
375
|
roots.set(el, root);
|
|
281
376
|
}
|
|
282
377
|
|
|
283
378
|
const schema = defineFormSchema(schemaInput);
|
|
379
|
+
const antdConfig = createAntdConfig(el, portalContainer);
|
|
284
380
|
root.render(
|
|
285
381
|
<StyleProvider hashPriority="high" container={getStyleContainer(el)}>
|
|
286
|
-
<ConfigProvider
|
|
287
|
-
locale={zhCN}
|
|
288
|
-
prefixCls="sy-ant"
|
|
289
|
-
iconPrefixCls="sy-anticon"
|
|
290
|
-
theme={antdTheme}
|
|
291
|
-
getPopupContainer={getPopupContainer(el)}
|
|
292
|
-
getTargetContainer={getTargetContainer(el)}
|
|
293
|
-
>
|
|
382
|
+
<ConfigProvider {...antdConfig}>
|
|
294
383
|
<AntdApp>
|
|
295
|
-
<
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
384
|
+
<div className="sy-app-workspace">
|
|
385
|
+
<StandardFormPage
|
|
386
|
+
schema={schema}
|
|
387
|
+
mode={context.mode || 'submit'}
|
|
388
|
+
initialValues={context.initialValues}
|
|
389
|
+
permissions={context.permissions}
|
|
390
|
+
formUuid={context.formUuid}
|
|
391
|
+
appType={context.appType}
|
|
392
|
+
formInstanceId={context.formInstanceId}
|
|
393
|
+
onSubmit={context.onSubmit}
|
|
394
|
+
inDrawer={context.inDrawer}
|
|
395
|
+
/>
|
|
396
|
+
</div>
|
|
306
397
|
</AntdApp>
|
|
307
398
|
</ConfigProvider>
|
|
308
399
|
</StyleProvider>
|
|
@@ -315,6 +406,12 @@ function unmountStandardForm(el) {
|
|
|
315
406
|
root.unmount();
|
|
316
407
|
roots.delete(el);
|
|
317
408
|
}
|
|
409
|
+
const releasePortalContainer = portalContainerCleanups.get(el);
|
|
410
|
+
if (releasePortalContainer) {
|
|
411
|
+
releasePortalContainer();
|
|
412
|
+
portalContainerCleanups.delete(el);
|
|
413
|
+
portalContainers.delete(el);
|
|
414
|
+
}
|
|
318
415
|
}
|
|
319
416
|
|
|
320
417
|
export function createStandardFormModule(schemaInput) {
|
|
@@ -356,6 +453,24 @@ export default formRuntime;
|
|
|
356
453
|
`;
|
|
357
454
|
}
|
|
358
455
|
|
|
456
|
+
function createAntdMobilePortalPatchPlugin() {
|
|
457
|
+
const resolverExpression = `globalThis.${portalContainerResolverGlobal}?.() || document.body`;
|
|
458
|
+
return {
|
|
459
|
+
name: "openxiangda-antd-mobile-portal-container",
|
|
460
|
+
transform(code, id) {
|
|
461
|
+
if (!id.includes("/antd-mobile/") && !id.includes("\\\\antd-mobile\\\\")) {
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
const nextCode = code.replace(
|
|
465
|
+
/getContainer:\s*\(\)\s*=>\s*document\.body/g,
|
|
466
|
+
`getContainer: () => ${resolverExpression}`,
|
|
467
|
+
);
|
|
468
|
+
if (nextCode === code) return null;
|
|
469
|
+
return { code: nextCode, map: null };
|
|
470
|
+
},
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
359
474
|
function createFormRuntimeProxyPlugin() {
|
|
360
475
|
return {
|
|
361
476
|
name: "sy-form-runtime-proxy",
|
|
@@ -547,7 +662,7 @@ async function buildSharedRuntime(options = {}) {
|
|
|
547
662
|
},
|
|
548
663
|
},
|
|
549
664
|
},
|
|
550
|
-
plugins: [reactPlugin],
|
|
665
|
+
plugins: [createAntdMobilePortalPatchPlugin(), reactPlugin],
|
|
551
666
|
logLevel: "warn",
|
|
552
667
|
});
|
|
553
668
|
|
|
@@ -689,7 +804,11 @@ async function buildForm(form) {
|
|
|
689
804
|
},
|
|
690
805
|
},
|
|
691
806
|
},
|
|
692
|
-
plugins: [
|
|
807
|
+
plugins: [
|
|
808
|
+
createAntdMobilePortalPatchPlugin(),
|
|
809
|
+
createFormRuntimeProxyPlugin(),
|
|
810
|
+
reactPlugin,
|
|
811
|
+
].filter(Boolean),
|
|
693
812
|
logLevel: "warn",
|
|
694
813
|
});
|
|
695
814
|
|
|
@@ -34,12 +34,14 @@ const tmpDir = path.join(rootDir, ".tmp");
|
|
|
34
34
|
const runtimeDistDir = path.join(distRoot, "page-runtime");
|
|
35
35
|
const runtimeVersionPlaceholder = "__SY_PAGE_RUNTIME_VERSION__";
|
|
36
36
|
const runtimeCacheFileName = "build-cache.json";
|
|
37
|
+
const portalContainerResolverGlobal = "__OPENXIANGDA_GET_PORTAL_CONTAINER__";
|
|
37
38
|
|
|
38
39
|
const runtimePackages = [
|
|
39
40
|
"react",
|
|
40
41
|
"react-dom",
|
|
41
42
|
"@ant-design/cssinjs",
|
|
42
43
|
"antd",
|
|
44
|
+
"antd-mobile",
|
|
43
45
|
"@ant-design/icons",
|
|
44
46
|
"openxiangda",
|
|
45
47
|
];
|
|
@@ -163,7 +165,6 @@ function parseArgs(argv) {
|
|
|
163
165
|
}
|
|
164
166
|
if (arg === "--force") {
|
|
165
167
|
result.force = true;
|
|
166
|
-
result.runtimeCache = false;
|
|
167
168
|
continue;
|
|
168
169
|
}
|
|
169
170
|
if (arg === "--dry-run") {
|
|
@@ -194,7 +195,7 @@ build-pages - 构建复杂代码页
|
|
|
194
195
|
选项:
|
|
195
196
|
--page <name> 只构建指定页面目录
|
|
196
197
|
--only <list> 只构建指定模块,如 forms/customer,pages/dashboard
|
|
197
|
-
--force
|
|
198
|
+
--force 忽略增量缓存,强制重建页面产物
|
|
198
199
|
--dry-run 只打印增量计划,不构建
|
|
199
200
|
--clean-cache 删除 .openxiangda/build-cache.json
|
|
200
201
|
--no-runtime-cache 强制重建共享 runtime
|
|
@@ -339,6 +340,24 @@ async function createCssOptions() {
|
|
|
339
340
|
};
|
|
340
341
|
}
|
|
341
342
|
|
|
343
|
+
function createAntdMobilePortalPatchPlugin() {
|
|
344
|
+
const resolverExpression = `globalThis.${portalContainerResolverGlobal}?.() || document.body`;
|
|
345
|
+
return {
|
|
346
|
+
name: "openxiangda-antd-mobile-portal-container",
|
|
347
|
+
transform(code, id) {
|
|
348
|
+
if (!id.includes("/antd-mobile/") && !id.includes("\\\\antd-mobile\\\\")) {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
const nextCode = code.replace(
|
|
352
|
+
/getContainer:\s*\(\)\s*=>\s*document\.body/g,
|
|
353
|
+
`getContainer: () => ${resolverExpression}`,
|
|
354
|
+
);
|
|
355
|
+
if (nextCode === code) return null;
|
|
356
|
+
return { code: nextCode, map: null };
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
342
361
|
function createResolveAlias() {
|
|
343
362
|
return [
|
|
344
363
|
{
|
|
@@ -373,7 +392,7 @@ async function buildSharedRuntime(options = {}) {
|
|
|
373
392
|
"process.env.NODE_ENV": JSON.stringify("production"),
|
|
374
393
|
"process.env": JSON.stringify({ NODE_ENV: "production" }),
|
|
375
394
|
},
|
|
376
|
-
plugins: [react()],
|
|
395
|
+
plugins: [createAntdMobilePortalPatchPlugin(), react()],
|
|
377
396
|
resolve: {
|
|
378
397
|
alias: createResolveAlias(),
|
|
379
398
|
dedupe: ["react", "react-dom", "antd", "@ant-design/cssinjs"],
|
|
@@ -655,7 +674,11 @@ async function buildPage(page) {
|
|
|
655
674
|
"process.env.NODE_ENV": JSON.stringify("production"),
|
|
656
675
|
"process.env": JSON.stringify({ NODE_ENV: "production" }),
|
|
657
676
|
},
|
|
658
|
-
plugins: [
|
|
677
|
+
plugins: [
|
|
678
|
+
createAntdMobilePortalPatchPlugin(),
|
|
679
|
+
createSharedRuntimeProxyPlugin(),
|
|
680
|
+
react(),
|
|
681
|
+
],
|
|
659
682
|
resolve: {
|
|
660
683
|
alias: createResolveAlias(),
|
|
661
684
|
dedupe: ["react", "react-dom", "antd", "@ant-design/cssinjs"],
|
|
@@ -37,6 +37,18 @@ function shouldSkipSelector(selector, prefix) {
|
|
|
37
37
|
return false;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
function createAntdPrefixAlias(selector) {
|
|
41
|
+
const aliased = selector
|
|
42
|
+
.replace(/(^|[^A-Za-z0-9_-])\.ant-/g, "$1.sy-ant-")
|
|
43
|
+
.replace(/(^|[^A-Za-z0-9_-])\.anticon-/g, "$1.sy-anticon-")
|
|
44
|
+
.replace(/(^|[^A-Za-z0-9_-])\.anticon(?=$|[^A-Za-z0-9_-])/g, "$1.sy-anticon");
|
|
45
|
+
return aliased === selector ? null : aliased;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function uniqueSelectors(selectors) {
|
|
49
|
+
return Array.from(new Set(selectors));
|
|
50
|
+
}
|
|
51
|
+
|
|
40
52
|
export function createNamespaceCssPlugin(prefix = DEFAULT_PREFIX) {
|
|
41
53
|
return {
|
|
42
54
|
postcssPlugin: "openxiangda-namespace-css",
|
|
@@ -49,11 +61,15 @@ export function createNamespaceCssPlugin(prefix = DEFAULT_PREFIX) {
|
|
|
49
61
|
}
|
|
50
62
|
parent = parent.parent;
|
|
51
63
|
}
|
|
52
|
-
rule.selector =
|
|
53
|
-
.
|
|
54
|
-
shouldSkipSelector(selector, prefix)
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
rule.selector = uniqueSelectors(
|
|
65
|
+
splitSelectors(rule.selector).flatMap((selector) => {
|
|
66
|
+
const scopedSelector = shouldSkipSelector(selector, prefix)
|
|
67
|
+
? selector
|
|
68
|
+
: `${prefix} ${selector}`;
|
|
69
|
+
const antdAlias = createAntdPrefixAlias(scopedSelector);
|
|
70
|
+
return antdAlias ? [scopedSelector, antdAlias] : [scopedSelector];
|
|
71
|
+
}),
|
|
72
|
+
).join(", ");
|
|
57
73
|
},
|
|
58
74
|
};
|
|
59
75
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import postcss from "postcss";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { createNamespaceCssPlugin } from "./namespace-css.mjs";
|
|
5
|
+
|
|
6
|
+
function transform(css: string) {
|
|
7
|
+
return postcss([createNamespaceCssPlugin()]).process(css, {
|
|
8
|
+
from: undefined,
|
|
9
|
+
}).css;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe("createNamespaceCssPlugin", () => {
|
|
13
|
+
it("adds sy-ant aliases for developer CSS that targets default antd classes", () => {
|
|
14
|
+
const css = transform(
|
|
15
|
+
".portal-search .ant-input-affix-wrapper,.ant-select-dropdown .ant-select-item,.anticon-close{background:#fff}",
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
expect(css).toContain(
|
|
19
|
+
".sy-app-workspace .portal-search .ant-input-affix-wrapper",
|
|
20
|
+
);
|
|
21
|
+
expect(css).toContain(
|
|
22
|
+
".sy-app-workspace .portal-search .sy-ant-input-affix-wrapper",
|
|
23
|
+
);
|
|
24
|
+
expect(css).toContain(".ant-select-dropdown .ant-select-item");
|
|
25
|
+
expect(css).toContain(".sy-ant-select-dropdown .sy-ant-select-item");
|
|
26
|
+
expect(css).toContain(".anticon-close");
|
|
27
|
+
expect(css).toContain(".sy-anticon-close");
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -29,6 +29,18 @@ const layeredTailwindCss = `@import "openxiangda/styles/tokens.css";
|
|
|
29
29
|
@tailwind components;
|
|
30
30
|
@tailwind utilities;
|
|
31
31
|
`;
|
|
32
|
+
const canonicalPostcssConfig = `const tailwindcss = require("tailwindcss");
|
|
33
|
+
const autoprefixer = require("autoprefixer");
|
|
34
|
+
const { createOpenXiangdaNamespaceCssPlugin } = require("openxiangda/build");
|
|
35
|
+
|
|
36
|
+
module.exports = {
|
|
37
|
+
plugins: [
|
|
38
|
+
tailwindcss(),
|
|
39
|
+
createOpenXiangdaNamespaceCssPlugin(),
|
|
40
|
+
autoprefixer(),
|
|
41
|
+
],
|
|
42
|
+
};
|
|
43
|
+
`;
|
|
32
44
|
|
|
33
45
|
export const canonicalTailwindConfig = `${managedHeader}
|
|
34
46
|
/** @type {import('tailwindcss').Config} */
|
|
@@ -71,6 +83,15 @@ export function isWorkspaceTailwindCssCurrent(content) {
|
|
|
71
83
|
);
|
|
72
84
|
}
|
|
73
85
|
|
|
86
|
+
export function isWorkspacePostcssConfigCurrent(content) {
|
|
87
|
+
return Boolean(
|
|
88
|
+
content.includes("createOpenXiangdaNamespaceCssPlugin") &&
|
|
89
|
+
content.includes("openxiangda/build") &&
|
|
90
|
+
content.includes("tailwindcss") &&
|
|
91
|
+
content.includes("autoprefixer"),
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
74
95
|
function hasBlocklistEntry(content, entry) {
|
|
75
96
|
const singleQuoted = `'${entry.slice(1, -1)}'`;
|
|
76
97
|
return content.includes(entry) || content.includes(singleQuoted);
|
|
@@ -176,6 +197,22 @@ export function patchWorkspaceTailwindCss(content) {
|
|
|
176
197
|
return customCss ? `${layeredTailwindCss}\n${customCss}` : layeredTailwindCss;
|
|
177
198
|
}
|
|
178
199
|
|
|
200
|
+
function isStandardPostcssConfig(content) {
|
|
201
|
+
return (
|
|
202
|
+
/plugins\s*:\s*\{[\s\S]*tailwindcss\s*:\s*\{[\s\S]*autoprefixer\s*:\s*\{[\s\S]*\}/s.test(
|
|
203
|
+
content,
|
|
204
|
+
) || /plugins\s*:\s*\[[\s\S]*tailwindcss\([\s\S]*autoprefixer\(/s.test(content)
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function patchWorkspacePostcssConfig(content) {
|
|
209
|
+
if (isWorkspacePostcssConfigCurrent(content)) return content;
|
|
210
|
+
if (!content.trim() || isStandardPostcssConfig(content)) {
|
|
211
|
+
return canonicalPostcssConfig;
|
|
212
|
+
}
|
|
213
|
+
return content;
|
|
214
|
+
}
|
|
215
|
+
|
|
179
216
|
export function ensureWorkspaceTailwindConfig(workspaceRoot) {
|
|
180
217
|
const configPath = path.join(workspaceRoot, "tailwind.config.cjs");
|
|
181
218
|
const current = fs.existsSync(configPath)
|
|
@@ -196,10 +233,23 @@ export function ensureWorkspaceTailwindConfig(workspaceRoot) {
|
|
|
196
233
|
fs.writeFileSync(cssPath, nextCss, "utf-8");
|
|
197
234
|
}
|
|
198
235
|
|
|
236
|
+
const postcssPath = path.join(workspaceRoot, "postcss.config.cjs");
|
|
237
|
+
const currentPostcss = fs.existsSync(postcssPath)
|
|
238
|
+
? fs.readFileSync(postcssPath, "utf-8")
|
|
239
|
+
: "";
|
|
240
|
+
const nextPostcss = patchWorkspacePostcssConfig(currentPostcss);
|
|
241
|
+
if (currentPostcss !== nextPostcss) {
|
|
242
|
+
fs.writeFileSync(postcssPath, nextPostcss, "utf-8");
|
|
243
|
+
}
|
|
244
|
+
|
|
199
245
|
return {
|
|
200
|
-
changed:
|
|
246
|
+
changed:
|
|
247
|
+
current !== nextContent ||
|
|
248
|
+
currentCss !== nextCss ||
|
|
249
|
+
currentPostcss !== nextPostcss,
|
|
201
250
|
path: configPath,
|
|
202
251
|
cssPath,
|
|
252
|
+
postcssPath,
|
|
203
253
|
};
|
|
204
254
|
}
|
|
205
255
|
|
|
@@ -6,6 +6,7 @@ import { afterEach, describe, expect, it } from "vitest";
|
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
ensureWorkspaceTailwindConfig,
|
|
9
|
+
patchWorkspacePostcssConfig,
|
|
9
10
|
patchWorkspaceTailwindCss,
|
|
10
11
|
patchWorkspaceTailwindConfig,
|
|
11
12
|
validateWorkspaceTailwindConfig,
|
|
@@ -42,6 +43,10 @@ describe("workspace Tailwind config helpers", () => {
|
|
|
42
43
|
path.join(workspaceRoot, "src", "index.css"),
|
|
43
44
|
"utf-8",
|
|
44
45
|
);
|
|
46
|
+
const postcssContent = fs.readFileSync(
|
|
47
|
+
path.join(workspaceRoot, "postcss.config.cjs"),
|
|
48
|
+
"utf-8",
|
|
49
|
+
);
|
|
45
50
|
|
|
46
51
|
expect(result.changed).toBe(true);
|
|
47
52
|
expect(nextContent).toContain(
|
|
@@ -53,6 +58,8 @@ describe("workspace Tailwind config helpers", () => {
|
|
|
53
58
|
expect(cssContent).toContain("@tailwind base;");
|
|
54
59
|
expect(cssContent).toContain("@tailwind components;");
|
|
55
60
|
expect(cssContent).toContain("@tailwind utilities;");
|
|
61
|
+
expect(postcssContent).toContain("createOpenXiangdaNamespaceCssPlugin");
|
|
62
|
+
expect(postcssContent).toContain('require("openxiangda/build")');
|
|
56
63
|
expect(validateWorkspaceTailwindConfig(workspaceRoot)).toEqual([]);
|
|
57
64
|
});
|
|
58
65
|
|
|
@@ -115,7 +122,7 @@ module.exports = {
|
|
|
115
122
|
}
|
|
116
123
|
`);
|
|
117
124
|
|
|
118
|
-
expect(nextContent.
|
|
125
|
+
expect(nextContent).toContain("@layer tailwind-base {");
|
|
119
126
|
expect(nextContent).toContain("@tailwind components;");
|
|
120
127
|
expect(nextContent).toContain("#root");
|
|
121
128
|
});
|
|
@@ -136,6 +143,28 @@ module.exports = {
|
|
|
136
143
|
expect(patchWorkspaceTailwindCss(source)).toBe(source);
|
|
137
144
|
});
|
|
138
145
|
|
|
146
|
+
it("patches standard PostCSS config with openxiangda namespace css plugin", () => {
|
|
147
|
+
const nextContent = patchWorkspacePostcssConfig(`module.exports = {
|
|
148
|
+
plugins: {
|
|
149
|
+
tailwindcss: {},
|
|
150
|
+
autoprefixer: {},
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
`);
|
|
154
|
+
|
|
155
|
+
expect(nextContent).toContain("createOpenXiangdaNamespaceCssPlugin");
|
|
156
|
+
expect(nextContent).toContain("tailwindcss()");
|
|
157
|
+
expect(nextContent).toContain("autoprefixer()");
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("preserves custom PostCSS config when it cannot be patched safely", () => {
|
|
161
|
+
const source = `const custom = require("./postcss-custom");
|
|
162
|
+
module.exports = { plugins: [custom()] };
|
|
163
|
+
`;
|
|
164
|
+
|
|
165
|
+
expect(patchWorkspacePostcssConfig(source)).toBe(source);
|
|
166
|
+
});
|
|
167
|
+
|
|
139
168
|
it("removes stale antd layer declaration from index css", () => {
|
|
140
169
|
const nextContent = patchWorkspaceTailwindCss(`@layer tailwind-base, antd;
|
|
141
170
|
|
|
@@ -148,7 +177,7 @@ module.exports = {
|
|
|
148
177
|
`);
|
|
149
178
|
|
|
150
179
|
expect(nextContent).not.toContain("@layer tailwind-base, antd;");
|
|
151
|
-
expect(nextContent.
|
|
180
|
+
expect(nextContent).toContain("@layer tailwind-base {");
|
|
152
181
|
});
|
|
153
182
|
|
|
154
183
|
it("reports stale config during update check", () => {
|
|
@@ -1,6 +1,24 @@
|
|
|
1
|
+
const tailwindcss = require("tailwindcss");
|
|
2
|
+
const autoprefixer = require("autoprefixer");
|
|
3
|
+
|
|
4
|
+
function loadOpenXiangdaBuild() {
|
|
5
|
+
try {
|
|
6
|
+
return require("openxiangda/build");
|
|
7
|
+
} catch (error) {
|
|
8
|
+
try {
|
|
9
|
+
return require("../../packages/sdk/dist/build/index.cjs");
|
|
10
|
+
} catch {
|
|
11
|
+
throw error;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { createOpenXiangdaNamespaceCssPlugin } = loadOpenXiangdaBuild();
|
|
17
|
+
|
|
1
18
|
module.exports = {
|
|
2
|
-
plugins:
|
|
3
|
-
tailwindcss
|
|
4
|
-
|
|
5
|
-
|
|
19
|
+
plugins: [
|
|
20
|
+
tailwindcss(),
|
|
21
|
+
createOpenXiangdaNamespaceCssPlugin(),
|
|
22
|
+
autoprefixer(),
|
|
23
|
+
],
|
|
6
24
|
};
|