UNPKG

@bg-dev/nuxt-naiveui

Version:

Unofficial Naive UI module for Nuxt

510 lines (500 loc) 15.2 kB
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 };