@maple-design/vite-plugins-vue 0.0.0-stage → 1.1.1

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/lib/index.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ interface MarkdownOptions {
4
+ style?: string;
5
+ }
6
+ declare function markdown(options: MarkdownOptions): Plugin;
7
+
8
+ interface HtmlOptions {
9
+ data?: Record<string, string | string[] | Record<string, any>>;
10
+ }
11
+ declare function html(options: HtmlOptions): Plugin;
12
+
13
+ interface InlineCssOptions {
14
+ cssFile: string;
15
+ jsFile: string;
16
+ onEnd?(): void;
17
+ }
18
+ declare function inlineCss(options: InlineCssOptions): Plugin;
19
+
20
+ interface CopyPath {
21
+ from: string;
22
+ to: string;
23
+ type: 'folder' | 'file';
24
+ }
25
+ interface CopyOptions {
26
+ paths: CopyPath[];
27
+ }
28
+ declare function copy(options: CopyOptions): Plugin;
29
+
30
+ export { CopyOptions, CopyPath, HtmlOptions, InlineCssOptions, MarkdownOptions, copy, html, inlineCss, markdown };
package/lib/index.js ADDED
@@ -0,0 +1,451 @@
1
+ // src/markdown.ts
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { basename, dirname, resolve } from "node:path";
4
+ import { kebabCase } from "@maple-design/shared-vue";
5
+ import hljs from "highlight.js";
6
+ import markdownIt from "markdown-it";
7
+ import { pinyin } from "pinyin-pro";
8
+ var includeChinese = (value) => /[\u4e00-\u9fa5]/.test(value);
9
+ function transformHash(hash) {
10
+ return (includeChinese(hash) ? pinyin(hash, { toneType: "num" }) : hash).replaceAll(" ", "");
11
+ }
12
+ function htmlWrapper(html2) {
13
+ const disclosures = [];
14
+ const ranges = [];
15
+ let depth = 0;
16
+ let start = 0;
17
+ for (const tag of html2.matchAll(/<\/?details\b[^>]*>/gi)) {
18
+ if (tag[0].startsWith("</")) {
19
+ if (depth && --depth === 0) {
20
+ ranges.push({ start, end: tag.index + tag[0].length });
21
+ }
22
+ } else {
23
+ if (depth++ === 0) {
24
+ start = tag.index;
25
+ }
26
+ }
27
+ }
28
+ ranges.forEach((range) => disclosures.push(html2.slice(range.start, range.end)));
29
+ for (let index = ranges.length - 1; index >= 0; index--) {
30
+ const range = ranges[index];
31
+ html2 = html2.slice(0, range.start) + `MAPLE_DISCLOSURE_${index}` + html2.slice(range.end);
32
+ }
33
+ const matches = html2.matchAll(/<h3>(.*?)<\/h3>/g);
34
+ const hGroup = html2.replace(/<h3>/g, () => {
35
+ const hash = transformHash(matches.next().value[1]);
36
+ return `:::<h3 id="${hash}"><router-link to="#${hash}">#</router-link>`;
37
+ }).replace(/<h2/g, ":::<h2").split(":::");
38
+ const cardGroup = hGroup.map((fragment) => {
39
+ if (!fragment.includes("<h3")) {
40
+ return fragment;
41
+ }
42
+ const demoClass = fragment.includes("maple-component-preview") ? " card--demo" : "";
43
+ return `<div class="card${demoClass}">${fragment}</div>`;
44
+ }).join("");
45
+ return cardGroup.replace(/MAPLE_DISCLOSURE_(\d+)/g, (_, index) => disclosures[Number(index)]).replace(/<code(?=[\s>])/g, "<code v-pre");
46
+ }
47
+ function extractComponents(source) {
48
+ const componentRE = /import ([A-Z]\w*) from ['"][^'"]+\.vue['"]/;
49
+ const importRE = /import .+ from ['"].+['"]/g;
50
+ const vueRE = /```vue((.|\r|\n)*?)```/g;
51
+ const imports = [];
52
+ const components = [];
53
+ source = source.replace(vueRE, (block, p1) => {
54
+ const partImports = p1.match(importRE);
55
+ if (!partImports?.length || partImports.some((importer) => !componentRE.test(importer))) {
56
+ return block;
57
+ }
58
+ const partComponents = partImports?.map((importer) => {
59
+ importer = importer.replace(/(\n|\r)/g, "");
60
+ const component = importer.replace(componentRE, "$1");
61
+ !imports.includes(importer) && imports.push(importer);
62
+ !components.includes(component) && components.push(component);
63
+ return `<${kebabCase(component)} />`;
64
+ });
65
+ return partComponents ? `<div class="maple-component-preview">${partComponents.join("\n")}</div>` : "";
66
+ });
67
+ return {
68
+ imports,
69
+ components,
70
+ source
71
+ };
72
+ }
73
+ function renderDemo(demo) {
74
+ const encodedSource = encodeURIComponent(demo.source);
75
+ const encodedHighlight = encodeURIComponent(highlight(demo.source, "html"));
76
+ const pageClass = demo.page ? " maple-component-preview--page" : "";
77
+ const mobileId = demo.mobileId ? ` data-mobile-demo="${encodeURIComponent(demo.mobileId)}"` : "";
78
+ return `<div class="maple-component-preview${pageClass}"${mobileId}>
79
+ <mp-site-demo-preview source-name="${basename(demo.path)}" source="${encodedSource}" highlight-code="${encodedHighlight}">
80
+ <${demo.component}></${demo.component}>
81
+ </mp-site-demo-preview>
82
+ </div>`;
83
+ }
84
+ var demoPlaceholder = (index) => `MAPLE_DOC_DEMO_${index}`;
85
+ function discoverGroupedDemos(indexPath, language, onDependency) {
86
+ const indexSource = readFileSync(indexPath, "utf8");
87
+ const imports = /* @__PURE__ */ new Map();
88
+ for (const match of indexSource.matchAll(/import\s+(\w+)\s+from\s+['"](\.\/demos\/[^'"]+\.vue)['"]/g)) {
89
+ imports.set(match[1], match[2]);
90
+ }
91
+ if (!imports.size) {
92
+ return [];
93
+ }
94
+ let messages = {};
95
+ const messagesRequest = indexSource.match(/import\s+messages\s+from\s+['"](.+?\.json)['"]/)?.[1];
96
+ if (messagesRequest) {
97
+ const messagesPath = resolve(dirname(indexPath), messagesRequest);
98
+ onDependency?.(messagesPath);
99
+ const catalog = JSON.parse(readFileSync(messagesPath, "utf8"));
100
+ messages = catalog[language] ?? {};
101
+ }
102
+ return [...indexSource.matchAll(/<section\b[^>]*data-demo=['"]([^'"]+)['"][^>]*>[\s\S]*?<([A-Z]\w*)\b/g)].map((match) => {
103
+ const request = imports.get(match[2]);
104
+ if (!request) {
105
+ return void 0;
106
+ }
107
+ const name = request.slice(request.lastIndexOf("/") + 1, -4);
108
+ return {
109
+ id: match[1],
110
+ request: `../example/${request.slice(2)}`,
111
+ path: resolve(dirname(indexPath), request),
112
+ title: messages[match[1]] ?? name.replaceAll("-", " ")
113
+ };
114
+ }).filter((demo) => !!demo);
115
+ }
116
+ function replaceGroupedExamples(source, demos, language) {
117
+ const exampleHeading = /^## (?:案例|Examples?)[ \t]*$/m;
118
+ const headingMatch = exampleHeading.exec(source);
119
+ if (!headingMatch) {
120
+ const heading = language === "en-US" ? "## Examples" : "## \u6848\u4F8B";
121
+ const generated = demos.map((demo, index) => `### ${demo.title}
122
+
123
+ ${demoPlaceholder(index)}`).join("\n\n");
124
+ const section = `${heading}
125
+
126
+ ${generated}`;
127
+ const firstSection = /^## .+$/m;
128
+ return firstSection.test(source) ? source.replace(firstSection, (existing) => `${section}
129
+
130
+ ${existing}`) : `${source.trim()}
131
+
132
+ ${section}
133
+ `;
134
+ }
135
+ const bodyStart = headingMatch.index + headingMatch[0].length;
136
+ const nextSectionOffset = source.slice(bodyStart).search(/^## .+$/m);
137
+ const bodyEnd = nextSectionOffset < 0 ? source.length : bodyStart + nextSectionOffset;
138
+ const body = source.slice(bodyStart, bodyEnd);
139
+ const headings = [...body.matchAll(/^### .+$/gm)];
140
+ if (headings.length < demos.length) {
141
+ const generated = demos.map((demo, index) => `
142
+
143
+ ### ${demo.title}
144
+
145
+ ${demoPlaceholder(index)}`).join("");
146
+ return `${source.slice(0, bodyStart)}${generated}
147
+
148
+ ${source.slice(bodyEnd)}`;
149
+ }
150
+ const segments = [];
151
+ let cursor = 0;
152
+ demos.forEach((_, index) => {
153
+ const start = headings[index].index;
154
+ const end = headings[index + 1]?.index ?? body.length;
155
+ segments.push(body.slice(cursor, start));
156
+ const segment = body.slice(start, end).replace(/```[^\n]*\n[\s\S]*?\n```[ \t]*/g, "").trimEnd();
157
+ segments.push(`${segment}
158
+
159
+ ${demoPlaceholder(index)}
160
+
161
+ `);
162
+ cursor = end;
163
+ });
164
+ segments.push(body.slice(cursor));
165
+ return `${source.slice(0, bodyStart)}${segments.join("")}${source.slice(bodyEnd)}`;
166
+ }
167
+ function expandDemoFiles(source, id, onDemo) {
168
+ let index = 0;
169
+ const demos = [];
170
+ const addDemo = (request) => {
171
+ const demoRequest = request.trim();
172
+ const demoPath = resolve(dirname(id), demoRequest);
173
+ onDemo?.(demoPath);
174
+ const demoSource = readFileSync(demoPath, "utf8").trim();
175
+ const component = `MapleDemo${index++}`;
176
+ const demo = { component, request: demoRequest, path: demoPath, source: demoSource };
177
+ demos.push(demo);
178
+ return demoPlaceholder(demos.length - 1);
179
+ };
180
+ const offsets = [0];
181
+ for (const line of source.split("\n")) {
182
+ offsets.push(offsets[offsets.length - 1] + line.length + 1);
183
+ }
184
+ const edits = [];
185
+ const demoTag = /<demo\b[^>]*\bsrc\s*=\s*(['"])([^'"]+)\1[^>]*(?:\/>|>\s*<\/demo>)/g;
186
+ for (const token of markdownIt({ html: true }).parse(source, {})) {
187
+ if (!token.map) {
188
+ continue;
189
+ }
190
+ const start = offsets[token.map[0]];
191
+ const end = Math.min(offsets[token.map[1]], source.length);
192
+ if (token.type === "fence" && token.info.trim() === "demo") {
193
+ edits.push({ start, end, content: `${addDemo(token.content)}
194
+ ` });
195
+ } else if ((token.type === "html_block" || token.type === "inline") && token.content.replace(demoTag, "").trim() === "") {
196
+ const original = source.slice(start, end);
197
+ const content = original.replace(demoTag, (_, _quote, request) => `
198
+
199
+ ${addDemo(request)}
200
+
201
+ `);
202
+ if (content !== original) {
203
+ edits.push({ start, end, content });
204
+ }
205
+ }
206
+ }
207
+ for (const edit of edits.reverse()) {
208
+ source = source.slice(0, edit.start) + edit.content + source.slice(edit.end);
209
+ }
210
+ if (!demos.length && /\/src\/[^/]+\/docs\/[^/]+\.md$/.test(id.replaceAll("\\", "/"))) {
211
+ const exampleIndexPath = resolve(dirname(id), "../example/index.vue");
212
+ const language = id.endsWith("/en-US.md") ? "en-US" : "zh-CN";
213
+ if (existsSync(exampleIndexPath)) {
214
+ onDemo?.(exampleIndexPath);
215
+ const grouped = discoverGroupedDemos(exampleIndexPath, language, onDemo);
216
+ if (grouped.length) {
217
+ grouped.forEach((item, groupedIndex) => {
218
+ const demo2 = {
219
+ component: `MapleDemo${groupedIndex}`,
220
+ request: item.request,
221
+ path: item.path,
222
+ source: readFileSync(item.path, "utf8").trim(),
223
+ title: item.title
224
+ };
225
+ demos.push(demo2);
226
+ onDemo?.(item.path);
227
+ });
228
+ source = replaceGroupedExamples(source, demos, language);
229
+ return { source, demos };
230
+ }
231
+ const demoPath = exampleIndexPath;
232
+ const exampleHeading = /^## (?:案例|Examples?)[ \t]*$/m;
233
+ const demo = {
234
+ component: "MaplePageDemo",
235
+ request: "../example/index.vue",
236
+ path: demoPath,
237
+ source: readFileSync(demoPath, "utf8").trim(),
238
+ page: true
239
+ };
240
+ demos.push(demo);
241
+ const placeholder = demoPlaceholder(demos.length - 1);
242
+ if (exampleHeading.test(source)) {
243
+ source = source.replace(exampleHeading, (heading) => `${heading}
244
+
245
+ ${placeholder}`);
246
+ } else {
247
+ const heading = id.endsWith("/en-US.md") ? "## Examples" : "## \u6848\u4F8B";
248
+ const firstSection = /^## .+$/m;
249
+ source = firstSection.test(source) ? source.replace(firstSection, (section) => `${heading}
250
+
251
+ ${placeholder}
252
+
253
+ ${section}`) : `${source.trim()}
254
+
255
+ ${heading}
256
+
257
+ ${placeholder}
258
+ `;
259
+ }
260
+ }
261
+ }
262
+ return { source, demos };
263
+ }
264
+ function injectCodeExample(source) {
265
+ const codeRE = /(<pre class="hljs">(.|\r|\n)*?<\/pre>)/g;
266
+ return source.replace(codeRE, (str) => {
267
+ const flags = [
268
+ "// playground-ignore\n",
269
+ '<span class="hljs-meta">#</span><span class="bash"> playground-ignore</span>\n',
270
+ '<span class="hljs-comment">// playground-ignore</span>\n',
271
+ '<span class="hljs-comment">/* playground-ignore */</span>\n',
272
+ '<span class="hljs-comment">&lt;!-- playground-ignore --&gt;</span>\n'
273
+ ];
274
+ const attr = flags.some((flag) => str.includes(flag)) ? "playground-ignore" : "";
275
+ str = flags.reduce((str2, flag) => str2.replace(flag, ""), str);
276
+ return `<mp-site-code-example ${attr}>${str}</mp-site-code-example>`;
277
+ });
278
+ }
279
+ function highlight(str, lang, style) {
280
+ let link = "";
281
+ if (style) {
282
+ link = '<link class="hljs-style" rel="stylesheet" href="' + style + '"/>';
283
+ }
284
+ if (lang && hljs.getLanguage(lang)) {
285
+ return '<pre class="hljs"><code>' + link + hljs.highlight(str, { language: lang, ignoreIllegals: true }).value + "</code></pre>";
286
+ }
287
+ return "";
288
+ }
289
+ function attachMobileDemoIds(demos, id, onDependency) {
290
+ if (!demos.length || !/\/src\/[^/]+\/docs\/[^/]+\.md$/.test(id.replaceAll("\\", "/"))) {
291
+ return;
292
+ }
293
+ const indexPath = resolve(dirname(id), "../example/index.vue");
294
+ if (!existsSync(indexPath)) {
295
+ return;
296
+ }
297
+ onDependency?.(indexPath);
298
+ const indexSource = readFileSync(indexPath, "utf8");
299
+ const imports = [...indexSource.matchAll(/import\s+(\w+)\s+from\s+['"](\.{1,2}\/[^'"]+\.vue)['"]/g)];
300
+ const canonical = (file) => file.replace(/\/demos\/(?:en-US|zh-CN)\//, "/demos/");
301
+ const ids = /* @__PURE__ */ new Map();
302
+ for (const section of indexSource.matchAll(/<section\b[^>]*data-demo=['"]([^'"]+)['"][^>]*>([\s\S]*?)<\/section>/g)) {
303
+ for (const [, name, request] of imports) {
304
+ if (new RegExp(`<${name}(?=[\\s/>])|:is=["'][^"']*\\b${name}\\b`).test(section[2])) {
305
+ ids.set(canonical(resolve(dirname(indexPath), request)), section[1]);
306
+ }
307
+ }
308
+ }
309
+ for (const demo of demos) {
310
+ demo.mobileId = ids.get(canonical(demo.path));
311
+ }
312
+ }
313
+ function markdownToVue(source, options, id, onDemo) {
314
+ const expanded = expandDemoFiles(source, id, onDemo);
315
+ attachMobileDemoIds(expanded.demos, id, onDemo);
316
+ source = expanded.source;
317
+ const { source: vueSource, imports, components } = extractComponents(source);
318
+ const md = markdownIt({
319
+ html: true,
320
+ highlight: (str, lang) => highlight(str, lang, options.style)
321
+ });
322
+ let rendered = md.render(vueSource);
323
+ expanded.demos.forEach((demo, index) => {
324
+ rendered = rendered.replace(`<p>${demoPlaceholder(index)}</p>`, renderDemo(demo));
325
+ });
326
+ let templateString = htmlWrapper(rendered);
327
+ templateString = templateString.replace(/process.env/g, "<span>process.env</span>");
328
+ templateString = injectCodeExample(templateString);
329
+ return `
330
+ <template><div class="maple-site-doc">${templateString}</div></template>
331
+
332
+ <script>
333
+ ${expanded.demos.map((demo) => `import ${demo.component} from '${demo.request}'`).join("\n")}
334
+ ${imports.join("\n")}
335
+
336
+ export default {
337
+ components: {
338
+ ${[...expanded.demos.map((demo) => demo.component), ...components].join(",")}
339
+ }
340
+ }
341
+ </script>
342
+ `;
343
+ }
344
+ function markdown(options) {
345
+ const demosByMarkdown = /* @__PURE__ */ new Map();
346
+ return {
347
+ name: "vite-plugin-maple-markdown",
348
+ enforce: "pre",
349
+ transform(source, id) {
350
+ if (!/\.md$/.test(id)) {
351
+ return;
352
+ }
353
+ try {
354
+ const markdownId = id.split("?", 1)[0];
355
+ const demos = [];
356
+ const result = markdownToVue(source, options, markdownId, (demo) => {
357
+ demos.push(demo);
358
+ this.addWatchFile(demo);
359
+ });
360
+ demosByMarkdown.set(markdownId, demos);
361
+ return result;
362
+ } catch (e) {
363
+ this.error(e);
364
+ }
365
+ },
366
+ handleHotUpdate(ctx) {
367
+ if (/\.md$/.test(ctx.file)) {
368
+ const { read } = ctx;
369
+ ctx.read = async () => markdownToVue(await read(), options, ctx.file);
370
+ return;
371
+ }
372
+ const markdownModules = [...demosByMarkdown].filter(([, demos]) => demos.includes(ctx.file)).flatMap(([markdownId]) => {
373
+ const module = ctx.server.moduleGraph.getModuleById(markdownId);
374
+ if (module) {
375
+ ctx.server.moduleGraph.invalidateModule(module);
376
+ return [module];
377
+ }
378
+ return [];
379
+ });
380
+ if (markdownModules.length) {
381
+ return [...ctx.modules, ...markdownModules];
382
+ }
383
+ }
384
+ };
385
+ }
386
+
387
+ // src/html.ts
388
+ import ejs from "ejs";
389
+ function html(options) {
390
+ return {
391
+ name: "vite-plugin-maple-html",
392
+ transformIndexHtml: {
393
+ order: "pre",
394
+ handler(html2) {
395
+ return ejs.render(html2, options.data);
396
+ }
397
+ }
398
+ };
399
+ }
400
+
401
+ // src/inlineCss.ts
402
+ import fse from "fs-extra";
403
+ var { pathExistsSync, writeFileSync, readFileSync: readFileSync2, removeSync } = fse;
404
+ function inlineCss(options) {
405
+ return {
406
+ name: "vite-plugin-maple-inline-css",
407
+ apply: "build",
408
+ closeBundle() {
409
+ const { cssFile, jsFile, onEnd } = options;
410
+ if (!pathExistsSync(cssFile)) {
411
+ this.warn("css file cannot found");
412
+ return;
413
+ }
414
+ if (!pathExistsSync(jsFile)) {
415
+ this.warn("js file cannot found");
416
+ return;
417
+ }
418
+ const cssCode = readFileSync2(cssFile, "utf-8");
419
+ const jsCode = readFileSync2(jsFile, "utf-8");
420
+ const injectCode = `;(function(){var style=document.createElement('style');style.type='text/css';style.rel='stylesheet';style.appendChild(document.createTextNode(\`${cssCode.replace(/\\/g, "\\\\")}\`));var head=document.querySelector('head');head.appendChild(style)})();`;
421
+ writeFileSync(jsFile, `${injectCode}${jsCode}`);
422
+ removeSync(cssFile);
423
+ onEnd?.();
424
+ }
425
+ };
426
+ }
427
+
428
+ // src/copy.ts
429
+ import fse2 from "fs-extra";
430
+ var { copySync, copyFileSync } = fse2;
431
+ function copy(options) {
432
+ return {
433
+ name: "vite-plugin-maple-copy",
434
+ buildStart() {
435
+ options.paths.forEach((copyPath) => {
436
+ try {
437
+ ;
438
+ (copyPath.type === "folder" ? copySync : copyFileSync)(copyPath.from, copyPath.to);
439
+ } catch (e) {
440
+ this.warn(e);
441
+ }
442
+ });
443
+ }
444
+ };
445
+ }
446
+ export {
447
+ copy,
448
+ html,
449
+ inlineCss,
450
+ markdown
451
+ };
package/package.json CHANGED
@@ -1,6 +1,54 @@
1
1
  {
2
2
  "name": "@maple-design/vite-plugins-vue",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "1.1.1",
4
+ "description": "vite plugins of maple",
5
+ "keywords": [
6
+ "vite",
7
+ "plugins",
8
+ "maple"
9
+ ],
10
+ "homepage": "https://github.com/loca-spirit/maple-design-mobile#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/loca-spirit/maple-design-mobile/issues"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/loca-spirit/maple-design-mobile.git",
17
+ "directory": "packages/vite-plugins"
18
+ },
19
+ "license": "MIT",
20
+ "author": "shuai.meng",
21
+ "type": "module",
22
+ "main": "lib/index.js",
23
+ "module": "lib/index.js",
24
+ "files": [
25
+ "lib"
26
+ ],
27
+ "dependencies": {
28
+ "ejs": "^3.1.10",
29
+ "fs-extra": "^9.0.1",
30
+ "highlight.js": "^10.7.3",
31
+ "markdown-it": "^14.3.0",
32
+ "pinyin-pro": "3.17.0",
33
+ "@maple-design/shared-vue": "1.1.1"
34
+ },
35
+ "devDependencies": {
36
+ "@types/ejs": "^3.1.5",
37
+ "@types/fs-extra": "^9.0.13",
38
+ "@types/markdown-it": "^12.2.3",
39
+ "@types/node": "^18.7.20",
40
+ "tsup": "7.2.0",
41
+ "typescript": "5.3.3"
42
+ },
43
+ "peerDependencies": {
44
+ "vite": "6.1.1"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public",
48
+ "registry": "https://registry.npmjs.org/"
49
+ },
50
+ "scripts": {
51
+ "build": "tsup src/index.ts --format esm --out-dir=lib --dts --clean",
52
+ "dev": "tsup src/index.ts --format esm --out-dir=lib --watch --dts"
53
+ }
6
54
  }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.