niuma-ui 1.2.1 → 1.2.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/CHANGELOG.md +13 -0
- package/dist/components/RsMonacoEditor.js +1 -0
- package/dist/components/RsTable.js +1 -0
- package/dist/monaco/debug-decorations2.js +2 -1
- package/dist/styles.css +2 -0
- package/dist/vite-plugins/niuma-ui-host.d.ts +10 -0
- package/dist/vite-plugins/niuma-ui-host.js +144 -34
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,19 @@
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.2.3] - 2026-08-30
|
|
10
|
+
|
|
11
|
+
### 修复
|
|
12
|
+
|
|
13
|
+
- 库打包把脚本里的 `import './x.css'` 写回入口。此前 Vite 收成 empty css 注释,`RsTable` 的 `rs-table.css`、Monaco 调试装饰等旁路样式进不了 npm,Ops 等宿主生产表格 / 编辑器与 `pnpm dev` 对不齐。
|
|
14
|
+
- `niumaUiHost` 给 `styles.css` 补 Tailwind `@source`(宿主根 + 包内 components/composables)。CI 只处理 `node_modules` 里的 CSS 时,扫描范围与本机联调源码一致。
|
|
15
|
+
|
|
16
|
+
## [1.2.2] - 2026-08-30
|
|
17
|
+
|
|
18
|
+
### 修复
|
|
19
|
+
|
|
20
|
+
- `niumaUiHost` 能解析 npm `dist/index.js` 桶(`import X_default` + `export { X_default as RsX }`)。此前只认源码 `export { default as RsX } from './...'`,宿主 `vite build` 会把具名 re-export 收成 `export type`,官网等产品打包报 `MISSING_EXPORT`。解析不到运行时导出时直接失败,不再静默改成 type-only。
|
|
21
|
+
|
|
9
22
|
## [1.2.1] - 2026-08-30
|
|
10
23
|
|
|
11
24
|
### 变更
|
package/dist/styles.css
CHANGED
|
@@ -15,8 +15,18 @@ export type NiumaUiBinding = {
|
|
|
15
15
|
* NiumaUiHost 返回一组插件(Vite 会摊平)。serve 做 styles alias,两侧都改写具名导入。
|
|
16
16
|
*/
|
|
17
17
|
export declare function niumaUiHost(options?: NiumaUiHostOptions): Plugin[];
|
|
18
|
+
/**
|
|
19
|
+
* ToSourceRel 把目录收成 Tailwind @source 可用的相对路径。
|
|
20
|
+
*/
|
|
21
|
+
export declare function toSourceRel(fromDir: string, targetDir: string): string;
|
|
18
22
|
/**
|
|
19
23
|
* RewriteHostStatement 改写一条已由 lexer 切出的静态 import/export。
|
|
20
24
|
* 类型-only、namespace、动态 import 原样返回 null。
|
|
21
25
|
*/
|
|
22
26
|
export declare function rewriteHostStatement(stmt: string, spec: string, map: Map<string, NiumaUiBinding>, targetOf: (from: string) => string): string | null;
|
|
27
|
+
/**
|
|
28
|
+
* ParseRuntimeBindings 从主入口抽出运行时导出名到文件的映射。
|
|
29
|
+
* 同时认源码 `export { default as RsX } from './...'` 和发布桶
|
|
30
|
+
* `import RsX_default from './...'` + `export { RsX_default as RsX }`。
|
|
31
|
+
*/
|
|
32
|
+
export declare function parseRuntimeBindings(source: string): Map<string, NiumaUiBinding>;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* apply / configResolved 区分 serve 与 build。
|
|
8
8
|
*/
|
|
9
9
|
import { existsSync, readFileSync } from 'node:fs';
|
|
10
|
-
import { dirname, join } from 'node:path';
|
|
10
|
+
import { dirname, join, relative } from 'node:path';
|
|
11
11
|
import { createRequire } from 'node:module';
|
|
12
12
|
import { createFilter } from '@rollup/pluginutils';
|
|
13
13
|
import { init, parse } from 'es-module-lexer';
|
|
@@ -20,12 +20,13 @@ export function niumaUiHost(options = {}) {
|
|
|
20
20
|
const root = options.root ?? resolvePkgRoot();
|
|
21
21
|
const ctx = {
|
|
22
22
|
root,
|
|
23
|
+
viteRoot: process.cwd(),
|
|
23
24
|
hasSrc: existsSync(join(root, 'src/index.ts')),
|
|
24
25
|
useSource: false,
|
|
25
26
|
map: loadRuntimeBindings(root),
|
|
26
27
|
filter: createFilter(options.include ?? [/\.([cm]?[jt]sx?|vue)(?:$|\?)/], options.exclude ?? [/\/node_modules\//]),
|
|
27
28
|
};
|
|
28
|
-
return [niumaUiHostAlias(ctx), niumaUiHostRewrite(ctx)];
|
|
29
|
+
return [niumaUiHostAlias(ctx), niumaUiHostRewrite(ctx), niumaUiHostTailwindSource(ctx)];
|
|
29
30
|
}
|
|
30
31
|
/**
|
|
31
32
|
* NiumaUiHostAlias 仅在 serve 且存在 src 时把 styles.css 指到源码,并排除整桶预构建。
|
|
@@ -63,6 +64,7 @@ function niumaUiHostRewrite(ctx) {
|
|
|
63
64
|
enforce: 'pre',
|
|
64
65
|
configResolved(config) {
|
|
65
66
|
ctx.useSource = config.command === 'serve' && ctx.hasSrc;
|
|
67
|
+
ctx.viteRoot = config.root;
|
|
66
68
|
},
|
|
67
69
|
async transform(code, id) {
|
|
68
70
|
if (!ctx.filter(id))
|
|
@@ -95,6 +97,57 @@ function niumaUiHostRewrite(ctx) {
|
|
|
95
97
|
},
|
|
96
98
|
};
|
|
97
99
|
}
|
|
100
|
+
const NIUMA_UI_STYLES = /niuma-ui[\\/](?:src|dist)[\\/]styles\.css$/;
|
|
101
|
+
/**
|
|
102
|
+
* NiumaUiHostTailwindSource 给 niuma-ui/styles.css 补上宿主根与包内扫描目录。
|
|
103
|
+
* CI 只看 node_modules 里的 styles.css 时,Tailwind 否则扫不到官网 / Ops 源码。
|
|
104
|
+
*/
|
|
105
|
+
function niumaUiHostTailwindSource(ctx) {
|
|
106
|
+
return {
|
|
107
|
+
name: 'niuma-ui-host:tailwind-source',
|
|
108
|
+
enforce: 'pre',
|
|
109
|
+
configResolved(config) {
|
|
110
|
+
ctx.viteRoot = config.root;
|
|
111
|
+
},
|
|
112
|
+
transform(code, id) {
|
|
113
|
+
const file = id.split('?')[0] ?? id;
|
|
114
|
+
if (!NIUMA_UI_STYLES.test(file.replace(/\\/g, '/')))
|
|
115
|
+
return null;
|
|
116
|
+
if (!code.includes('tailwindcss'))
|
|
117
|
+
return null;
|
|
118
|
+
const cssDir = dirname(file);
|
|
119
|
+
const pkgScan = ctx.useSource && existsSync(join(ctx.root, 'src'))
|
|
120
|
+
? join(ctx.root, 'src')
|
|
121
|
+
: existsSync(join(ctx.root, 'dist'))
|
|
122
|
+
? join(ctx.root, 'dist')
|
|
123
|
+
: ctx.root;
|
|
124
|
+
const specs = [ctx.viteRoot, pkgScan]
|
|
125
|
+
.map((dir) => toSourceRel(cssDir, dir))
|
|
126
|
+
.filter((spec, i, all) => all.indexOf(spec) === i);
|
|
127
|
+
const inject = specs
|
|
128
|
+
.filter((spec) => !code.includes(`@source '${spec}'`) && !code.includes(`@source "${spec}"`))
|
|
129
|
+
.map((spec) => `@source '${spec}';`)
|
|
130
|
+
.join('\n');
|
|
131
|
+
if (!inject)
|
|
132
|
+
return null;
|
|
133
|
+
const next = code.replace(/@import\s+['"]tailwindcss['"]\s*;/, `@import 'tailwindcss';\n${inject}`);
|
|
134
|
+
if (next === code)
|
|
135
|
+
return null;
|
|
136
|
+
return { code: next, map: null };
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* ToSourceRel 把目录收成 Tailwind @source 可用的相对路径。
|
|
142
|
+
*/
|
|
143
|
+
export function toSourceRel(fromDir, targetDir) {
|
|
144
|
+
let rel = relative(fromDir, targetDir).replace(/\\/g, '/');
|
|
145
|
+
if (rel === '')
|
|
146
|
+
return '.';
|
|
147
|
+
if (!rel.startsWith('.'))
|
|
148
|
+
rel = `./${rel}`;
|
|
149
|
+
return rel;
|
|
150
|
+
}
|
|
98
151
|
/**
|
|
99
152
|
* RewriteHostStatement 改写一条已由 lexer 切出的静态 import/export。
|
|
100
153
|
* 类型-only、namespace、动态 import 原样返回 null。
|
|
@@ -109,12 +162,9 @@ export function rewriteHostStatement(stmt, spec, map, targetOf) {
|
|
|
109
162
|
if (parsed.defaultName) {
|
|
110
163
|
const binding = map.get(parsed.defaultName);
|
|
111
164
|
if (!binding) {
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
const target = targetOf(binding.from);
|
|
116
|
-
lines.push(emitBinding(keyword, binding.kind, parsed.defaultName, parsed.defaultName, target));
|
|
165
|
+
throw new Error(`niumaUiHost: 未找到运行时导出 ${parsed.defaultName},无法改写到发布子路径`);
|
|
117
166
|
}
|
|
167
|
+
lines.push(emitBinding(keyword, binding.kind, parsed.defaultName, parsed.defaultName, targetOf(binding.from)));
|
|
118
168
|
}
|
|
119
169
|
for (const part of parsed.named) {
|
|
120
170
|
if (part.isType) {
|
|
@@ -123,11 +173,9 @@ export function rewriteHostStatement(stmt, spec, map, targetOf) {
|
|
|
123
173
|
}
|
|
124
174
|
const binding = map.get(part.imported);
|
|
125
175
|
if (!binding) {
|
|
126
|
-
|
|
127
|
-
continue;
|
|
176
|
+
throw new Error(`niumaUiHost: 未找到运行时导出 ${part.imported},无法改写到发布子路径`);
|
|
128
177
|
}
|
|
129
|
-
|
|
130
|
-
lines.push(emitBinding(keyword, binding.kind, part.imported, part.local, target));
|
|
178
|
+
lines.push(emitBinding(keyword, binding.kind, part.imported, part.local, targetOf(binding.from)));
|
|
131
179
|
}
|
|
132
180
|
if (types.length > 0) {
|
|
133
181
|
const kw = parsed.isExport ? 'export type' : 'import type';
|
|
@@ -191,37 +239,99 @@ function resolvePkgRoot() {
|
|
|
191
239
|
return dirname(require.resolve('niuma-ui/package.json'));
|
|
192
240
|
}
|
|
193
241
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
242
|
+
/**
|
|
243
|
+
* ParseRuntimeBindings 从主入口抽出运行时导出名到文件的映射。
|
|
244
|
+
* 同时认源码 `export { default as RsX } from './...'` 和发布桶
|
|
245
|
+
* `import RsX_default from './...'` + `export { RsX_default as RsX }`。
|
|
246
|
+
*/
|
|
247
|
+
export function parseRuntimeBindings(source) {
|
|
199
248
|
const map = new Map();
|
|
200
|
-
const
|
|
249
|
+
const reexport = /^[ \t]*export\s+(?!type\b)\{([^}]+)\}\s+from\s*['"](\.[^'"]+)['"]/gm;
|
|
201
250
|
let match;
|
|
202
|
-
while ((match =
|
|
251
|
+
while ((match = reexport.exec(source)) !== null) {
|
|
203
252
|
const from = match[2] ?? '';
|
|
204
253
|
if (!from)
|
|
205
254
|
continue;
|
|
206
|
-
for (const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
255
|
+
for (const spec of parseSpecifierList(match[1] ?? '')) {
|
|
256
|
+
map.set(spec.right, { from, kind: spec.isDefault ? 'default' : 'named' });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const locals = new Map();
|
|
260
|
+
const imports = /^[ \t]*import\s+(?!type\b)([\s\S]+?)\s+from\s*['"](\.[^'"]+)['"]/gm;
|
|
261
|
+
while ((match = imports.exec(source)) !== null) {
|
|
262
|
+
const from = match[2] ?? '';
|
|
263
|
+
if (!from)
|
|
264
|
+
continue;
|
|
265
|
+
recordImportLocals(match[1] ?? '', from, locals);
|
|
266
|
+
}
|
|
267
|
+
const barrel = /^[ \t]*export\s+(?!type\b)\{([^}]+)\}(?!\s*from)/gm;
|
|
268
|
+
while ((match = barrel.exec(source)) !== null) {
|
|
269
|
+
for (const spec of parseSpecifierList(match[1] ?? '')) {
|
|
270
|
+
if (map.has(spec.right))
|
|
218
271
|
continue;
|
|
219
|
-
|
|
220
|
-
if (
|
|
221
|
-
map.set(
|
|
222
|
-
|
|
272
|
+
const binding = locals.get(spec.left);
|
|
273
|
+
if (binding)
|
|
274
|
+
map.set(spec.right, binding);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return map;
|
|
278
|
+
}
|
|
279
|
+
function parseSpecifierList(inner) {
|
|
280
|
+
const out = [];
|
|
281
|
+
for (const raw of inner.split(',')) {
|
|
282
|
+
const part = raw.trim();
|
|
283
|
+
if (!part || part.startsWith('type '))
|
|
284
|
+
continue;
|
|
285
|
+
const def = part.match(/^default\s+as\s+(\w+)$/);
|
|
286
|
+
if (def?.[1]) {
|
|
287
|
+
out.push({ left: 'default', right: def[1], isDefault: true });
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
const aliased = part.match(/^(\w+)\s+as\s+(\w+)$/);
|
|
291
|
+
if (aliased?.[1] && aliased[2]) {
|
|
292
|
+
out.push({ left: aliased[1], right: aliased[2], isDefault: false });
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if (/^\w+$/.test(part)) {
|
|
296
|
+
out.push({ left: part, right: part, isDefault: false });
|
|
223
297
|
}
|
|
224
298
|
}
|
|
299
|
+
return out;
|
|
300
|
+
}
|
|
301
|
+
function recordImportLocals(clause, from, locals) {
|
|
302
|
+
const trimmed = clause.trim();
|
|
303
|
+
if (!trimmed || trimmed.startsWith('*'))
|
|
304
|
+
return;
|
|
305
|
+
const namedStart = trimmed.indexOf('{');
|
|
306
|
+
if (namedStart === -1) {
|
|
307
|
+
if (/^\w+$/.test(trimmed))
|
|
308
|
+
locals.set(trimmed, { from, kind: 'default' });
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const before = trimmed.slice(0, namedStart).replace(/,\s*$/, '').trim();
|
|
312
|
+
if (before && /^\w+$/.test(before)) {
|
|
313
|
+
locals.set(before, { from, kind: 'default' });
|
|
314
|
+
}
|
|
315
|
+
const namedEnd = trimmed.lastIndexOf('}');
|
|
316
|
+
if (namedEnd <= namedStart)
|
|
317
|
+
return;
|
|
318
|
+
for (const spec of parseSpecifierList(trimmed.slice(namedStart + 1, namedEnd))) {
|
|
319
|
+
if (spec.isDefault)
|
|
320
|
+
continue;
|
|
321
|
+
locals.set(spec.right, { from, kind: 'named' });
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function loadRuntimeBindings(root) {
|
|
325
|
+
const srcIndex = join(root, 'src/index.ts');
|
|
326
|
+
const distIndex = join(root, 'dist/index.js');
|
|
327
|
+
const indexPath = existsSync(srcIndex) ? srcIndex : distIndex;
|
|
328
|
+
if (!existsSync(indexPath)) {
|
|
329
|
+
throw new Error(`niumaUiHost: 未找到 ${srcIndex} 或 ${distIndex}`);
|
|
330
|
+
}
|
|
331
|
+
const map = parseRuntimeBindings(readFileSync(indexPath, 'utf8'));
|
|
332
|
+
if (map.size === 0) {
|
|
333
|
+
throw new Error(`niumaUiHost: ${indexPath} 没有解析到运行时导出(需要 src re-export 或 dist 桶 import/export)`);
|
|
334
|
+
}
|
|
225
335
|
return map;
|
|
226
336
|
}
|
|
227
337
|
function resolveTarget(from, spec, root, useSource) {
|