prev-cli 0.24.12 → 0.24.13
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/cli.js +191 -106
- package/dist/preview-runtime/build-optimized.d.ts +11 -0
- package/dist/preview-runtime/tailwind.d.ts +11 -0
- package/dist/preview-runtime/vendors.d.ts +6 -0
- package/package.json +1 -1
- package/src/preview-runtime/build-optimized.test.ts +47 -0
- package/src/preview-runtime/build-optimized.ts +136 -0
- package/src/preview-runtime/tailwind.test.ts +30 -0
- package/src/preview-runtime/tailwind.ts +64 -0
- package/src/preview-runtime/vendors.test.ts +15 -0
- package/src/preview-runtime/vendors.ts +52 -0
package/dist/cli.js
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { parseArgs } from "util";
|
|
5
5
|
import path11 from "path";
|
|
6
|
-
import { existsSync as existsSync8, mkdirSync as
|
|
7
|
-
import { fileURLToPath as
|
|
6
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, writeFileSync as writeFileSync6, rmSync as rmSync4, readFileSync as readFileSync7 } from "fs";
|
|
7
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
8
8
|
|
|
9
9
|
// src/vite/start.ts
|
|
10
|
-
import { createServer as createServer2, build as
|
|
10
|
+
import { createServer as createServer2, build as build3, preview } from "vite";
|
|
11
11
|
|
|
12
12
|
// src/vite/config.ts
|
|
13
13
|
import { createLogger } from "vite";
|
|
@@ -17,8 +17,8 @@ import remarkGfm from "remark-gfm";
|
|
|
17
17
|
import rehypeHighlight from "rehype-highlight";
|
|
18
18
|
import path8 from "path";
|
|
19
19
|
import os from "os";
|
|
20
|
-
import { fileURLToPath as
|
|
21
|
-
import { existsSync as existsSync6, readFileSync as
|
|
20
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
21
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
22
22
|
|
|
23
23
|
// src/vite/plugins/pages-plugin.ts
|
|
24
24
|
import path2 from "path";
|
|
@@ -614,9 +614,95 @@ async function detectUnitFiles(unitDir, type) {
|
|
|
614
614
|
return result;
|
|
615
615
|
}
|
|
616
616
|
|
|
617
|
-
// src/preview-runtime/
|
|
617
|
+
// src/preview-runtime/vendors.ts
|
|
618
618
|
import { build } from "esbuild";
|
|
619
|
-
|
|
619
|
+
import { dirname } from "path";
|
|
620
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
621
|
+
var __dirname2 = dirname(fileURLToPath2(import.meta.url));
|
|
622
|
+
async function buildVendorBundle() {
|
|
623
|
+
try {
|
|
624
|
+
const entryCode = `
|
|
625
|
+
import * as React from 'react'
|
|
626
|
+
import * as ReactDOM from 'react-dom'
|
|
627
|
+
import { createRoot } from 'react-dom/client'
|
|
628
|
+
export { jsx, jsxs, Fragment } from 'react/jsx-runtime'
|
|
629
|
+
export { React, ReactDOM, createRoot }
|
|
630
|
+
export default React
|
|
631
|
+
`;
|
|
632
|
+
const result = await build({
|
|
633
|
+
stdin: {
|
|
634
|
+
contents: entryCode,
|
|
635
|
+
loader: "ts",
|
|
636
|
+
resolveDir: __dirname2
|
|
637
|
+
},
|
|
638
|
+
bundle: true,
|
|
639
|
+
write: false,
|
|
640
|
+
format: "esm",
|
|
641
|
+
target: "es2020",
|
|
642
|
+
minify: true
|
|
643
|
+
});
|
|
644
|
+
const jsFile = result.outputFiles?.find((f) => f.path.endsWith(".js")) || result.outputFiles?.[0];
|
|
645
|
+
if (!jsFile) {
|
|
646
|
+
return { success: false, code: "", error: "No output generated" };
|
|
647
|
+
}
|
|
648
|
+
return { success: true, code: jsFile.text };
|
|
649
|
+
} catch (err) {
|
|
650
|
+
return {
|
|
651
|
+
success: false,
|
|
652
|
+
code: "",
|
|
653
|
+
error: err instanceof Error ? err.message : String(err)
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// src/preview-runtime/build-optimized.ts
|
|
659
|
+
import { build as build2 } from "esbuild";
|
|
660
|
+
|
|
661
|
+
// src/preview-runtime/tailwind.ts
|
|
662
|
+
import { $ } from "bun";
|
|
663
|
+
import { mkdtempSync, mkdirSync, writeFileSync as writeFileSync2, readFileSync as readFileSync4, rmSync } from "fs";
|
|
664
|
+
import { join, dirname as dirname2 } from "path";
|
|
665
|
+
import { tmpdir } from "os";
|
|
666
|
+
async function compileTailwind(files) {
|
|
667
|
+
const tempDir = mkdtempSync(join(tmpdir(), "prev-tailwind-"));
|
|
668
|
+
try {
|
|
669
|
+
for (const file of files) {
|
|
670
|
+
const filePath = join(tempDir, file.path);
|
|
671
|
+
const parentDir = dirname2(filePath);
|
|
672
|
+
mkdirSync(parentDir, { recursive: true });
|
|
673
|
+
writeFileSync2(filePath, file.content);
|
|
674
|
+
}
|
|
675
|
+
const configContent = `
|
|
676
|
+
module.exports = {
|
|
677
|
+
content: [${JSON.stringify(tempDir + "/**/*.{tsx,jsx,ts,js,html}")}],
|
|
678
|
+
}
|
|
679
|
+
`;
|
|
680
|
+
const configPath = join(tempDir, "tailwind.config.cjs");
|
|
681
|
+
writeFileSync2(configPath, configContent);
|
|
682
|
+
const inputCss = `
|
|
683
|
+
@tailwind base;
|
|
684
|
+
@tailwind components;
|
|
685
|
+
@tailwind utilities;
|
|
686
|
+
`;
|
|
687
|
+
const inputPath = join(tempDir, "input.css");
|
|
688
|
+
writeFileSync2(inputPath, inputCss);
|
|
689
|
+
const outputPath = join(tempDir, "output.css");
|
|
690
|
+
await $`bunx tailwindcss -c ${configPath} -i ${inputPath} -o ${outputPath} --minify`.quiet();
|
|
691
|
+
const css = readFileSync4(outputPath, "utf-8");
|
|
692
|
+
return { success: true, css };
|
|
693
|
+
} catch (err) {
|
|
694
|
+
return {
|
|
695
|
+
success: false,
|
|
696
|
+
css: "",
|
|
697
|
+
error: err instanceof Error ? err.message : String(err)
|
|
698
|
+
};
|
|
699
|
+
} finally {
|
|
700
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// src/preview-runtime/build-optimized.ts
|
|
705
|
+
async function buildOptimizedPreview(config, options) {
|
|
620
706
|
try {
|
|
621
707
|
const virtualFs = {};
|
|
622
708
|
for (const file of config.files) {
|
|
@@ -626,25 +712,18 @@ async function buildPreviewHtml(config) {
|
|
|
626
712
|
}
|
|
627
713
|
const entryFile = config.files.find((f) => f.path === config.entry);
|
|
628
714
|
if (!entryFile) {
|
|
629
|
-
return { html: "", error: `Entry file not found: ${config.entry}` };
|
|
715
|
+
return { success: false, html: "", css: "", error: `Entry file not found: ${config.entry}` };
|
|
630
716
|
}
|
|
631
717
|
const hasDefaultExport = /export\s+default/.test(entryFile.content);
|
|
718
|
+
const userCssCollected = [];
|
|
632
719
|
const entryCode = hasDefaultExport ? `
|
|
633
|
-
import React from '
|
|
634
|
-
import { createRoot } from 'react-dom/client'
|
|
720
|
+
import React, { createRoot } from '${options.vendorPath}'
|
|
635
721
|
import App from './${config.entry}'
|
|
636
|
-
|
|
637
722
|
const root = createRoot(document.getElementById('root'))
|
|
638
723
|
root.render(React.createElement(App))
|
|
639
|
-
` : `
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
const result = await build({
|
|
643
|
-
stdin: {
|
|
644
|
-
contents: entryCode,
|
|
645
|
-
loader: "tsx",
|
|
646
|
-
resolveDir: "/"
|
|
647
|
-
},
|
|
724
|
+
` : `import './${config.entry}'`;
|
|
725
|
+
const result = await build2({
|
|
726
|
+
stdin: { contents: entryCode, loader: "tsx", resolveDir: "/" },
|
|
648
727
|
bundle: true,
|
|
649
728
|
write: false,
|
|
650
729
|
format: "esm",
|
|
@@ -652,84 +731,78 @@ async function buildPreviewHtml(config) {
|
|
|
652
731
|
jsxImportSource: "react",
|
|
653
732
|
target: "es2020",
|
|
654
733
|
minify: true,
|
|
655
|
-
plugins: [
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
for (const ext of [".tsx", ".ts", ".jsx", ".js", ".css"]) {
|
|
674
|
-
if (virtualFs[resolved + ext]) {
|
|
675
|
-
resolved = resolved + ext;
|
|
676
|
-
break;
|
|
734
|
+
plugins: [
|
|
735
|
+
{
|
|
736
|
+
name: "optimized-preview",
|
|
737
|
+
setup(build3) {
|
|
738
|
+
build3.onResolve({ filter: new RegExp(options.vendorPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")) }, (args) => {
|
|
739
|
+
return { path: args.path, external: true };
|
|
740
|
+
});
|
|
741
|
+
build3.onResolve({ filter: /^react(-dom)?(\/.*)?$/ }, () => {
|
|
742
|
+
return { path: options.vendorPath, external: true };
|
|
743
|
+
});
|
|
744
|
+
build3.onResolve({ filter: /^\./ }, (args) => {
|
|
745
|
+
let resolved = args.path.replace(/^\.\//, "");
|
|
746
|
+
if (!resolved.includes(".")) {
|
|
747
|
+
for (const ext of [".tsx", ".ts", ".jsx", ".js", ".css"]) {
|
|
748
|
+
if (virtualFs[resolved + ext]) {
|
|
749
|
+
resolved = resolved + ext;
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
677
752
|
}
|
|
678
753
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
const style = document.createElement('style');
|
|
690
|
-
style.textContent = \`${css}\`;
|
|
691
|
-
document.head.appendChild(style);
|
|
692
|
-
`,
|
|
693
|
-
loader: "js"
|
|
694
|
-
};
|
|
754
|
+
return { path: resolved, namespace: "virtual" };
|
|
755
|
+
});
|
|
756
|
+
build3.onLoad({ filter: /.*/, namespace: "virtual" }, (args) => {
|
|
757
|
+
const file = virtualFs[args.path];
|
|
758
|
+
if (file) {
|
|
759
|
+
if (file.loader === "css") {
|
|
760
|
+
userCssCollected.push(file.contents);
|
|
761
|
+
return { contents: "", loader: "js" };
|
|
762
|
+
}
|
|
763
|
+
return { contents: file.contents, loader: file.loader };
|
|
695
764
|
}
|
|
696
|
-
return { contents:
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
});
|
|
765
|
+
return { contents: "", loader: "empty" };
|
|
766
|
+
});
|
|
767
|
+
}
|
|
700
768
|
}
|
|
701
|
-
|
|
769
|
+
]
|
|
702
770
|
});
|
|
703
|
-
const jsFile = result.outputFiles
|
|
771
|
+
const jsFile = result.outputFiles?.find((f) => f.path.endsWith(".js")) || result.outputFiles?.[0];
|
|
704
772
|
const jsCode = jsFile?.text || "";
|
|
773
|
+
let css = "";
|
|
774
|
+
if (config.tailwind) {
|
|
775
|
+
const tailwindResult = await compileTailwind(config.files.map((f) => ({ path: f.path, content: f.content })));
|
|
776
|
+
if (tailwindResult.success)
|
|
777
|
+
css = tailwindResult.css;
|
|
778
|
+
}
|
|
779
|
+
const userCss = userCssCollected.join(`
|
|
780
|
+
`);
|
|
781
|
+
const allCss = css + `
|
|
782
|
+
` + userCss;
|
|
705
783
|
const html = `<!DOCTYPE html>
|
|
706
784
|
<html lang="en">
|
|
707
785
|
<head>
|
|
708
786
|
<meta charset="UTF-8">
|
|
709
787
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
710
788
|
<title>Preview</title>
|
|
711
|
-
<
|
|
712
|
-
<style>
|
|
713
|
-
body { margin: 0; }
|
|
714
|
-
#root { min-height: 100vh; }
|
|
715
|
-
</style>
|
|
789
|
+
<style>${allCss}</style>
|
|
790
|
+
<style>body { margin: 0; } #root { min-height: 100vh; }</style>
|
|
716
791
|
</head>
|
|
717
792
|
<body>
|
|
718
793
|
<div id="root"></div>
|
|
794
|
+
<script type="module" src="${options.vendorPath}"></script>
|
|
719
795
|
<script type="module">${jsCode}</script>
|
|
720
796
|
</body>
|
|
721
797
|
</html>`;
|
|
722
|
-
return { html };
|
|
798
|
+
return { success: true, html, css: allCss };
|
|
723
799
|
} catch (err) {
|
|
724
|
-
return {
|
|
725
|
-
html: "",
|
|
726
|
-
error: err instanceof Error ? err.message : String(err)
|
|
727
|
-
};
|
|
800
|
+
return { success: false, html: "", css: "", error: err instanceof Error ? err.message : String(err) };
|
|
728
801
|
}
|
|
729
802
|
}
|
|
730
803
|
|
|
731
804
|
// src/vite/plugins/previews-plugin.ts
|
|
732
|
-
import { existsSync as existsSync4, mkdirSync, rmSync, writeFileSync as
|
|
805
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
733
806
|
import path5 from "path";
|
|
734
807
|
var VIRTUAL_MODULE_ID2 = "virtual:prev-previews";
|
|
735
808
|
var RESOLVED_VIRTUAL_MODULE_ID2 = "\x00" + VIRTUAL_MODULE_ID2;
|
|
@@ -792,31 +865,43 @@ export function getByStatus(status) {
|
|
|
792
865
|
return;
|
|
793
866
|
const distDir = path5.join(rootDir, "dist");
|
|
794
867
|
const targetDir = path5.join(distDir, "_preview");
|
|
868
|
+
const vendorsDir = path5.join(targetDir, "_vendors");
|
|
795
869
|
const previewsDir = path5.join(rootDir, "previews");
|
|
796
870
|
const oldPreviewsDir = path5.join(distDir, "previews");
|
|
797
871
|
if (existsSync4(oldPreviewsDir)) {
|
|
798
|
-
|
|
872
|
+
rmSync2(oldPreviewsDir, { recursive: true });
|
|
799
873
|
}
|
|
800
874
|
if (existsSync4(targetDir)) {
|
|
801
|
-
|
|
875
|
+
rmSync2(targetDir, { recursive: true });
|
|
802
876
|
}
|
|
803
877
|
const previews = await scanPreviews(rootDir);
|
|
804
878
|
if (previews.length === 0)
|
|
805
879
|
return;
|
|
806
880
|
console.log(`
|
|
807
881
|
Building ${previews.length} preview(s)...`);
|
|
882
|
+
console.log(" Building shared vendor bundle...");
|
|
883
|
+
mkdirSync2(vendorsDir, { recursive: true });
|
|
884
|
+
const vendorResult = await buildVendorBundle();
|
|
885
|
+
if (!vendorResult.success) {
|
|
886
|
+
console.error(` ✗ Vendor bundle: ${vendorResult.error}`);
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
writeFileSync3(path5.join(vendorsDir, "runtime.js"), vendorResult.code);
|
|
890
|
+
console.log(" ✓ _vendors/runtime.js");
|
|
808
891
|
for (const preview of previews) {
|
|
809
892
|
const previewDir = path5.join(previewsDir, preview.name);
|
|
810
893
|
try {
|
|
811
894
|
const config = await buildPreviewConfig(previewDir);
|
|
812
|
-
const
|
|
813
|
-
|
|
895
|
+
const depth = preview.name.split("/").length;
|
|
896
|
+
const vendorPath = "../".repeat(depth) + "_vendors/runtime.js";
|
|
897
|
+
const result = await buildOptimizedPreview(config, { vendorPath });
|
|
898
|
+
if (!result.success) {
|
|
814
899
|
console.error(` ✗ ${preview.name}: ${result.error}`);
|
|
815
900
|
continue;
|
|
816
901
|
}
|
|
817
902
|
const outputDir = path5.join(targetDir, preview.name);
|
|
818
|
-
|
|
819
|
-
|
|
903
|
+
mkdirSync2(outputDir, { recursive: true });
|
|
904
|
+
writeFileSync3(path5.join(outputDir, "index.html"), result.html);
|
|
820
905
|
console.log(` ✓ ${preview.name}`);
|
|
821
906
|
} catch (err) {
|
|
822
907
|
console.error(` ✗ ${preview.name}: ${err}`);
|
|
@@ -936,7 +1021,7 @@ function validateConfig(raw) {
|
|
|
936
1021
|
return config;
|
|
937
1022
|
}
|
|
938
1023
|
// src/config/loader.ts
|
|
939
|
-
import { readFileSync as
|
|
1024
|
+
import { readFileSync as readFileSync5, existsSync as existsSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
940
1025
|
import path6 from "path";
|
|
941
1026
|
import yaml2 from "js-yaml";
|
|
942
1027
|
function findConfigFile(rootDir) {
|
|
@@ -954,7 +1039,7 @@ function loadConfig(rootDir) {
|
|
|
954
1039
|
return defaultConfig;
|
|
955
1040
|
}
|
|
956
1041
|
try {
|
|
957
|
-
const content =
|
|
1042
|
+
const content = readFileSync5(configPath, "utf-8");
|
|
958
1043
|
const raw = yaml2.load(content);
|
|
959
1044
|
return validateConfig(raw);
|
|
960
1045
|
} catch (error) {
|
|
@@ -970,7 +1055,7 @@ function saveConfig(rootDir, config) {
|
|
|
970
1055
|
quotingType: '"',
|
|
971
1056
|
forceQuotes: false
|
|
972
1057
|
});
|
|
973
|
-
|
|
1058
|
+
writeFileSync4(configPath, content, "utf-8");
|
|
974
1059
|
}
|
|
975
1060
|
function updateOrder(rootDir, pathKey, order) {
|
|
976
1061
|
const config = loadConfig(rootDir);
|
|
@@ -978,7 +1063,7 @@ function updateOrder(rootDir, pathKey, order) {
|
|
|
978
1063
|
saveConfig(rootDir, config);
|
|
979
1064
|
}
|
|
980
1065
|
// src/utils/debug.ts
|
|
981
|
-
import { mkdirSync as
|
|
1066
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
982
1067
|
import path7 from "path";
|
|
983
1068
|
|
|
984
1069
|
class DebugCollector {
|
|
@@ -1058,11 +1143,11 @@ class DebugCollector {
|
|
|
1058
1143
|
summary: this.generateSummary()
|
|
1059
1144
|
};
|
|
1060
1145
|
const debugDir = path7.join(this.rootDir, ".prev-debug");
|
|
1061
|
-
|
|
1146
|
+
mkdirSync3(debugDir, { recursive: true });
|
|
1062
1147
|
const date = new Date;
|
|
1063
1148
|
const filename = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}-${String(date.getHours()).padStart(2, "0")}-${String(date.getMinutes()).padStart(2, "0")}-${String(date.getSeconds()).padStart(2, "0")}.json`;
|
|
1064
1149
|
const filepath = path7.join(debugDir, filename);
|
|
1065
|
-
|
|
1150
|
+
writeFileSync5(filepath, JSON.stringify(report, null, 2));
|
|
1066
1151
|
return filepath;
|
|
1067
1152
|
}
|
|
1068
1153
|
}
|
|
@@ -1136,12 +1221,12 @@ function createFriendlyLogger() {
|
|
|
1136
1221
|
};
|
|
1137
1222
|
}
|
|
1138
1223
|
function findCliRoot2() {
|
|
1139
|
-
let dir = path8.dirname(
|
|
1224
|
+
let dir = path8.dirname(fileURLToPath3(import.meta.url));
|
|
1140
1225
|
for (let i = 0;i < 10; i++) {
|
|
1141
1226
|
const pkgPath = path8.join(dir, "package.json");
|
|
1142
1227
|
if (existsSync6(pkgPath)) {
|
|
1143
1228
|
try {
|
|
1144
|
-
const pkg = JSON.parse(
|
|
1229
|
+
const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
|
|
1145
1230
|
if (pkg.name === "prev-cli") {
|
|
1146
1231
|
return dir;
|
|
1147
1232
|
}
|
|
@@ -1152,7 +1237,7 @@ function findCliRoot2() {
|
|
|
1152
1237
|
break;
|
|
1153
1238
|
dir = parent;
|
|
1154
1239
|
}
|
|
1155
|
-
return path8.dirname(path8.dirname(
|
|
1240
|
+
return path8.dirname(path8.dirname(fileURLToPath3(import.meta.url)));
|
|
1156
1241
|
}
|
|
1157
1242
|
function findNodeModules(cliRoot2) {
|
|
1158
1243
|
const localNodeModules = path8.join(cliRoot2, "node_modules");
|
|
@@ -1249,7 +1334,7 @@ async function createViteConfig(options) {
|
|
|
1249
1334
|
}
|
|
1250
1335
|
const indexPath = path8.join(srcRoot2, "theme/index.html");
|
|
1251
1336
|
if (existsSync6(indexPath)) {
|
|
1252
|
-
server.transformIndexHtml(req.url,
|
|
1337
|
+
server.transformIndexHtml(req.url, readFileSync6(indexPath, "utf-8")).then((html) => {
|
|
1253
1338
|
res.setHeader("Content-Type", "text/html");
|
|
1254
1339
|
res.end(html);
|
|
1255
1340
|
}).catch(next);
|
|
@@ -1278,7 +1363,7 @@ async function createViteConfig(options) {
|
|
|
1278
1363
|
if (urlPath === "/_preview-runtime") {
|
|
1279
1364
|
const templatePath = path8.join(srcRoot2, "preview-runtime/template.html");
|
|
1280
1365
|
if (existsSync6(templatePath)) {
|
|
1281
|
-
const html =
|
|
1366
|
+
const html = readFileSync6(templatePath, "utf-8");
|
|
1282
1367
|
res.setHeader("Content-Type", "text/html");
|
|
1283
1368
|
res.end(html);
|
|
1284
1369
|
return;
|
|
@@ -1394,7 +1479,7 @@ async function createViteConfig(options) {
|
|
|
1394
1479
|
}
|
|
1395
1480
|
if (existsSync6(htmlPath)) {
|
|
1396
1481
|
try {
|
|
1397
|
-
let html =
|
|
1482
|
+
let html = readFileSync6(htmlPath, "utf-8");
|
|
1398
1483
|
const previewBase = `/_preview/${previewName}/`;
|
|
1399
1484
|
html = html.replace(/(src|href)=["']\.\/([^"']+)["']/g, `$1="${previewBase}$2"`);
|
|
1400
1485
|
const transformed = await server.transformIndexHtml(req.url, html);
|
|
@@ -1521,7 +1606,7 @@ async function findAvailablePort(minPort, maxPort) {
|
|
|
1521
1606
|
|
|
1522
1607
|
// src/vite/start.ts
|
|
1523
1608
|
import { exec } from "child_process";
|
|
1524
|
-
import { existsSync as existsSync7, rmSync as
|
|
1609
|
+
import { existsSync as existsSync7, rmSync as rmSync3, copyFileSync } from "fs";
|
|
1525
1610
|
import path9 from "path";
|
|
1526
1611
|
function printWelcome(type) {
|
|
1527
1612
|
console.log();
|
|
@@ -1559,11 +1644,11 @@ function clearCache(rootDir) {
|
|
|
1559
1644
|
const nodeModulesVite = path9.join(rootDir, "node_modules", ".vite");
|
|
1560
1645
|
let cleared = 0;
|
|
1561
1646
|
if (existsSync7(viteCacheDir)) {
|
|
1562
|
-
|
|
1647
|
+
rmSync3(viteCacheDir, { recursive: true });
|
|
1563
1648
|
cleared++;
|
|
1564
1649
|
}
|
|
1565
1650
|
if (existsSync7(nodeModulesVite)) {
|
|
1566
|
-
|
|
1651
|
+
rmSync3(nodeModulesVite, { recursive: true });
|
|
1567
1652
|
cleared++;
|
|
1568
1653
|
}
|
|
1569
1654
|
if (cleared === 0) {
|
|
@@ -1674,7 +1759,7 @@ async function buildSite(rootDir, options = {}) {
|
|
|
1674
1759
|
base: options.base,
|
|
1675
1760
|
debug: options.debug
|
|
1676
1761
|
});
|
|
1677
|
-
await
|
|
1762
|
+
await build3(config);
|
|
1678
1763
|
const debugCollector = getDebugCollector();
|
|
1679
1764
|
if (debugCollector) {
|
|
1680
1765
|
debugCollector.startPhase("buildComplete");
|
|
@@ -1766,11 +1851,11 @@ async function cleanCache(options) {
|
|
|
1766
1851
|
import yaml3 from "js-yaml";
|
|
1767
1852
|
function getVersion() {
|
|
1768
1853
|
try {
|
|
1769
|
-
let dir = path11.dirname(
|
|
1854
|
+
let dir = path11.dirname(fileURLToPath4(import.meta.url));
|
|
1770
1855
|
for (let i = 0;i < 5; i++) {
|
|
1771
1856
|
const pkgPath = path11.join(dir, "package.json");
|
|
1772
1857
|
if (existsSync8(pkgPath)) {
|
|
1773
|
-
const pkg = JSON.parse(
|
|
1858
|
+
const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
1774
1859
|
if (pkg.name === "prev-cli")
|
|
1775
1860
|
return pkg.version;
|
|
1776
1861
|
}
|
|
@@ -1898,7 +1983,7 @@ async function clearViteCache(rootDir2) {
|
|
|
1898
1983
|
try {
|
|
1899
1984
|
const prevCacheDir = await getCacheDir(rootDir2);
|
|
1900
1985
|
if (existsSync8(prevCacheDir)) {
|
|
1901
|
-
|
|
1986
|
+
rmSync4(prevCacheDir, { recursive: true });
|
|
1902
1987
|
cleared++;
|
|
1903
1988
|
console.log(` ✓ Removed ${prevCacheDir}`);
|
|
1904
1989
|
}
|
|
@@ -1906,12 +1991,12 @@ async function clearViteCache(rootDir2) {
|
|
|
1906
1991
|
const viteCacheDir = path11.join(rootDir2, ".vite");
|
|
1907
1992
|
const nodeModulesVite = path11.join(rootDir2, "node_modules", ".vite");
|
|
1908
1993
|
if (existsSync8(viteCacheDir)) {
|
|
1909
|
-
|
|
1994
|
+
rmSync4(viteCacheDir, { recursive: true });
|
|
1910
1995
|
cleared++;
|
|
1911
1996
|
console.log(` ✓ Removed .vite/`);
|
|
1912
1997
|
}
|
|
1913
1998
|
if (existsSync8(nodeModulesVite)) {
|
|
1914
|
-
|
|
1999
|
+
rmSync4(nodeModulesVite, { recursive: true });
|
|
1915
2000
|
cleared++;
|
|
1916
2001
|
console.log(` ✓ Removed node_modules/.vite/`);
|
|
1917
2002
|
}
|
|
@@ -1992,7 +2077,7 @@ order: {}
|
|
|
1992
2077
|
# - "getting-started.md"
|
|
1993
2078
|
# - "guides/"
|
|
1994
2079
|
`;
|
|
1995
|
-
|
|
2080
|
+
writeFileSync6(targetPath, configContent, "utf-8");
|
|
1996
2081
|
console.log(`
|
|
1997
2082
|
✨ Created ${targetPath}
|
|
1998
2083
|
`);
|
|
@@ -2020,7 +2105,7 @@ function createPreview(rootDir2, name) {
|
|
|
2020
2105
|
console.error(`Preview "${name}" already exists at: ${previewDir}`);
|
|
2021
2106
|
process.exit(1);
|
|
2022
2107
|
}
|
|
2023
|
-
|
|
2108
|
+
mkdirSync4(previewDir, { recursive: true });
|
|
2024
2109
|
const appTsx = `import { useState } from 'react'
|
|
2025
2110
|
import './styles.css'
|
|
2026
2111
|
|
|
@@ -2141,8 +2226,8 @@ export default function App() {
|
|
|
2141
2226
|
.dark\\:text-white { color: #fff; }
|
|
2142
2227
|
}
|
|
2143
2228
|
`;
|
|
2144
|
-
|
|
2145
|
-
|
|
2229
|
+
writeFileSync6(path11.join(previewDir, "App.tsx"), appTsx);
|
|
2230
|
+
writeFileSync6(path11.join(previewDir, "styles.css"), stylesCss);
|
|
2146
2231
|
console.log(`
|
|
2147
2232
|
✨ Created preview: previews/${name}/
|
|
2148
2233
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PreviewConfig } from './types';
|
|
2
|
+
export interface OptimizedBuildOptions {
|
|
3
|
+
vendorPath: string;
|
|
4
|
+
}
|
|
5
|
+
export interface OptimizedBuildResult {
|
|
6
|
+
success: boolean;
|
|
7
|
+
html: string;
|
|
8
|
+
css: string;
|
|
9
|
+
error?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function buildOptimizedPreview(config: PreviewConfig, options: OptimizedBuildOptions): Promise<OptimizedBuildResult>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface TailwindResult {
|
|
2
|
+
success: boolean;
|
|
3
|
+
css: string;
|
|
4
|
+
error?: string;
|
|
5
|
+
}
|
|
6
|
+
interface ContentFile {
|
|
7
|
+
path: string;
|
|
8
|
+
content: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function compileTailwind(files: ContentFile[]): Promise<TailwindResult>;
|
|
11
|
+
export {};
|
package/package.json
CHANGED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { test, expect } from 'bun:test'
|
|
2
|
+
import { buildOptimizedPreview } from './build-optimized'
|
|
3
|
+
import type { PreviewConfig } from './types'
|
|
4
|
+
|
|
5
|
+
// Tailwind CLI can be slow on first run
|
|
6
|
+
const TAILWIND_TIMEOUT = 30000
|
|
7
|
+
|
|
8
|
+
test('buildOptimizedPreview generates HTML with local vendor imports', async () => {
|
|
9
|
+
const config: PreviewConfig = {
|
|
10
|
+
files: [
|
|
11
|
+
{
|
|
12
|
+
path: 'index.tsx',
|
|
13
|
+
content: `export default function App() { return <div className="p-4">Hello</div> }`,
|
|
14
|
+
type: 'tsx',
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
entry: 'index.tsx',
|
|
18
|
+
tailwind: true,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const result = await buildOptimizedPreview(config, { vendorPath: '../_vendors/runtime.js' })
|
|
22
|
+
|
|
23
|
+
expect(result.success).toBe(true)
|
|
24
|
+
expect(result.html).toContain('../_vendors/runtime.js')
|
|
25
|
+
expect(result.html).not.toContain('esm.sh')
|
|
26
|
+
expect(result.html).not.toContain('tailwindcss/browser')
|
|
27
|
+
}, TAILWIND_TIMEOUT)
|
|
28
|
+
|
|
29
|
+
test('buildOptimizedPreview includes compiled CSS', async () => {
|
|
30
|
+
const config: PreviewConfig = {
|
|
31
|
+
files: [
|
|
32
|
+
{
|
|
33
|
+
path: 'index.tsx',
|
|
34
|
+
content: `export default function App() { return <div className="flex items-center bg-red-500">Hello</div> }`,
|
|
35
|
+
type: 'tsx',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
entry: 'index.tsx',
|
|
39
|
+
tailwind: true,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const result = await buildOptimizedPreview(config, { vendorPath: '../_vendors/runtime.js' })
|
|
43
|
+
|
|
44
|
+
expect(result.success).toBe(true)
|
|
45
|
+
expect(result.css).toContain('flex')
|
|
46
|
+
expect(result.css).toContain('bg-red-500')
|
|
47
|
+
}, TAILWIND_TIMEOUT)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { build } from 'esbuild'
|
|
2
|
+
import type { PreviewConfig } from './types'
|
|
3
|
+
import { compileTailwind } from './tailwind'
|
|
4
|
+
|
|
5
|
+
export interface OptimizedBuildOptions {
|
|
6
|
+
vendorPath: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface OptimizedBuildResult {
|
|
10
|
+
success: boolean
|
|
11
|
+
html: string
|
|
12
|
+
css: string
|
|
13
|
+
error?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function buildOptimizedPreview(
|
|
17
|
+
config: PreviewConfig,
|
|
18
|
+
options: OptimizedBuildOptions
|
|
19
|
+
): Promise<OptimizedBuildResult> {
|
|
20
|
+
try {
|
|
21
|
+
const virtualFs: Record<string, { contents: string; loader: string }> = {}
|
|
22
|
+
for (const file of config.files) {
|
|
23
|
+
const ext = file.path.split('.').pop()?.toLowerCase()
|
|
24
|
+
const loader = ext === 'css' ? 'css' : ext === 'json' ? 'json' : ext || 'tsx'
|
|
25
|
+
virtualFs[file.path] = { contents: file.content, loader }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const entryFile = config.files.find(f => f.path === config.entry)
|
|
29
|
+
if (!entryFile) {
|
|
30
|
+
return { success: false, html: '', css: '', error: `Entry file not found: ${config.entry}` }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const hasDefaultExport = /export\s+default/.test(entryFile.content)
|
|
34
|
+
const userCssCollected: string[] = []
|
|
35
|
+
|
|
36
|
+
const entryCode = hasDefaultExport
|
|
37
|
+
? `
|
|
38
|
+
import React, { createRoot } from '${options.vendorPath}'
|
|
39
|
+
import App from './${config.entry}'
|
|
40
|
+
const root = createRoot(document.getElementById('root'))
|
|
41
|
+
root.render(React.createElement(App))
|
|
42
|
+
`
|
|
43
|
+
: `import './${config.entry}'`
|
|
44
|
+
|
|
45
|
+
const result = await build({
|
|
46
|
+
stdin: { contents: entryCode, loader: 'tsx', resolveDir: '/' },
|
|
47
|
+
bundle: true,
|
|
48
|
+
write: false,
|
|
49
|
+
format: 'esm',
|
|
50
|
+
jsx: 'automatic',
|
|
51
|
+
jsxImportSource: 'react',
|
|
52
|
+
target: 'es2020',
|
|
53
|
+
minify: true,
|
|
54
|
+
plugins: [
|
|
55
|
+
{
|
|
56
|
+
name: 'optimized-preview',
|
|
57
|
+
setup(build) {
|
|
58
|
+
// External: vendor runtime
|
|
59
|
+
build.onResolve(
|
|
60
|
+
{ filter: new RegExp(options.vendorPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) },
|
|
61
|
+
args => {
|
|
62
|
+
return { path: args.path, external: true }
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
// External: React (map to vendor bundle)
|
|
67
|
+
build.onResolve({ filter: /^react(-dom)?(\/.*)?$/ }, () => {
|
|
68
|
+
return { path: options.vendorPath, external: true }
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
// Resolve relative imports
|
|
72
|
+
build.onResolve({ filter: /^\./ }, args => {
|
|
73
|
+
let resolved = args.path.replace(/^\.\//, '')
|
|
74
|
+
if (!resolved.includes('.')) {
|
|
75
|
+
for (const ext of ['.tsx', '.ts', '.jsx', '.js', '.css']) {
|
|
76
|
+
if (virtualFs[resolved + ext]) {
|
|
77
|
+
resolved = resolved + ext
|
|
78
|
+
break
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { path: resolved, namespace: 'virtual' }
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
// Load from virtual FS
|
|
86
|
+
build.onLoad({ filter: /.*/, namespace: 'virtual' }, args => {
|
|
87
|
+
const file = virtualFs[args.path]
|
|
88
|
+
if (file) {
|
|
89
|
+
if (file.loader === 'css') {
|
|
90
|
+
userCssCollected.push(file.contents)
|
|
91
|
+
return { contents: '', loader: 'js' }
|
|
92
|
+
}
|
|
93
|
+
return { contents: file.contents, loader: file.loader as any }
|
|
94
|
+
}
|
|
95
|
+
return { contents: '', loader: 'empty' }
|
|
96
|
+
})
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
const jsFile = result.outputFiles?.find(f => f.path.endsWith('.js')) || result.outputFiles?.[0]
|
|
103
|
+
const jsCode = jsFile?.text || ''
|
|
104
|
+
|
|
105
|
+
let css = ''
|
|
106
|
+
if (config.tailwind) {
|
|
107
|
+
const tailwindResult = await compileTailwind(
|
|
108
|
+
config.files.map(f => ({ path: f.path, content: f.content }))
|
|
109
|
+
)
|
|
110
|
+
if (tailwindResult.success) css = tailwindResult.css
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const userCss = userCssCollected.join('\n')
|
|
114
|
+
const allCss = css + '\n' + userCss
|
|
115
|
+
|
|
116
|
+
const html = `<!DOCTYPE html>
|
|
117
|
+
<html lang="en">
|
|
118
|
+
<head>
|
|
119
|
+
<meta charset="UTF-8">
|
|
120
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
121
|
+
<title>Preview</title>
|
|
122
|
+
<style>${allCss}</style>
|
|
123
|
+
<style>body { margin: 0; } #root { min-height: 100vh; }</style>
|
|
124
|
+
</head>
|
|
125
|
+
<body>
|
|
126
|
+
<div id="root"></div>
|
|
127
|
+
<script type="module" src="${options.vendorPath}"></script>
|
|
128
|
+
<script type="module">${jsCode}</script>
|
|
129
|
+
</body>
|
|
130
|
+
</html>`
|
|
131
|
+
|
|
132
|
+
return { success: true, html, css: allCss }
|
|
133
|
+
} catch (err) {
|
|
134
|
+
return { success: false, html: '', css: '', error: err instanceof Error ? err.message : String(err) }
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { test, expect } from 'bun:test'
|
|
2
|
+
import { compileTailwind } from './tailwind'
|
|
3
|
+
|
|
4
|
+
test('compileTailwind extracts used classes from content', async () => {
|
|
5
|
+
const content = `
|
|
6
|
+
export default function App() {
|
|
7
|
+
return <div className="flex items-center p-4 bg-blue-500">Hello</div>
|
|
8
|
+
}
|
|
9
|
+
`
|
|
10
|
+
|
|
11
|
+
const result = await compileTailwind([{ path: 'App.tsx', content }])
|
|
12
|
+
|
|
13
|
+
expect(result.success).toBe(true)
|
|
14
|
+
expect(result.css).toBeDefined()
|
|
15
|
+
expect(result.css).toContain('flex')
|
|
16
|
+
expect(result.css).toContain('items-center')
|
|
17
|
+
expect(result.css).toContain('bg-blue-500')
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('compileTailwind returns empty CSS for no Tailwind classes', async () => {
|
|
21
|
+
const content = `
|
|
22
|
+
export default function App() {
|
|
23
|
+
return <div style={{ color: 'red' }}>Hello</div>
|
|
24
|
+
}
|
|
25
|
+
`
|
|
26
|
+
|
|
27
|
+
const result = await compileTailwind([{ path: 'App.tsx', content }])
|
|
28
|
+
|
|
29
|
+
expect(result.success).toBe(true)
|
|
30
|
+
})
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { $ } from 'bun'
|
|
2
|
+
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync } from 'fs'
|
|
3
|
+
import { join, dirname } from 'path'
|
|
4
|
+
import { tmpdir } from 'os'
|
|
5
|
+
|
|
6
|
+
export interface TailwindResult {
|
|
7
|
+
success: boolean
|
|
8
|
+
css: string
|
|
9
|
+
error?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface ContentFile {
|
|
13
|
+
path: string
|
|
14
|
+
content: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function compileTailwind(files: ContentFile[]): Promise<TailwindResult> {
|
|
18
|
+
const tempDir = mkdtempSync(join(tmpdir(), 'prev-tailwind-'))
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
// Write content files (create parent dirs for nested paths)
|
|
22
|
+
for (const file of files) {
|
|
23
|
+
const filePath = join(tempDir, file.path)
|
|
24
|
+
const parentDir = dirname(filePath)
|
|
25
|
+
mkdirSync(parentDir, { recursive: true })
|
|
26
|
+
writeFileSync(filePath, file.content)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Create Tailwind config - use .cjs for compatibility
|
|
30
|
+
const configContent = `
|
|
31
|
+
module.exports = {
|
|
32
|
+
content: [${JSON.stringify(tempDir + '/**/*.{tsx,jsx,ts,js,html}')}],
|
|
33
|
+
}
|
|
34
|
+
`
|
|
35
|
+
const configPath = join(tempDir, 'tailwind.config.cjs')
|
|
36
|
+
writeFileSync(configPath, configContent)
|
|
37
|
+
|
|
38
|
+
// Create input CSS
|
|
39
|
+
const inputCss = `
|
|
40
|
+
@tailwind base;
|
|
41
|
+
@tailwind components;
|
|
42
|
+
@tailwind utilities;
|
|
43
|
+
`
|
|
44
|
+
const inputPath = join(tempDir, 'input.css')
|
|
45
|
+
writeFileSync(inputPath, inputCss)
|
|
46
|
+
|
|
47
|
+
const outputPath = join(tempDir, 'output.css')
|
|
48
|
+
|
|
49
|
+
// Run Tailwind CLI
|
|
50
|
+
await $`bunx tailwindcss -c ${configPath} -i ${inputPath} -o ${outputPath} --minify`.quiet()
|
|
51
|
+
|
|
52
|
+
const css = readFileSync(outputPath, 'utf-8')
|
|
53
|
+
|
|
54
|
+
return { success: true, css }
|
|
55
|
+
} catch (err) {
|
|
56
|
+
return {
|
|
57
|
+
success: false,
|
|
58
|
+
css: '',
|
|
59
|
+
error: err instanceof Error ? err.message : String(err),
|
|
60
|
+
}
|
|
61
|
+
} finally {
|
|
62
|
+
rmSync(tempDir, { recursive: true, force: true })
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { test, expect } from 'bun:test'
|
|
2
|
+
import { buildVendorBundle } from './vendors'
|
|
3
|
+
|
|
4
|
+
test('buildVendorBundle creates runtime.js with React', async () => {
|
|
5
|
+
const result = await buildVendorBundle()
|
|
6
|
+
expect(result.success).toBe(true)
|
|
7
|
+
expect(result.code).toBeDefined()
|
|
8
|
+
expect(result.code).toContain('createElement')
|
|
9
|
+
expect(result.code).toContain('createRoot')
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test('buildVendorBundle output is valid ESM', async () => {
|
|
13
|
+
const result = await buildVendorBundle()
|
|
14
|
+
expect(result.code).toContain('export')
|
|
15
|
+
})
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { build } from 'esbuild'
|
|
2
|
+
import { dirname } from 'path'
|
|
3
|
+
import { fileURLToPath } from 'url'
|
|
4
|
+
|
|
5
|
+
// Resolve from CLI's location, not user's project (React is our dependency)
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
7
|
+
|
|
8
|
+
export interface VendorBundleResult {
|
|
9
|
+
success: boolean
|
|
10
|
+
code: string
|
|
11
|
+
error?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function buildVendorBundle(): Promise<VendorBundleResult> {
|
|
15
|
+
try {
|
|
16
|
+
const entryCode = `
|
|
17
|
+
import * as React from 'react'
|
|
18
|
+
import * as ReactDOM from 'react-dom'
|
|
19
|
+
import { createRoot } from 'react-dom/client'
|
|
20
|
+
export { jsx, jsxs, Fragment } from 'react/jsx-runtime'
|
|
21
|
+
export { React, ReactDOM, createRoot }
|
|
22
|
+
export default React
|
|
23
|
+
`
|
|
24
|
+
|
|
25
|
+
const result = await build({
|
|
26
|
+
stdin: {
|
|
27
|
+
contents: entryCode,
|
|
28
|
+
loader: 'ts',
|
|
29
|
+
resolveDir: __dirname, // Resolve React from CLI's node_modules
|
|
30
|
+
},
|
|
31
|
+
bundle: true,
|
|
32
|
+
write: false,
|
|
33
|
+
format: 'esm',
|
|
34
|
+
target: 'es2020',
|
|
35
|
+
minify: true,
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
// Select JS output file explicitly (in case sourcemaps are added later)
|
|
39
|
+
const jsFile = result.outputFiles?.find(f => f.path.endsWith('.js')) || result.outputFiles?.[0]
|
|
40
|
+
if (!jsFile) {
|
|
41
|
+
return { success: false, code: '', error: 'No output generated' }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return { success: true, code: jsFile.text }
|
|
45
|
+
} catch (err) {
|
|
46
|
+
return {
|
|
47
|
+
success: false,
|
|
48
|
+
code: '',
|
|
49
|
+
error: err instanceof Error ? err.message : String(err),
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|