@bg-dev/nuxt-naiveui
Version:
Unofficial Naive UI module for Nuxt
510 lines (500 loc) • 15.2 kB
JavaScript
import path from 'node:path';
import { defineNuxtModule, createResolver, addPlugin, addImportsDir, addComponent, addImports, extendViteConfig, addTypeTemplate } from '@nuxt/kit';
import naive from 'naive-ui';
import { defu } from 'defu';
import fs from 'node:fs';
import { loadIcons, getIcon } from '@iconify/vue';
const name = "@bg-dev/nuxt-naiveui";
const version = "2.0.0-rc.5";
const download = (icons) => new Promise((resolve, reject) => {
loadIcons(icons, (loaded, missing, pending) => {
if (pending.length) {
return;
}
const fullname = (e) => `${e.prefix}:${e.name}`;
if (missing.length) {
const error = new Error(`\u274C \u{E0020} [nuxt-naiveui] download failed of ${missing.map(fullname).join(" ")}`);
reject(error);
}
const output = loaded.map((i) => ({
prefix: i.prefix,
icons: {
[i.name]: getIcon(fullname(i))
}
}));
resolve(output);
});
});
const makeDir = (path2) => {
const exists = fs.existsSync(path2);
if (!exists) {
fs.mkdirSync(path2);
}
};
const makeFile = (path2, content) => {
const exists = fs.existsSync(path2);
if (!exists) {
fs.writeFileSync(path2, content);
}
};
const save = (iconsJSON, iconsDir) => {
iconsJSON.forEach((i) => {
const prefix = i.prefix;
const name = Object.keys(i.icons)[0];
const dirPath = path.resolve(iconsDir, prefix);
const filePath = path.resolve(dirPath, `${name}.json`);
makeDir(dirPath);
makeFile(filePath, JSON.stringify(i));
});
};
const removeSavedIcons = (icons, iconsDir) => {
icons.forEach((icon) => {
const [prefix, name] = icon.split(":");
if (prefix && name) {
const dirPath = path.resolve(iconsDir, prefix);
const filePath = path.resolve(dirPath, `${name}.json`);
fs.rmSync(filePath);
if (fs.readdirSync(dirPath).length === 0) {
fs.rmdirSync(dirPath);
}
}
});
};
const getSavedIcons = (iconsDir) => {
return fs.readdirSync(iconsDir, { recursive: true }).map((p) => path.parse(p.toString())).filter((p) => p.dir).map((p) => `${p.dir}:${p.name}`);
};
const COLLECTIONS_URL = "https://raw.githubusercontent.com/iconify/icon-sets/master/collections.json";
function iconifyVitePlugin(path, collectionsUrl = COLLECTIONS_URL) {
if (process.env.NODE_ENV === "development") {
return;
}
let regex;
const icons = /* @__PURE__ */ new Set();
makeDir(path);
return {
name: "iconify-download-icons",
async buildStart() {
if (regex) {
return;
}
const fetchError = new Error(`\u274C \u{E0020} [nuxt-naiveui] failed to fetch collections from ${collectionsUrl}`);
const collections = await fetch(collectionsUrl).then((r) => {
if (r.ok) {
return r.json();
}
throw fetchError;
}).catch(() => {
throw fetchError;
});
const prefixes = Object.keys(collections);
regex = new RegExp(`("|'|\`)(${prefixes.join("|")}):[a-z0-9]+(?:-[a-z0-9]+)*("|'|\`)`, "g");
},
transform(code) {
code.match(regex)?.forEach((m) => icons.add(m.replace(/['"`]/g, "")));
return { code, map: null };
},
async buildEnd() {
const savedIcons = getSavedIcons(path);
const unusedIcons = savedIcons.filter((i) => !icons.has(i));
removeSavedIcons(unusedIcons, path);
const missingIcons = /* @__PURE__ */ new Set();
icons.forEach((i) => savedIcons.includes(i) || missingIcons.add(i));
if (missingIcons.size) {
await download([...missingIcons.values()]).then((d) => save(d, path));
}
console.log(`\u2714\uFE0F \u{E0020} [nuxt-naiveui] download ${missingIcons.size} new icons and remove ${unusedIcons.length} unused icons`);
}
};
}
const defaultLightTheme = {
common: {
lineHeight: "normal",
textColorBase: "black",
bodyColor: "white",
textColor1: "#262626",
textColor2: "#525252",
textColor3: "#a3a3a3"
},
Menu: {
itemTextColorHorizontalInverted: "#787878",
itemIconColorInverted: "#787878",
itemTextColorInverted: "#787878",
itemTextColorHoverHorizontalInverted: "#525252",
itemIconColorHoverHorizontalInverted: "#525252",
itemTextColorActiveHorizontalInverted: "#525252",
itemIconColorActiveHorizontalInverted: "#525252",
itemTextColorActiveHoverHorizontalInverted: "#525252",
itemIconColorActiveHoverHorizontalInverted: "#525252",
itemTextColorChildActiveHorizontalInverted: "#525252",
itemIconColorChildActiveHorizontalInverted: "#525252",
itemTextColorChildActiveHoverHorizontalInverted: "#525252",
itemIconColorChildActiveHoverHorizontalInverted: "#525252",
itemTextColorHoverInverted: "#525252",
itemIconColorHoverInverted: "#525252",
arrowColorHoverInverted: "#525252",
itemTextColorChildActiveInverted: "var(--n-item-color-active)",
itemIconColorChildActiveHoverInverted: "var(--n-item-color-active-hover)",
itemIconColorChildActiveInverted: "var(--n-item-color-active)",
arrowColorChildActiveInverted: "var(--n-item-color-active)",
arrowColorChildActiveHoverInverted: "var(--n-item-color-active-hover)",
arrowColorActiveInverted: "var(--n-item-color-active)",
arrowColorActiveHoverInverted: "var(--n-item-color-active-hover)"
},
Layout: {
siderColor: "white",
headerColor: "white",
footerColor: "white"
},
IconWrapper: {
color: "transparent",
iconColor: "inherit"
},
Input: {
lineHeightTextarea: "1.6"
},
LoadingBar: {
height: "3px"
},
Form: {
feedbackPadding: "8px 0px 10px 0px"
}
};
const defaultDarkTheme = {
common: {
lineHeight: "normal",
textColorBase: "white",
primaryColor: "#63e2b7",
primaryColorHover: "#7fe7c4",
primaryColorPressed: "#5acea7",
primaryColorSuppl: "#2a947d",
infoColor: "#70c0e8",
infoColorHover: "#8acbec",
infoColorPressed: "#66afd3",
infoColorSuppl: "#3889c5",
successColor: "#63e2b7",
successColorHover: "#7fe7c4",
successColorPressed: "#5acea7",
successColorSuppl: "#2a947d",
warningColor: "#f2c97d",
warningColorHover: "#f5d599",
warningColorPressed: "#e6c260",
warningColorSuppl: "#f08a00",
errorColor: "#e88080",
errorColorHover: "#e98b8b",
errorColorPressed: "#e57272",
errorColorSuppl: "#d03a52",
tabColor: "#ffffff0a",
tableColorStriped: "#ffffff0d",
pressedColor: "#ffffff0d",
actionColor: "#ffffff0f",
tableHeaderColor: "#ffffff0f",
tableColorHover: "#ffffff0f",
inputColorDisabled: "#ffffff0f",
buttonColor2: "#ffffff14",
buttonColor2Pressed: "#ffffff14",
closeColorPressed: "#ffffff14",
dividerColor: "#ffffff17",
borderColor: "#ffffff17",
hoverColor: "#ffffff17",
inputColor: "#ffffff1a",
buttonColor2Hover: "#ffffff1f",
closeColorHover: "#ffffff1f",
progressRailColor: "#ffffff1f",
codeColor: "#ffffff1f",
avatarColor: "#ffffff2e",
scrollbarColor: "#ffffff33",
railColor: "#ffffff33",
placeholderColorDisabled: "#ffffff47",
iconColorDisabled: "#ffffff47",
iconColorPressed: "#ffffff4d",
clearColorPressed: "#ffffff4d",
scrollbarColorHover: "#ffffff4d",
placeholderColor: "#ffffff61",
textColorDisabled: "#ffffff61",
clearColor: "#ffffff61",
iconColor: "#ffffff61",
iconColorHover: "#ffffff79",
clearColorHover: "#ffffff7a",
closeIconColorHover: "#ffffff85",
closeIconColor: "#ffffff85",
closeIconColorPressed: "#ffffff85",
textColor3: "#ffffff85",
textColor2: "#ffffffd1",
textColor1: "#ffffffe6",
bodyColor: "#101014",
baseColor: "#101014",
invertedColor: "#101014",
tableColor: "#18181c",
cardColor: "#18181c",
tagColor: "#333333",
modalColor: "#2c2c32",
popoverColor: "#48484e"
},
Skeleton: {
color: "#ffffff1f",
colorEnd: "#ffffff2e"
},
Tag: {
colorBordered: "#00000000"
},
Tooltip: {
color: "#48484e",
textColor: "#ffffffe6"
},
Slider: {
indicatorColor: "#48484e",
indicatorTextColor: "#ffffffe6"
},
Layout: {
siderColor: "#101014",
headerColor: "#101014",
footerColor: "#101014"
},
Tabs: {
tabColorSegment: "#ffffff1a"
},
IconWrapper: {
color: "transparent",
iconColor: "inherit"
},
Input: {
lineHeightTextarea: "1.6"
},
LoadingBar: {
height: "3px"
},
Form: {
feedbackPadding: "8px 0px 10px 0px"
}
};
const defaultMobileOrTabletTheme = {
common: {
fontSize: "16px",
heightMedium: "42px",
fontSizeMedium: "16px"
},
Form: {
labelFontSizeTopMedium: "16px"
},
Input: {
heightMedium: "42px",
fontSizeMedium: "16px"
},
Button: {
heightMedium: "42px",
fontSizeMedium: "16px"
},
Card: {
fontSizeMedium: "16px"
},
Avatar: {
heightMedium: "42px",
fontSize: "16px"
},
ColorPicker: {
heightMedium: "42px",
fontSizeMedium: "16px"
},
Dropdown: {
optionHeightMedium: "42px",
fontSizeMedium: "16px"
},
Radio: {
buttonHeightMedium: "42px",
fontSizeMedium: "16px"
},
Skeleton: {
heightMedium: "42px"
},
Tag: {
heightMedium: "36px",
fontSizeMedium: "16px"
},
Result: {
fontSizeMedium: "16px"
},
Tabs: {
tabFontSizeMedium: "16px"
},
Pagination: {
itemSizeMedium: "36px"
}
};
function mergeThemeConfig(themeConfig) {
return {
dark: themeConfig?.dark?.defaults === false ? themeConfig.dark : defu(themeConfig?.dark, defaultDarkTheme),
light: themeConfig?.light?.defaults === false ? themeConfig.light : defu(themeConfig?.light, defaultLightTheme),
mobileOrTablet: themeConfig?.mobileOrTablet?.defaults === false ? themeConfig.mobileOrTablet : defu(themeConfig?.mobileOrTablet, defaultMobileOrTabletTheme),
mobile: themeConfig?.mobile,
shared: themeConfig?.shared
};
}
const module = defineNuxtModule({
meta: {
name,
version,
configKey: "naiveui",
compatibility: {
nuxt: ">=3.16.0"
}
},
// Default configuration options of the Nuxt module
defaults: {
colorModePreference: "light",
colorModePreferenceCookieName: "naive_color_mode_preference",
iconSize: 20,
iconDownload: false,
iconCollectionsUrl: "https://iconify-icon-sets.netlify.app",
themeConfig: {}
},
// Add types for volar
hooks: {
"prepare:types": ({ references }) => {
references.push({
types: "naive-ui/volar"
});
}
},
setup(options, nuxt) {
const { resolve } = createResolver(import.meta.url);
nuxt.options.css.push(resolve("./runtime/assets/style.css"));
nuxt.options.runtimeConfig.public = defu(nuxt.options.runtimeConfig.public, {
naiveui: {
colorModePreference: options.colorModePreference,
colorModePreferenceCookieName: options.colorModePreferenceCookieName,
iconDownload: options.iconDownload,
iconCollectionsUrl: options.iconCollectionsUrl,
iconSize: options.iconSize,
spaLoadingTemplate: options.spaLoadingTemplate,
themeConfig: mergeThemeConfig(options.themeConfig)
}
});
addPlugin(resolve("./runtime/plugins/naive.server"));
addPlugin(resolve("./runtime/plugins/colorMode"));
addImportsDir(resolve("./runtime/composables"));
addComponent({
name: "NaiveConfig",
filePath: resolve("./runtime/components/NaiveConfig.vue")
});
addComponent({
name: "NaiveNavbar",
filePath: resolve("./runtime/components/NaiveNavbar.vue")
});
addComponent({
name: "NaiveColorModeSwitch",
filePath: resolve("./runtime/components/NaiveColorModeSwitch.vue")
});
addComponent({
name: "NaiveTabbar",
filePath: resolve("./runtime/components/NaiveTabbar.vue")
});
addComponent({
name: "NaiveMenuLink",
filePath: resolve("./runtime/components/NaiveMenuLink.vue")
});
addComponent({
name: "NaiveLayoutSidebar",
filePath: resolve("./runtime/components/NaiveLayoutSidebar.vue")
});
addComponent({
name: "NaiveLayoutNavbar",
filePath: resolve("./runtime/components/NaiveLayoutNavbar.vue")
});
addComponent({
name: "NaiveDrawerLink",
filePath: resolve("./runtime/components/NaiveDrawerLink.client.vue")
});
addComponent({
name: "NaiveLoadingBar",
filePath: resolve("./runtime/components/NaiveLoadingBar.client.vue")
});
addComponent({
name: "NaiveNotification",
filePath: resolve("./runtime/components/NaiveNotification.client.vue")
});
addComponent({
name: "NaiveIcon",
filePath: resolve(
"./runtime/components",
options?.iconDownload ? "NaiveIconOffline.vue" : "NaiveIcon.vue"
)
});
const naiveComponents = Object.keys(naive).filter(
(name2) => /^N[A-Z]|n-[a-z]/.test(name2)
);
const naiveClientOnlyComponents = [
"NDrawer",
"NDrawerContent",
"NModal"
];
naiveComponents.forEach((name2) => {
addComponent({
export: name2,
name: name2,
filePath: "naive-ui",
mode: naiveClientOnlyComponents.includes(name2) ? "client" : "all"
});
});
const naiveComposables = [
"useDialog",
"useMessage",
"useNotification",
"useLoadingBar",
"useDialogReactiveList",
"useThemeVars",
"useModal"
];
naiveComposables.forEach((name2) => {
addImports({
name: name2,
as: name2,
from: "naive-ui"
});
});
if (process.env.NODE_ENV === "development") {
nuxt.options.build.transpile.push("naive-ui");
extendViteConfig((config) => {
config.plugins ||= [];
config.plugins.push({
name: "fix-transpile-juggle-resize-observer",
enforce: "pre",
transform(code, id) {
if (id.includes("@juggle/resize-observer/lib/algorithms/calculateBoxSize.js")) {
return code.replace("global.navigator && global.navigator.userAgent", "global.navigator?.userAgent");
}
}
});
});
} else {
nuxt.options.build.transpile.push("naive-ui", "vueuc", "@css-render/vue3-ssr", "@iconify/vue");
}
if (options?.iconDownload) {
extendViteConfig((config) => {
config.plugins ||= [];
const iconsDir = path.resolve(nuxt.options.rootDir, "public/iconify");
config.plugins.push(iconifyVitePlugin(iconsDir, options.iconCollectionsUrl));
});
}
if (options.spaLoadingTemplate && typeof nuxt.options.spaLoadingTemplate !== "string") {
nuxt.options.spaLoadingTemplate = resolve(`./runtime/templates/${options.spaLoadingTemplate.name}.html`);
}
addTypeTemplate({
filename: "types/naiveui.d.ts",
getContents: () => `
import {RouteLocationRaw} from '#vue-router'
export interface MenuLinkRoute {
label: string
icon?: string
to?: RouteLocationRaw
children?: MenuLinkRoute[]
}
export interface TabbarRoute {
label: string
iconSelected: string
iconUnselected: string
to: RouteLocationRaw
}`
});
}
});
export { module as default };