@nubisco/ui 4.1.0 → 4.1.2
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/InfoHint-DesESwdO.cjs +1 -0
- package/dist/InfoHint-JwhhBFZP.js +165 -0
- package/dist/NotificationCenter-D51ty3Lq.cjs +1 -0
- package/dist/{NotificationCenter-CrwDwpt6.js → NotificationCenter-DL_Tshyb.js} +193 -192
- package/dist/all.cjs +1 -1
- package/dist/all.mjs +2 -2
- package/dist/components/InfoHint.cjs +1 -1
- package/dist/components/InfoHint.mjs +1 -1
- package/dist/components/InfoHint.vue.d.ts.map +1 -1
- package/dist/components/NotificationCenter.cjs +1 -1
- package/dist/components/NotificationCenter.mjs +1 -1
- package/dist/components/NotificationCenter.vue.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +2 -2
- package/dist/plugins/vite/glyphs.d.ts +9 -0
- package/dist/plugins/vite/glyphs.d.ts.map +1 -1
- package/dist/plugins/vite/glyphs.js +138 -14
- package/dist/plugins/vite/imports.d.ts +51 -0
- package/dist/plugins/vite/imports.d.ts.map +1 -0
- package/dist/plugins/vite/imports.js +125 -0
- package/dist/plugins/vite/index.d.ts +4 -0
- package/dist/plugins/vite/index.d.ts.map +1 -1
- package/dist/plugins/vite/index.js +35 -0
- package/dist/styles/InfoHint.css +1 -1
- package/dist/ui.css +1 -1
- package/package.json +4 -3
- package/src/plugins/vite/glyphs.ts +163 -13
- package/src/plugins/vite/imports.ts +192 -0
- package/src/plugins/vite/index.ts +44 -0
- package/dist/InfoHint-BH3UEadb.cjs +0 -1
- package/dist/InfoHint-Cj0Us0mD.js +0 -164
- package/dist/NotificationCenter-CrCoVe8p.cjs +0 -1
|
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
import Components from 'unplugin-vue-components/vite';
|
|
5
5
|
import { COMPONENT_MANIFEST } from '../../components/manifest.js';
|
|
6
6
|
import { nubiscoGlyphs } from './glyphs.js';
|
|
7
|
+
import { nubiscoImports } from './imports.js';
|
|
7
8
|
/** dist/plugins/vite/index.js → dist */
|
|
8
9
|
const defaultDistRoot = () => path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
|
9
10
|
/** Which stylesheets each component needs, recorded by the library's build. */
|
|
@@ -19,6 +20,7 @@ export function nubiscoUI(options = {}) {
|
|
|
19
20
|
const { components = true, glyphs = true, styles = true } = options;
|
|
20
21
|
const componentOptions = typeof components === 'object' ? components : {};
|
|
21
22
|
const plugins = [];
|
|
23
|
+
let resolvedComponents = 0;
|
|
22
24
|
if (glyphs !== false) {
|
|
23
25
|
plugins.push(nubiscoGlyphs(typeof glyphs === 'object' ? glyphs : {}));
|
|
24
26
|
}
|
|
@@ -27,6 +29,15 @@ export function nubiscoUI(options = {}) {
|
|
|
27
29
|
const styleManifest = styles
|
|
28
30
|
? loadStyleManifest(options.distRoot ?? defaultDistRoot())
|
|
29
31
|
: {};
|
|
32
|
+
plugins.push(nubiscoImports({
|
|
33
|
+
manifest: lookup,
|
|
34
|
+
styleManifest,
|
|
35
|
+
styles,
|
|
36
|
+
packageName: '@nubisco/ui',
|
|
37
|
+
onResolve: (count) => {
|
|
38
|
+
resolvedComponents += count;
|
|
39
|
+
},
|
|
40
|
+
}));
|
|
30
41
|
plugins.push(Components({
|
|
31
42
|
dirs: componentOptions.dirs ?? [],
|
|
32
43
|
dts: componentOptions.dts ?? 'components.d.ts',
|
|
@@ -35,6 +46,7 @@ export function nubiscoUI(options = {}) {
|
|
|
35
46
|
const file = lookup.get(name);
|
|
36
47
|
if (!file)
|
|
37
48
|
return;
|
|
49
|
+
resolvedComponents += 1;
|
|
38
50
|
const sheets = styleManifest[file] ?? [];
|
|
39
51
|
return {
|
|
40
52
|
name,
|
|
@@ -44,6 +56,29 @@ export function nubiscoUI(options = {}) {
|
|
|
44
56
|
},
|
|
45
57
|
],
|
|
46
58
|
}));
|
|
59
|
+
// A build that linked no component at all cannot have linked any component
|
|
60
|
+
// CSS either, and `styles: true` says the app is relying on this plugin
|
|
61
|
+
// for it. That combination ships an unstyled app and fails nothing, so it
|
|
62
|
+
// is worth a line. It is reachable when every usage goes through a
|
|
63
|
+
// namespace import (`import * as UI from '@nubisco/ui'`), or a dynamic
|
|
64
|
+
// one, neither of which names the components it uses.
|
|
65
|
+
if (styles) {
|
|
66
|
+
plugins.push({
|
|
67
|
+
name: 'nubisco-ui:styles-check',
|
|
68
|
+
apply: 'build',
|
|
69
|
+
closeBundle() {
|
|
70
|
+
if (resolvedComponents)
|
|
71
|
+
return;
|
|
72
|
+
console.warn(`[nubisco-ui] no components were linked in this build, so none of ` +
|
|
73
|
+
`the component stylesheets were either. If this app renders ` +
|
|
74
|
+
`Nubisco components, it will render unstyled. Import them by ` +
|
|
75
|
+
`name (\`import { NbButton } from '@nubisco/ui'\`) or use the ` +
|
|
76
|
+
`tags directly so they can be resolved; or pass ` +
|
|
77
|
+
`\`nubiscoUI({ styles: false })\` and import ` +
|
|
78
|
+
`'@nubisco/ui/css' yourself.`);
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}
|
|
47
82
|
}
|
|
48
83
|
return plugins;
|
|
49
84
|
}
|
package/dist/styles/InfoHint.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.nb-info-hint[data-v-
|
|
1
|
+
.nb-info-hint[data-v-e7ec3f8e]{vertical-align:middle;align-items:center;display:inline-flex}.nb-info-hint--trigger[data-v-e7ec3f8e]{color:var(--nb-c-text-subtle);cursor:help;border-radius:var(--nb-radius-pill);transition:color var(--nb-animation-fast) ease;background:0 0;border:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.nb-info-hint--trigger[data-v-e7ec3f8e]:hover,.nb-info-hint--trigger.nb-info-hint--trigger-open[data-v-e7ec3f8e]{color:var(--nb-c-text)}.nb-info-hint--trigger[data-v-e7ec3f8e]:focus-visible{outline:2px solid var(--nb-c-focus-ring);outline-offset:2px;color:var(--nb-c-text)}.nb-info-hint--trigger[data-v-e7ec3f8e]:disabled{cursor:default;color:var(--nb-c-component-disabled)}.nb-info-hint--popover[data-v-e7ec3f8e]{z-index:var(--nb-zindex-tooltip);max-width:calc(var(--nb-base-unit) * 34);padding:calc(var(--nb-base-unit) * 1.25) calc(var(--nb-base-unit) * 1.5);border:1px solid var(--nb-c-border);border-radius:var(--nb-radius-md);background:var(--nb-c-surface);color:var(--nb-c-text);box-shadow:0 calc(var(--nb-base-unit) * .5) calc(var(--nb-base-unit) * 2) var(--nb-c-scrim);text-align:left;animation:nb-info-hint-in-e7ec3f8e var(--nb-animation-fast) ease-out;position:fixed}.nb-info-hint--title[data-v-e7ec3f8e]{margin:0 0 calc(var(--nb-base-unit) * .5);font-family:var(--nb-type-label-md-family);font-size:var(--nb-type-label-md-size);font-weight:var(--nb-type-label-md-weight);line-height:var(--nb-type-label-md-line-height);color:var(--nb-c-text)}.nb-info-hint--body[data-v-e7ec3f8e]{font-family:var(--nb-type-body-sm-family);font-size:var(--nb-type-body-sm-size);font-weight:var(--nb-type-body-sm-weight);line-height:var(--nb-type-body-sm-line-height);color:var(--nb-c-text-muted)}.nb-info-hint--body[data-v-e7ec3f8e] p{margin:0 0 calc(var(--nb-base-unit) * .5)}.nb-info-hint--body[data-v-e7ec3f8e] p:last-child{margin-bottom:0}.nb-info-hint--body[data-v-e7ec3f8e] code{font-family:var(--nb-font-family-mono);font-size:var(--nb-font-size-11)}.nb-info-hint--popover[data-v-e7ec3f8e]:after{content:"";width:var(--nb-base-unit);height:var(--nb-base-unit);background:var(--nb-c-surface);border:1px solid var(--nb-c-border);position:absolute;transform:rotate(45deg)}.nb-info-hint--popover-top[data-v-e7ec3f8e]:after{bottom:calc(var(--nb-base-unit) / -2 - 1px);left:var(--nb-info-hint-arrow-offset,50%);margin-left:calc(var(--nb-base-unit) / -2);border-top:none;border-left:none}.nb-info-hint--popover-bottom[data-v-e7ec3f8e]:after{top:calc(var(--nb-base-unit) / -2 - 1px);left:var(--nb-info-hint-arrow-offset,50%);margin-left:calc(var(--nb-base-unit) / -2);border-bottom:none;border-right:none}.nb-info-hint--popover-left[data-v-e7ec3f8e]:after{right:calc(var(--nb-base-unit) / -2 - 1px);top:var(--nb-info-hint-arrow-offset,50%);margin-top:calc(var(--nb-base-unit) / -2);border-bottom:none;border-left:none}.nb-info-hint--popover-right[data-v-e7ec3f8e]:after{left:calc(var(--nb-base-unit) / -2 - 1px);top:var(--nb-info-hint-arrow-offset,50%);margin-top:calc(var(--nb-base-unit) / -2);border-top:none;border-right:none}@keyframes nb-info-hint-in-e7ec3f8e{0%{opacity:0;transform:scale(.96)}to{opacity:1;transform:scale(1)}}@media (prefers-reduced-motion:reduce){.nb-info-hint--popover[data-v-e7ec3f8e]{animation:none}.nb-info-hint--trigger[data-v-e7ec3f8e]{transition:none}}
|
package/dist/ui.css
CHANGED
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
.nb-file-uploader[data-v-1adb0140]{flex-direction:column;gap:.75rem;display:flex}.nb-file-uploader__heading[data-v-1adb0140]{color:var(--nb-c-text);margin:0;font-size:1rem;font-weight:600}.nb-file-uploader__description[data-v-1adb0140]{color:var(--nb-c-text-muted);border-left:3px solid var(--nb-c-border);margin:0;padding-left:.75rem;font-size:.875rem;line-height:1.5}.nb-file-uploader__input[data-v-1adb0140]{display:none}.nb-file-uploader__btn[data-v-1adb0140]{align-self:flex-start}.nb-file-uploader__dropzone[data-v-1adb0140]{border:2px dashed var(--nb-c-border);cursor:pointer;text-align:center;border-radius:6px;justify-content:center;align-items:center;padding:2rem 1.5rem;transition:border-color .15s,background .15s;display:flex}.nb-file-uploader__dropzone--primary[data-v-1adb0140]{background:color-mix(in srgb, var(--nb-c-primary) 6%, transparent);border-color:color-mix(in srgb, var(--nb-c-primary) 60%, transparent)}.nb-file-uploader__dropzone--primary .nb-file-uploader__drop-label[data-v-1adb0140]{color:var(--nb-c-primary)}.nb-file-uploader__dropzone--secondary[data-v-1adb0140]{background:color-mix(in srgb, var(--nb-c-secondary) 6%, transparent);border-color:color-mix(in srgb, var(--nb-c-secondary) 60%, transparent)}.nb-file-uploader__dropzone--secondary .nb-file-uploader__drop-label[data-v-1adb0140]{color:var(--nb-c-secondary)}.nb-file-uploader__dropzone--danger[data-v-1adb0140]{background:color-mix(in srgb, var(--nb-c-danger) 6%, transparent);border-color:color-mix(in srgb, var(--nb-c-danger) 60%, transparent)}.nb-file-uploader__dropzone--danger .nb-file-uploader__drop-label[data-v-1adb0140]{color:var(--nb-c-danger)}.nb-file-uploader__dropzone--success[data-v-1adb0140]{background:color-mix(in srgb, var(--nb-c-success) 6%, transparent);border-color:color-mix(in srgb, var(--nb-c-success) 60%, transparent)}.nb-file-uploader__dropzone--success .nb-file-uploader__drop-label[data-v-1adb0140]{color:var(--nb-c-success)}.nb-file-uploader__dropzone--warning[data-v-1adb0140]{background:color-mix(in srgb, var(--nb-c-warning) 6%, transparent);border-color:color-mix(in srgb, var(--nb-c-warning) 60%, transparent)}.nb-file-uploader__dropzone--warning .nb-file-uploader__drop-label[data-v-1adb0140]{color:var(--nb-c-warning)}.nb-file-uploader__drop-label[data-v-1adb0140]{pointer-events:none;font-size:.9rem;line-height:1.6}.nb-file-uploader__list[data-v-1adb0140]{flex-direction:column;gap:.5rem;margin:0;padding:0;list-style:none;display:flex}.nb-file-uploader__item[data-v-1adb0140]{background:var(--nb-c-surface);color:var(--nb-c-text);border:1px solid #0000;border-radius:4px;grid-template-rows:auto auto;grid-template-columns:1fr auto;align-items:center;padding:.6rem .875rem;font-size:.9rem;transition:border-color .15s;display:grid}.nb-file-uploader__item--error[data-v-1adb0140]{border-color:var(--nb-c-danger)}.nb-file-uploader__name[data-v-1adb0140]{white-space:nowrap;text-overflow:ellipsis;grid-area:1/1;padding-right:.5rem;overflow:hidden}.nb-file-uploader__status[data-v-1adb0140]{grid-area:1/2;align-items:center;gap:.25rem;display:flex}.nb-file-uploader__error[data-v-1adb0140]{color:var(--nb-c-danger);border-top:1px solid var(--nb-c-border);grid-area:2/1/auto/-1;margin-top:.35rem;padding-top:.35rem;font-size:.8125rem}.nb-file-uploader__remove[data-v-1adb0140]{cursor:pointer;color:var(--nb-c-text-muted);background:0 0;border:none;align-items:center;padding:0;line-height:1;display:flex}.nb-file-uploader__remove[data-v-1adb0140]:hover{color:var(--nb-c-text)}.nb-file-uploader__spinner[data-v-1adb0140]{color:var(--nb-c-primary);animation:.8s linear infinite nb-spin-1adb0140}.nb-file-uploader__check[data-v-1adb0140]{color:var(--nb-c-primary)}.nb-file-uploader__warn[data-v-1adb0140]{color:var(--nb-c-danger)}@keyframes nb-spin-1adb0140{to{transform:rotate(360deg)}}
|
|
42
42
|
.nb-flag[data-v-7bdc2000]{height:fit-content;line-height:1em;display:flex;position:relative}.nb-flag[data-v-7bdc2000]:not(.box-clickable){pointer-events:none}.nb-flag--lg[data-v-7bdc2000]{width:32px;max-width:32px;max-height:32px}.nb-flag--md[data-v-7bdc2000]{width:20px;max-width:20px;max-height:20px}.nb-flag--sm[data-v-7bdc2000]{width:16px;max-width:16px;max-height:16px}
|
|
43
43
|
.nb-image-cropper[data-v-29cde507]{-webkit-user-select:none;user-select:none;max-width:100%;height:auto;display:inline-block;position:relative;overflow:hidden}.nb-image-cropper .canvas-container[data-v-29cde507]{width:100%;height:auto;display:inline-block;position:relative}.nb-image-cropper .canvas-container canvas[data-v-29cde507]{max-width:100%;height:auto;display:block}.nb-image-cropper .resize-handle[data-v-29cde507]{background:var(--nb-c-surface);border:1px solid var(--nb-c-border);z-index:1;width:10px;height:10px;position:absolute}.nb-image-cropper .resize-handle.top-left[data-v-29cde507]{cursor:nwse-resize}.nb-image-cropper .resize-handle.top[data-v-29cde507]{cursor:ns-resize}.nb-image-cropper .resize-handle.top-right[data-v-29cde507]{cursor:nesw-resize}.nb-image-cropper .resize-handle.right[data-v-29cde507]{cursor:ew-resize}.nb-image-cropper .resize-handle.bottom-right[data-v-29cde507]{cursor:nwse-resize}.nb-image-cropper .resize-handle.bottom[data-v-29cde507]{cursor:ns-resize}.nb-image-cropper .resize-handle.bottom-left[data-v-29cde507]{cursor:nesw-resize}.nb-image-cropper .resize-handle.left[data-v-29cde507]{cursor:ew-resize}.nb-image-cropper .cropped-image-preview[data-v-29cde507]{right:calc(var(--nb-base-unit,8px) * 2);bottom:calc(var(--nb-base-unit,8px) * 2);border-radius:var(--nb-border-radius-sm,6px);height:calc(var(--nb-base-unit,8px) * 14);border:dotted 1px var(--nb-c-border);background:var(--nb-c-surface);-webkit-user-select:none;user-select:none;pointer-events:none;margin-top:20px;position:absolute;overflow:hidden}.nb-image-cropper .cropped-image-preview ul[data-v-29cde507]{min-width:calc(var(--nb-base-unit,8px) * 9);background:var(--nb-c-contrast);color:var(--nb-c-surface);font-size:var(--nb-font-size-10,10px);padding:calc(var(--nb-base-unit,8px) / 2);line-height:var(--nb-font-size-14,14px)}.nb-image-cropper .cropped-image-preview img[data-v-29cde507]{object-fit:contain;object-position:center;width:100%;height:100%}
|
|
44
|
-
.nb-info-hint[data-v-
|
|
44
|
+
.nb-info-hint[data-v-e7ec3f8e]{vertical-align:middle;align-items:center;display:inline-flex}.nb-info-hint--trigger[data-v-e7ec3f8e]{color:var(--nb-c-text-subtle);cursor:help;border-radius:var(--nb-radius-pill);transition:color var(--nb-animation-fast) ease;background:0 0;border:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.nb-info-hint--trigger[data-v-e7ec3f8e]:hover,.nb-info-hint--trigger.nb-info-hint--trigger-open[data-v-e7ec3f8e]{color:var(--nb-c-text)}.nb-info-hint--trigger[data-v-e7ec3f8e]:focus-visible{outline:2px solid var(--nb-c-focus-ring);outline-offset:2px;color:var(--nb-c-text)}.nb-info-hint--trigger[data-v-e7ec3f8e]:disabled{cursor:default;color:var(--nb-c-component-disabled)}.nb-info-hint--popover[data-v-e7ec3f8e]{z-index:var(--nb-zindex-tooltip);max-width:calc(var(--nb-base-unit) * 34);padding:calc(var(--nb-base-unit) * 1.25) calc(var(--nb-base-unit) * 1.5);border:1px solid var(--nb-c-border);border-radius:var(--nb-radius-md);background:var(--nb-c-surface);color:var(--nb-c-text);box-shadow:0 calc(var(--nb-base-unit) * .5) calc(var(--nb-base-unit) * 2) var(--nb-c-scrim);text-align:left;animation:nb-info-hint-in-e7ec3f8e var(--nb-animation-fast) ease-out;position:fixed}.nb-info-hint--title[data-v-e7ec3f8e]{margin:0 0 calc(var(--nb-base-unit) * .5);font-family:var(--nb-type-label-md-family);font-size:var(--nb-type-label-md-size);font-weight:var(--nb-type-label-md-weight);line-height:var(--nb-type-label-md-line-height);color:var(--nb-c-text)}.nb-info-hint--body[data-v-e7ec3f8e]{font-family:var(--nb-type-body-sm-family);font-size:var(--nb-type-body-sm-size);font-weight:var(--nb-type-body-sm-weight);line-height:var(--nb-type-body-sm-line-height);color:var(--nb-c-text-muted)}.nb-info-hint--body[data-v-e7ec3f8e] p{margin:0 0 calc(var(--nb-base-unit) * .5)}.nb-info-hint--body[data-v-e7ec3f8e] p:last-child{margin-bottom:0}.nb-info-hint--body[data-v-e7ec3f8e] code{font-family:var(--nb-font-family-mono);font-size:var(--nb-font-size-11)}.nb-info-hint--popover[data-v-e7ec3f8e]:after{content:"";width:var(--nb-base-unit);height:var(--nb-base-unit);background:var(--nb-c-surface);border:1px solid var(--nb-c-border);position:absolute;transform:rotate(45deg)}.nb-info-hint--popover-top[data-v-e7ec3f8e]:after{bottom:calc(var(--nb-base-unit) / -2 - 1px);left:var(--nb-info-hint-arrow-offset,50%);margin-left:calc(var(--nb-base-unit) / -2);border-top:none;border-left:none}.nb-info-hint--popover-bottom[data-v-e7ec3f8e]:after{top:calc(var(--nb-base-unit) / -2 - 1px);left:var(--nb-info-hint-arrow-offset,50%);margin-left:calc(var(--nb-base-unit) / -2);border-bottom:none;border-right:none}.nb-info-hint--popover-left[data-v-e7ec3f8e]:after{right:calc(var(--nb-base-unit) / -2 - 1px);top:var(--nb-info-hint-arrow-offset,50%);margin-top:calc(var(--nb-base-unit) / -2);border-bottom:none;border-left:none}.nb-info-hint--popover-right[data-v-e7ec3f8e]:after{left:calc(var(--nb-base-unit) / -2 - 1px);top:var(--nb-info-hint-arrow-offset,50%);margin-top:calc(var(--nb-base-unit) / -2);border-top:none;border-right:none}@keyframes nb-info-hint-in-e7ec3f8e{0%{opacity:0;transform:scale(.96)}to{opacity:1;transform:scale(1)}}@media (prefers-reduced-motion:reduce){.nb-info-hint--popover[data-v-e7ec3f8e]{animation:none}.nb-info-hint--trigger[data-v-e7ec3f8e]{transition:none}}
|
|
45
45
|
.nb-inline-edit__text[data-v-d602ce76]{align-items:baseline;gap:calc(var(--nb-base-unit) * .75);max-width:100%;padding:calc(var(--nb-base-unit) * .25) calc(var(--nb-base-unit) * .75);margin:0;border-radius:var(--nb-radius-sm);color:inherit;text-align:start;cursor:text;background:0 0;border:0;margin-inline-start:calc(var(--nb-base-unit) * -.75);display:inline-flex}.nb-inline-edit__text span[data-v-d602ce76]{overflow-wrap:anywhere}.nb-inline-edit__text .nb-inline-edit__pencil[data-v-d602ce76]{color:var(--nb-c-text-subtle);opacity:0;flex:none;align-self:center;transition:opacity .12s}.nb-inline-edit__text[data-v-d602ce76]:hover:not(.is-disabled){background:var(--nb-c-surface-hover)}.nb-inline-edit__text:hover:not(.is-disabled) .nb-inline-edit__pencil[data-v-d602ce76]{opacity:1}.nb-inline-edit__text[data-v-d602ce76]:focus-visible{outline:2px solid var(--nb-c-focus-ring,var(--nb-c-primary));outline-offset:1px}.nb-inline-edit__text:focus-visible .nb-inline-edit__pencil[data-v-d602ce76]{opacity:1}.nb-inline-edit__text.is-disabled[data-v-d602ce76]{cursor:default}.nb-inline-edit__placeholder[data-v-d602ce76]{color:var(--nb-c-text-subtle)}.nb-inline-edit__input[data-v-d602ce76]{width:100%;padding:calc(var(--nb-base-unit) * .25) calc(var(--nb-base-unit) * .75);border:1px solid var(--nb-c-primary);border-radius:var(--nb-radius-sm);background:var(--nb-c-surface);color:inherit;margin:0;margin-inline-start:calc(var(--nb-base-unit) * -.75)}.nb-inline-edit__input[data-v-d602ce76]:focus{box-shadow:0 0 0 2px color-mix(in srgb, var(--nb-c-primary) 30%, transparent);outline:none}.nb-inline-edit__text--md[data-v-d602ce76],.nb-inline-edit__input--md[data-v-d602ce76]{font-family:var(--nb-type-body-md-family,var(--nb-font-family-sans));font-size:var(--nb-type-body-md-size);font-weight:var(--nb-type-body-md-weight);line-height:var(--nb-type-body-md-line-height);letter-spacing:var(--nb-type-body-md-letter-spacing,normal)}.nb-inline-edit__text--lg[data-v-d602ce76],.nb-inline-edit__input--lg[data-v-d602ce76]{font-family:var(--nb-type-heading-01-family,var(--nb-font-family-sans));font-size:var(--nb-type-heading-01-size);font-weight:var(--nb-type-heading-01-weight);line-height:var(--nb-type-heading-01-line-height);letter-spacing:var(--nb-type-heading-01-letter-spacing,normal)}.nb-inline-edit__text--xl[data-v-d602ce76],.nb-inline-edit__input--xl[data-v-d602ce76]{font-family:var(--nb-type-heading-02-family,var(--nb-font-family-sans));font-size:var(--nb-type-heading-02-size);font-weight:var(--nb-type-heading-02-weight);line-height:var(--nb-type-heading-02-line-height);letter-spacing:var(--nb-type-heading-02-letter-spacing,normal)}
|
|
46
46
|
.nb-spinner[data-v-55d1147d]{align-items:center;gap:var(--nb-base-unit);font-family:var(--nb-font-family-sans);color:var(--nb-c-primary);--nb-spinner-diameter:calc(var(--nb-field-height-md) / 2);display:inline-flex}.nb-spinner--xs[data-v-55d1147d]{--nb-spinner-diameter:calc(var(--nb-field-height-xs) / 2)}.nb-spinner--sm[data-v-55d1147d]{--nb-spinner-diameter:calc(var(--nb-field-height-sm) / 2)}.nb-spinner--md[data-v-55d1147d]{--nb-spinner-diameter:calc(var(--nb-field-height-md) / 2)}.nb-spinner--lg[data-v-55d1147d]{--nb-spinner-diameter:calc(var(--nb-field-height-lg) / 2)}.nb-spinner--tone-inherit[data-v-55d1147d]{color:inherit}.nb-spinner__ring[data-v-55d1147d]{width:var(--nb-spinner-diameter);height:var(--nb-spinner-diameter);flex-shrink:0;display:block}.nb-spinner__track[data-v-55d1147d],.nb-spinner__arc[data-v-55d1147d]{fill:none;stroke-width:4px}.nb-spinner__track[data-v-55d1147d]{stroke:var(--nb-c-discrete)}.nb-spinner__arc[data-v-55d1147d]{stroke:currentColor;stroke-linecap:round;stroke-dasharray:22 66;transform-origin:50%;animation:.9s linear infinite nb-spinner-spin-55d1147d}.nb-spinner__actions[data-v-55d1147d]{gap:var(--nb-base-unit);margin-top:var(--nb-base-unit);align-items:center;display:flex}.nb-spinner__label[data-v-55d1147d]{font-size:var(--nb-font-size-12);color:var(--nb-c-text-muted);line-height:1.4}.nb-spinner__announcement[data-v-55d1147d]{white-space:nowrap;clip-path:inset(50%);border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.nb-spinner--overlay-container[data-v-55d1147d],.nb-spinner--overlay-page[data-v-55d1147d]{gap:var(--nb-base-unit);background:var(--nb-c-scrim);flex-direction:column;justify-content:center;align-items:center;display:flex}.nb-spinner--overlay-container .nb-spinner__label[data-v-55d1147d],.nb-spinner--overlay-page .nb-spinner__label[data-v-55d1147d]{color:var(--nb-c-white)}.nb-spinner--overlay-container[data-v-55d1147d]{z-index:var(--nb-zindex-pageheader);position:absolute;inset:0}.nb-spinner--overlay-page[data-v-55d1147d]{z-index:var(--nb-zindex-modal);position:fixed;inset:0}.nb-spinner--overlay-page.nb-spinner--overlay-stacked[data-v-55d1147d]{background:0 0}.nb-spinner--overlay-page[data-v-55d1147d]:focus{outline:none}@keyframes nb-spinner-spin-55d1147d{to{transform:rotate(360deg)}}@media (prefers-reduced-motion:reduce){.nb-spinner .nb-spinner__arc[data-v-55d1147d]{stroke-dasharray:none;animation:1.6s ease-in-out infinite nb-spinner-breathe-55d1147d}}.nb-spinner--reduced-motion .nb-spinner__arc[data-v-55d1147d]{stroke-dasharray:none;animation:1.6s ease-in-out infinite nb-spinner-breathe-55d1147d}@keyframes nb-spinner-breathe-55d1147d{0%,to{stroke-opacity:.25}50%{stroke-opacity:1}}@media (forced-colors:active){.nb-spinner[data-v-55d1147d]{color:canvastext}.nb-spinner .nb-spinner__arc[data-v-55d1147d]{forced-color-adjust:none;stroke:canvastext}.nb-spinner .nb-spinner__track[data-v-55d1147d]{forced-color-adjust:none;stroke:canvas}.nb-spinner.nb-spinner--overlay-container[data-v-55d1147d],.nb-spinner.nb-spinner--overlay-page[data-v-55d1147d]{outline-offset:-1px;background:canvas;outline:1px solid canvastext}.nb-spinner .nb-spinner__label[data-v-55d1147d]{color:canvastext}}
|
|
47
47
|
.nb-inline-loading[data-v-077014e8]{gap:calc(var(--nb-base-unit) / 2);min-height:var(--nb-field-height-sm);font-family:var(--nb-font-family-sans);font-size:var(--nb-font-size-12);color:var(--nb-c-text-muted);align-items:center;line-height:1.4;display:inline-flex}.nb-inline-loading__spinner[data-v-077014e8],.nb-inline-loading__icon[data-v-077014e8]{flex-shrink:0}.nb-inline-loading__text[data-v-077014e8]{min-width:var(--nb-inline-loading-reserve,0)}.nb-inline-loading--finished[data-v-077014e8]{color:var(--nb-c-success)}.nb-inline-loading--error[data-v-077014e8]{color:var(--nb-c-danger)}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nubisco/ui",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.2",
|
|
4
4
|
"description": "Vue 3 UI component library",
|
|
5
5
|
"packageManager": "pnpm@11.0.8",
|
|
6
6
|
"repository": {
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
"build:lib": "vite build",
|
|
141
141
|
"build:types": "vue-tsc --noEmit false --declaration --emitDeclarationOnly --outDir dist && node scripts/copy-type-modules.mjs && node scripts/rewrite-dist-aliases.mjs dist",
|
|
142
142
|
"build:plugins": "tsc --project tsconfig.plugins.json",
|
|
143
|
-
"verify:types": "pnpm run verify:dist-types && pnpm run verify:consumer-types && pnpm run verify:consumer-globals && pnpm run verify:resolution && pnpm run verify:styles",
|
|
143
|
+
"verify:types": "pnpm run verify:dist-types && pnpm run verify:consumer-types && pnpm run verify:consumer-globals && pnpm run verify:resolution && pnpm run verify:styles && pnpm run verify:glyph-defaults",
|
|
144
144
|
"verify:dist-types": "node scripts/verify-dist-types.mjs dist",
|
|
145
145
|
"verify:consumer-types": "node scripts/verify-consumer-types.mjs",
|
|
146
146
|
"verify:consumer-globals": "node scripts/verify-consumer-globals.mjs",
|
|
@@ -161,7 +161,8 @@
|
|
|
161
161
|
"verify:marks": "node scripts/verify-inlined-marks.mjs",
|
|
162
162
|
"generate": "node scripts/generate-component-manifest.mjs && node scripts/generate-assets.mjs && node scripts/generate-entries.mjs",
|
|
163
163
|
"verify:resolution": "node scripts/verify-compile-time-resolution.mjs dist",
|
|
164
|
-
"verify:styles": "node scripts/verify-style-split.mjs dist"
|
|
164
|
+
"verify:styles": "node scripts/verify-style-split.mjs dist",
|
|
165
|
+
"verify:glyph-defaults": "node scripts/verify-default-glyphs.mjs dist"
|
|
165
166
|
},
|
|
166
167
|
"simple-git-hooks": {
|
|
167
168
|
"pre-commit": "npx lint-staged && pnpm run quality:check",
|
|
@@ -3,7 +3,7 @@ import { existsSync } from 'node:fs'
|
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import { fileURLToPath } from 'node:url'
|
|
5
5
|
import MagicString from 'magic-string'
|
|
6
|
-
import { parse } from 'vue/compiler-sfc'
|
|
6
|
+
import { parse, babelParse } from 'vue/compiler-sfc'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Resolves icon and flag artwork at compile time.
|
|
@@ -63,6 +63,15 @@ export interface IGlyphOptions {
|
|
|
63
63
|
packageName?: string
|
|
64
64
|
/** Log every rewrite. Useful when auditing what a page actually links. */
|
|
65
65
|
verbose?: boolean
|
|
66
|
+
/**
|
|
67
|
+
* Warn when a glyph forwarded through another component (`<NbButton
|
|
68
|
+
* :icon="…">`) is a value this plugin cannot see through, so no artwork was
|
|
69
|
+
* linked for it. On by default, once per file, because the alternative is
|
|
70
|
+
* the silence that lets an icon reach `NbIcon` with nothing to resolve it
|
|
71
|
+
* against. Turn it off in a codebase where those bindings are deliberate
|
|
72
|
+
* pass-through props, resolved by whoever passes them in.
|
|
73
|
+
*/
|
|
74
|
+
warnUnresolved?: boolean
|
|
66
75
|
}
|
|
67
76
|
|
|
68
77
|
type TKind = 'icon' | 'flag'
|
|
@@ -136,6 +145,99 @@ function staticWeight(element: any): string | undefined {
|
|
|
136
145
|
return 'regular'
|
|
137
146
|
}
|
|
138
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Parse one template expression on its own. `babelParse` wants a program, so
|
|
150
|
+
* the expression is wrapped; a binding Vue accepts but Babel cannot read is
|
|
151
|
+
* simply unknown, which the caller treats as unresolved.
|
|
152
|
+
*/
|
|
153
|
+
function parseExpression(expression: string): any {
|
|
154
|
+
try {
|
|
155
|
+
const { program } = babelParse(`(${expression})`, {
|
|
156
|
+
sourceType: 'module',
|
|
157
|
+
plugins: ['typescript'],
|
|
158
|
+
})
|
|
159
|
+
const statement: any = program.body[0]
|
|
160
|
+
return statement?.type === 'ExpressionStatement'
|
|
161
|
+
? statement.expression
|
|
162
|
+
: undefined
|
|
163
|
+
} catch {
|
|
164
|
+
return undefined
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Whether every value this expression can hand to the component is artwork the
|
|
170
|
+
* bundle already links: a literal this plugin just rewrote, or a glyph module
|
|
171
|
+
* the file imported itself.
|
|
172
|
+
*
|
|
173
|
+
* The point is what it refuses. `open ? 'caret-up' : 'caret-down'` produces one
|
|
174
|
+
* of two rewritten literals and nothing else, so it is resolved. `block?.icon
|
|
175
|
+
* || 'cube'` also contains a rewritten literal, but it can just as easily
|
|
176
|
+
* produce `block.icon`, which is a runtime value and needs the catalogue.
|
|
177
|
+
* Judging by "a rewritten literal appears and no quoted string survives" called
|
|
178
|
+
* that second one resolved, which meant adding a literal fallback silently
|
|
179
|
+
* removed the coverage the same expression had without one.
|
|
180
|
+
*/
|
|
181
|
+
function producesOnlyLinkedGlyphs(
|
|
182
|
+
node: any,
|
|
183
|
+
isLinked: (name: string) => boolean,
|
|
184
|
+
): boolean {
|
|
185
|
+
if (!node) return false
|
|
186
|
+
const recurse = (child: any) => producesOnlyLinkedGlyphs(child, isLinked)
|
|
187
|
+
switch (node.type) {
|
|
188
|
+
case 'Identifier':
|
|
189
|
+
return isLinked(node.name)
|
|
190
|
+
// The test is not a value the prop receives; the two arms are.
|
|
191
|
+
case 'ConditionalExpression':
|
|
192
|
+
return recurse(node.consequent) && recurse(node.alternate)
|
|
193
|
+
// `a || b`, `a ?? b` and `a && b` can all produce either side.
|
|
194
|
+
case 'LogicalExpression':
|
|
195
|
+
return recurse(node.left) && recurse(node.right)
|
|
196
|
+
case 'SequenceExpression':
|
|
197
|
+
return recurse(node.expressions[node.expressions.length - 1])
|
|
198
|
+
case 'TSAsExpression':
|
|
199
|
+
case 'TSNonNullExpression':
|
|
200
|
+
case 'TSSatisfiesExpression':
|
|
201
|
+
case 'ParenthesizedExpression':
|
|
202
|
+
return recurse(node.expression)
|
|
203
|
+
default:
|
|
204
|
+
return false
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Local names bound to glyph modules by the file's own imports, so that
|
|
210
|
+
* `<NbIcon :name="GithubLogo" />` is understood as already-linked artwork
|
|
211
|
+
* rather than as a runtime name needing the whole catalogue.
|
|
212
|
+
*/
|
|
213
|
+
function glyphImportLocals(script: string, packageName: string): Set<string> {
|
|
214
|
+
const locals = new Set<string>()
|
|
215
|
+
const from = packageName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
216
|
+
const pattern = new RegExp(
|
|
217
|
+
`import\\s+([^;'"\n]+?)\\s+from\\s*['"]${from}/(?:icons|flags)/[^'"]+['"]`,
|
|
218
|
+
'g',
|
|
219
|
+
)
|
|
220
|
+
for (const match of script.matchAll(pattern)) {
|
|
221
|
+
const clause = match[1]
|
|
222
|
+
const namespace = clause.match(/\*\s+as\s+(\w+)/)
|
|
223
|
+
if (namespace) locals.add(namespace[1])
|
|
224
|
+
const defaultImport = clause.match(/^\s*(\w+)/)
|
|
225
|
+
if (defaultImport) locals.add(defaultImport[1])
|
|
226
|
+
const named = clause.match(/\{([^}]*)\}/)
|
|
227
|
+
if (named) {
|
|
228
|
+
for (const part of named[1].split(',')) {
|
|
229
|
+
const local = part
|
|
230
|
+
.trim()
|
|
231
|
+
.split(/\s+as\s+/)
|
|
232
|
+
.pop()
|
|
233
|
+
?.trim()
|
|
234
|
+
if (local) locals.add(local)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return locals
|
|
239
|
+
}
|
|
240
|
+
|
|
139
241
|
function staticPropKind(
|
|
140
242
|
name: string,
|
|
141
243
|
isIcon: boolean,
|
|
@@ -157,6 +259,7 @@ export function nubiscoGlyphs(options: IGlyphOptions = {}) {
|
|
|
157
259
|
flags = true,
|
|
158
260
|
packageName = '@nubisco/ui',
|
|
159
261
|
verbose = false,
|
|
262
|
+
warnUnresolved = true,
|
|
160
263
|
} = options
|
|
161
264
|
|
|
162
265
|
const glyphRoot = options.glyphRoot ?? defaultGlyphRoot()
|
|
@@ -186,6 +289,16 @@ export function nubiscoGlyphs(options: IGlyphOptions = {}) {
|
|
|
186
289
|
const edits: IEdit[] = []
|
|
187
290
|
const used = new Map<string, IGlyphImport>()
|
|
188
291
|
const catalogsNeeded = new Set<TKind>()
|
|
292
|
+
const unresolvedForwards: { tag: string; prop: string; exp: string }[] =
|
|
293
|
+
[]
|
|
294
|
+
|
|
295
|
+
// Artwork the file already imports by hand counts as linked: binding an
|
|
296
|
+
// imported module is the documented no-plugin path, and pulling a
|
|
297
|
+
// catalogue in on top of it would be pure waste.
|
|
298
|
+
const glyphLocals = glyphImportLocals(
|
|
299
|
+
`${descriptor.scriptSetup?.content ?? ''}\n${descriptor.script?.content ?? ''}`,
|
|
300
|
+
packageName,
|
|
301
|
+
)
|
|
189
302
|
|
|
190
303
|
const use = (glyph: IGlyphImport) => {
|
|
191
304
|
const variable = identifierFor(glyph)
|
|
@@ -229,17 +342,18 @@ export function nubiscoGlyphs(options: IGlyphOptions = {}) {
|
|
|
229
342
|
continue
|
|
230
343
|
}
|
|
231
344
|
|
|
232
|
-
/* ---- bound: :name="…"
|
|
345
|
+
/* ---- bound: :name="…", :icon="…", :flag="…" ------------------ */
|
|
233
346
|
if (
|
|
234
347
|
prop.type === NODE_DIRECTIVE &&
|
|
235
348
|
prop.name === 'bind' &&
|
|
236
349
|
prop.arg?.type === EXPR_SIMPLE &&
|
|
237
|
-
prop.exp?.type === EXPR_SIMPLE
|
|
238
|
-
(isIcon || isFlag)
|
|
350
|
+
prop.exp?.type === EXPR_SIMPLE
|
|
239
351
|
) {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
352
|
+
// Same rule as the static branch, so `:icon="a ? 'check' : 'copy'"`
|
|
353
|
+
// links the two icons whether it is written on NbIcon or forwarded
|
|
354
|
+
// through NbButton. Only the tag decides the kind.
|
|
355
|
+
const kind = staticPropKind(prop.arg.content, isIcon, isFlag)
|
|
356
|
+
if (!kind || !enabled(kind)) continue
|
|
243
357
|
|
|
244
358
|
const expression: string = prop.exp.content
|
|
245
359
|
const rewritten = expression.replace(
|
|
@@ -262,16 +376,52 @@ export function nubiscoGlyphs(options: IGlyphOptions = {}) {
|
|
|
262
376
|
})
|
|
263
377
|
}
|
|
264
378
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if (
|
|
379
|
+
const resolved = producesOnlyLinkedGlyphs(
|
|
380
|
+
parseExpression(rewritten),
|
|
381
|
+
(identifier) =>
|
|
382
|
+
used.has(identifier) || glyphLocals.has(identifier),
|
|
383
|
+
)
|
|
384
|
+
if (resolved) continue
|
|
385
|
+
|
|
386
|
+
// On NbIcon or NbFlag an unresolvable name is fatal at first
|
|
387
|
+
// render, so the catalogue is the only thing that can save it. On
|
|
388
|
+
// a forwarding component the same expression may just as well be
|
|
389
|
+
// a module arriving as a prop, and linking 1,500 icons on that
|
|
390
|
+
// guess would make the common wrapper the most expensive file in
|
|
391
|
+
// the bundle. So that case is reported rather than paid for.
|
|
392
|
+
if (isIcon || isFlag) catalogsNeeded.add(kind)
|
|
393
|
+
else
|
|
394
|
+
unresolvedForwards.push({
|
|
395
|
+
tag,
|
|
396
|
+
prop: prop.arg.content,
|
|
397
|
+
exp: expression,
|
|
398
|
+
})
|
|
271
399
|
}
|
|
272
400
|
}
|
|
273
401
|
})
|
|
274
402
|
|
|
403
|
+
// One line per file, however many bindings it has: this is a note about
|
|
404
|
+
// the file, and repeating the same advice per expression would train
|
|
405
|
+
// people to skip it.
|
|
406
|
+
if (warnUnresolved && unresolvedForwards.length) {
|
|
407
|
+
const kinds = new Set(unresolvedForwards.map((f) => f.prop))
|
|
408
|
+
const register = kinds.has('flag')
|
|
409
|
+
? 'registerFlags()'
|
|
410
|
+
: 'registerIcons()'
|
|
411
|
+
const catalogue = `${packageName}/${kinds.has('flag') ? 'flags' : 'icons'}/all`
|
|
412
|
+
console.warn(
|
|
413
|
+
`[nubisco-ui] ${path.relative(process.cwd(), file)}: no artwork ` +
|
|
414
|
+
`linked for ` +
|
|
415
|
+
unresolvedForwards
|
|
416
|
+
.map((f) => `<${f.tag} :${f.prop}="${f.exp}">`)
|
|
417
|
+
.join(', ') +
|
|
418
|
+
`. The value is only known at runtime, so it will throw on first ` +
|
|
419
|
+
`render unless something resolves it: ${register} for a known ` +
|
|
420
|
+
`set, or import '${catalogue}' in this file. Nothing to do if ` +
|
|
421
|
+
`these are pass-through props (set warnUnresolved: false).`,
|
|
422
|
+
)
|
|
423
|
+
}
|
|
424
|
+
|
|
275
425
|
if (!used.size && !catalogsNeeded.size) return
|
|
276
426
|
|
|
277
427
|
const imports: string[] = []
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import MagicString from 'magic-string'
|
|
2
|
+
import { parse, babelParse } from 'vue/compiler-sfc'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Makes a hand-written component import behave like an auto-imported one.
|
|
6
|
+
*
|
|
7
|
+
* The library ships one stylesheet per chunk, and the auto-import resolver
|
|
8
|
+
* declares each component's sheets as side effects so the bundler links them
|
|
9
|
+
* next to the component. That only happens for tags the resolver saw. An app
|
|
10
|
+
* that writes its imports by hand:
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { NbButton, NbPanel } from '@nubisco/ui'
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* never went through the resolver, so it got no stylesheets at all. With
|
|
17
|
+
* `styles: true` (the default) and the `@nubisco/ui/css` import dropped, as
|
|
18
|
+
* the upgrade guide said to, that shipped an app with no component CSS: every
|
|
19
|
+
* page rendered unstyled, and nothing failed. The CSS bundle still looked
|
|
20
|
+
* plausible, because the app's own styles and the design tokens were still in
|
|
21
|
+
* it.
|
|
22
|
+
*
|
|
23
|
+
* So this plugin gives the same treatment to imports the app wrote itself:
|
|
24
|
+
*
|
|
25
|
+
* 1. A named component imported from the barrel is rewritten to its own entry
|
|
26
|
+
* point, which is what the resolver would have emitted, and which also
|
|
27
|
+
* stops the barrel from pulling in every other component.
|
|
28
|
+
* 2. Either way, that component's stylesheets are imported alongside it.
|
|
29
|
+
*
|
|
30
|
+
* Names the manifest does not know (composables, types, `registerIcons`) stay
|
|
31
|
+
* on the barrel import untouched, and a namespace or default import is left
|
|
32
|
+
* alone entirely, since there is no way to tell from it which components the
|
|
33
|
+
* file actually uses.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
interface INubiscoImportOptions {
|
|
37
|
+
/** `Nb*` name to entry file, from the component manifest. */
|
|
38
|
+
manifest: Map<string, string>
|
|
39
|
+
/** Which stylesheets each component entry needs. */
|
|
40
|
+
styleManifest: Record<string, string[]>
|
|
41
|
+
/** Inject stylesheets. When false this plugin only rewrites imports. */
|
|
42
|
+
styles: boolean
|
|
43
|
+
packageName: string
|
|
44
|
+
/** Called with the number of components resolved in a file. */
|
|
45
|
+
onResolve: (count: number) => void
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface IScriptBlock {
|
|
49
|
+
content: string
|
|
50
|
+
offset: number
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The script of a `.vue` file, or the whole module for a plain script. */
|
|
54
|
+
function scriptBlocks(code: string, file: string): IScriptBlock[] {
|
|
55
|
+
if (!file.endsWith('.vue')) return [{ content: code, offset: 0 }]
|
|
56
|
+
const { descriptor, errors } = parse(code, { filename: file })
|
|
57
|
+
if (errors.length) return []
|
|
58
|
+
return [descriptor.script, descriptor.scriptSetup]
|
|
59
|
+
.filter((block): block is NonNullable<typeof block> => Boolean(block))
|
|
60
|
+
.map((block) => ({
|
|
61
|
+
content: block.content,
|
|
62
|
+
offset: block.loc.start.offset,
|
|
63
|
+
}))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface ISpecifier {
|
|
67
|
+
imported: string
|
|
68
|
+
local: string
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function nubiscoImports(options: INubiscoImportOptions) {
|
|
72
|
+
const { manifest, styleManifest, styles, packageName, onResolve } = options
|
|
73
|
+
const componentPrefix = `${packageName}/components/`
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
name: 'nubisco-ui:imports',
|
|
77
|
+
enforce: 'pre' as const,
|
|
78
|
+
|
|
79
|
+
transform(code: string, id: string) {
|
|
80
|
+
const file = id.split('?')[0]
|
|
81
|
+
if (file.includes('/node_modules/')) return
|
|
82
|
+
if (!/\.(vue|[cm]?[jt]sx?)$/.test(file)) return
|
|
83
|
+
if (!code.includes(packageName)) return
|
|
84
|
+
|
|
85
|
+
const magic = new MagicString(code)
|
|
86
|
+
const sheetsSeen = new Set<string>()
|
|
87
|
+
let resolved = 0
|
|
88
|
+
let changed = false
|
|
89
|
+
|
|
90
|
+
// Stylesheets are appended after the import they belong to, so they
|
|
91
|
+
// land in the consumer's module graph in the order the resolver would
|
|
92
|
+
// have produced.
|
|
93
|
+
const sheetsFor = (entry: string) => {
|
|
94
|
+
if (!styles) return ''
|
|
95
|
+
const sheets = (styleManifest[entry] ?? []).filter(
|
|
96
|
+
(sheet) => !sheetsSeen.has(sheet),
|
|
97
|
+
)
|
|
98
|
+
sheets.forEach((sheet) => sheetsSeen.add(sheet))
|
|
99
|
+
return sheets
|
|
100
|
+
.map((sheet) => `\nimport '${packageName}/dist/${sheet}'`)
|
|
101
|
+
.join('')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
for (const block of scriptBlocks(code, file)) {
|
|
105
|
+
let program
|
|
106
|
+
try {
|
|
107
|
+
program = babelParse(block.content, {
|
|
108
|
+
sourceType: 'module',
|
|
109
|
+
plugins: ['typescript', 'jsx'],
|
|
110
|
+
}).program
|
|
111
|
+
} catch {
|
|
112
|
+
// Not parseable on its own: the Vue plugin will report it.
|
|
113
|
+
continue
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
for (const node of program.body as any[]) {
|
|
117
|
+
if (node.type !== 'ImportDeclaration') continue
|
|
118
|
+
// `import type { … }` links nothing at runtime, so it needs no
|
|
119
|
+
// stylesheet and must not be rewritten into a value import.
|
|
120
|
+
if (node.importKind === 'type') continue
|
|
121
|
+
const source: string = node.source.value
|
|
122
|
+
const start = block.offset + node.start
|
|
123
|
+
const end = block.offset + node.end
|
|
124
|
+
|
|
125
|
+
/* An entry point the app already named: it only needs its styles. */
|
|
126
|
+
if (source.startsWith(componentPrefix)) {
|
|
127
|
+
const entry = source.slice(componentPrefix.length)
|
|
128
|
+
const sheets = sheetsFor(entry)
|
|
129
|
+
resolved += 1
|
|
130
|
+
if (sheets) {
|
|
131
|
+
magic.appendRight(end, sheets)
|
|
132
|
+
changed = true
|
|
133
|
+
}
|
|
134
|
+
continue
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (source !== packageName) continue
|
|
138
|
+
|
|
139
|
+
const components: ISpecifier[] = []
|
|
140
|
+
const rest: string[] = []
|
|
141
|
+
let splittable = true
|
|
142
|
+
|
|
143
|
+
for (const specifier of node.specifiers as any[]) {
|
|
144
|
+
if (specifier.type !== 'ImportSpecifier') {
|
|
145
|
+
// A default or namespace import says nothing about which
|
|
146
|
+
// components the file uses, so it stays as it is.
|
|
147
|
+
splittable = false
|
|
148
|
+
break
|
|
149
|
+
}
|
|
150
|
+
const imported: string =
|
|
151
|
+
specifier.imported.name ?? specifier.imported.value
|
|
152
|
+
const local: string = specifier.local.name
|
|
153
|
+
if (specifier.importKind === 'type' || !manifest.has(imported)) {
|
|
154
|
+
rest.push(
|
|
155
|
+
block.content.slice(specifier.start, specifier.end).trim(),
|
|
156
|
+
)
|
|
157
|
+
continue
|
|
158
|
+
}
|
|
159
|
+
components.push({ imported, local })
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!splittable || !components.length) continue
|
|
163
|
+
|
|
164
|
+
const lines = components.map(({ imported, local }) => {
|
|
165
|
+
const entry = manifest.get(imported) as string
|
|
166
|
+
const binding =
|
|
167
|
+
imported === local ? imported : `${imported} as ${local}`
|
|
168
|
+
return (
|
|
169
|
+
`import { ${binding} } from '${componentPrefix}${entry}'` +
|
|
170
|
+
sheetsFor(entry)
|
|
171
|
+
)
|
|
172
|
+
})
|
|
173
|
+
if (rest.length) {
|
|
174
|
+
lines.push(`import { ${rest.join(', ')} } from '${source}'`)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
resolved += components.length
|
|
178
|
+
magic.overwrite(start, end, lines.join('\n'))
|
|
179
|
+
changed = true
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
onResolve(resolved)
|
|
184
|
+
if (!changed) return
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
code: magic.toString(),
|
|
188
|
+
map: magic.generateMap({ hires: true, source: file }),
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
}
|
|
192
|
+
}
|