@dev-to/react-plugin 0.1.1 → 0.2.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/dist/constants.d.ts +1 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/debugTools.d.ts +3 -1
- package/dist/debugTools.d.ts.map +1 -1
- package/dist/index.js +527 -396
- package/dist/libBuildUtils.d.ts.map +1 -1
- package/dist/loaderUmdWrapper.d.ts +12 -0
- package/dist/loaderUmdWrapper.d.ts.map +1 -0
- package/dist/virtualModules.d.ts +9 -0
- package/dist/virtualModules.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/constants.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export declare const STABLE_INIT_PATH: "/__dev_to_react__/init.js";
|
|
|
6
6
|
export declare const STABLE_REACT_RUNTIME_PATH: "/__dev_to_react__/react-runtime.js";
|
|
7
7
|
export declare const STABLE_DEBUG_HTML_PATH: "/__dev_to_react__/debug.html";
|
|
8
8
|
export declare const STABLE_DEBUG_JSON_PATH: "/__dev_to_react__/debug.json";
|
|
9
|
+
export declare const STABLE_LOADER_BASE_PATH: "/__dev_to_react__/loader";
|
|
9
10
|
export declare const EVENT_FULL_RELOAD: "dev_to_react:full-reload";
|
|
10
11
|
export declare const EVENT_HMR_UPDATE: "dev_to_react:hmr-update";
|
|
11
12
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,WAAW,gBAAyB,CAAA;AACjD,eAAO,MAAM,iBAAiB,kBAA8B,CAAA;AAE5D,eAAO,MAAM,gBAAgB,qBAAyB,CAAA;AACtD,eAAO,MAAM,oBAAoB,iCAA6B,CAAA;AAC9D,eAAO,MAAM,gBAAgB,6BAAyB,CAAA;AACtD,eAAO,MAAM,yBAAyB,sCAAkC,CAAA;AACxE,eAAO,MAAM,sBAAsB,gCAA+B,CAAA;AAClE,eAAO,MAAM,sBAAsB,gCAA+B,CAAA;AAClE,eAAO,MAAM,uBAAuB,4BAAgC,CAAA;AAEpE,eAAO,MAAM,iBAAiB,4BAAiC,CAAA;AAC/D,eAAO,MAAM,gBAAgB,2BAAgC,CAAA"}
|
package/dist/debugTools.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { ViteDevServer } from 'vite';
|
|
2
|
-
import type { BridgeContract, BridgeStats, DebugStartupState, DevComponentAudit } from './types.js';
|
|
2
|
+
import type { BridgeContract, BridgeStats, DebugStartupState, DevComponentAudit, ResolvedDevComponentConfig } from './types.js';
|
|
3
3
|
export interface DebugToolsContext {
|
|
4
4
|
contract: BridgeContract;
|
|
5
5
|
stats: BridgeStats;
|
|
6
6
|
audit: DevComponentAudit;
|
|
7
|
+
resolvedConfig: ResolvedDevComponentConfig;
|
|
8
|
+
configDir: string;
|
|
7
9
|
open?: boolean;
|
|
8
10
|
}
|
|
9
11
|
export declare function installDebugTools(server: ViteDevServer, ctx: DebugToolsContext, state: DebugStartupState): void;
|
package/dist/debugTools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"debugTools.d.ts","sourceRoot":"","sources":["../src/debugTools.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"debugTools.d.ts","sourceRoot":"","sources":["../src/debugTools.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,MAAM,CAAA;AAEzC,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAA;AAG/H,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,cAAc,CAAA;IACxB,KAAK,EAAE,WAAW,CAAA;IAClB,KAAK,EAAE,iBAAiB,CAAA;IACxB,cAAc,EAAE,0BAA0B,CAAA;IAC1C,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAqFD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,iBAAiB,EAAE,KAAK,EAAE,iBAAiB,QAuSxG"}
|
package/dist/index.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import { DEV_TO_REACT_BASE_PATH, DEV_TO_REACT_CONTRACT_KEY, DEV_TO_REACT_CONTRACT_PATH, DEV_TO_REACT_DEBUG_HTML_PATH, DEV_TO_REACT_DEBUG_JSON_PATH, DEV_TO_REACT_DEBUG_STATE_KEY, DEV_TO_REACT_DID_OPEN_BROWSER_KEY, DEV_TO_REACT_EVENT_FULL_RELOAD, DEV_TO_REACT_EVENT_HMR_UPDATE, DEV_TO_REACT_INIT_PATH, DEV_TO_REACT_NAMESPACE, DEV_TO_REACT_ORIGIN_KEY, DEV_TO_REACT_REACT_RUNTIME_PATH, DEV_TO_REACT_RESOLVE_ASSET_KEY } from "@dev-to/react-shared";
|
|
1
|
+
import { DEV_TO_REACT_BASE_PATH, DEV_TO_REACT_CONTRACT_KEY, DEV_TO_REACT_CONTRACT_PATH, DEV_TO_REACT_DEBUG_HTML_PATH, DEV_TO_REACT_DEBUG_JSON_PATH, DEV_TO_REACT_DEBUG_STATE_KEY, DEV_TO_REACT_DID_OPEN_BROWSER_KEY, DEV_TO_REACT_EVENT_FULL_RELOAD, DEV_TO_REACT_EVENT_HMR_UPDATE, DEV_TO_REACT_INIT_PATH, DEV_TO_REACT_LOADER_BASE_PATH, DEV_TO_REACT_NAMESPACE, DEV_TO_REACT_ORIGIN_KEY, DEV_TO_REACT_REACT_RUNTIME_PATH, DEV_TO_REACT_RESOLVE_ASSET_KEY } from "@dev-to/react-shared";
|
|
2
2
|
import node_fs from "node:fs";
|
|
3
3
|
import node_path from "node:path";
|
|
4
4
|
import picocolors from "picocolors";
|
|
5
5
|
import { mergeConfig } from "vite";
|
|
6
6
|
import { exec } from "node:child_process";
|
|
7
7
|
import node_os from "node:os";
|
|
8
|
-
import { pathToFileURL } from "node:url";
|
|
9
8
|
import typescript from "typescript";
|
|
10
9
|
const PLUGIN_NAME = DEV_TO_REACT_NAMESPACE;
|
|
11
|
-
const
|
|
10
|
+
const constants_PLUGIN_LOG_PREFIX = `[${PLUGIN_NAME}]`;
|
|
12
11
|
const STABLE_BASE_PATH = DEV_TO_REACT_BASE_PATH;
|
|
13
|
-
const
|
|
12
|
+
const constants_STABLE_CONTRACT_PATH = DEV_TO_REACT_CONTRACT_PATH;
|
|
14
13
|
const STABLE_INIT_PATH = DEV_TO_REACT_INIT_PATH;
|
|
15
14
|
const STABLE_REACT_RUNTIME_PATH = DEV_TO_REACT_REACT_RUNTIME_PATH;
|
|
16
15
|
const STABLE_DEBUG_HTML_PATH = DEV_TO_REACT_DEBUG_HTML_PATH;
|
|
17
16
|
const STABLE_DEBUG_JSON_PATH = DEV_TO_REACT_DEBUG_JSON_PATH;
|
|
17
|
+
const STABLE_LOADER_BASE_PATH = DEV_TO_REACT_LOADER_BASE_PATH;
|
|
18
18
|
const EVENT_FULL_RELOAD = DEV_TO_REACT_EVENT_FULL_RELOAD;
|
|
19
19
|
const EVENT_HMR_UPDATE = DEV_TO_REACT_EVENT_HMR_UPDATE;
|
|
20
20
|
function renderDebugHtml(params) {
|
|
@@ -51,7 +51,7 @@ function renderDebugHtml(params) {
|
|
|
51
51
|
lines.push(dim(' */'));
|
|
52
52
|
return `${lines.join('\n')}\n\n${escapeHtml(content)}`;
|
|
53
53
|
} catch (e) {
|
|
54
|
-
return escapeHtml(`// ${
|
|
54
|
+
return escapeHtml(`// ${constants_PLUGIN_LOG_PREFIX} 无法读取配置文件: ${e}`);
|
|
55
55
|
}
|
|
56
56
|
})();
|
|
57
57
|
return `<!doctype html>
|
|
@@ -374,7 +374,7 @@ CSS: <span class="str">dist/<name>/<name>.css</span></pre>
|
|
|
374
374
|
<div style="margin-top: 12px;">
|
|
375
375
|
<pre style="font-size: 12px; line-height: 1.7;">
|
|
376
376
|
<span class="kw">Endpoints:</span>
|
|
377
|
-
- Contract: <span class="str">${
|
|
377
|
+
- Contract: <span class="str">${constants_STABLE_CONTRACT_PATH}</span>
|
|
378
378
|
- Init: <span class="str">${STABLE_INIT_PATH}</span>
|
|
379
379
|
- Runtime: <span class="str">${STABLE_REACT_RUNTIME_PATH}</span>
|
|
380
380
|
|
|
@@ -510,7 +510,7 @@ function tryResolveWithExtensions(p) {
|
|
|
510
510
|
function resolveEntryAbsPath(rootDir, entry, defaultEntryAbs, fallbackRoot) {
|
|
511
511
|
const tryResolveWithBase = (baseDir)=>{
|
|
512
512
|
if ('/' === entry) {
|
|
513
|
-
if (!defaultEntryAbs) throw new Error(`${
|
|
513
|
+
if (!defaultEntryAbs) throw new Error(`${constants_PLUGIN_LOG_PREFIX} 使用 / 作为入口时,必须提供 defaultEntryAbs 参数`);
|
|
514
514
|
return defaultEntryAbs;
|
|
515
515
|
}
|
|
516
516
|
const fsPath = toFsPathFromViteEntry(entry);
|
|
@@ -528,6 +528,396 @@ function resolveEntryAbsPath(rootDir, entry, defaultEntryAbs, fallbackRoot) {
|
|
|
528
528
|
if (fallbackRoot && fallbackRoot !== rootDir) return tryResolveWithBase(fallbackRoot);
|
|
529
529
|
return resolved;
|
|
530
530
|
}
|
|
531
|
+
function isLibBuild(env) {
|
|
532
|
+
return env?.command === 'build' && env?.mode === 'lib';
|
|
533
|
+
}
|
|
534
|
+
function toSafeOutDirName(componentName) {
|
|
535
|
+
return componentName.replace(/[\\/]/g, '_').replace(/\.\./g, '_');
|
|
536
|
+
}
|
|
537
|
+
function toSafeUmdName(componentName) {
|
|
538
|
+
let s = componentName.replace(/[^A-Za-z0-9_$]+/g, '_');
|
|
539
|
+
if (!s) s = 'ViteDevComponent';
|
|
540
|
+
if (/^\d/.test(s)) s = `_${s}`;
|
|
541
|
+
return s;
|
|
542
|
+
}
|
|
543
|
+
function isValidJsIdentifier(name) {
|
|
544
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
|
|
545
|
+
}
|
|
546
|
+
function analyzeExports(filePath) {
|
|
547
|
+
if (!node_fs.existsSync(filePath)) return {
|
|
548
|
+
hasDefault: false,
|
|
549
|
+
namedExports: []
|
|
550
|
+
};
|
|
551
|
+
const content = node_fs.readFileSync(filePath, 'utf-8');
|
|
552
|
+
const namedExports = [];
|
|
553
|
+
let hasDefault = false;
|
|
554
|
+
let scriptKind = typescript.ScriptKind.TS;
|
|
555
|
+
const ext = node_path.extname(filePath).toLowerCase();
|
|
556
|
+
if ('.tsx' === ext) scriptKind = typescript.ScriptKind.TSX;
|
|
557
|
+
else if ('.jsx' === ext) scriptKind = typescript.ScriptKind.JSX;
|
|
558
|
+
else if ('.js' === ext) scriptKind = typescript.ScriptKind.JS;
|
|
559
|
+
else if ('.ts' === ext) scriptKind = typescript.ScriptKind.TS;
|
|
560
|
+
let sourceFile;
|
|
561
|
+
try {
|
|
562
|
+
sourceFile = typescript.createSourceFile(filePath, content, typescript.ScriptTarget.Latest, true, scriptKind);
|
|
563
|
+
} catch (parseError) {
|
|
564
|
+
throw new Error(`${constants_PLUGIN_LOG_PREFIX} 无法解析入口文件 "${filePath}"。\n解析错误: ${parseError instanceof Error ? parseError.message : String(parseError)}\n请确保文件是有效的 TypeScript/JavaScript 文件。`);
|
|
565
|
+
}
|
|
566
|
+
typescript.getPreEmitDiagnostics(typescript.createProgram([
|
|
567
|
+
filePath
|
|
568
|
+
], {
|
|
569
|
+
target: typescript.ScriptTarget.Latest,
|
|
570
|
+
module: typescript.ModuleKind.ESNext,
|
|
571
|
+
jsx: scriptKind === typescript.ScriptKind.TSX || scriptKind === typescript.ScriptKind.JSX ? typescript.JsxEmit.React : void 0
|
|
572
|
+
}));
|
|
573
|
+
function visit(node) {
|
|
574
|
+
if (typescript.isExportAssignment(node)) {
|
|
575
|
+
if (true !== node.isExportEquals) hasDefault = true;
|
|
576
|
+
}
|
|
577
|
+
if (typescript.isFunctionDeclaration(node) || typescript.isClassDeclaration(node) || typescript.isVariableStatement(node) || typescript.isInterfaceDeclaration(node) || typescript.isTypeAliasDeclaration(node) || typescript.isEnumDeclaration(node)) {
|
|
578
|
+
const modifiers = typescript.getModifiers(node);
|
|
579
|
+
if (modifiers?.some((m)=>m.kind === typescript.SyntaxKind.ExportKeyword)) if (modifiers.some((m)=>m.kind === typescript.SyntaxKind.DefaultKeyword)) hasDefault = true;
|
|
580
|
+
else {
|
|
581
|
+
if (typescript.isFunctionDeclaration(node) && node.name) namedExports.push(node.name.text);
|
|
582
|
+
if (typescript.isClassDeclaration(node) && node.name) namedExports.push(node.name.text);
|
|
583
|
+
if (typescript.isVariableStatement(node)) node.declarationList.declarations.forEach((decl)=>{
|
|
584
|
+
if (typescript.isIdentifier(decl.name)) namedExports.push(decl.name.text);
|
|
585
|
+
});
|
|
586
|
+
if (typescript.isInterfaceDeclaration(node) && node.name) namedExports.push(node.name.text);
|
|
587
|
+
if (typescript.isTypeAliasDeclaration(node) && node.name) namedExports.push(node.name.text);
|
|
588
|
+
if (typescript.isEnumDeclaration(node) && node.name) namedExports.push(node.name.text);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
if (typescript.isExportDeclaration(node) && node.exportClause) {
|
|
592
|
+
if (typescript.isNamedExports(node.exportClause)) node.exportClause.elements.forEach((element)=>{
|
|
593
|
+
if (element.name) {
|
|
594
|
+
const exportName = element.name.text;
|
|
595
|
+
const { propertyName } = element;
|
|
596
|
+
if (propertyName && 'default' === propertyName.text) namedExports.push(exportName);
|
|
597
|
+
else if ('default' === exportName) hasDefault = true;
|
|
598
|
+
else namedExports.push(exportName);
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
else if (typescript.isNamespaceExport(node.exportClause)) namedExports.push(node.exportClause.name.text);
|
|
602
|
+
}
|
|
603
|
+
typescript.isExportDeclaration(node) && node.exportClause;
|
|
604
|
+
typescript.forEachChild(node, visit);
|
|
605
|
+
}
|
|
606
|
+
visit(sourceFile);
|
|
607
|
+
const uniqueExports = Array.from(new Set(namedExports));
|
|
608
|
+
if (!hasDefault && 0 === uniqueExports.length) {
|
|
609
|
+
const hasDefaultRegex = /export\s+default\s+/;
|
|
610
|
+
const hasNamedRegex = /export\s+(?:const|let|var|function|class|interface|type|enum)\s+([A-Za-z_$][A-Za-z0-9_$]*)/g;
|
|
611
|
+
const regexHasDefault = hasDefaultRegex.test(content);
|
|
612
|
+
const regexNamedMatches = [];
|
|
613
|
+
let match;
|
|
614
|
+
while(null !== (match = hasNamedRegex.exec(content)))regexNamedMatches.push(match[1]);
|
|
615
|
+
if (regexHasDefault || regexNamedMatches.length > 0) {
|
|
616
|
+
console.warn(`${constants_PLUGIN_LOG_PREFIX} 警告:AST 分析未检测到导出,但正则检测到:\n 文件: ${filePath}\n 正则检测 default: ${regexHasDefault}\n 正则检测命名导出: ${regexNamedMatches.join(', ') || '无'}\n 这可能是 AST 解析问题,将尝试继续构建。`);
|
|
617
|
+
if (regexHasDefault) hasDefault = true;
|
|
618
|
+
if (regexNamedMatches.length > 0) namedExports.push(...regexNamedMatches);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return {
|
|
622
|
+
hasDefault,
|
|
623
|
+
namedExports: Array.from(new Set(namedExports))
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
function generateLibVirtualEntryCode(params) {
|
|
627
|
+
const { defaultEntryAbs, componentName } = params;
|
|
628
|
+
if (!node_fs.existsSync(defaultEntryAbs)) throw new Error(`${constants_PLUGIN_LOG_PREFIX} 入口文件不存在: "${defaultEntryAbs}"\n请检查文件路径是否正确。`);
|
|
629
|
+
const actualFile = tryResolveWithExtensions(defaultEntryAbs) || defaultEntryAbs;
|
|
630
|
+
if (!node_fs.existsSync(actualFile)) throw new Error(`${constants_PLUGIN_LOG_PREFIX} 入口文件不存在: "${defaultEntryAbs}"\n尝试解析后的路径: "${actualFile}"\n请检查文件路径是否正确。`);
|
|
631
|
+
const importTarget = actualFile;
|
|
632
|
+
let exports;
|
|
633
|
+
try {
|
|
634
|
+
exports = analyzeExports(actualFile);
|
|
635
|
+
} catch (error) {
|
|
636
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
637
|
+
throw new Error(`${constants_PLUGIN_LOG_PREFIX} 分析入口文件 "${actualFile}" 的导出时出错:\n${errorMsg}\n\n请检查文件:\n 1. 文件是否存在且可读\n 2. 文件是否有语法错误\n 3. 文件是否有导出(export default 或命名导出)\n\n原始路径: "${defaultEntryAbs}"\n实际解析路径: "${actualFile}"`);
|
|
638
|
+
}
|
|
639
|
+
let code;
|
|
640
|
+
if (exports.hasDefault || 0 !== exports.namedExports.length) if (exports.hasDefault) {
|
|
641
|
+
let prefer = '';
|
|
642
|
+
if (exports.namedExports.includes(componentName)) prefer = isValidJsIdentifier(componentName) ? `mod.${componentName}` : `mod[${JSON.stringify(componentName)}]`;
|
|
643
|
+
const pickedExpr = prefer ? `${prefer} || mod.default || mod` : 'mod.default || mod';
|
|
644
|
+
code = `/** AUTO-GENERATED by ${PLUGIN_NAME} */
|
|
645
|
+
import * as mod from ${JSON.stringify(importTarget)};
|
|
646
|
+
const picked = ${pickedExpr};
|
|
647
|
+
const Card = picked && picked.default ? picked.default : picked;
|
|
648
|
+
export default Card;
|
|
649
|
+
export * from ${JSON.stringify(importTarget)};
|
|
650
|
+
`;
|
|
651
|
+
} else if (1 === exports.namedExports.length) {
|
|
652
|
+
const singleExport = exports.namedExports[0];
|
|
653
|
+
const exportAccess = isValidJsIdentifier(singleExport) ? `mod.${singleExport}` : `mod[${JSON.stringify(singleExport)}]`;
|
|
654
|
+
code = `/** AUTO-GENERATED by ${PLUGIN_NAME} */
|
|
655
|
+
import * as mod from ${JSON.stringify(importTarget)};
|
|
656
|
+
const Card = ${exportAccess};
|
|
657
|
+
export default Card;
|
|
658
|
+
export * from ${JSON.stringify(importTarget)};
|
|
659
|
+
`;
|
|
660
|
+
} else {
|
|
661
|
+
const hasComponentNameExport = exports.namedExports.some((exp)=>exp === componentName);
|
|
662
|
+
if (hasComponentNameExport) {
|
|
663
|
+
const exportAccess = isValidJsIdentifier(componentName) ? `mod.${componentName}` : `mod[${JSON.stringify(componentName)}]`;
|
|
664
|
+
code = `/** AUTO-GENERATED by ${PLUGIN_NAME} */
|
|
665
|
+
import * as mod from ${JSON.stringify(importTarget)};
|
|
666
|
+
const Card = ${exportAccess};
|
|
667
|
+
export default Card;
|
|
668
|
+
export * from ${JSON.stringify(importTarget)};
|
|
669
|
+
`;
|
|
670
|
+
} else throw new Error(`${constants_PLUGIN_LOG_PREFIX} Entry file "${defaultEntryAbs}" has multiple named exports (${exports.namedExports.join(', ')}), but none match componentName "${componentName}".\n\nPlease resolve this by:\n\n 1. Adding a default export (Recommended):\n export default function ${componentName}() { ... }\n\n 2. Adding a named export called "${componentName}":\n export function ${componentName}() { ... }\n\n 3. Keeping only one named export (it will be used automatically).\n\nCurrent componentName: "${componentName}"\nCurrent named exports: ${exports.namedExports.join(', ')}`);
|
|
671
|
+
}
|
|
672
|
+
else throw new Error(`${constants_PLUGIN_LOG_PREFIX} Entry file "${defaultEntryAbs}" does not have any exports.\n\nPlease ensure the file has one of the following:\n\n 1. export default (Recommended):\n export default function ${componentName}() { ... }\n or\n const ${componentName} = () => { ... };\n export default ${componentName};\n\n 2. Named export matching componentName:\n export function ${componentName}() { ... }\n or\n export const ${componentName} = () => { ... };\n\n 3. A single named export (any name):\n export function MyComponent() { ... }\n // If there is only one named export, it will be used automatically.\n\nCurrent componentName: "${componentName}"`);
|
|
673
|
+
return code;
|
|
674
|
+
}
|
|
675
|
+
function getLibVirtualEntryPath(componentName) {
|
|
676
|
+
return `virtual:${PLUGIN_NAME}-lib-entry:${componentName}`;
|
|
677
|
+
}
|
|
678
|
+
function normalizeLibCss(outDir, baseName) {
|
|
679
|
+
if (!node_fs.existsSync(outDir)) return;
|
|
680
|
+
const target = node_path.join(outDir, `${baseName}.css`);
|
|
681
|
+
if (node_fs.existsSync(target)) return;
|
|
682
|
+
const cssCandidates = [];
|
|
683
|
+
const scanDir = (dir, depth)=>{
|
|
684
|
+
if (depth < 0 || !node_fs.existsSync(dir)) return;
|
|
685
|
+
const entries = node_fs.readdirSync(dir, {
|
|
686
|
+
withFileTypes: true
|
|
687
|
+
});
|
|
688
|
+
for (const e of entries){
|
|
689
|
+
const full = node_path.join(dir, e.name);
|
|
690
|
+
if (e.isDirectory()) scanDir(full, depth - 1);
|
|
691
|
+
else if (e.isFile() && e.name.endsWith('.css')) cssCandidates.push(full);
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
scanDir(outDir, 2);
|
|
695
|
+
if (1 !== cssCandidates.length) return;
|
|
696
|
+
const from = cssCandidates[0];
|
|
697
|
+
try {
|
|
698
|
+
node_fs.renameSync(from, target);
|
|
699
|
+
} catch {
|
|
700
|
+
try {
|
|
701
|
+
node_fs.copyFileSync(from, target);
|
|
702
|
+
node_fs.unlinkSync(from);
|
|
703
|
+
} catch {}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
function resolveBuildTargets(params) {
|
|
707
|
+
const { componentMap, requestedRaw, defaultEntryAbs } = params;
|
|
708
|
+
const componentNames = Object.keys(componentMap);
|
|
709
|
+
const requestedList = requestedRaw ? requestedRaw.split(',').map((s)=>s.trim()).filter(Boolean) : [];
|
|
710
|
+
const actualConfiguredNames = componentNames.filter((n)=>'*' !== n);
|
|
711
|
+
if (actualConfiguredNames.length > 0) {
|
|
712
|
+
if (requestedList.length > 0) {
|
|
713
|
+
const picked = requestedList.filter((n)=>actualConfiguredNames.includes(n));
|
|
714
|
+
if (0 === picked.length) throw new Error(`${constants_PLUGIN_LOG_PREFIX} 指定的 component 不在配置列表中:${requestedRaw}`);
|
|
715
|
+
return picked;
|
|
716
|
+
}
|
|
717
|
+
return actualConfiguredNames;
|
|
718
|
+
}
|
|
719
|
+
if (requestedList.length > 0) return requestedList;
|
|
720
|
+
const fallbackName = node_path.parse(defaultEntryAbs || 'index').name || 'index';
|
|
721
|
+
return [
|
|
722
|
+
fallbackName
|
|
723
|
+
];
|
|
724
|
+
}
|
|
725
|
+
function generateLibBuildNextConfig(params) {
|
|
726
|
+
const { rootDir, picked, componentMap, resolvedConfig, options, userConfig, configDir } = params;
|
|
727
|
+
const outBase = toSafeOutDirName(picked);
|
|
728
|
+
const outDir = node_path.resolve(rootDir, 'dist', outBase);
|
|
729
|
+
let resolvedEntryAbs = resolvedConfig.defaultEntryAbs;
|
|
730
|
+
const entryAbs = (()=>{
|
|
731
|
+
const entryFromMap = componentMap[picked];
|
|
732
|
+
if (entryFromMap) {
|
|
733
|
+
const abs = resolveEntryAbsPath(rootDir, entryFromMap, resolvedConfig.defaultEntryAbs, configDir);
|
|
734
|
+
if (!abs) throw new Error(`${constants_PLUGIN_LOG_PREFIX} 无法解析入口:component="${picked}", entry="${entryFromMap}"`);
|
|
735
|
+
resolvedEntryAbs = abs;
|
|
736
|
+
}
|
|
737
|
+
return getLibVirtualEntryPath(picked);
|
|
738
|
+
})();
|
|
739
|
+
const virtualEntryCode = (()=>{
|
|
740
|
+
const entryFromMap = componentMap[picked];
|
|
741
|
+
if (entryFromMap) {
|
|
742
|
+
const abs = resolveEntryAbsPath(rootDir, entryFromMap, resolvedConfig.defaultEntryAbs, configDir);
|
|
743
|
+
if (!abs) throw new Error(`${constants_PLUGIN_LOG_PREFIX} 无法解析入口:component="${picked}", entry="${entryFromMap}"`);
|
|
744
|
+
resolvedEntryAbs = abs;
|
|
745
|
+
return generateLibVirtualEntryCode({
|
|
746
|
+
rootDir,
|
|
747
|
+
defaultEntryAbs: abs,
|
|
748
|
+
componentName: picked
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
resolvedEntryAbs = resolvedConfig.defaultEntryAbs;
|
|
752
|
+
return generateLibVirtualEntryCode({
|
|
753
|
+
rootDir,
|
|
754
|
+
defaultEntryAbs: resolvedConfig.defaultEntryAbs,
|
|
755
|
+
componentName: picked
|
|
756
|
+
});
|
|
757
|
+
})();
|
|
758
|
+
const next = {
|
|
759
|
+
root: rootDir,
|
|
760
|
+
define: {
|
|
761
|
+
...userConfig.define || {},
|
|
762
|
+
'process.env.NODE_ENV': JSON.stringify('production')
|
|
763
|
+
},
|
|
764
|
+
build: {
|
|
765
|
+
outDir,
|
|
766
|
+
emptyOutDir: true,
|
|
767
|
+
cssCodeSplit: false,
|
|
768
|
+
lib: {
|
|
769
|
+
entry: entryAbs,
|
|
770
|
+
name: toSafeUmdName(picked),
|
|
771
|
+
formats: [
|
|
772
|
+
'umd'
|
|
773
|
+
],
|
|
774
|
+
fileName: ()=>`${outBase}.js`
|
|
775
|
+
},
|
|
776
|
+
rollupOptions: {
|
|
777
|
+
external: [
|
|
778
|
+
'react',
|
|
779
|
+
'react-dom',
|
|
780
|
+
'react-dom/client'
|
|
781
|
+
],
|
|
782
|
+
output: {
|
|
783
|
+
inlineDynamicImports: true,
|
|
784
|
+
exports: 'named',
|
|
785
|
+
globals: {
|
|
786
|
+
react: 'React',
|
|
787
|
+
'react-dom': 'ReactDOM',
|
|
788
|
+
'react-dom/client': 'ReactDOMClient'
|
|
789
|
+
},
|
|
790
|
+
assetFileNames: (assetInfo)=>{
|
|
791
|
+
const name = assetInfo?.name || '';
|
|
792
|
+
if (name.endsWith('.css')) return `${outBase}.css`;
|
|
793
|
+
return 'assets/[name]-[hash][extname]';
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
if (options.build) {
|
|
800
|
+
const merged = mergeConfig({
|
|
801
|
+
build: next.build
|
|
802
|
+
}, {
|
|
803
|
+
build: options.build
|
|
804
|
+
});
|
|
805
|
+
next.build = merged.build;
|
|
806
|
+
}
|
|
807
|
+
return {
|
|
808
|
+
next,
|
|
809
|
+
outDir: next.build?.outDir || outDir,
|
|
810
|
+
outBase,
|
|
811
|
+
buildTargets: [],
|
|
812
|
+
virtualEntryCode,
|
|
813
|
+
resolvedEntryAbs
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
function createLoaderUmdWrapper(options) {
|
|
817
|
+
const { componentName, origin, contractEndpoint = constants_STABLE_CONTRACT_PATH } = options;
|
|
818
|
+
const globalName = toSafeUmdName(componentName);
|
|
819
|
+
const code = `/**
|
|
820
|
+
* UMD Loader Wrapper for component: ${componentName}
|
|
821
|
+
* Global name: ${globalName}
|
|
822
|
+
* Generated by ${constants_PLUGIN_LOG_PREFIX}
|
|
823
|
+
*
|
|
824
|
+
* This wrapper uses @dev-to/react-loader to dynamically load and render the component.
|
|
825
|
+
*
|
|
826
|
+
* Usage:
|
|
827
|
+
* 1. Load React and ReactDOM:
|
|
828
|
+
* <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
|
|
829
|
+
* <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
|
|
830
|
+
*
|
|
831
|
+
* 2. Load ReactLoader:
|
|
832
|
+
* <script src="https://cdn.jsdelivr.net/npm/@dev-to/react-loader@latest/dist/index.umd.js"></script>
|
|
833
|
+
*
|
|
834
|
+
* 3. Load this wrapper:
|
|
835
|
+
* <script src="${origin}/__dev_to_react__/loader/${componentName}.js"></script>
|
|
836
|
+
*
|
|
837
|
+
* 4. Render the component:
|
|
838
|
+
* <div id="app"></div>
|
|
839
|
+
* <script>
|
|
840
|
+
* window.${globalName}.render(
|
|
841
|
+
* document.getElementById('app'),
|
|
842
|
+
* { /* your props */ }
|
|
843
|
+
* );
|
|
844
|
+
* </script>
|
|
845
|
+
*/
|
|
846
|
+
(function (root, factory) {
|
|
847
|
+
if (typeof exports === 'object' && typeof module !== 'undefined') {
|
|
848
|
+
// CommonJS
|
|
849
|
+
factory(exports, require('react'), require('react-dom'), require('@dev-to/react-loader'));
|
|
850
|
+
} else if (typeof define === 'function' && define.amd) {
|
|
851
|
+
// AMD
|
|
852
|
+
define(['exports', 'react', 'react-dom', '@dev-to/react-loader'], factory);
|
|
853
|
+
} else {
|
|
854
|
+
// Browser globals
|
|
855
|
+
var globalObj = typeof globalThis !== 'undefined' ? globalThis : (typeof self !== 'undefined' ? self : root);
|
|
856
|
+
factory((globalObj.${globalName} = {}), globalObj.React, globalObj.ReactDOM, globalObj.DevToReactLoader);
|
|
857
|
+
}
|
|
858
|
+
})(this, function (exports, React, ReactDOM, ReactLoaderModule) {
|
|
859
|
+
'use strict';
|
|
860
|
+
|
|
861
|
+
// Get ReactLoader component from the module
|
|
862
|
+
var ReactLoader = ReactLoaderModule && ReactLoaderModule.ReactLoader;
|
|
863
|
+
|
|
864
|
+
if (!ReactLoader) {
|
|
865
|
+
throw new Error(
|
|
866
|
+
'${constants_PLUGIN_LOG_PREFIX} ReactLoader not found. ' +
|
|
867
|
+
'Please load @dev-to/react-loader before this script: ' +
|
|
868
|
+
'<script src="https://cdn.jsdelivr.net/npm/@dev-to/react-loader@latest/dist/index.umd.js"></script>'
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// Component configuration
|
|
873
|
+
var config = {
|
|
874
|
+
origin: ${JSON.stringify(origin)},
|
|
875
|
+
name: ${JSON.stringify(componentName)},
|
|
876
|
+
contractEndpoint: ${JSON.stringify(contractEndpoint)}
|
|
877
|
+
};
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* Render the component using ReactLoader
|
|
881
|
+
*/
|
|
882
|
+
function render(targetElement, componentProps) {
|
|
883
|
+
if (!targetElement) {
|
|
884
|
+
throw new Error('${constants_PLUGIN_LOG_PREFIX} Target element is required');
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
if (!React || !React.createElement) {
|
|
888
|
+
throw new Error('${constants_PLUGIN_LOG_PREFIX} React is not loaded');
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
if (!ReactDOM || !ReactDOM.createRoot) {
|
|
892
|
+
throw new Error('${constants_PLUGIN_LOG_PREFIX} ReactDOM is not loaded');
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// Create ReactLoader component props
|
|
896
|
+
var loaderProps = {
|
|
897
|
+
origin: config.origin,
|
|
898
|
+
name: config.name,
|
|
899
|
+
contractEndpoint: config.contractEndpoint,
|
|
900
|
+
componentProps: componentProps || {}
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
// Render using ReactLoader
|
|
904
|
+
var root = ReactDOM.createRoot(targetElement);
|
|
905
|
+
root.render(React.createElement(ReactLoader, loaderProps));
|
|
906
|
+
|
|
907
|
+
return root;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// Export the API
|
|
911
|
+
exports.render = render;
|
|
912
|
+
exports.config = config;
|
|
913
|
+
exports.default = { render: render, config: config };
|
|
914
|
+
|
|
915
|
+
// Mark as ES module
|
|
916
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
917
|
+
});
|
|
918
|
+
`;
|
|
919
|
+
return code;
|
|
920
|
+
}
|
|
531
921
|
function openBrowser(url) {
|
|
532
922
|
const bridgePath = STABLE_DEBUG_HTML_PATH;
|
|
533
923
|
if ('darwin' === process.platform) {
|
|
@@ -615,7 +1005,7 @@ function installDebugTools(server, ctx, state) {
|
|
|
615
1005
|
const logger = server.config.logger;
|
|
616
1006
|
const info = 'function' == typeof logger?.info ? logger.info.bind(logger) : console.log;
|
|
617
1007
|
info('');
|
|
618
|
-
info(`${
|
|
1008
|
+
info(`${constants_PLUGIN_LOG_PREFIX} Debug panel:`);
|
|
619
1009
|
urls.forEach((u)=>info(` ${picocolors.cyan(u)}`));
|
|
620
1010
|
info(` JSON: ${picocolors.cyan(`${proto}://localhost:${port}${STABLE_DEBUG_JSON_PATH}`)}`);
|
|
621
1011
|
info('');
|
|
@@ -635,7 +1025,7 @@ function installDebugTools(server, ctx, state) {
|
|
|
635
1025
|
const url = req.url || '';
|
|
636
1026
|
const pathname = String(url).split('?')[0];
|
|
637
1027
|
const now = Date.now();
|
|
638
|
-
if (pathname ===
|
|
1028
|
+
if (pathname === constants_STABLE_CONTRACT_PATH) {
|
|
639
1029
|
ctx.stats.contract.count += 1;
|
|
640
1030
|
ctx.stats.contract.lastAt = now;
|
|
641
1031
|
} else if (pathname === STABLE_INIT_PATH) {
|
|
@@ -723,6 +1113,30 @@ function installDebugTools(server, ctx, state) {
|
|
|
723
1113
|
}, null, 2));
|
|
724
1114
|
return;
|
|
725
1115
|
}
|
|
1116
|
+
if (pathname.startsWith(STABLE_LOADER_BASE_PATH)) {
|
|
1117
|
+
const loaderPathPattern = new RegExp(`^${STABLE_LOADER_BASE_PATH}/([^/]+)\\.js$`);
|
|
1118
|
+
const match = pathname.match(loaderPathPattern);
|
|
1119
|
+
if (match) {
|
|
1120
|
+
const componentName = match[1];
|
|
1121
|
+
const isHttps = !!server.config.server.https;
|
|
1122
|
+
const proto = isHttps ? 'https' : 'http';
|
|
1123
|
+
const hostHeader = String(req.headers.host || '');
|
|
1124
|
+
const addr = server.httpServer?.address();
|
|
1125
|
+
const actualPort = addr && 'object' == typeof addr ? addr.port : void 0;
|
|
1126
|
+
const origin = hostHeader ? `${proto}://${hostHeader}` : `${proto}://localhost${actualPort ? `:${actualPort}` : ''}`;
|
|
1127
|
+
const code = createLoaderUmdWrapper({
|
|
1128
|
+
componentName,
|
|
1129
|
+
origin,
|
|
1130
|
+
contractEndpoint: constants_STABLE_CONTRACT_PATH
|
|
1131
|
+
});
|
|
1132
|
+
res.statusCode = 200;
|
|
1133
|
+
res.setHeader('Content-Type', "application/javascript; charset=utf-8");
|
|
1134
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
1135
|
+
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
1136
|
+
res.end(code);
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
726
1140
|
if (url.startsWith(STABLE_DEBUG_HTML_PATH)) {
|
|
727
1141
|
const addr = server.httpServer?.address();
|
|
728
1142
|
const actualPort = addr && 'object' == typeof addr ? addr.port : void 0;
|
|
@@ -792,387 +1206,102 @@ function installDebugTools(server, ctx, state) {
|
|
|
792
1206
|
res.end(html);
|
|
793
1207
|
return;
|
|
794
1208
|
}
|
|
795
|
-
next();
|
|
796
|
-
});
|
|
797
|
-
}
|
|
798
|
-
function toViteFsPath(filePath) {
|
|
799
|
-
const normalized = filePath.replace(/\\/g, '/');
|
|
800
|
-
return normalized.startsWith('/') ? `/@fs${normalized}` : `/@fs/${normalized}`;
|
|
801
|
-
}
|
|
802
|
-
function resolveDefaultEntryAbs(rootDir) {
|
|
803
|
-
const appCandidates = [
|
|
804
|
-
node_path.resolve(rootDir, 'src/App.tsx'),
|
|
805
|
-
node_path.resolve(rootDir, 'src/App.jsx'),
|
|
806
|
-
node_path.resolve(rootDir, 'src/App.ts'),
|
|
807
|
-
node_path.resolve(rootDir, 'src/App.js')
|
|
808
|
-
];
|
|
809
|
-
const foundApp = appCandidates.find((p)=>node_fs.existsSync(p));
|
|
810
|
-
if (foundApp) return foundApp;
|
|
811
|
-
const indexCandidates = [
|
|
812
|
-
node_path.resolve(rootDir, 'src/index.ts'),
|
|
813
|
-
node_path.resolve(rootDir, 'src/index.tsx'),
|
|
814
|
-
node_path.resolve(rootDir, 'src/index.jsx'),
|
|
815
|
-
node_path.resolve(rootDir, 'src/index.js')
|
|
816
|
-
];
|
|
817
|
-
const foundIndex = indexCandidates.find((p)=>node_fs.existsSync(p));
|
|
818
|
-
if (foundIndex) return foundIndex;
|
|
819
|
-
return appCandidates[0];
|
|
820
|
-
}
|
|
821
|
-
function buildDevComponentMapFromRecord(rootDir, input, defaultEntryAbs, convertAt = false, fallbackRoot) {
|
|
822
|
-
const out = {};
|
|
823
|
-
for (const [componentName, entry] of Object.entries(input)){
|
|
824
|
-
if (!componentName || !entry) continue;
|
|
825
|
-
if ('/' === entry) {
|
|
826
|
-
out[componentName] = convertAt ? toViteFsPath(defaultEntryAbs) : '/';
|
|
827
|
-
continue;
|
|
828
|
-
}
|
|
829
|
-
if (entry.startsWith('http://') || entry.startsWith('https://') || entry.startsWith('/')) {
|
|
830
|
-
out[componentName] = entry;
|
|
831
|
-
continue;
|
|
832
|
-
}
|
|
833
|
-
const abs = node_path.isAbsolute(entry) ? entry : node_path.resolve(rootDir, entry);
|
|
834
|
-
let resolved = tryResolveWithExtensions(abs);
|
|
835
|
-
if (!resolved && fallbackRoot && fallbackRoot !== rootDir) {
|
|
836
|
-
const fallbackAbs = node_path.isAbsolute(entry) ? entry : node_path.resolve(fallbackRoot, entry);
|
|
837
|
-
resolved = tryResolveWithExtensions(fallbackAbs);
|
|
838
|
-
}
|
|
839
|
-
resolved = resolved || abs;
|
|
840
|
-
out[componentName] = toViteFsPath(resolved);
|
|
841
|
-
}
|
|
842
|
-
return out;
|
|
843
|
-
}
|
|
844
|
-
function getFsPathFromViteEntry(entry) {
|
|
845
|
-
if (!entry.startsWith('/@fs')) return null;
|
|
846
|
-
let p = entry.slice(4);
|
|
847
|
-
if (p.startsWith('/') && /\/[A-Za-z]:\//.test(p)) p = p.slice(1);
|
|
848
|
-
return p;
|
|
849
|
-
}
|
|
850
|
-
function resolveDevComponentConfig(rootDir, devComponentMap, fallbackRoot) {
|
|
851
|
-
const defaultEntryAbs = resolveDefaultEntryAbs(rootDir);
|
|
852
|
-
const defaultEntry = toViteFsPath(defaultEntryAbs);
|
|
853
|
-
let resolvedDevComponentMap = {};
|
|
854
|
-
if ('string' == typeof devComponentMap) {
|
|
855
|
-
const name = devComponentMap.trim();
|
|
856
|
-
resolvedDevComponentMap = name ? buildDevComponentMapFromRecord(rootDir, {
|
|
857
|
-
[name]: '/'
|
|
858
|
-
}, defaultEntryAbs, false, fallbackRoot) : {
|
|
859
|
-
'*': '/'
|
|
860
|
-
};
|
|
861
|
-
} else {
|
|
862
|
-
const input = devComponentMap ?? {};
|
|
863
|
-
resolvedDevComponentMap = 0 === Object.keys(input).length ? {
|
|
864
|
-
'*': '/'
|
|
865
|
-
} : buildDevComponentMapFromRecord(rootDir, input, defaultEntryAbs, false, fallbackRoot);
|
|
866
|
-
}
|
|
867
|
-
const audit = (()=>{
|
|
868
|
-
const missing = [];
|
|
869
|
-
for (const [componentName, entry] of Object.entries(resolvedDevComponentMap)){
|
|
870
|
-
const fsPath = getFsPathFromViteEntry(entry);
|
|
871
|
-
if (fsPath) {
|
|
872
|
-
const resolved = tryResolveWithExtensions(fsPath);
|
|
873
|
-
if (!resolved || !node_fs.existsSync(resolved)) missing.push({
|
|
874
|
-
componentName,
|
|
875
|
-
filePath: fsPath
|
|
876
|
-
});
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
return {
|
|
880
|
-
defaultEntryAbs,
|
|
881
|
-
defaultEntryExists: node_fs.existsSync(defaultEntryAbs),
|
|
882
|
-
componentMapCount: Object.keys(resolvedDevComponentMap).length,
|
|
883
|
-
missingEntries: missing
|
|
884
|
-
};
|
|
885
|
-
})();
|
|
886
|
-
return {
|
|
887
|
-
defaultEntryAbs,
|
|
888
|
-
defaultEntry,
|
|
889
|
-
componentMap: resolvedDevComponentMap,
|
|
890
|
-
audit
|
|
891
|
-
};
|
|
892
|
-
}
|
|
893
|
-
function isLibBuild(env) {
|
|
894
|
-
return env?.command === 'build' && env?.mode === 'lib';
|
|
895
|
-
}
|
|
896
|
-
function toSafeOutDirName(componentName) {
|
|
897
|
-
return componentName.replace(/[\\/]/g, '_').replace(/\.\./g, '_');
|
|
898
|
-
}
|
|
899
|
-
function toSafeUmdName(componentName) {
|
|
900
|
-
let s = componentName.replace(/[^A-Za-z0-9_$]+/g, '_');
|
|
901
|
-
if (!s) s = 'ViteDevComponent';
|
|
902
|
-
if (/^\d/.test(s)) s = `_${s}`;
|
|
903
|
-
return s;
|
|
904
|
-
}
|
|
905
|
-
function isValidJsIdentifier(name) {
|
|
906
|
-
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
|
|
907
|
-
}
|
|
908
|
-
function analyzeExports(filePath) {
|
|
909
|
-
if (!node_fs.existsSync(filePath)) return {
|
|
910
|
-
hasDefault: false,
|
|
911
|
-
namedExports: []
|
|
912
|
-
};
|
|
913
|
-
const content = node_fs.readFileSync(filePath, 'utf-8');
|
|
914
|
-
const namedExports = [];
|
|
915
|
-
let hasDefault = false;
|
|
916
|
-
let scriptKind = typescript.ScriptKind.TS;
|
|
917
|
-
const ext = node_path.extname(filePath).toLowerCase();
|
|
918
|
-
if ('.tsx' === ext) scriptKind = typescript.ScriptKind.TSX;
|
|
919
|
-
else if ('.jsx' === ext) scriptKind = typescript.ScriptKind.JSX;
|
|
920
|
-
else if ('.js' === ext) scriptKind = typescript.ScriptKind.JS;
|
|
921
|
-
else if ('.ts' === ext) scriptKind = typescript.ScriptKind.TS;
|
|
922
|
-
let sourceFile;
|
|
923
|
-
try {
|
|
924
|
-
sourceFile = typescript.createSourceFile(filePath, content, typescript.ScriptTarget.Latest, true, scriptKind);
|
|
925
|
-
} catch (parseError) {
|
|
926
|
-
throw new Error(`${PLUGIN_LOG_PREFIX} 无法解析入口文件 "${filePath}"。\n解析错误: ${parseError instanceof Error ? parseError.message : String(parseError)}\n请确保文件是有效的 TypeScript/JavaScript 文件。`);
|
|
927
|
-
}
|
|
928
|
-
typescript.getPreEmitDiagnostics(typescript.createProgram([
|
|
929
|
-
filePath
|
|
930
|
-
], {
|
|
931
|
-
target: typescript.ScriptTarget.Latest,
|
|
932
|
-
module: typescript.ModuleKind.ESNext,
|
|
933
|
-
jsx: scriptKind === typescript.ScriptKind.TSX || scriptKind === typescript.ScriptKind.JSX ? typescript.JsxEmit.React : void 0
|
|
934
|
-
}));
|
|
935
|
-
function visit(node) {
|
|
936
|
-
if (typescript.isExportAssignment(node)) {
|
|
937
|
-
if (true !== node.isExportEquals) hasDefault = true;
|
|
938
|
-
}
|
|
939
|
-
if (typescript.isFunctionDeclaration(node) || typescript.isClassDeclaration(node) || typescript.isVariableStatement(node) || typescript.isInterfaceDeclaration(node) || typescript.isTypeAliasDeclaration(node) || typescript.isEnumDeclaration(node)) {
|
|
940
|
-
const modifiers = typescript.getModifiers(node);
|
|
941
|
-
if (modifiers?.some((m)=>m.kind === typescript.SyntaxKind.ExportKeyword)) if (modifiers.some((m)=>m.kind === typescript.SyntaxKind.DefaultKeyword)) hasDefault = true;
|
|
942
|
-
else {
|
|
943
|
-
if (typescript.isFunctionDeclaration(node) && node.name) namedExports.push(node.name.text);
|
|
944
|
-
if (typescript.isClassDeclaration(node) && node.name) namedExports.push(node.name.text);
|
|
945
|
-
if (typescript.isVariableStatement(node)) node.declarationList.declarations.forEach((decl)=>{
|
|
946
|
-
if (typescript.isIdentifier(decl.name)) namedExports.push(decl.name.text);
|
|
947
|
-
});
|
|
948
|
-
if (typescript.isInterfaceDeclaration(node) && node.name) namedExports.push(node.name.text);
|
|
949
|
-
if (typescript.isTypeAliasDeclaration(node) && node.name) namedExports.push(node.name.text);
|
|
950
|
-
if (typescript.isEnumDeclaration(node) && node.name) namedExports.push(node.name.text);
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
if (typescript.isExportDeclaration(node) && node.exportClause) {
|
|
954
|
-
if (typescript.isNamedExports(node.exportClause)) node.exportClause.elements.forEach((element)=>{
|
|
955
|
-
if (element.name) {
|
|
956
|
-
const exportName = element.name.text;
|
|
957
|
-
const { propertyName } = element;
|
|
958
|
-
if (propertyName && 'default' === propertyName.text) namedExports.push(exportName);
|
|
959
|
-
else if ('default' === exportName) hasDefault = true;
|
|
960
|
-
else namedExports.push(exportName);
|
|
961
|
-
}
|
|
962
|
-
});
|
|
963
|
-
else if (typescript.isNamespaceExport(node.exportClause)) namedExports.push(node.exportClause.name.text);
|
|
964
|
-
}
|
|
965
|
-
typescript.isExportDeclaration(node) && node.exportClause;
|
|
966
|
-
typescript.forEachChild(node, visit);
|
|
967
|
-
}
|
|
968
|
-
visit(sourceFile);
|
|
969
|
-
const uniqueExports = Array.from(new Set(namedExports));
|
|
970
|
-
if (!hasDefault && 0 === uniqueExports.length) {
|
|
971
|
-
const hasDefaultRegex = /export\s+default\s+/;
|
|
972
|
-
const hasNamedRegex = /export\s+(?:const|let|var|function|class|interface|type|enum)\s+([A-Za-z_$][A-Za-z0-9_$]*)/g;
|
|
973
|
-
const regexHasDefault = hasDefaultRegex.test(content);
|
|
974
|
-
const regexNamedMatches = [];
|
|
975
|
-
let match;
|
|
976
|
-
while(null !== (match = hasNamedRegex.exec(content)))regexNamedMatches.push(match[1]);
|
|
977
|
-
if (regexHasDefault || regexNamedMatches.length > 0) {
|
|
978
|
-
console.warn(`${PLUGIN_LOG_PREFIX} 警告:AST 分析未检测到导出,但正则检测到:\n 文件: ${filePath}\n 正则检测 default: ${regexHasDefault}\n 正则检测命名导出: ${regexNamedMatches.join(', ') || '无'}\n 这可能是 AST 解析问题,将尝试继续构建。`);
|
|
979
|
-
if (regexHasDefault) hasDefault = true;
|
|
980
|
-
if (regexNamedMatches.length > 0) namedExports.push(...regexNamedMatches);
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
return {
|
|
984
|
-
hasDefault,
|
|
985
|
-
namedExports: Array.from(new Set(namedExports))
|
|
986
|
-
};
|
|
1209
|
+
next();
|
|
1210
|
+
});
|
|
987
1211
|
}
|
|
988
|
-
function
|
|
989
|
-
const
|
|
990
|
-
|
|
991
|
-
const actualFile = tryResolveWithExtensions(defaultEntryAbs) || defaultEntryAbs;
|
|
992
|
-
if (!node_fs.existsSync(actualFile)) throw new Error(`${PLUGIN_LOG_PREFIX} 入口文件不存在: "${defaultEntryAbs}"\n尝试解析后的路径: "${actualFile}"\n请检查文件路径是否正确。`);
|
|
993
|
-
const importTarget = pathToFileURL(actualFile).href;
|
|
994
|
-
let exports;
|
|
995
|
-
try {
|
|
996
|
-
exports = analyzeExports(actualFile);
|
|
997
|
-
} catch (error) {
|
|
998
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
999
|
-
throw new Error(`${PLUGIN_LOG_PREFIX} 分析入口文件 "${actualFile}" 的导出时出错:\n${errorMsg}\n\n请检查文件:\n 1. 文件是否存在且可读\n 2. 文件是否有语法错误\n 3. 文件是否有导出(export default 或命名导出)\n\n原始路径: "${defaultEntryAbs}"\n实际解析路径: "${actualFile}"`);
|
|
1000
|
-
}
|
|
1001
|
-
let code;
|
|
1002
|
-
if (exports.hasDefault || 0 !== exports.namedExports.length) if (exports.hasDefault) {
|
|
1003
|
-
let prefer = '';
|
|
1004
|
-
if (exports.namedExports.includes(componentName)) prefer = isValidJsIdentifier(componentName) ? `mod.${componentName}` : `mod[${JSON.stringify(componentName)}]`;
|
|
1005
|
-
const pickedExpr = prefer ? `${prefer} || mod.default || mod` : 'mod.default || mod';
|
|
1006
|
-
code = `/** AUTO-GENERATED by ${PLUGIN_NAME} */
|
|
1007
|
-
import * as mod from ${JSON.stringify(importTarget)};
|
|
1008
|
-
const picked = ${pickedExpr};
|
|
1009
|
-
const Card = picked && picked.default ? picked.default : picked;
|
|
1010
|
-
export default Card;
|
|
1011
|
-
export * from ${JSON.stringify(importTarget)};
|
|
1012
|
-
`;
|
|
1013
|
-
} else if (1 === exports.namedExports.length) {
|
|
1014
|
-
const singleExport = exports.namedExports[0];
|
|
1015
|
-
const exportAccess = isValidJsIdentifier(singleExport) ? `mod.${singleExport}` : `mod[${JSON.stringify(singleExport)}]`;
|
|
1016
|
-
code = `/** AUTO-GENERATED by ${PLUGIN_NAME} */
|
|
1017
|
-
import * as mod from ${JSON.stringify(importTarget)};
|
|
1018
|
-
const Card = ${exportAccess};
|
|
1019
|
-
export default Card;
|
|
1020
|
-
export * from ${JSON.stringify(importTarget)};
|
|
1021
|
-
`;
|
|
1022
|
-
} else {
|
|
1023
|
-
const hasComponentNameExport = exports.namedExports.some((exp)=>exp === componentName);
|
|
1024
|
-
if (hasComponentNameExport) {
|
|
1025
|
-
const exportAccess = isValidJsIdentifier(componentName) ? `mod.${componentName}` : `mod[${JSON.stringify(componentName)}]`;
|
|
1026
|
-
code = `/** AUTO-GENERATED by ${PLUGIN_NAME} */
|
|
1027
|
-
import * as mod from ${JSON.stringify(importTarget)};
|
|
1028
|
-
const Card = ${exportAccess};
|
|
1029
|
-
export default Card;
|
|
1030
|
-
export * from ${JSON.stringify(importTarget)};
|
|
1031
|
-
`;
|
|
1032
|
-
} else throw new Error(`${PLUGIN_LOG_PREFIX} Entry file "${defaultEntryAbs}" has multiple named exports (${exports.namedExports.join(', ')}), but none match componentName "${componentName}".\n\nPlease resolve this by:\n\n 1. Adding a default export (Recommended):\n export default function ${componentName}() { ... }\n\n 2. Adding a named export called "${componentName}":\n export function ${componentName}() { ... }\n\n 3. Keeping only one named export (it will be used automatically).\n\nCurrent componentName: "${componentName}"\nCurrent named exports: ${exports.namedExports.join(', ')}`);
|
|
1033
|
-
}
|
|
1034
|
-
else throw new Error(`${PLUGIN_LOG_PREFIX} Entry file "${defaultEntryAbs}" does not have any exports.\n\nPlease ensure the file has one of the following:\n\n 1. export default (Recommended):\n export default function ${componentName}() { ... }\n or\n const ${componentName} = () => { ... };\n export default ${componentName};\n\n 2. Named export matching componentName:\n export function ${componentName}() { ... }\n or\n export const ${componentName} = () => { ... };\n\n 3. A single named export (any name):\n export function MyComponent() { ... }\n // If there is only one named export, it will be used automatically.\n\nCurrent componentName: "${componentName}"`);
|
|
1035
|
-
return code;
|
|
1212
|
+
function toViteFsPath(filePath) {
|
|
1213
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
1214
|
+
return normalized.startsWith('/') ? `/@fs${normalized}` : `/@fs/${normalized}`;
|
|
1036
1215
|
}
|
|
1037
|
-
function
|
|
1038
|
-
|
|
1216
|
+
function resolveDefaultEntryAbs(rootDir) {
|
|
1217
|
+
const appCandidates = [
|
|
1218
|
+
node_path.resolve(rootDir, 'src/App.tsx'),
|
|
1219
|
+
node_path.resolve(rootDir, 'src/App.jsx'),
|
|
1220
|
+
node_path.resolve(rootDir, 'src/App.ts'),
|
|
1221
|
+
node_path.resolve(rootDir, 'src/App.js')
|
|
1222
|
+
];
|
|
1223
|
+
const foundApp = appCandidates.find((p)=>node_fs.existsSync(p));
|
|
1224
|
+
if (foundApp) return foundApp;
|
|
1225
|
+
const indexCandidates = [
|
|
1226
|
+
node_path.resolve(rootDir, 'src/index.ts'),
|
|
1227
|
+
node_path.resolve(rootDir, 'src/index.tsx'),
|
|
1228
|
+
node_path.resolve(rootDir, 'src/index.jsx'),
|
|
1229
|
+
node_path.resolve(rootDir, 'src/index.js')
|
|
1230
|
+
];
|
|
1231
|
+
const foundIndex = indexCandidates.find((p)=>node_fs.existsSync(p));
|
|
1232
|
+
if (foundIndex) return foundIndex;
|
|
1233
|
+
return appCandidates[0];
|
|
1039
1234
|
}
|
|
1040
|
-
function
|
|
1041
|
-
|
|
1042
|
-
const
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
const entries = node_fs.readdirSync(dir, {
|
|
1048
|
-
withFileTypes: true
|
|
1049
|
-
});
|
|
1050
|
-
for (const e of entries){
|
|
1051
|
-
const full = node_path.join(dir, e.name);
|
|
1052
|
-
if (e.isDirectory()) scanDir(full, depth - 1);
|
|
1053
|
-
else if (e.isFile() && e.name.endsWith('.css')) cssCandidates.push(full);
|
|
1235
|
+
function buildDevComponentMapFromRecord(rootDir, input, defaultEntryAbs, convertAt = false, fallbackRoot) {
|
|
1236
|
+
const out = {};
|
|
1237
|
+
for (const [componentName, entry] of Object.entries(input)){
|
|
1238
|
+
if (!componentName || !entry) continue;
|
|
1239
|
+
if ('/' === entry) {
|
|
1240
|
+
out[componentName] = convertAt ? toViteFsPath(defaultEntryAbs) : '/';
|
|
1241
|
+
continue;
|
|
1054
1242
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
const from = cssCandidates[0];
|
|
1059
|
-
try {
|
|
1060
|
-
node_fs.renameSync(from, target);
|
|
1061
|
-
} catch {
|
|
1062
|
-
try {
|
|
1063
|
-
node_fs.copyFileSync(from, target);
|
|
1064
|
-
node_fs.unlinkSync(from);
|
|
1065
|
-
} catch {}
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
function resolveBuildTargets(params) {
|
|
1069
|
-
const { componentMap, requestedRaw, defaultEntryAbs } = params;
|
|
1070
|
-
const componentNames = Object.keys(componentMap);
|
|
1071
|
-
const requestedList = requestedRaw ? requestedRaw.split(',').map((s)=>s.trim()).filter(Boolean) : [];
|
|
1072
|
-
const actualConfiguredNames = componentNames.filter((n)=>'*' !== n);
|
|
1073
|
-
if (actualConfiguredNames.length > 0) {
|
|
1074
|
-
if (requestedList.length > 0) {
|
|
1075
|
-
const picked = requestedList.filter((n)=>actualConfiguredNames.includes(n));
|
|
1076
|
-
if (0 === picked.length) throw new Error(`${PLUGIN_LOG_PREFIX} 指定的 component 不在配置列表中:${requestedRaw}`);
|
|
1077
|
-
return picked;
|
|
1243
|
+
if (entry.startsWith('http://') || entry.startsWith('https://') || entry.startsWith('/')) {
|
|
1244
|
+
out[componentName] = entry;
|
|
1245
|
+
continue;
|
|
1078
1246
|
}
|
|
1079
|
-
|
|
1247
|
+
const abs = node_path.isAbsolute(entry) ? entry : node_path.resolve(rootDir, entry);
|
|
1248
|
+
let resolved = tryResolveWithExtensions(abs);
|
|
1249
|
+
if (!resolved && fallbackRoot && fallbackRoot !== rootDir) {
|
|
1250
|
+
const fallbackAbs = node_path.isAbsolute(entry) ? entry : node_path.resolve(fallbackRoot, entry);
|
|
1251
|
+
resolved = tryResolveWithExtensions(fallbackAbs);
|
|
1252
|
+
}
|
|
1253
|
+
resolved = resolved || abs;
|
|
1254
|
+
out[componentName] = toViteFsPath(resolved);
|
|
1080
1255
|
}
|
|
1081
|
-
|
|
1082
|
-
const fallbackName = node_path.parse(defaultEntryAbs || 'index').name || 'index';
|
|
1083
|
-
return [
|
|
1084
|
-
fallbackName
|
|
1085
|
-
];
|
|
1256
|
+
return out;
|
|
1086
1257
|
}
|
|
1087
|
-
function
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
const next = {
|
|
1121
|
-
root: rootDir,
|
|
1122
|
-
define: {
|
|
1123
|
-
...userConfig.define || {},
|
|
1124
|
-
'process.env.NODE_ENV': JSON.stringify('production')
|
|
1125
|
-
},
|
|
1126
|
-
build: {
|
|
1127
|
-
outDir,
|
|
1128
|
-
emptyOutDir: true,
|
|
1129
|
-
cssCodeSplit: false,
|
|
1130
|
-
lib: {
|
|
1131
|
-
entry: entryAbs,
|
|
1132
|
-
name: toSafeUmdName(picked),
|
|
1133
|
-
formats: [
|
|
1134
|
-
'umd'
|
|
1135
|
-
],
|
|
1136
|
-
fileName: ()=>`${outBase}.js`
|
|
1137
|
-
},
|
|
1138
|
-
rollupOptions: {
|
|
1139
|
-
external: [
|
|
1140
|
-
'react',
|
|
1141
|
-
'react-dom',
|
|
1142
|
-
'react-dom/client'
|
|
1143
|
-
],
|
|
1144
|
-
output: {
|
|
1145
|
-
inlineDynamicImports: true,
|
|
1146
|
-
exports: 'named',
|
|
1147
|
-
globals: {
|
|
1148
|
-
react: 'React',
|
|
1149
|
-
'react-dom': 'ReactDOM',
|
|
1150
|
-
'react-dom/client': 'ReactDOMClient'
|
|
1151
|
-
},
|
|
1152
|
-
assetFileNames: (assetInfo)=>{
|
|
1153
|
-
const name = assetInfo?.name || '';
|
|
1154
|
-
if (name.endsWith('.css')) return `${outBase}.css`;
|
|
1155
|
-
return 'assets/[name]-[hash][extname]';
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1258
|
+
function getFsPathFromViteEntry(entry) {
|
|
1259
|
+
if (!entry.startsWith('/@fs')) return null;
|
|
1260
|
+
let p = entry.slice(4);
|
|
1261
|
+
if (p.startsWith('/') && /\/[A-Za-z]:\//.test(p)) p = p.slice(1);
|
|
1262
|
+
return p;
|
|
1263
|
+
}
|
|
1264
|
+
function resolveDevComponentConfig(rootDir, devComponentMap, fallbackRoot) {
|
|
1265
|
+
const defaultEntryAbs = resolveDefaultEntryAbs(rootDir);
|
|
1266
|
+
const defaultEntry = toViteFsPath(defaultEntryAbs);
|
|
1267
|
+
let resolvedDevComponentMap = {};
|
|
1268
|
+
if ('string' == typeof devComponentMap) {
|
|
1269
|
+
const name = devComponentMap.trim();
|
|
1270
|
+
resolvedDevComponentMap = name ? buildDevComponentMapFromRecord(rootDir, {
|
|
1271
|
+
[name]: '/'
|
|
1272
|
+
}, defaultEntryAbs, false, fallbackRoot) : {
|
|
1273
|
+
'*': '/'
|
|
1274
|
+
};
|
|
1275
|
+
} else {
|
|
1276
|
+
const input = devComponentMap ?? {};
|
|
1277
|
+
resolvedDevComponentMap = 0 === Object.keys(input).length ? {
|
|
1278
|
+
'*': '/'
|
|
1279
|
+
} : buildDevComponentMapFromRecord(rootDir, input, defaultEntryAbs, false, fallbackRoot);
|
|
1280
|
+
}
|
|
1281
|
+
const audit = (()=>{
|
|
1282
|
+
const missing = [];
|
|
1283
|
+
for (const [componentName, entry] of Object.entries(resolvedDevComponentMap)){
|
|
1284
|
+
const fsPath = getFsPathFromViteEntry(entry);
|
|
1285
|
+
if (fsPath) {
|
|
1286
|
+
const resolved = tryResolveWithExtensions(fsPath);
|
|
1287
|
+
if (!resolved || !node_fs.existsSync(resolved)) missing.push({
|
|
1288
|
+
componentName,
|
|
1289
|
+
filePath: fsPath
|
|
1290
|
+
});
|
|
1158
1291
|
}
|
|
1159
1292
|
}
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
next.build = merged.build;
|
|
1168
|
-
}
|
|
1293
|
+
return {
|
|
1294
|
+
defaultEntryAbs,
|
|
1295
|
+
defaultEntryExists: node_fs.existsSync(defaultEntryAbs),
|
|
1296
|
+
componentMapCount: Object.keys(resolvedDevComponentMap).length,
|
|
1297
|
+
missingEntries: missing
|
|
1298
|
+
};
|
|
1299
|
+
})();
|
|
1169
1300
|
return {
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
virtualEntryCode,
|
|
1175
|
-
resolvedEntryAbs
|
|
1301
|
+
defaultEntryAbs,
|
|
1302
|
+
defaultEntry,
|
|
1303
|
+
componentMap: resolvedDevComponentMap,
|
|
1304
|
+
audit
|
|
1176
1305
|
};
|
|
1177
1306
|
}
|
|
1178
1307
|
function injectReactImport(code, id) {
|
|
@@ -1201,7 +1330,7 @@ function transformAssetUrl(code, id) {
|
|
|
1201
1330
|
const ORIGIN = new URL(import.meta.url).origin;
|
|
1202
1331
|
return path.startsWith('http') ? path : ORIGIN + path;
|
|
1203
1332
|
} catch (e) {
|
|
1204
|
-
console.warn('${
|
|
1333
|
+
console.warn('${constants_PLUGIN_LOG_PREFIX} Failed to resolve static asset URL:', path, e);
|
|
1205
1334
|
return path;
|
|
1206
1335
|
}
|
|
1207
1336
|
})()`);
|
|
@@ -1239,7 +1368,7 @@ const __dev_to_react_resolveAsset = (path) => {
|
|
|
1239
1368
|
const origin = new URL(import.meta.url).origin;
|
|
1240
1369
|
return path.startsWith('/') ? origin + path : origin + '/' + path;
|
|
1241
1370
|
} catch (e) {
|
|
1242
|
-
console.warn('${
|
|
1371
|
+
console.warn('${constants_PLUGIN_LOG_PREFIX} Failed to resolve CSS asset URL:', path, e);
|
|
1243
1372
|
return path;
|
|
1244
1373
|
}
|
|
1245
1374
|
};
|
|
@@ -1263,7 +1392,7 @@ function createContractVirtualModuleCode(contract) {
|
|
|
1263
1392
|
const STATE = (G[DEBUG_KEY] ||= { logged: {} });
|
|
1264
1393
|
if (!STATE.logged.contract) {
|
|
1265
1394
|
STATE.logged.contract = true;
|
|
1266
|
-
console.groupCollapsed('${
|
|
1395
|
+
console.groupCollapsed('${constants_PLUGIN_LOG_PREFIX} contract loaded');
|
|
1267
1396
|
console.log('Origin:', ORIGIN);
|
|
1268
1397
|
console.log('Paths:', CONTRACT.paths);
|
|
1269
1398
|
console.log('Events:', CONTRACT.events);
|
|
@@ -1284,14 +1413,14 @@ function createInitVirtualModuleCode() {
|
|
|
1284
1413
|
import "/@vite/client";
|
|
1285
1414
|
import RefreshRuntime from "/@react-refresh";
|
|
1286
1415
|
|
|
1287
|
-
import CONTRACT, { ${contractExportName} as CONTRACT_NAMED } from "${
|
|
1416
|
+
import CONTRACT, { ${contractExportName} as CONTRACT_NAMED } from "${constants_STABLE_CONTRACT_PATH}";
|
|
1288
1417
|
|
|
1289
1418
|
if (typeof window !== 'undefined' && !window.__vite_plugin_react_preamble_installed__) {
|
|
1290
1419
|
RefreshRuntime.injectIntoGlobalHook(window);
|
|
1291
1420
|
window.$RefreshReg$ = (type, id) => RefreshRuntime.register(type, id);
|
|
1292
1421
|
window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
|
|
1293
1422
|
window.__vite_plugin_react_preamble_installed__ = true;
|
|
1294
|
-
console.log('${
|
|
1423
|
+
console.log('${constants_PLUGIN_LOG_PREFIX} React Refresh preamble installed.');
|
|
1295
1424
|
}
|
|
1296
1425
|
|
|
1297
1426
|
{
|
|
@@ -1301,7 +1430,7 @@ function createInitVirtualModuleCode() {
|
|
|
1301
1430
|
const STATE = (G[DEBUG_KEY] ||= { logged: {} });
|
|
1302
1431
|
if (!STATE.logged.init) {
|
|
1303
1432
|
STATE.logged.init = true;
|
|
1304
|
-
console.groupCollapsed('${
|
|
1433
|
+
console.groupCollapsed('${constants_PLUGIN_LOG_PREFIX} init loaded (HMR enabled)');
|
|
1305
1434
|
console.log('Origin:', ORIGIN);
|
|
1306
1435
|
console.log('This module imports /@vite/client and installs react-refresh preamble.');
|
|
1307
1436
|
console.log('Important: init must run BEFORE importing react-dom/client in the host.');
|
|
@@ -1357,7 +1486,7 @@ function createReactRuntimeVirtualModuleCode() {
|
|
|
1357
1486
|
const STATE = (G[DEBUG_KEY] ||= { logged: {} });
|
|
1358
1487
|
if (!STATE.logged.runtime) {
|
|
1359
1488
|
STATE.logged.runtime = true;
|
|
1360
|
-
console.groupCollapsed('${
|
|
1489
|
+
console.groupCollapsed('${constants_PLUGIN_LOG_PREFIX} react-runtime loaded');
|
|
1361
1490
|
console.log('Origin:', ORIGIN);
|
|
1362
1491
|
console.log('React.version:', React?.version);
|
|
1363
1492
|
console.log('ReactDOMClient keys:', Object.keys(ReactDOMClient || {}));
|
|
@@ -1404,7 +1533,7 @@ const devToReactPlugin = (devComponentMap = {}, options = {})=>{
|
|
|
1404
1533
|
else processedComponentMap[key] = value;
|
|
1405
1534
|
return {
|
|
1406
1535
|
paths: {
|
|
1407
|
-
contract:
|
|
1536
|
+
contract: constants_STABLE_CONTRACT_PATH,
|
|
1408
1537
|
initClient: STABLE_INIT_PATH,
|
|
1409
1538
|
reactRuntime: STABLE_REACT_RUNTIME_PATH
|
|
1410
1539
|
},
|
|
@@ -1492,7 +1621,7 @@ const devToReactPlugin = (devComponentMap = {}, options = {})=>{
|
|
|
1492
1621
|
if (1 === Object.keys(resolvedConfig.componentMap).length && '/' === resolvedConfig.componentMap['*']) {
|
|
1493
1622
|
const warn = server.config.logger?.warn?.bind(server.config.logger) ?? console.warn;
|
|
1494
1623
|
warn('');
|
|
1495
|
-
warn(`⚠️ ${
|
|
1624
|
+
warn(`⚠️ ${constants_PLUGIN_LOG_PREFIX} No componentName configured. This works in dev mode but will fail in library build (--mode lib).`);
|
|
1496
1625
|
warn(' Please use devToReactPlugin({ ComponentName: "src/ComponentName.tsx" }) or devToReactPlugin({ ComponentName: "/" }) to specify components.');
|
|
1497
1626
|
warn(' Or use wildcard: devToReactPlugin({ "*": "/" }) or devToReactPlugin("*")');
|
|
1498
1627
|
warn('');
|
|
@@ -1501,6 +1630,8 @@ const devToReactPlugin = (devComponentMap = {}, options = {})=>{
|
|
|
1501
1630
|
contract,
|
|
1502
1631
|
stats,
|
|
1503
1632
|
audit: resolvedConfig.audit,
|
|
1633
|
+
resolvedConfig,
|
|
1634
|
+
configDir,
|
|
1504
1635
|
open: options.open
|
|
1505
1636
|
}, debugState);
|
|
1506
1637
|
},
|
|
@@ -1531,7 +1662,7 @@ const devToReactPlugin = (devComponentMap = {}, options = {})=>{
|
|
|
1531
1662
|
}).css;
|
|
1532
1663
|
if (isLibBuild(env)) {
|
|
1533
1664
|
const actualNames = Object.keys(contract.dev.componentMap).filter((n)=>'*' !== n);
|
|
1534
|
-
if (0 === actualNames.length && !process.env.DEV_TO_REACT_LIB_SECTION) throw new Error(` ${
|
|
1665
|
+
if (0 === actualNames.length && !process.env.DEV_TO_REACT_LIB_SECTION) throw new Error(` ${constants_PLUGIN_LOG_PREFIX} Library build (--mode lib) requires at least one explicit componentName for identification and distribution.\nCurrent configuration is in "global fallback mode", which cannot determine build targets.\n\nPlease use one of the following to specify components:\n - devToReactPlugin('ComponentName')\n - devToReactPlugin({ ComponentName: '/' })\n - devToReactPlugin({ ComponentName: 'src/ComponentName.tsx' })\n\n💡 Tip: Wildcards are convenient for development, but explicit naming is required for production builds.`);
|
|
1535
1666
|
const isChild = '1' === process.env.DEV_TO_REACT_LIB_CHILD;
|
|
1536
1667
|
const buildTargets = resolveBuildTargets({
|
|
1537
1668
|
componentMap: contract.dev.componentMap,
|
|
@@ -1595,7 +1726,7 @@ const devToReactPlugin = (devComponentMap = {}, options = {})=>{
|
|
|
1595
1726
|
}
|
|
1596
1727
|
},
|
|
1597
1728
|
buildStart () {
|
|
1598
|
-
if (libBuildState.enabled && !libBuildState.virtualEntryCode) throw new Error(`${
|
|
1729
|
+
if (libBuildState.enabled && !libBuildState.virtualEntryCode) throw new Error(`${constants_PLUGIN_LOG_PREFIX} lib 构建模式已启用,但虚拟入口模块代码未生成。\n当前 component: "${libBuildState.currentComponent}"\n这可能是插件配置问题,请检查 vite.config.ts 中的配置。`);
|
|
1599
1730
|
},
|
|
1600
1731
|
async closeBundle () {
|
|
1601
1732
|
if (!libBuildState.enabled) return;
|
|
@@ -1637,7 +1768,7 @@ const devToReactPlugin = (devComponentMap = {}, options = {})=>{
|
|
|
1637
1768
|
}
|
|
1638
1769
|
},
|
|
1639
1770
|
resolveId (source) {
|
|
1640
|
-
if (source.includes(
|
|
1771
|
+
if (source.includes(constants_STABLE_CONTRACT_PATH)) return `\0virtual:${PLUGIN_NAME}-contract`;
|
|
1641
1772
|
if (source.includes(STABLE_INIT_PATH)) return `\0virtual:${PLUGIN_NAME}-init`;
|
|
1642
1773
|
if (source.includes(STABLE_REACT_RUNTIME_PATH)) return `\0virtual:${PLUGIN_NAME}-react-runtime`;
|
|
1643
1774
|
if (source.includes(`virtual:${PLUGIN_NAME}-lib-entry:`)) {
|
|
@@ -1655,7 +1786,7 @@ const devToReactPlugin = (devComponentMap = {}, options = {})=>{
|
|
|
1655
1786
|
const componentName = id.replace(`\0virtual:${PLUGIN_NAME}-lib-entry:`, '');
|
|
1656
1787
|
if (libBuildState.enabled) {
|
|
1657
1788
|
if (libBuildState.virtualEntryCode) return libBuildState.virtualEntryCode;
|
|
1658
|
-
throw new Error(`${
|
|
1789
|
+
throw new Error(`${constants_PLUGIN_LOG_PREFIX} 虚拟入口模块 "${id}" (componentName: "${componentName}") 的代码未生成。\n这可能是插件配置问题,请检查 vite.config.ts 中的配置。\n当前 libBuildState: ${JSON.stringify({
|
|
1659
1790
|
enabled: libBuildState.enabled,
|
|
1660
1791
|
currentComponent: libBuildState.currentComponent,
|
|
1661
1792
|
hasCode: !!libBuildState.virtualEntryCode
|
|
@@ -1676,11 +1807,11 @@ const devToReactPlugin = (devComponentMap = {}, options = {})=>{
|
|
|
1676
1807
|
libBuildState.currentComponent = componentName;
|
|
1677
1808
|
return code;
|
|
1678
1809
|
} catch (error) {
|
|
1679
|
-
throw new Error(`${
|
|
1810
|
+
throw new Error(`${constants_PLUGIN_LOG_PREFIX} 无法生成虚拟入口模块代码:${error instanceof Error ? error.message : String(error)}`);
|
|
1680
1811
|
}
|
|
1681
1812
|
}
|
|
1682
1813
|
}
|
|
1683
|
-
throw new Error(`${
|
|
1814
|
+
throw new Error(`${constants_PLUGIN_LOG_PREFIX} 虚拟入口模块 "${id}" (componentName: "${componentName}") 在插件配置完成之前被请求,且无法延迟生成代码。\n这可能是 Vite/Rollup 的内部时序问题。\n请尝试重新运行构建命令,如果问题持续,请检查 vite.config.ts 中的配置。`);
|
|
1684
1815
|
}
|
|
1685
1816
|
return null;
|
|
1686
1817
|
},
|
|
@@ -1720,4 +1851,4 @@ const devToReactPlugin = (devComponentMap = {}, options = {})=>{
|
|
|
1720
1851
|
];
|
|
1721
1852
|
};
|
|
1722
1853
|
const viteHostReactBridgePlugin = devToReactPlugin;
|
|
1723
|
-
export { EVENT_FULL_RELOAD, EVENT_HMR_UPDATE,
|
|
1854
|
+
export { EVENT_FULL_RELOAD, EVENT_HMR_UPDATE, PLUGIN_NAME, STABLE_BASE_PATH, STABLE_DEBUG_HTML_PATH, STABLE_DEBUG_JSON_PATH, STABLE_INIT_PATH, STABLE_REACT_RUNTIME_PATH, constants_PLUGIN_LOG_PREFIX as PLUGIN_LOG_PREFIX, constants_STABLE_CONTRACT_PATH as STABLE_CONTRACT_PATH, devToReactPlugin, viteHostReactBridgePlugin };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"libBuildUtils.d.ts","sourceRoot":"","sources":["../src/libBuildUtils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"libBuildUtils.d.ts","sourceRoot":"","sources":["../src/libBuildUtils.ts"],"names":[],"mappings":"AAIA,OAAO,EAAe,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,MAAM,MAAM,CAAA;AAKnE,OAAO,KAAK,EAAE,uBAAuB,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAA;AAErF,qBAAqB;AACrB,wBAAgB,UAAU,CAAC,GAAG,CAAC,EAAE,SAAS,WAEzC;AAED,kBAAkB;AAClB,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,UAErD;AAED,uBAAuB;AACvB,wBAAgB,aAAa,CAAC,aAAa,EAAE,MAAM,UAKlD;AAED,qBAAqB;AACrB,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,WAE/C;AA8LD,6BAA6B;AAC7B,wBAAgB,2BAA2B,CAAC,MAAM,EAAE;IAClD,OAAO,EAAE,MAAM,CAAA;IACf,eAAe,EAAE,MAAM,CAAA;IACvB,aAAa,EAAE,MAAM,CAAA;CACtB,GAAG,MAAM,CA+IT;AAED,kCAAkC;AAClC,wBAAgB,sBAAsB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED,oCAAoC;AACpC,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,QA+B/D;AAED,kBAAkB;AAClB,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAC1C,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACpC,YAAY,EAAE,MAAM,CAAA;IACpB,eAAe,EAAE,MAAM,CAAA;CACxB,YA8BA;AAED,iBAAiB;AACjB,wBAAgB,0BAA0B,CAAC,MAAM,EAAE;IACjD,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACpC,cAAc,EAAE,0BAA0B,CAAA;IAC1C,OAAO,EAAE,uBAAuB,CAAA;IAChC,UAAU,EAAE,UAAU,CAAA;CACvB,GAAG;IACF,IAAI,EAAE,UAAU,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,gBAAgB,EAAE,MAAM,CAAA;IACxB,gBAAgB,EAAE,MAAM,CAAA;CACzB,CAuGA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface CreateLoaderUmdWrapperOptions {
|
|
2
|
+
componentName: string;
|
|
3
|
+
origin: string;
|
|
4
|
+
contractEndpoint?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Generate a lightweight UMD wrapper that uses @dev-to/react-loader to load the component.
|
|
8
|
+
* This wrapper depends on react-loader UMD build being available.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createLoaderUmdWrapper(options: CreateLoaderUmdWrapperOptions): string;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=loaderUmdWrapper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loaderUmdWrapper.d.ts","sourceRoot":"","sources":["../src/loaderUmdWrapper.ts"],"names":[],"mappings":"AAGA,UAAU,6BAA6B;IACrC,aAAa,EAAE,MAAM,CAAA;IACrB,MAAM,EAAE,MAAM,CAAA;IACd,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,6BAA6B,GAAG,MAAM,CA2GrF"}
|
package/dist/virtualModules.d.ts
CHANGED
|
@@ -2,4 +2,13 @@ import type { BridgeContract } from './types.js';
|
|
|
2
2
|
export declare function createContractVirtualModuleCode(contract: BridgeContract): string;
|
|
3
3
|
export declare function createInitVirtualModuleCode(): string;
|
|
4
4
|
export declare function createReactRuntimeVirtualModuleCode(): string;
|
|
5
|
+
/**
|
|
6
|
+
* Generate loader wrapper code for a specific component.
|
|
7
|
+
* This creates a pre-configured loader script that can be imported directly.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createLoaderWrapperCode(params: {
|
|
10
|
+
componentName: string;
|
|
11
|
+
origin: string;
|
|
12
|
+
contractEndpoint?: string;
|
|
13
|
+
}): string;
|
|
5
14
|
//# sourceMappingURL=virtualModules.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"virtualModules.d.ts","sourceRoot":"","sources":["../src/virtualModules.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEhD,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,cAAc,UAuBvE;AAED,wBAAgB,2BAA2B,WAqE1C;AAED,wBAAgB,mCAAmC,WAuBlD"}
|
|
1
|
+
{"version":3,"file":"virtualModules.d.ts","sourceRoot":"","sources":["../src/virtualModules.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEhD,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,cAAc,UAuBvE;AAED,wBAAgB,2BAA2B,WAqE1C;AAED,wBAAgB,mCAAmC,WAuBlD;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,aAAa,EAAE,MAAM,CAAA;IACrB,MAAM,EAAE,MAAM,CAAA;IACd,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B,UAgDA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dev-to/react-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"picocolors": "^1.1.0",
|
|
33
33
|
"typescript": "^5.4.5",
|
|
34
|
-
"@dev-to/react-shared": "0.1.
|
|
34
|
+
"@dev-to/react-shared": "0.1.1"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
37
|
"build": "rslib build",
|