UNPKG

@octohash/vite-config

Version:

A personal ready-to-use Vite configuration repository, opinionated but customizable.

497 lines (481 loc) 16.2 kB
import { __dirname, init_esm_shims } from "./esm-shims-CLMD_LGJ.js"; import { isPackageExists } from "local-pkg"; import { defineConfig as defineConfig$1, mergeConfig } from "vite"; import { existsSync } from "node:fs"; import path, { isAbsolute, join, resolve } from "node:path"; import process from "node:process"; import deepmerge from "deepmerge"; import { findUp } from "find-up"; import { readPackageJSON } from "pkg-types"; import { fileURLToPath } from "node:url"; import fsp from "node:fs/promises"; import { visualizer } from "rollup-plugin-visualizer"; import { EOL } from "node:os"; import dayjs from "dayjs"; import Vue from "@vitejs/plugin-vue"; import VueJsx from "@vitejs/plugin-vue-jsx"; import Dts from "vite-plugin-dts"; //#region src/utils.ts function getProjectType() { const htmlPath = join(process.cwd(), "index.html"); return existsSync(htmlPath) ? "app" : "lib"; } async function loadMergedPackageJson() { const root = process.cwd(); const rootPkgJsonPath = await findUp("pnpm-lock.yaml", { cwd: root, type: "file" }); const rootPkgJson = rootPkgJsonPath ? await readPackageJSON(rootPkgJsonPath) : {}; const pkgJson = await readPackageJSON(root); return deepmerge(rootPkgJson, pkgJson); } function extractAuthorInfo(pkgJson) { const { author } = pkgJson; const isObject = typeof author === "object"; const name = isObject ? author.name : author; const email = isObject ? author.email : void 0; const url = isObject ? author.url : void 0; return { name, email, url }; } async function loadConditionPlugins(conditionPlugins) { const plugins = []; for (const conditionPlugin of conditionPlugins) if (conditionPlugin.condition) { const realPlugins = await conditionPlugin.plugins(); plugins.push(...realPlugins); } return plugins.flat(); } function resolveSubOptions(options, key) { return typeof options[key] === "boolean" ? {} : options[key] || {}; } //#endregion //#region src/plugins/app-loading/index.ts init_esm_shims(); const INJECT_SCRIPT = ` <script data-app-loading="inject-js"> ;(function () { const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches const setting = localStorage.getItem('vueuse-color-scheme') || 'auto' if (setting === 'dark' || (prefersDark && setting !== 'light')) document.documentElement.classList.toggle('dark', true) })() </script> `; async function AppLoadingPlugin(options) { const { rootContainer = "app", title = "", filePath = path.join(__dirname, "./default-loading.html") } = options || {}; const loadingHtml = await getLoadingRawByHtmlTemplate(filePath); return { name: "vite-plugin-app-loading", enforce: "pre", transformIndexHtml: { order: "pre", handler: (html) => { const rootContainerPattern = new RegExp(`<div id="${rootContainer}"\\s*></div>`, "i"); if (!rootContainerPattern.test(html)) return html; const processedLoadingHtml = loadingHtml.replace("[app-loading-title]", title); const injectedContent = `${INJECT_SCRIPT}${processedLoadingHtml}`; return html.replace(rootContainerPattern, `<div id="${rootContainer}">${injectedContent}</div>`); } } }; } async function getLoadingRawByHtmlTemplate(filePath) { return await fsp.readFile(filePath, "utf8"); } //#endregion //#region src/ensure.ts const scopeUrl = fileURLToPath(new URL(".", import.meta.url)); const isCwdInScope = isPackageExists("@octohash/vite-config"); function isPackageInScope(name) { return isPackageExists(name, { paths: [scopeUrl] }); } async function ensurePackages(packages) { if (process.env.CI || process.stdout.isTTY === false || isCwdInScope === false) return; const nonExistingPackages = packages.filter((i) => i && !isPackageInScope(i)); if (nonExistingPackages.length === 0) return; const p = await import("@clack/prompts"); const result = await p.confirm({ message: `${nonExistingPackages.length === 1 ? "Package is" : "Packages are"} required for this config: ${nonExistingPackages.join(", ")}. Do you want to install them?` }); if (result) await import("@antfu/install-pkg").then((i) => i.installPackage(nonExistingPackages, { dev: true })); } //#endregion //#region src/plugins/license.ts async function LicensePlugin(options) { const licenseText = await generateLicenseText(options || {}); return { name: "vite-plugin-license", enforce: "post", apply: "build", generateBundle: { order: "post", handler: (_options, bundle) => { for (const [, fileContent] of Object.entries(bundle)) if (fileContent.type === "chunk" && fileContent.isEntry) { const chunkContent = fileContent; const content = chunkContent.code; const updatedContent = `${licenseText}${EOL}${content}`; fileContent.code = updatedContent; } } } }; } async function generateLicenseText(options) { const pkgJson = await loadMergedPackageJson(); const { name: authorName, email: authorEmail, url: authorUrl } = extractAuthorInfo(pkgJson); const { name = pkgJson.name, author = authorName, version = pkgJson.version, description = pkgJson.description, homepage = pkgJson.homepage ?? authorUrl, license, contact = authorEmail, copyright = { holder: authorName, year: new Date().getFullYear() } } = options ?? {}; const date = dayjs().format("YYYY-MM-DD"); const lines = []; lines.push("/*!"); if (name) lines.push(` * ${name}`); if (version) lines.push(` * Version: ${version}`); if (author) lines.push(` * Author: ${author}`); if (license) lines.push(` * License: ${license}`); if (description) lines.push(` * Description: ${description}`); if (homepage) lines.push(` * Homepage: ${homepage}`); if (contact) lines.push(` * Contact: ${contact}`); if (copyright?.holder) lines.push(` * Copyright (C) ${copyright.year} ${copyright.holder}`); if (date) lines.push(` * Date: ${date}`); lines.push(" */"); return lines.join("\n"); } //#endregion //#region src/plugins/common.ts async function loadCommonPlugins(options) { const { isBuild, visualizer: visualizer$1 = false, license = true, federation } = options; ensurePackages([federation ? "@originjs/vite-plugin-federation" : void 0]); return await loadConditionPlugins([ { condition: isBuild && !!visualizer$1, plugins: () => [visualizer(typeof visualizer$1 === "boolean" ? { filename: "./node_modules/.cache/visualizer/stats.html", gzipSize: true, open: true } : visualizer$1)] }, { condition: isBuild && !!license, plugins: async () => [await LicensePlugin(typeof license === "boolean" ? void 0 : license)] }, { condition: !!federation, plugins: async () => { const module = await import("@originjs/vite-plugin-federation"); return [module.default(federation)]; } } ]); } //#endregion //#region src/plugins/import-map.ts const shimsSubpath = `dist/es-module-shims.js`; const providerShimsMap = { "jspm.io": `https://ga.jspm.io/npm:es-module-shims@{version}/${shimsSubpath}`, "jsdelivr": `https://cdn.jsdelivr.net/npm/es-module-shims@{version}/${shimsSubpath}`, "unpkg": `https://unpkg.com/es-module-shims@{version}/${shimsSubpath}`, "esm.sh": `https://esm.sh/es-module-shims@{version}/${shimsSubpath}` }; async function ImportMapPlugin(options = {}) { await ensurePackages(["vite-plugin-jspm"]); const module = await import("vite-plugin-jspm"); const { defaultProvider = "jspm.io", include = [], exclude = [] } = options; const [scan, mapping, post] = await module.default({ ...options, pollyfillProvider: (version) => providerShimsMap[defaultProvider]?.replace("{version}", version) }); const _resolveId = scan.resolveId; scan.resolveId = function(id, importer, ctx) { if (include.length && !include.includes(id) || exclude.length && exclude.includes(id)) return null; return typeof _resolveId === "function" ? _resolveId.call(this, id, importer, ctx) : null; }; const _load = mapping.load; mapping.load = function(id) { if (include.length && !include.includes(id)) return null; return typeof _load === "function" ? _load.call(this, id) : null; }; return [ scan, mapping, post ]; } //#endregion //#region src/plugins/metadata.ts async function MetadataPlugin(options) { const { extendMetadata = {} } = options ?? {}; const pkgJson = await loadMergedPackageJson(); const { name, description, homepage, license, version } = pkgJson; const { name: authorName, email: authorEmail, url: authorUrl } = extractAuthorInfo(pkgJson); const buildTime = dayjs().format("YYYY-MM-DD HH:mm:ss"); return { name: "vite-plugin-metadata", enforce: "post", config: () => { return { define: { __VITE_APP_METADATA__: JSON.stringify({ authorName, authorEmail, authorUrl, buildTime, name, description, homepage, license, version, ...extendMetadata }) } }; } }; } //#endregion //#region src/plugins/vue.ts async function loadVuePlugins(projectType, options) { const { isBuild } = options; const isApp = projectType === "app"; const { devtools = false, i18n = false, imports = isApp, components = isApp, pages = isApp } = resolveSubOptions(options, "vue"); await ensurePackages([devtools ? "vite-plugin-vue-devtools" : void 0, i18n ? "@intlify/unplugin-vue-i18n" : void 0]); return loadConditionPlugins([ { condition: true, plugins: () => [Vue(), VueJsx()] }, { condition: !isBuild && !!devtools, plugins: async () => { const module = await import("vite-plugin-vue-devtools"); return [module.default(typeof devtools === "boolean" ? void 0 : devtools)]; } }, { condition: !!i18n, plugins: async () => { const module = await import("./vite-D44mTpAn.js"); return [module.default(typeof i18n === "boolean" ? { compositionOnly: true, fullInstall: true, runtimeOnly: true } : i18n)]; } }, { condition: isApp && !!imports, plugins: async () => { const module = await import("unplugin-auto-import/vite"); return [module.default(typeof imports === "boolean" ? { dts: "src/typings/auto-imports.d.ts", imports: await resolveAutoImports(), resolvers: await resolveUIComponentResolvers(), dirs: ["src/composables", "src/utils"], vueTemplate: true } : imports)]; } }, { condition: isApp && !!components, plugins: async () => { const module = await import("unplugin-vue-components/vite"); return [module.default(typeof components === "boolean" ? { dts: "src/typings/components.d.ts", directoryAsNamespace: true } : components)]; } }, { condition: isApp && !!pages, plugins: async () => { const module = await import("unplugin-vue-router/vite"); return [module.default(typeof pages === "boolean" ? { dts: "src/typings/typed-router.d.ts" } : pages)]; } } ]); } async function resolveAutoImports() { const imports = ["vue"]; if (isPackageExists("vue-router")) imports.push("vue-router"); if (isPackageExists("pinia")) imports.push("pinia"); if (isPackageExists("@vueuse/core")) imports.push("@vueuse/core"); if (isPackageExists("vue-i18n")) imports.push("vue-i18n"); return imports; } async function resolveUIComponentResolvers() { const resolvers = []; if (isPackageExists("ant-design-vue")) { const { AntDesignVueResolver } = await import("unplugin-vue-components/resolvers"); resolvers.push(AntDesignVueResolver({ importStyle: "css-in-js", prefix: "" })); } if (isPackageExists("element-plus")) { const { ElementPlusResolver } = await import("unplugin-vue-components/resolvers"); resolvers.push(...ElementPlusResolver()); } if (isPackageExists("naive-ui")) { const { NaiveUiResolver } = await import("unplugin-vue-components/resolvers"); resolvers.push(NaiveUiResolver()); } if (isPackageExists("vant")) { const { VantResolver } = await import("unplugin-vue-components/resolvers"); resolvers.push(VantResolver()); } return resolvers; } //#endregion //#region src/plugins/app.ts async function loadAppPlugins(options) { const { isBuild, dynamicBase, appLoading = true, metadata = true, importMap = false, vue } = options; const plugins = await loadCommonPlugins(options); plugins.push(await loadConditionPlugins([ { condition: !!dynamicBase, plugins: async () => { const module = await import("vite-plugin-dynamic-base"); return [module.dynamicBase({ publicPath: dynamicBase, transformIndexHtml: true })]; } }, { condition: !!appLoading, plugins: async () => [await AppLoadingPlugin(typeof appLoading === "boolean" ? void 0 : appLoading)] }, { condition: !!metadata, plugins: async () => { return [await MetadataPlugin(typeof metadata === "boolean" ? void 0 : metadata)]; } }, { condition: isBuild && !!importMap, plugins: () => { return [ImportMapPlugin(typeof importMap === "boolean" ? void 0 : importMap)]; } } ])); if (vue) plugins.push(await loadVuePlugins("app", options)); return plugins; } //#endregion //#region src/config/common.ts async function getCommonConfig(options) { const { alias = {} } = options; const resolvedAlias = Object.entries(alias).reduce((acc, [key, value]) => { acc[key] = isAbsolute(value) ? value : resolve(process.cwd(), value); return acc; }, {}); return { resolve: { alias: { "@": resolve(process.cwd(), "./src"), ...resolvedAlias } }, build: { chunkSizeWarningLimit: 2e3, reportCompressedSize: false, sourcemap: false } }; } //#endregion //#region src/config/app.ts function defineAppConfig(options) { return defineConfig$1(async (config) => { const { dynamicBase, vite = {} } = options; const { command } = config; const isBuild = command === "build"; const plugins = await loadAppPlugins({ ...options, isBuild }); const appConfig = { base: dynamicBase ? "/__dynamic_base__/" : "/", plugins, build: { target: "es2015", rollupOptions: { output: { assetFileNames: "[ext]/[name]-[hash].[ext]", chunkFileNames: "js/[name]-[hash].js", entryFileNames: "jse/index-[name]-[hash].js" } } }, esbuild: { drop: isBuild ? ["debugger"] : [], legalComments: "none" }, server: { host: true } }; const mergedCommonConfig = mergeConfig(await getCommonConfig(options), appConfig); return mergeConfig(mergedCommonConfig, vite); }); } //#endregion //#region src/plugins/lib.ts async function loadLibPlugins(options) { const { isBuild, dts = true, vue } = options; const plugins = await loadCommonPlugins(options); plugins.push(await loadConditionPlugins([{ condition: isBuild && !!dts, plugins: () => [Dts(typeof dts === "boolean" ? { logLevel: "error" } : dts)] }])); if (vue) plugins.push(await loadVuePlugins("lib", options)); return plugins; } //#endregion //#region src/config/lib.ts function defineLibConfig(options) { return defineConfig$1(async (config) => { const root = process.cwd(); const { vite = {} } = options; const { command } = config; const isBuild = command === "build"; const plugins = await loadLibPlugins({ ...options, isBuild }); const { dependencies = {}, peerDependencies = {} } = await readPackageJSON(root); const externalPackages = [...Object.keys(dependencies), ...Object.keys(peerDependencies)]; const libConfig = { plugins, build: { lib: { entry: "src/index.ts", fileName: () => "index.mjs", formats: ["es"] }, rollupOptions: { external: (id) => { return externalPackages.some((pkg) => id === pkg || id.startsWith(`${pkg}/`)); } } } }; const mergedCommonConfig = mergeConfig(await getCommonConfig(options), libConfig); return mergeConfig(mergedCommonConfig, vite); }); } //#endregion //#region src/constants.ts const VUE_PACKAGES = [ "vue", "nuxt", "vitepress" ]; //#endregion //#region src/index.ts function defineConfig(options) { const resolved = { type: getProjectType(), vue: VUE_PACKAGES.some((pkg) => isPackageExists(pkg)), ...options }; switch (resolved.type) { case "app": return defineAppConfig(resolved); case "lib": return defineLibConfig(resolved); default: throw new Error(`Unsupported project type: ${resolved.type}`); } } //#endregion export { defineConfig };