@octohash/vite-config
Version:
A personal ready-to-use Vite configuration repository, opinionated but customizable.
497 lines (481 loc) • 16.2 kB
JavaScript
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 };