From d168d496b86d25832874f5dbaa4a22ac820fa627 Mon Sep 17 00:00:00 2001 From: Junrim Date: Wed, 11 Dec 2024 20:09:35 +0800 Subject: [PATCH] V3.1.0 (#40) * v3.1.0 * Interface `capo` to `withCapo` JSDoc: Powered by `astro-capo`, it keeps the `` content well-organized and tidy. * Update index.ts * v3.1.0 * Update index.ts * Update package.json * Update packge.ts * Update README.md * Update Middleware to export `localizedHTML` for Manual injected of HTML to `` --- README.md | 26 ++++++++++++-- package.json | 3 +- src/config/defaults.ts | 1 - src/config/packge.ts | 2 ++ src/core/index.ts | 24 ++++++------- src/index.ts | 10 +++--- src/middleware/index.ts | 78 +++++++++++++++++++++++++++++------------ src/plugin.ts | 34 +++++++++--------- 8 files changed, 120 insertions(+), 58 deletions(-) create mode 100644 src/config/packge.ts diff --git a/README.md b/README.md index 66058a4..eaf3384 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,29 @@ By default, `astro-favicons` will insert 20 HTML tags into all pages, including > - **`Localized`** (requires `name_localized` configuration). -> - **`Capo.js`** rule is enabled by default. If you don't want `` tags to be ordered automatically, you can disable it by setting the relevant option to `false`. +> - **`withCapo`** is defaults to `true` (based on `AstroConfig.compressHTML`). To prevent automatic reordering and tidying of ` ` tags, set the relevant option or `compressHTML` to `false`. + +
+ + Manual Injected of HTML Tags Now Supported + +Added in: `v3.1.0` + +> + + + +e.g `~/components/Meta.astro` + +```ts +--- +import { localizedHTML as favicons } from 'astro-favicons/middleware'; +--- + + +``` + +
### 3. Build @@ -156,6 +178,7 @@ export default defineConfig({ defaultLocale: "zh-CN", locales: ["zh-CN", "en", "ar"], }, + compressHTML: import.meta.env.PROD, integrations: [ favicons({ input: { @@ -194,7 +217,6 @@ export default defineConfig({ html: true, assetPrefix: "/", }, - capo: true, // Extra options... }), ], diff --git a/package.json b/package.json index 757da03..82f0270 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "astro-favicons", - "version": "3.0.6", + "version": "3.1.0", "description": "An all-in-one favicon and PWA assets generator for Astro projects. It automates the creation of favicons, manifest, and supports hot reloading for efficient development. Powered by `astro-capo`, it keeps your οΉ€πš‘πšŽπšŠπšοΉ₯ content well-organized and tidy.", "type": "module", "main": "./dist/index.mjs", @@ -17,6 +17,7 @@ } }, "files": [ + "dist/shared", "dist/*.d.ts", "dist/*.mjs" ], diff --git a/src/config/defaults.ts b/src/config/defaults.ts index f709d25..b323d44 100644 --- a/src/config/defaults.ts +++ b/src/config/defaults.ts @@ -26,5 +26,4 @@ export const defaults: Options = { windows: true, yandex: true, }, - capo: true, }; diff --git a/src/config/packge.ts b/src/config/packge.ts new file mode 100644 index 0000000..f25ef70 --- /dev/null +++ b/src/config/packge.ts @@ -0,0 +1,2 @@ +import { name, version, homepage } from "../../package.json"; +export { name, version, homepage }; diff --git a/src/core/index.ts b/src/core/index.ts index 31d6feb..cbc2a9e 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -22,7 +22,8 @@ type PlatformedResponse = { type Params = { platform: PlatformName; - options: Options; + options?: Options; + response?: FaviconResponse; }; async function getIconsForPlatform( @@ -47,23 +48,19 @@ async function getIconsForPlatform( }); } -function mergeResults( - results: { platform: PlatformName; response: FaviconResponse }[], -): PlatformedResponse { +function mergeResults(results: Params[]): PlatformedResponse { return results.reduce( - (acc, { platform, response }) => { - acc.images.push( - ...response.images.map((image) => ({ ...image, platform })), - ); - acc.files.push(...response.files.map((file) => ({ ...file, platform }))); - acc.html.push(...response.html); + (acc, { platform, response: res }) => { + acc.images.push(...res.images.map((image) => ({ ...image, platform }))); + acc.files.push(...res.files.map((file) => ({ ...file, platform }))); + acc.html.push(...res.html); return acc; }, { images: [], files: [], html: [] } as PlatformedResponse, ); } -export async function collect( +export async function fetch( input: InputSource, options: Options, ): Promise { @@ -71,7 +68,10 @@ export async function collect( const results = await Promise.all( platforms.map(async (platform) => ({ platform, - response: await getIconsForPlatform(input?.[platform], { platform, options }), + response: await getIconsForPlatform(input?.[platform], { + platform, + options, + }), })), ); return mergeResults(results); diff --git a/src/index.ts b/src/index.ts index 8e73a2b..e476837 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,8 +2,8 @@ import type { AstroIntegration } from "astro"; import type { FaviconOptions, Input } from "./types"; import { defaults } from "./config/defaults"; import { handleAssets } from "./plugin"; +import { name } from "./config/packge"; -export const name = "astro-favicons"; export interface Options extends FaviconOptions { /** * Specify the source image(s) used to generate platform-specific assets. @@ -17,10 +17,10 @@ export interface Options extends FaviconOptions { */ input?: Input; /** - * Get the οΉ€πš‘πšŽπšŠπšοΉ₯ in order - * @default true + * Powered by `astro-capo`, it keeps the `` content well-organized and tidy. + * @default config.compressHTML `true` */ - capo?: boolean; + withCapo?: boolean; } export default function createIntegration(options?: Options): AstroIntegration { @@ -30,12 +30,14 @@ export default function createIntegration(options?: Options): AstroIntegration { name, hooks: { "astro:config:setup": async ({ + config, isRestart, command: cmd, updateConfig, logger, addMiddleware, }) => { + opts.withCapo = opts.withCapo ?? config.compressHTML; if (cmd === "build" || cmd === "dev") { if (!isRestart) { logger.info(`Processing source...`); diff --git a/src/middleware/index.ts b/src/middleware/index.ts index 91374a4..c43fa89 100644 --- a/src/middleware/index.ts +++ b/src/middleware/index.ts @@ -1,40 +1,74 @@ +import type { ElementNode } from "ultrahtml"; +import { + parse, + walkSync, + renderSync, + ELEMENT_NODE, + COMMENT_NODE, +} from "ultrahtml"; import { html, opts } from "virtual:astro-favicons"; import { defineMiddleware, sequence } from "astro/middleware"; +import { name, version, homepage } from "../config/packge"; import capo from "./capo"; -const getLocalizedName = (locale: string) => { +const banner = `Made by \`${name}\` v${version} - ${homepage}`; + +const useLocaleName = (locale?: string) => { + if (!locale) return opts.name; const localized = opts.name_localized?.[locale]; - if (!localized) return opts.name; - return typeof localized === "string" ? localized : localized.value; + return localized + ? typeof localized === "string" + ? localized + : localized.value + : opts.name; +}; + +export const localizedHTML = (locale?: string) => { + const tags = html + .map((line) => + line.replace( + /(name="(application-name|apple-mobile-web-app-title)")\scontent="[^"]*"/, + `name="$2" content="${useLocaleName(locale)}"`, + ), + ) + .join("\n"); + + return `${tags}`; }; -export const withCapo = defineMiddleware(async (context, next) => { +function injectToHead(ast: ElementNode, locale?: string): boolean { + let hasInjected = false; + + walkSync(ast, (node) => { + if (node.type === ELEMENT_NODE && node.name === "head") { + const alreadyInjected = node.children.some( + (child) => child.type === COMMENT_NODE && child.value.trim() === banner, + ); + const injectedHTML = localizedHTML(locale); + if (!alreadyInjected) { + const injectedNodes = parse(injectedHTML).children; + node.children.push(...injectedNodes); // η›΄ζŽ₯插ε…₯δΈΊε­θŠ‚η‚Ή + hasInjected = true; + } + } + }); + return hasInjected; +} + +export const withCapo = defineMiddleware(async (ctx, next) => { const res = await next(); if (!res.headers.get("Content-Type").includes("text/html")) { return next(); } - const document = await res.text(); - const headIndex = document.indexOf(""); - - if (headIndex === -1) return next(); + const doc = await res.text(); + const ast = parse(doc); - const locale = context.currentLocale; - const localizedName = getLocalizedName(locale); + injectToHead(ast, ctx.currentLocale); - const updatedHtml = - document.slice(0, headIndex) + - html - .map((line) => - line.replace( - /(name="(application-name|apple-mobile-web-app-title)")\scontent="[^"]*"/, - `name="$2" content="${localizedName}"`, - ), - ) - .join("\n") + - document.slice(headIndex); + const document = renderSync(ast); - return new Response(opts.capo ? capo(updatedHtml) : updatedHtml, { + return new Response(opts.withCapo ? capo(document) : document, { status: res.status, headers: res.headers, }); diff --git a/src/plugin.ts b/src/plugin.ts index 05cdb82..b3ecfcf 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,7 +1,8 @@ import type { AstroIntegrationLogger } from "astro"; -import type { Plugin } from "vite"; -import { name, type Options } from "."; -import { collect } from "./core"; +import type { Plugin, ResolvedConfig } from "vite"; +import type { Options } from "."; +import { name } from "./config/packge"; +import { fetch } from "./core"; import { getInput, normalizePath, mime } from "./helpers"; import { formatTime } from "./utils/timer"; import { styler as $s } from "./utils/styler"; @@ -21,20 +22,22 @@ export async function handleAssets( let sources = getInput(opts); const startAt = performance.now(); - const data = await collect(sources, opts); + const { images, files, html } = await fetch(sources, opts); const processedTime = performance.now() - startAt; const { isRestart, logger } = params; let base = normalizePath(opts.output?.assetsPrefix); // logger.info( - `${data.files.length} file(s), ${data.images.length} image(s)` + + `${files.length} file(s), ${images.length} image(s)` + $s( `${!isRestart ? " \u2713 Completed in" : ""} ${formatTime(processedTime)}.`, [`${!isRestart ? "FgGreen" : "Dim"}`], ), ); + // let config: ResolvedConfig; + return { name, enforce: "pre", @@ -45,7 +48,7 @@ export async function handleAssets( }, load(id) { if (id === resolvedVirtualModuleId) { - return `export const html = ${JSON.stringify(data.html)}; export const opts = ${JSON.stringify(opts)}`; + return `export const html = ${JSON.stringify(html)}; export const opts = ${JSON.stringify(opts)}`; } }, @@ -53,14 +56,14 @@ export async function handleAssets( server.middlewares.use(async (req, res, next) => { try { const reqUrl = decodeURIComponent(req.url || ""); // 解码整δΈͺθ·―εΎ„ - const resourceName = reqUrl.split("?")[0].split("/").pop(); // εŽ»ι™€ε‚ζ•°εΉΆθŽ·ε–ζœ€εŽηš„ζ–‡δ»Άε + const fileName = reqUrl.split("?")[0].split("/").pop(); // εŽ»ι™€ε‚ζ•°εΉΆθŽ·ε–ζœ€εŽηš„ζ–‡δ»Άε const resource = - data.images.find((img) => img.name === resourceName) || - data.files.find((file) => file.name === resourceName); + images.find((img) => img.name === fileName) || + files.find((file) => file.name === fileName); if (resource && req.url?.startsWith(`/${base}${resource?.name}`)) { - const mimeType = mime(resourceName); + const mimeType = mime(fileName); res.setHeader("Content-Type", mimeType); res.setHeader("Cache-Control", "no-cache"); res.end(resource.contents); @@ -78,19 +81,18 @@ export async function handleAssets( generateBundle() { try { - const emitFile = (resource: { + const emitFile = (file: { name: string; contents: Buffer | string; }) => { const fileId = this.emitFile({ type: "asset", - fileName: base + resource.name, - source: resource.contents, + fileName: base + file.name, + source: file.contents, }); }; - - data.images.forEach((image) => emitFile(image)); - data.files.forEach((file) => emitFile(file)); + images.forEach((image) => emitFile(image)); + files.forEach((file) => emitFile(file)); } catch (err) { throw err; }