iconfont-preview-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +255 -0
- package/app/assets/index-BN1JFPmQ.js +22 -0
- package/app/assets/index-fas_yV9m.css +1 -0
- package/app/index.html +14 -0
- package/app/vite.svg +1 -0
- package/components/index.cjs +1 -0
- package/components/index.css +1 -0
- package/components/index.mjs +1233 -0
- package/components/types/index.d.ts +2 -0
- package/components/types/render-icon-list/index.d.ts +3 -0
- package/components/types/render-icon-list/src/components/render-icon-class.d.ts +23 -0
- package/components/types/render-icon-list/src/components/render-icon.d.ts +26 -0
- package/components/types/render-icon-list/src/render-icon-list.d.ts +33 -0
- package/components/types/render-icon-list/src/render-icon-list.vue.d.ts +61 -0
- package/package.json +66 -0
- package/server/bin.cjs +163 -0
- package/server/bin.mjs +150 -0
- package/server/index.cjs +141 -0
- package/server/index.d.cts +6 -0
- package/server/index.d.mts +6 -0
- package/server/index.d.ts +6 -0
- package/server/index.mjs +133 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { PropType } from "vue";
|
|
2
|
+
declare const _default: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
3
|
+
iconClass: {
|
|
4
|
+
type: StringConstructor;
|
|
5
|
+
required: true;
|
|
6
|
+
};
|
|
7
|
+
highlightChunks: {
|
|
8
|
+
type: PropType<Array<[number, number]>>;
|
|
9
|
+
default: () => never[];
|
|
10
|
+
};
|
|
11
|
+
}>, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
12
|
+
iconClass: {
|
|
13
|
+
type: StringConstructor;
|
|
14
|
+
required: true;
|
|
15
|
+
};
|
|
16
|
+
highlightChunks: {
|
|
17
|
+
type: PropType<Array<[number, number]>>;
|
|
18
|
+
default: () => never[];
|
|
19
|
+
};
|
|
20
|
+
}>> & Readonly<{}>, {
|
|
21
|
+
highlightChunks: [number, number][];
|
|
22
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
23
|
+
export default _default;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { PropType } from "vue";
|
|
2
|
+
import type { InnerIconInfo } from "../render-icon-list";
|
|
3
|
+
declare const _default: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
4
|
+
iconInfo: {
|
|
5
|
+
type: PropType<InnerIconInfo>;
|
|
6
|
+
default: string;
|
|
7
|
+
};
|
|
8
|
+
iconClass: {
|
|
9
|
+
type: StringConstructor;
|
|
10
|
+
required: true;
|
|
11
|
+
};
|
|
12
|
+
copyHandler: PropType<(iconName: string) => void | Promise<void>>;
|
|
13
|
+
}>, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
14
|
+
iconInfo: {
|
|
15
|
+
type: PropType<InnerIconInfo>;
|
|
16
|
+
default: string;
|
|
17
|
+
};
|
|
18
|
+
iconClass: {
|
|
19
|
+
type: StringConstructor;
|
|
20
|
+
required: true;
|
|
21
|
+
};
|
|
22
|
+
copyHandler: PropType<(iconName: string) => void | Promise<void>>;
|
|
23
|
+
}>> & Readonly<{}>, {
|
|
24
|
+
iconInfo: InnerIconInfo;
|
|
25
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
26
|
+
export default _default;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type RenderIconList from './render-icon-list.vue';
|
|
2
|
+
import type { Component, ExtractPropTypes, PropType } from 'vue';
|
|
3
|
+
/** 图标相关信息 */
|
|
4
|
+
export type IconInfo = {
|
|
5
|
+
/** 文件地址(相对地址) */
|
|
6
|
+
filePath: string;
|
|
7
|
+
/** 图标的基础类名 */
|
|
8
|
+
baseClassName: string;
|
|
9
|
+
/** 图标的类名集合 */
|
|
10
|
+
classNames: string[];
|
|
11
|
+
/** 自定义渲染图标的函数,返回一个组件 */
|
|
12
|
+
renderIcon?: (iconName: string) => Component;
|
|
13
|
+
};
|
|
14
|
+
/** 前端页面时候的图标信息 (包含高亮信息) */
|
|
15
|
+
export interface InnerIconInfo extends IconInfo {
|
|
16
|
+
/** 匹配项map映射,
|
|
17
|
+
* key为图标class类名
|
|
18
|
+
* value为符合要求的索引位子集合,每项都包含开始索引、结束索引
|
|
19
|
+
*/
|
|
20
|
+
matchesMap?: Map<string, Array<[number, number]>>;
|
|
21
|
+
}
|
|
22
|
+
export declare const renderIconListProps: {
|
|
23
|
+
getIconsInfo: {
|
|
24
|
+
type: PropType<() => Promise<IconInfo[]> | IconInfo[]>;
|
|
25
|
+
required: boolean;
|
|
26
|
+
};
|
|
27
|
+
cssLinkFormat: {
|
|
28
|
+
type: PropType<(href: string) => string>;
|
|
29
|
+
default: (href: string) => string;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
export type RenderIconListProps = ExtractPropTypes<typeof renderIconListProps>;
|
|
33
|
+
export type RenderIconListInstance = InstanceType<typeof RenderIconList>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { IconInfo, InnerIconInfo } from "./render-icon-list";
|
|
2
|
+
declare var __VLS_1: {
|
|
3
|
+
iconsInfo: InnerIconInfo[] | undefined;
|
|
4
|
+
renderIcon: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
5
|
+
iconInfo: {
|
|
6
|
+
type: import("vue").PropType<InnerIconInfo>;
|
|
7
|
+
default: string;
|
|
8
|
+
};
|
|
9
|
+
iconClass: {
|
|
10
|
+
type: StringConstructor;
|
|
11
|
+
required: true;
|
|
12
|
+
};
|
|
13
|
+
copyHandler: import("vue").PropType<(iconName: string) => void | Promise<void>>;
|
|
14
|
+
}>, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
15
|
+
iconInfo: {
|
|
16
|
+
type: import("vue").PropType<InnerIconInfo>;
|
|
17
|
+
default: string;
|
|
18
|
+
};
|
|
19
|
+
iconClass: {
|
|
20
|
+
type: StringConstructor;
|
|
21
|
+
required: true;
|
|
22
|
+
};
|
|
23
|
+
copyHandler: import("vue").PropType<(iconName: string) => void | Promise<void>>;
|
|
24
|
+
}>> & Readonly<{}>, {
|
|
25
|
+
iconInfo: InnerIconInfo;
|
|
26
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
27
|
+
};
|
|
28
|
+
type __VLS_Slots = {} & {
|
|
29
|
+
default?: (props: typeof __VLS_1) => any;
|
|
30
|
+
};
|
|
31
|
+
declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
32
|
+
getIconsInfo: {
|
|
33
|
+
type: import("vue").PropType<() => Promise<IconInfo[]> | IconInfo[]>;
|
|
34
|
+
required: boolean;
|
|
35
|
+
};
|
|
36
|
+
cssLinkFormat: {
|
|
37
|
+
type: import("vue").PropType<(href: string) => string>;
|
|
38
|
+
default: (href: string) => string;
|
|
39
|
+
};
|
|
40
|
+
}>, {
|
|
41
|
+
onSearch: (val: string) => void;
|
|
42
|
+
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
43
|
+
getIconsInfo: {
|
|
44
|
+
type: import("vue").PropType<() => Promise<IconInfo[]> | IconInfo[]>;
|
|
45
|
+
required: boolean;
|
|
46
|
+
};
|
|
47
|
+
cssLinkFormat: {
|
|
48
|
+
type: import("vue").PropType<(href: string) => string>;
|
|
49
|
+
default: (href: string) => string;
|
|
50
|
+
};
|
|
51
|
+
}>> & Readonly<{}>, {
|
|
52
|
+
cssLinkFormat: (href: string) => string;
|
|
53
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
54
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
55
|
+
declare const _default: typeof __VLS_export;
|
|
56
|
+
export default _default;
|
|
57
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
58
|
+
new (): {
|
|
59
|
+
$slots: S;
|
|
60
|
+
};
|
|
61
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "iconfont-preview-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"author": "wangzl",
|
|
5
|
+
"description": "字体图标cli",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"iconfont",
|
|
8
|
+
"字体图标",
|
|
9
|
+
"字体图标预览",
|
|
10
|
+
"字体图标vite插件"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/phone-number/iconfont-preview-cli.git"
|
|
16
|
+
},
|
|
17
|
+
"bin": {
|
|
18
|
+
"iconfont-preview-cli": "./server/bin.mjs"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"import": "./server/index.mjs",
|
|
23
|
+
"require": "./server/index.cjs"
|
|
24
|
+
},
|
|
25
|
+
"./components": {
|
|
26
|
+
"import": "./components/index.mjs",
|
|
27
|
+
"require": "./components/index.cjs"
|
|
28
|
+
},
|
|
29
|
+
"./server": {
|
|
30
|
+
"import": "./server/index.mjs",
|
|
31
|
+
"require": "./server/index.cjs"
|
|
32
|
+
},
|
|
33
|
+
"./*": "./*"
|
|
34
|
+
},
|
|
35
|
+
"typesVersions": {
|
|
36
|
+
"*": {
|
|
37
|
+
"components": [
|
|
38
|
+
"./components/types/index.d.ts"
|
|
39
|
+
],
|
|
40
|
+
"server": [
|
|
41
|
+
"./server/index.d.ts"
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"rimraf": "^3.0.2"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=18"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"axios": "^1.13.2",
|
|
53
|
+
"clipboard-copy": "^4.0.1",
|
|
54
|
+
"element-plus": "^2.11.8",
|
|
55
|
+
"fuse.js": "^7.1.0",
|
|
56
|
+
"vue": "^3.5.24",
|
|
57
|
+
"chalk": "^4.1.2",
|
|
58
|
+
"commander": "^11.0.0",
|
|
59
|
+
"css": "^3.0.0",
|
|
60
|
+
"koa": "^3.1.1",
|
|
61
|
+
"koa-router": "^14.0.0",
|
|
62
|
+
"koa-static": "^5.0.0",
|
|
63
|
+
"portfinder": "^1.0.38"
|
|
64
|
+
},
|
|
65
|
+
"peerDependencies": {}
|
|
66
|
+
}
|
package/server/bin.cjs
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const commander = require('commander');
|
|
5
|
+
const Koa = require('koa');
|
|
6
|
+
const serve = require('koa-static');
|
|
7
|
+
const Router = require('koa-router');
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
const node_path = require('node:path');
|
|
10
|
+
const css = require('css');
|
|
11
|
+
const promises = require('node:fs/promises');
|
|
12
|
+
const os = require('node:os');
|
|
13
|
+
const node_url = require('node:url');
|
|
14
|
+
const portfinder = require('portfinder');
|
|
15
|
+
const node_fs = require('node:fs');
|
|
16
|
+
|
|
17
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
18
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
19
|
+
|
|
20
|
+
const Koa__default = /*#__PURE__*/_interopDefaultCompat(Koa);
|
|
21
|
+
const serve__default = /*#__PURE__*/_interopDefaultCompat(serve);
|
|
22
|
+
const Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
|
|
23
|
+
const chalk__default = /*#__PURE__*/_interopDefaultCompat(chalk);
|
|
24
|
+
const css__default = /*#__PURE__*/_interopDefaultCompat(css);
|
|
25
|
+
const os__default = /*#__PURE__*/_interopDefaultCompat(os);
|
|
26
|
+
const portfinder__default = /*#__PURE__*/_interopDefaultCompat(portfinder);
|
|
27
|
+
|
|
28
|
+
async function readCssFilesAsync(dirPath) {
|
|
29
|
+
const cssFiles = [];
|
|
30
|
+
async function traverseDirectory(currentPath) {
|
|
31
|
+
const items = await promises.readdir(currentPath);
|
|
32
|
+
for (const item of items) {
|
|
33
|
+
const fullPath = node_path.resolve(currentPath, item);
|
|
34
|
+
let fileStat;
|
|
35
|
+
try {
|
|
36
|
+
fileStat = await promises.stat(fullPath);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error(`\u65E0\u6CD5\u8BBF\u95EE\u6587\u4EF6: ${fullPath}`, error);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (fileStat.isDirectory()) {
|
|
42
|
+
await traverseDirectory(fullPath);
|
|
43
|
+
} else {
|
|
44
|
+
const ext = node_path.extname(fullPath).toLowerCase();
|
|
45
|
+
if (ext === ".css") {
|
|
46
|
+
cssFiles.push(fullPath);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
await traverseDirectory(dirPath);
|
|
52
|
+
return cssFiles;
|
|
53
|
+
}
|
|
54
|
+
async function extractClassNames(cssFilePath, basePath) {
|
|
55
|
+
try {
|
|
56
|
+
const cssContent = await promises.readFile(cssFilePath, "utf8");
|
|
57
|
+
const ast = css__default.parse(cssContent);
|
|
58
|
+
let baseClassName = "";
|
|
59
|
+
const classNames = /* @__PURE__ */ new Set();
|
|
60
|
+
const fontFamilyRule = ast.stylesheet?.rules.find((rule) => rule.type === "font-face");
|
|
61
|
+
const fontFamilyDeclaration = fontFamilyRule?.declarations?.find((declaration) => declaration.property === "font-family");
|
|
62
|
+
const fontFamily = fontFamilyDeclaration?.value?.replaceAll(/["']/g, "");
|
|
63
|
+
const rules = ast.stylesheet?.rules || [];
|
|
64
|
+
for (const rule of rules) {
|
|
65
|
+
if (rule.type === "rule") {
|
|
66
|
+
const declarations = rule.declarations || [];
|
|
67
|
+
const selectors = rule.selectors || [];
|
|
68
|
+
if (!baseClassName && fontFamily && declarations.some((declaration) => declaration.value?.includes(fontFamily))) {
|
|
69
|
+
baseClassName = selectors.join(" ").replaceAll(".", "");
|
|
70
|
+
}
|
|
71
|
+
if (declarations.some((declaration) => declaration.property === "content")) {
|
|
72
|
+
for (const selector of selectors) {
|
|
73
|
+
const classSelectorMatch = selector.match(/^\.([\w-]+)(?::before)?$/);
|
|
74
|
+
if (classSelectorMatch) {
|
|
75
|
+
const className = classSelectorMatch[1];
|
|
76
|
+
classNames.add(className);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { filePath: cssFilePath.replace(basePath, ""), baseClassName, classNames: [...classNames] };
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error("\u63D0\u53D6\u7C7B\u540D\u65F6\u51FA\u9519:", error.message);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async function getCssInfo(dirPath) {
|
|
88
|
+
const filePaths = await readCssFilesAsync(dirPath);
|
|
89
|
+
const data = [];
|
|
90
|
+
await Promise.all(filePaths.map(async (filePath) => {
|
|
91
|
+
const res = await extractClassNames(filePath, dirPath);
|
|
92
|
+
res && data.push(res);
|
|
93
|
+
}));
|
|
94
|
+
return data;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function getLocalIPs() {
|
|
98
|
+
const interfaces = os__default.networkInterfaces();
|
|
99
|
+
const ips = [];
|
|
100
|
+
for (const name of Object.keys(interfaces)) {
|
|
101
|
+
const nets = interfaces[name];
|
|
102
|
+
if (!nets) continue;
|
|
103
|
+
for (const net of nets) {
|
|
104
|
+
if (net.family !== "IPv4" || net.internal) continue;
|
|
105
|
+
ips.push(net.address);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return ips;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const __dirname$1 = node_path.dirname(node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('bin.cjs', document.baseURI).href))));
|
|
112
|
+
const runtimeProjRoot = process.env.NODE_ENV === "dev" ? node_path.resolve(__dirname$1, "..", "..", "..") : node_path.resolve(__dirname$1, "..");
|
|
113
|
+
node_path.resolve(__dirname$1, "..", "..", "..");
|
|
114
|
+
const buildOutput = process.env.NODE_ENV === "dev" ? node_path.resolve(runtimeProjRoot, "dist") : runtimeProjRoot;
|
|
115
|
+
const appOutput = node_path.resolve(buildOutput, "app");
|
|
116
|
+
|
|
117
|
+
const findAvailablePort = async (startPort) => {
|
|
118
|
+
return await portfinder__default.getPortPromise({ port: startPort });
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const getVersion = () => {
|
|
122
|
+
const packageJsonContent = node_fs.readFileSync(node_path.resolve(runtimeProjRoot, "package.json"), "utf8");
|
|
123
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
124
|
+
const { version = "" } = packageJson;
|
|
125
|
+
return version ? `v${version}` : version;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const logTitle = (start) => {
|
|
129
|
+
const readyIn = start ? ` ${chalk__default.gray("ready in")} ${chalk__default.bold(Date.now() - start)} ms` : "";
|
|
130
|
+
console.log(`
|
|
131
|
+
${chalk__default.green.bold("iconfont-preview-cli")} ${chalk__default.green(getVersion())}${readyIn}
|
|
132
|
+
`);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const fontServer = async (options) => {
|
|
136
|
+
const start = Date.now();
|
|
137
|
+
const fontDir = node_path.resolve(process.cwd(), options.dir || "");
|
|
138
|
+
const app = new Koa__default();
|
|
139
|
+
const router = new Router__default();
|
|
140
|
+
app.use(router.routes());
|
|
141
|
+
app.use(serve__default(fontDir));
|
|
142
|
+
app.use(serve__default(appOutput));
|
|
143
|
+
router.get("/api/iconsInfo", async (context) => {
|
|
144
|
+
const data = await getCssInfo(fontDir);
|
|
145
|
+
context.type = "application/json";
|
|
146
|
+
context.body = {
|
|
147
|
+
data,
|
|
148
|
+
dir: options.dir || "",
|
|
149
|
+
absDir: fontDir
|
|
150
|
+
};
|
|
151
|
+
});
|
|
152
|
+
const port = await findAvailablePort(Number(options.port));
|
|
153
|
+
app.listen(port, () => {
|
|
154
|
+
logTitle(start);
|
|
155
|
+
console.log(` ${chalk__default.green("\u2192")} Local: ${chalk__default.cyan(`http://localhost:${port}`)}`);
|
|
156
|
+
getLocalIPs().forEach((ip) => {
|
|
157
|
+
console.log(` ${chalk__default.green("\u2192")} Network: ${chalk__default.cyan(`http://${ip}:${port}`)}`);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
commander.program.option("-p, --port <port>", "\u670D\u52A1\u7AEF\u53E3", "3000").option("-d, --dir <dir>", "\u5B57\u4F53\u56FE\u6807\u6587\u4EF6\u5939\u8DEF\u5F84").action(fontServer);
|
|
163
|
+
commander.program.parse(process.argv);
|
package/server/bin.mjs
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { program } from 'commander';
|
|
3
|
+
import Koa from 'koa';
|
|
4
|
+
import serve from 'koa-static';
|
|
5
|
+
import Router from 'koa-router';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { resolve, extname, dirname } from 'node:path';
|
|
8
|
+
import css from 'css';
|
|
9
|
+
import { readFile, readdir, stat } from 'node:fs/promises';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import portfinder from 'portfinder';
|
|
13
|
+
import { readFileSync } from 'node:fs';
|
|
14
|
+
|
|
15
|
+
async function readCssFilesAsync(dirPath) {
|
|
16
|
+
const cssFiles = [];
|
|
17
|
+
async function traverseDirectory(currentPath) {
|
|
18
|
+
const items = await readdir(currentPath);
|
|
19
|
+
for (const item of items) {
|
|
20
|
+
const fullPath = resolve(currentPath, item);
|
|
21
|
+
let fileStat;
|
|
22
|
+
try {
|
|
23
|
+
fileStat = await stat(fullPath);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error(`\u65E0\u6CD5\u8BBF\u95EE\u6587\u4EF6: ${fullPath}`, error);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (fileStat.isDirectory()) {
|
|
29
|
+
await traverseDirectory(fullPath);
|
|
30
|
+
} else {
|
|
31
|
+
const ext = extname(fullPath).toLowerCase();
|
|
32
|
+
if (ext === ".css") {
|
|
33
|
+
cssFiles.push(fullPath);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
await traverseDirectory(dirPath);
|
|
39
|
+
return cssFiles;
|
|
40
|
+
}
|
|
41
|
+
async function extractClassNames(cssFilePath, basePath) {
|
|
42
|
+
try {
|
|
43
|
+
const cssContent = await readFile(cssFilePath, "utf8");
|
|
44
|
+
const ast = css.parse(cssContent);
|
|
45
|
+
let baseClassName = "";
|
|
46
|
+
const classNames = /* @__PURE__ */ new Set();
|
|
47
|
+
const fontFamilyRule = ast.stylesheet?.rules.find((rule) => rule.type === "font-face");
|
|
48
|
+
const fontFamilyDeclaration = fontFamilyRule?.declarations?.find((declaration) => declaration.property === "font-family");
|
|
49
|
+
const fontFamily = fontFamilyDeclaration?.value?.replaceAll(/["']/g, "");
|
|
50
|
+
const rules = ast.stylesheet?.rules || [];
|
|
51
|
+
for (const rule of rules) {
|
|
52
|
+
if (rule.type === "rule") {
|
|
53
|
+
const declarations = rule.declarations || [];
|
|
54
|
+
const selectors = rule.selectors || [];
|
|
55
|
+
if (!baseClassName && fontFamily && declarations.some((declaration) => declaration.value?.includes(fontFamily))) {
|
|
56
|
+
baseClassName = selectors.join(" ").replaceAll(".", "");
|
|
57
|
+
}
|
|
58
|
+
if (declarations.some((declaration) => declaration.property === "content")) {
|
|
59
|
+
for (const selector of selectors) {
|
|
60
|
+
const classSelectorMatch = selector.match(/^\.([\w-]+)(?::before)?$/);
|
|
61
|
+
if (classSelectorMatch) {
|
|
62
|
+
const className = classSelectorMatch[1];
|
|
63
|
+
classNames.add(className);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return { filePath: cssFilePath.replace(basePath, ""), baseClassName, classNames: [...classNames] };
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error("\u63D0\u53D6\u7C7B\u540D\u65F6\u51FA\u9519:", error.message);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async function getCssInfo(dirPath) {
|
|
75
|
+
const filePaths = await readCssFilesAsync(dirPath);
|
|
76
|
+
const data = [];
|
|
77
|
+
await Promise.all(filePaths.map(async (filePath) => {
|
|
78
|
+
const res = await extractClassNames(filePath, dirPath);
|
|
79
|
+
res && data.push(res);
|
|
80
|
+
}));
|
|
81
|
+
return data;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getLocalIPs() {
|
|
85
|
+
const interfaces = os.networkInterfaces();
|
|
86
|
+
const ips = [];
|
|
87
|
+
for (const name of Object.keys(interfaces)) {
|
|
88
|
+
const nets = interfaces[name];
|
|
89
|
+
if (!nets) continue;
|
|
90
|
+
for (const net of nets) {
|
|
91
|
+
if (net.family !== "IPv4" || net.internal) continue;
|
|
92
|
+
ips.push(net.address);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return ips;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const __dirname$1 = dirname(fileURLToPath(import.meta.url));
|
|
99
|
+
const runtimeProjRoot = process.env.NODE_ENV === "dev" ? resolve(__dirname$1, "..", "..", "..") : resolve(__dirname$1, "..");
|
|
100
|
+
resolve(__dirname$1, "..", "..", "..");
|
|
101
|
+
const buildOutput = process.env.NODE_ENV === "dev" ? resolve(runtimeProjRoot, "dist") : runtimeProjRoot;
|
|
102
|
+
const appOutput = resolve(buildOutput, "app");
|
|
103
|
+
|
|
104
|
+
const findAvailablePort = async (startPort) => {
|
|
105
|
+
return await portfinder.getPortPromise({ port: startPort });
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const getVersion = () => {
|
|
109
|
+
const packageJsonContent = readFileSync(resolve(runtimeProjRoot, "package.json"), "utf8");
|
|
110
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
111
|
+
const { version = "" } = packageJson;
|
|
112
|
+
return version ? `v${version}` : version;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const logTitle = (start) => {
|
|
116
|
+
const readyIn = start ? ` ${chalk.gray("ready in")} ${chalk.bold(Date.now() - start)} ms` : "";
|
|
117
|
+
console.log(`
|
|
118
|
+
${chalk.green.bold("iconfont-preview-cli")} ${chalk.green(getVersion())}${readyIn}
|
|
119
|
+
`);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const fontServer = async (options) => {
|
|
123
|
+
const start = Date.now();
|
|
124
|
+
const fontDir = resolve(process.cwd(), options.dir || "");
|
|
125
|
+
const app = new Koa();
|
|
126
|
+
const router = new Router();
|
|
127
|
+
app.use(router.routes());
|
|
128
|
+
app.use(serve(fontDir));
|
|
129
|
+
app.use(serve(appOutput));
|
|
130
|
+
router.get("/api/iconsInfo", async (context) => {
|
|
131
|
+
const data = await getCssInfo(fontDir);
|
|
132
|
+
context.type = "application/json";
|
|
133
|
+
context.body = {
|
|
134
|
+
data,
|
|
135
|
+
dir: options.dir || "",
|
|
136
|
+
absDir: fontDir
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
const port = await findAvailablePort(Number(options.port));
|
|
140
|
+
app.listen(port, () => {
|
|
141
|
+
logTitle(start);
|
|
142
|
+
console.log(` ${chalk.green("\u2192")} Local: ${chalk.cyan(`http://localhost:${port}`)}`);
|
|
143
|
+
getLocalIPs().forEach((ip) => {
|
|
144
|
+
console.log(` ${chalk.green("\u2192")} Network: ${chalk.cyan(`http://${ip}:${port}`)}`);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
program.option("-p, --port <port>", "\u670D\u52A1\u7AEF\u53E3", "3000").option("-d, --dir <dir>", "\u5B57\u4F53\u56FE\u6807\u6587\u4EF6\u5939\u8DEF\u5F84").action(fontServer);
|
|
150
|
+
program.parse(process.argv);
|
package/server/index.cjs
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const css = require('css');
|
|
5
|
+
const promises = require('node:fs/promises');
|
|
6
|
+
const node_path = require('node:path');
|
|
7
|
+
require('node:os');
|
|
8
|
+
const node_url = require('node:url');
|
|
9
|
+
require('portfinder');
|
|
10
|
+
const node_fs = require('node:fs');
|
|
11
|
+
|
|
12
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
13
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
14
|
+
|
|
15
|
+
const chalk__default = /*#__PURE__*/_interopDefaultCompat(chalk);
|
|
16
|
+
const css__default = /*#__PURE__*/_interopDefaultCompat(css);
|
|
17
|
+
|
|
18
|
+
async function readCssFilesAsync(dirPath) {
|
|
19
|
+
const cssFiles = [];
|
|
20
|
+
async function traverseDirectory(currentPath) {
|
|
21
|
+
const items = await promises.readdir(currentPath);
|
|
22
|
+
for (const item of items) {
|
|
23
|
+
const fullPath = node_path.resolve(currentPath, item);
|
|
24
|
+
let fileStat;
|
|
25
|
+
try {
|
|
26
|
+
fileStat = await promises.stat(fullPath);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error(`\u65E0\u6CD5\u8BBF\u95EE\u6587\u4EF6: ${fullPath}`, error);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (fileStat.isDirectory()) {
|
|
32
|
+
await traverseDirectory(fullPath);
|
|
33
|
+
} else {
|
|
34
|
+
const ext = node_path.extname(fullPath).toLowerCase();
|
|
35
|
+
if (ext === ".css") {
|
|
36
|
+
cssFiles.push(fullPath);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
await traverseDirectory(dirPath);
|
|
42
|
+
return cssFiles;
|
|
43
|
+
}
|
|
44
|
+
async function extractClassNames(cssFilePath, basePath) {
|
|
45
|
+
try {
|
|
46
|
+
const cssContent = await promises.readFile(cssFilePath, "utf8");
|
|
47
|
+
const ast = css__default.parse(cssContent);
|
|
48
|
+
let baseClassName = "";
|
|
49
|
+
const classNames = /* @__PURE__ */ new Set();
|
|
50
|
+
const fontFamilyRule = ast.stylesheet?.rules.find((rule) => rule.type === "font-face");
|
|
51
|
+
const fontFamilyDeclaration = fontFamilyRule?.declarations?.find((declaration) => declaration.property === "font-family");
|
|
52
|
+
const fontFamily = fontFamilyDeclaration?.value?.replaceAll(/["']/g, "");
|
|
53
|
+
const rules = ast.stylesheet?.rules || [];
|
|
54
|
+
for (const rule of rules) {
|
|
55
|
+
if (rule.type === "rule") {
|
|
56
|
+
const declarations = rule.declarations || [];
|
|
57
|
+
const selectors = rule.selectors || [];
|
|
58
|
+
if (!baseClassName && fontFamily && declarations.some((declaration) => declaration.value?.includes(fontFamily))) {
|
|
59
|
+
baseClassName = selectors.join(" ").replaceAll(".", "");
|
|
60
|
+
}
|
|
61
|
+
if (declarations.some((declaration) => declaration.property === "content")) {
|
|
62
|
+
for (const selector of selectors) {
|
|
63
|
+
const classSelectorMatch = selector.match(/^\.([\w-]+)(?::before)?$/);
|
|
64
|
+
if (classSelectorMatch) {
|
|
65
|
+
const className = classSelectorMatch[1];
|
|
66
|
+
classNames.add(className);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { filePath: cssFilePath.replace(basePath, ""), baseClassName, classNames: [...classNames] };
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error("\u63D0\u53D6\u7C7B\u540D\u65F6\u51FA\u9519:", error.message);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async function getCssInfo(dirPath) {
|
|
78
|
+
const filePaths = await readCssFilesAsync(dirPath);
|
|
79
|
+
const data = [];
|
|
80
|
+
await Promise.all(filePaths.map(async (filePath) => {
|
|
81
|
+
const res = await extractClassNames(filePath, dirPath);
|
|
82
|
+
res && data.push(res);
|
|
83
|
+
}));
|
|
84
|
+
return data;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const __dirname$1 = node_path.dirname(node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
|
|
88
|
+
const runtimeProjRoot = process.env.NODE_ENV === "dev" ? node_path.resolve(__dirname$1, "..", "..", "..") : node_path.resolve(__dirname$1, "..");
|
|
89
|
+
node_path.resolve(__dirname$1, "..", "..", "..");
|
|
90
|
+
const buildOutput = process.env.NODE_ENV === "dev" ? node_path.resolve(runtimeProjRoot, "dist") : runtimeProjRoot;
|
|
91
|
+
node_path.resolve(buildOutput, "app");
|
|
92
|
+
|
|
93
|
+
const getVersion = () => {
|
|
94
|
+
const packageJsonContent = node_fs.readFileSync(node_path.resolve(runtimeProjRoot, "package.json"), "utf8");
|
|
95
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
96
|
+
const { version = "" } = packageJson;
|
|
97
|
+
return version ? `v${version}` : version;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const logTitle = (start) => {
|
|
101
|
+
const readyIn = "";
|
|
102
|
+
console.log(`
|
|
103
|
+
${chalk__default.green.bold("iconfont-preview-cli")} ${chalk__default.green(getVersion())}${readyIn}
|
|
104
|
+
`);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const iconfontServer = async (options) => {
|
|
108
|
+
const pluginOption = {
|
|
109
|
+
name: "iconfont-server",
|
|
110
|
+
enforce: "pre",
|
|
111
|
+
apply: "serve",
|
|
112
|
+
configureServer(server) {
|
|
113
|
+
const { urlPrefix = "/iconfont-proxy", iconDir = "" } = options;
|
|
114
|
+
logTitle();
|
|
115
|
+
console.log(` \u5B57\u4F53\u56FE\u6807\u670D\u52A1\u4E2D\u95F4\u4EF6\u5DF2\u542F\u52A8\uFF0C${chalk__default.cyan(urlPrefix)}\u5F00\u5934\u7684\u8BF7\u6C42\u5C06\u88AB\u4EE3\u7406\u5230\u5B57\u4F53\u56FE\u6807\u670D\u52A1`);
|
|
116
|
+
server.middlewares.use(async (req, res, next) => {
|
|
117
|
+
const codeServiceReg = new RegExp(`^${urlPrefix}`);
|
|
118
|
+
if (req.url && codeServiceReg.test(req.url)) {
|
|
119
|
+
if (req.url === `${urlPrefix}/api/iconsInfo`) {
|
|
120
|
+
const fontDir = node_path.resolve(process.cwd(), iconDir);
|
|
121
|
+
const data = await getCssInfo(fontDir);
|
|
122
|
+
res.setHeader("Content-Type", "application/json");
|
|
123
|
+
res.end(JSON.stringify({
|
|
124
|
+
data,
|
|
125
|
+
dir: options.iconDir || "",
|
|
126
|
+
absDir: fontDir
|
|
127
|
+
}));
|
|
128
|
+
} else {
|
|
129
|
+
res.statusCode = 404;
|
|
130
|
+
res.end(req.url);
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
next();
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
return pluginOption;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
exports.iconfontServer = iconfontServer;
|