Skip to content

Commit

Permalink
V3.1.0 (#40)
Browse files Browse the repository at this point in the history
* v3.1.0

* Interface `capo` to `withCapo`

JSDoc: Powered by `astro-capo`, it keeps the `<head>` 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 `<head>`
  • Loading branch information
JakiChen authored Dec 11, 2024
1 parent db703ca commit d168d49
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 58 deletions.
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,29 @@ By default, `astro-favicons` will insert 20 HTML tags into all pages, including
</details>

> - **`Localized`** (requires `name_localized` configuration).
> - **`Capo.js`** rule is enabled by default. If you don't want `<head>` 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 ` <head>` tags, set the relevant option or `compressHTML` to `false`.
<details>
<summary>
<b> Manual Injected of HTML Tags Now Supported</b>

Added in: `v3.1.0`

>
</summary>

e.g `~/components/Meta.astro`

```ts
---
import { localizedHTML as favicons } from 'astro-favicons/middleware';
---
<meta charset="utf-8" />
<Fragment set:html={favicons(Astro.currentLocale)} />
```

</details>

### 3. Build

Expand Down Expand Up @@ -156,6 +178,7 @@ export default defineConfig({
defaultLocale: "zh-CN",
locales: ["zh-CN", "en", "ar"],
},
compressHTML: import.meta.env.PROD,
integrations: [
favicons({
input: {
Expand Down Expand Up @@ -194,7 +217,6 @@ export default defineConfig({
html: true,
assetPrefix: "/",
},
capo: true,
// Extra options...
}),
],
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -17,6 +17,7 @@
}
},
"files": [
"dist/shared",
"dist/*.d.ts",
"dist/*.mjs"
],
Expand Down
1 change: 0 additions & 1 deletion src/config/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,4 @@ export const defaults: Options = {
windows: true,
yandex: true,
},
capo: true,
};
2 changes: 2 additions & 0 deletions src/config/packge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { name, version, homepage } from "../../package.json";
export { name, version, homepage };
24 changes: 12 additions & 12 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ type PlatformedResponse = {

type Params = {
platform: PlatformName;
options: Options;
options?: Options;
response?: FaviconResponse;
};

async function getIconsForPlatform(
Expand All @@ -47,31 +48,30 @@ 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<PlatformedResponse> {
const platforms = Object.keys(options.icons) as PlatformName[];
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);
Expand Down
10 changes: 6 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -17,10 +17,10 @@ export interface Options extends FaviconOptions {
*/
input?: Input;
/**
* Get the ﹤𝚑𝚎𝚊𝚍﹥ in order
* @default true
* Powered by `astro-capo`, it keeps the `<head>` content well-organized and tidy.
* @default config.compressHTML `true`
*/
capo?: boolean;
withCapo?: boolean;
}

export default function createIntegration(options?: Options): AstroIntegration {
Expand All @@ -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...`);
Expand Down
78 changes: 56 additions & 22 deletions src/middleware/index.ts
Original file line number Diff line number Diff line change
@@ -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 `<!--${banner}-->${tags}<!--/Total ${html.length} tag(s)-->`;
};

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("</head>");

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,
});
Expand Down
34 changes: 18 additions & 16 deletions src/plugin.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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",
Expand All @@ -45,22 +48,22 @@ 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)}`;
}
},

configureServer(server) {
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);
Expand All @@ -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;
}
Expand Down

0 comments on commit d168d49

Please sign in to comment.