exportc 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -0
- package/commands/deploy.js +64 -0
- package/commands/dev.js +57 -0
- package/commands/init.js +310 -0
- package/index.js +75 -0
- package/package.json +53 -0
- package/vite-plugin.d.ts +41 -0
- package/vite-plugin.js +144 -0
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# exportc
|
|
2
|
+
|
|
3
|
+
Add [export](https://github.com/ihasq/export) to existing Vite projects.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# In your existing Vite project
|
|
9
|
+
npx exportc init
|
|
10
|
+
|
|
11
|
+
# Install dependencies
|
|
12
|
+
cd export && npm install
|
|
13
|
+
|
|
14
|
+
# Start development (two terminals)
|
|
15
|
+
npm run export:dev # Terminal 1: Wrangler dev server
|
|
16
|
+
npm run dev # Terminal 2: Vite dev server
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
After initialization, import your server exports using the `export:/` prefix:
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// In your Vite app
|
|
25
|
+
import { hello, Counter } from "export:/";
|
|
26
|
+
|
|
27
|
+
const message = await hello("World"); // "Hello, World!"
|
|
28
|
+
|
|
29
|
+
const counter = await new Counter(0);
|
|
30
|
+
await counter.increment(); // 1
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Commands
|
|
34
|
+
|
|
35
|
+
### `exportc init`
|
|
36
|
+
|
|
37
|
+
Initialize export in your Vite project:
|
|
38
|
+
- Creates `export/` directory with example server code
|
|
39
|
+
- Updates `vite.config.ts` with the export plugin
|
|
40
|
+
- Adds npm scripts for development and deployment
|
|
41
|
+
|
|
42
|
+
### `exportc dev`
|
|
43
|
+
|
|
44
|
+
Start the Wrangler development server for your exports.
|
|
45
|
+
|
|
46
|
+
### `exportc deploy`
|
|
47
|
+
|
|
48
|
+
Deploy your exports to Cloudflare Workers.
|
|
49
|
+
|
|
50
|
+
## Vite Plugin
|
|
51
|
+
|
|
52
|
+
The `exportPlugin` transforms `export:/` imports:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
// vite.config.ts
|
|
56
|
+
import { defineConfig } from "vite";
|
|
57
|
+
import { exportPlugin } from "exportc/vite";
|
|
58
|
+
|
|
59
|
+
export default defineConfig({
|
|
60
|
+
plugins: [
|
|
61
|
+
exportPlugin({
|
|
62
|
+
// Development server (default: http://localhost:8787)
|
|
63
|
+
dev: "http://localhost:8787",
|
|
64
|
+
// Production Worker URL (required for production builds)
|
|
65
|
+
production: "https://my-api.workers.dev",
|
|
66
|
+
}),
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Project Structure
|
|
72
|
+
|
|
73
|
+
After running `exportc init`:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
my-vite-app/
|
|
77
|
+
├── src/ # Your Vite app
|
|
78
|
+
├── export/ # Server exports (Cloudflare Worker)
|
|
79
|
+
│ ├── index.ts # Your server code
|
|
80
|
+
│ └── package.json # Worker configuration
|
|
81
|
+
├── export-env.d.ts # TypeScript declarations
|
|
82
|
+
└── vite.config.ts # Updated with exportPlugin
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## TypeScript Support
|
|
86
|
+
|
|
87
|
+
The `export-env.d.ts` file provides type declarations for `export:/` imports. Update it when you add new exports:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// export-env.d.ts
|
|
91
|
+
declare module "export:/" {
|
|
92
|
+
export function hello(name: string): Promise<string>;
|
|
93
|
+
export function myNewFunction(): Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
declare module "export:/utils" {
|
|
97
|
+
export function formatDate(date: Date): Promise<string>;
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
MIT
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
import pc from "picocolors";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
|
|
7
|
+
export async function deploy(argv) {
|
|
8
|
+
const cwd = process.cwd();
|
|
9
|
+
const exportDir = path.join(cwd, "export");
|
|
10
|
+
|
|
11
|
+
// Check if export directory exists
|
|
12
|
+
if (!fs.existsSync(exportDir)) {
|
|
13
|
+
p.cancel(`No export/ directory found. Run ${pc.cyan("exportc init")} first.`);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Check if dependencies are installed
|
|
18
|
+
const nodeModules = path.join(exportDir, "node_modules");
|
|
19
|
+
if (!fs.existsSync(nodeModules)) {
|
|
20
|
+
p.cancel(`Dependencies not installed. Run ${pc.cyan("cd export && npm install")} first.`);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
p.intro(pc.bgCyan(pc.black(" exportc deploy ")));
|
|
25
|
+
|
|
26
|
+
const s = p.spinner();
|
|
27
|
+
s.start("Deploying to Cloudflare Workers...");
|
|
28
|
+
|
|
29
|
+
// Run deploy
|
|
30
|
+
const deployProcess = spawn("npm", ["run", "deploy"], {
|
|
31
|
+
cwd: exportDir,
|
|
32
|
+
stdio: "inherit",
|
|
33
|
+
shell: true,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const exitCode = await new Promise((resolve) => {
|
|
37
|
+
deployProcess.on("close", resolve);
|
|
38
|
+
deployProcess.on("error", () => resolve(1));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (exitCode !== 0) {
|
|
42
|
+
s.stop("Deployment failed");
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
s.stop("Deployed successfully!");
|
|
47
|
+
|
|
48
|
+
// Read worker name from export/package.json
|
|
49
|
+
const exportPkgPath = path.join(exportDir, "package.json");
|
|
50
|
+
const exportPkg = JSON.parse(fs.readFileSync(exportPkgPath, "utf8"));
|
|
51
|
+
const workerName = exportPkg.name;
|
|
52
|
+
|
|
53
|
+
p.note(
|
|
54
|
+
`Your exports are now live at:
|
|
55
|
+
${pc.cyan(`https://${workerName}.workers.dev/`)}
|
|
56
|
+
|
|
57
|
+
${pc.bold("Update your Vite config for production:")}
|
|
58
|
+
${pc.dim("// vite.config.ts")}
|
|
59
|
+
${pc.cyan(`exportPlugin({ production: "https://${workerName}.workers.dev" })`)}`,
|
|
60
|
+
"Deployed"
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
p.outro("Done!");
|
|
64
|
+
}
|
package/commands/dev.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
import pc from "picocolors";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
|
|
7
|
+
export async function dev(argv) {
|
|
8
|
+
const cwd = process.cwd();
|
|
9
|
+
const exportDir = path.join(cwd, "export");
|
|
10
|
+
|
|
11
|
+
// Check if export directory exists
|
|
12
|
+
if (!fs.existsSync(exportDir)) {
|
|
13
|
+
p.cancel(`No export/ directory found. Run ${pc.cyan("exportc init")} first.`);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Check if dependencies are installed
|
|
18
|
+
const nodeModules = path.join(exportDir, "node_modules");
|
|
19
|
+
if (!fs.existsSync(nodeModules)) {
|
|
20
|
+
p.cancel(`Dependencies not installed. Run ${pc.cyan("cd export && npm install")} first.`);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
p.intro(pc.bgCyan(pc.black(" exportc dev ")));
|
|
25
|
+
|
|
26
|
+
const s = p.spinner();
|
|
27
|
+
s.start("Starting export dev server...");
|
|
28
|
+
|
|
29
|
+
// Generate types first
|
|
30
|
+
const generateTypes = spawn("npm", ["run", "dev"], {
|
|
31
|
+
cwd: exportDir,
|
|
32
|
+
stdio: "inherit",
|
|
33
|
+
shell: true,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
generateTypes.on("error", (err) => {
|
|
37
|
+
s.stop("Failed to start dev server");
|
|
38
|
+
console.error(pc.red(err.message));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
s.stop("Export dev server started!");
|
|
43
|
+
|
|
44
|
+
p.note(
|
|
45
|
+
`Wrangler dev server is running at ${pc.cyan("http://localhost:8787")}
|
|
46
|
+
|
|
47
|
+
${pc.bold("In your Vite app:")}
|
|
48
|
+
${pc.cyan(`import { hello } from "export:/";`)}
|
|
49
|
+
${pc.cyan(`const message = await hello("World");`)}
|
|
50
|
+
|
|
51
|
+
${pc.dim("Press Ctrl+C to stop")}`,
|
|
52
|
+
"Running"
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// Keep the process running
|
|
56
|
+
await new Promise(() => {});
|
|
57
|
+
}
|
package/commands/init.js
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
import pc from "picocolors";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
export async function init(argv) {
|
|
10
|
+
const cwd = process.cwd();
|
|
11
|
+
|
|
12
|
+
p.intro(pc.bgCyan(pc.black(" exportc init ")));
|
|
13
|
+
|
|
14
|
+
// Check for Vite project
|
|
15
|
+
const viteConfigFiles = ["vite.config.ts", "vite.config.js", "vite.config.mts", "vite.config.mjs"];
|
|
16
|
+
const viteConfig = viteConfigFiles.find((f) => fs.existsSync(path.join(cwd, f)));
|
|
17
|
+
|
|
18
|
+
if (!viteConfig) {
|
|
19
|
+
p.cancel("No Vite config found. exportc currently only supports Vite projects.");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Check for package.json
|
|
24
|
+
const pkgPath = path.join(cwd, "package.json");
|
|
25
|
+
if (!fs.existsSync(pkgPath)) {
|
|
26
|
+
p.cancel("No package.json found.");
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
31
|
+
|
|
32
|
+
// Check if already initialized
|
|
33
|
+
const exportDir = path.join(cwd, "export");
|
|
34
|
+
if (fs.existsSync(exportDir)) {
|
|
35
|
+
const overwrite = await p.confirm({
|
|
36
|
+
message: "export/ directory already exists. Continue and overwrite?",
|
|
37
|
+
initialValue: false,
|
|
38
|
+
});
|
|
39
|
+
if (p.isCancel(overwrite) || !overwrite) {
|
|
40
|
+
p.cancel("Operation cancelled.");
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Get worker name
|
|
46
|
+
const workerName = await p.text({
|
|
47
|
+
message: "Worker name:",
|
|
48
|
+
placeholder: pkg.name ? `${pkg.name}-api` : "my-api",
|
|
49
|
+
defaultValue: pkg.name ? `${pkg.name}-api` : "my-api",
|
|
50
|
+
validate: (v) => {
|
|
51
|
+
if (!v) return "Worker name is required";
|
|
52
|
+
if (!/^[a-z0-9-]+$/.test(v)) return "Use lowercase letters, numbers, and hyphens only";
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (p.isCancel(workerName)) {
|
|
57
|
+
p.cancel("Operation cancelled.");
|
|
58
|
+
process.exit(0);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check for TypeScript
|
|
62
|
+
const isTypeScript = viteConfig.endsWith(".ts") || viteConfig.endsWith(".mts") ||
|
|
63
|
+
fs.existsSync(path.join(cwd, "tsconfig.json"));
|
|
64
|
+
|
|
65
|
+
const s = p.spinner();
|
|
66
|
+
s.start("Initializing export...");
|
|
67
|
+
|
|
68
|
+
// Create export directory
|
|
69
|
+
if (!fs.existsSync(exportDir)) {
|
|
70
|
+
fs.mkdirSync(exportDir, { recursive: true });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Create export/index.ts or index.js
|
|
74
|
+
const ext = isTypeScript ? "ts" : "js";
|
|
75
|
+
const indexPath = path.join(exportDir, `index.${ext}`);
|
|
76
|
+
if (!fs.existsSync(indexPath)) {
|
|
77
|
+
const template = isTypeScript
|
|
78
|
+
? `// Server-side exports - these will be available to your client code
|
|
79
|
+
// Import from "export:/" in your client code
|
|
80
|
+
|
|
81
|
+
export async function hello(name: string): Promise<string> {
|
|
82
|
+
return \`Hello, \${name}!\`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function add(a: number, b: number): number {
|
|
86
|
+
return a + b;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export class Counter {
|
|
90
|
+
private count: number;
|
|
91
|
+
|
|
92
|
+
constructor(initial: number = 0) {
|
|
93
|
+
this.count = initial;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
increment(): number {
|
|
97
|
+
return ++this.count;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
getCount(): number {
|
|
101
|
+
return this.count;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
`
|
|
105
|
+
: `// Server-side exports - these will be available to your client code
|
|
106
|
+
// Import from "export:/" in your client code
|
|
107
|
+
|
|
108
|
+
export async function hello(name) {
|
|
109
|
+
return \`Hello, \${name}!\`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function add(a, b) {
|
|
113
|
+
return a + b;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export class Counter {
|
|
117
|
+
#count;
|
|
118
|
+
|
|
119
|
+
constructor(initial = 0) {
|
|
120
|
+
this.#count = initial;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
increment() {
|
|
124
|
+
return ++this.#count;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
getCount() {
|
|
128
|
+
return this.#count;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
`;
|
|
132
|
+
fs.writeFileSync(indexPath, template);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Create export/package.json
|
|
136
|
+
const exportPkgPath = path.join(exportDir, "package.json");
|
|
137
|
+
const exportPkg = {
|
|
138
|
+
name: workerName,
|
|
139
|
+
private: true,
|
|
140
|
+
type: "module",
|
|
141
|
+
exports: "./",
|
|
142
|
+
scripts: {
|
|
143
|
+
dev: "generate-export-types && wrangler dev",
|
|
144
|
+
deploy: "generate-export-types && wrangler deploy",
|
|
145
|
+
},
|
|
146
|
+
dependencies: {
|
|
147
|
+
"export-runtime": "^0.0.14",
|
|
148
|
+
},
|
|
149
|
+
devDependencies: {
|
|
150
|
+
wrangler: "^4.0.0",
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
if (isTypeScript) {
|
|
155
|
+
exportPkg.devDependencies["@cloudflare/workers-types"] = "^4.20241127.0";
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
fs.writeFileSync(exportPkgPath, JSON.stringify(exportPkg, null, 2) + "\n");
|
|
159
|
+
|
|
160
|
+
// Update main package.json scripts
|
|
161
|
+
pkg.scripts = pkg.scripts || {};
|
|
162
|
+
if (!pkg.scripts["export:dev"]) {
|
|
163
|
+
pkg.scripts["export:dev"] = "cd export && npm run dev";
|
|
164
|
+
}
|
|
165
|
+
if (!pkg.scripts["export:deploy"]) {
|
|
166
|
+
pkg.scripts["export:deploy"] = "cd export && npm run deploy";
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Add exportc to devDependencies
|
|
170
|
+
pkg.devDependencies = pkg.devDependencies || {};
|
|
171
|
+
if (!pkg.devDependencies.exportc) {
|
|
172
|
+
pkg.devDependencies.exportc = "^0.0.1";
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
176
|
+
|
|
177
|
+
// Update vite.config
|
|
178
|
+
const viteConfigPath = path.join(cwd, viteConfig);
|
|
179
|
+
let viteConfigContent = fs.readFileSync(viteConfigPath, "utf8");
|
|
180
|
+
|
|
181
|
+
// Check if export plugin is already added
|
|
182
|
+
if (!viteConfigContent.includes("exportc/vite")) {
|
|
183
|
+
// Add import at the top
|
|
184
|
+
const importStatement = `import { exportPlugin } from "exportc/vite";\n`;
|
|
185
|
+
|
|
186
|
+
// Find the first import or the start of the file
|
|
187
|
+
const importMatch = viteConfigContent.match(/^(import\s+.+\s+from\s+['"].+['"];?\s*\n?)+/m);
|
|
188
|
+
if (importMatch) {
|
|
189
|
+
const insertPos = importMatch.index + importMatch[0].length;
|
|
190
|
+
viteConfigContent =
|
|
191
|
+
viteConfigContent.slice(0, insertPos) +
|
|
192
|
+
importStatement +
|
|
193
|
+
viteConfigContent.slice(insertPos);
|
|
194
|
+
} else {
|
|
195
|
+
viteConfigContent = importStatement + viteConfigContent;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Add plugin to defineConfig
|
|
199
|
+
// Look for plugins array
|
|
200
|
+
if (viteConfigContent.includes("plugins:")) {
|
|
201
|
+
// Add to existing plugins array
|
|
202
|
+
viteConfigContent = viteConfigContent.replace(
|
|
203
|
+
/plugins:\s*\[/,
|
|
204
|
+
"plugins: [exportPlugin(), "
|
|
205
|
+
);
|
|
206
|
+
} else {
|
|
207
|
+
// Add plugins array to defineConfig
|
|
208
|
+
viteConfigContent = viteConfigContent.replace(
|
|
209
|
+
/defineConfig\(\s*\{/,
|
|
210
|
+
"defineConfig({\n plugins: [exportPlugin()],"
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
fs.writeFileSync(viteConfigPath, viteConfigContent);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Create .gitignore entries for export directory
|
|
218
|
+
const exportGitignorePath = path.join(exportDir, ".gitignore");
|
|
219
|
+
const gitignoreContent = `# Generated files
|
|
220
|
+
.export-*.js
|
|
221
|
+
.export-*.d.ts
|
|
222
|
+
export.d.ts
|
|
223
|
+
wrangler.toml
|
|
224
|
+
.wrangler/
|
|
225
|
+
.dev.vars
|
|
226
|
+
`;
|
|
227
|
+
fs.writeFileSync(exportGitignorePath, gitignoreContent);
|
|
228
|
+
|
|
229
|
+
// Create export-env.d.ts in main project for TypeScript support
|
|
230
|
+
if (isTypeScript) {
|
|
231
|
+
const envDtsPath = path.join(cwd, "export-env.d.ts");
|
|
232
|
+
if (!fs.existsSync(envDtsPath)) {
|
|
233
|
+
const envDtsContent = `// Type declarations for export:/ imports
|
|
234
|
+
// This file is auto-generated by exportc. You can modify it to add custom types.
|
|
235
|
+
|
|
236
|
+
// Re-export types from your export directory
|
|
237
|
+
// Update this when you add new exports
|
|
238
|
+
|
|
239
|
+
declare module "export:/" {
|
|
240
|
+
export function hello(name: string): Promise<string>;
|
|
241
|
+
export function add(a: number, b: number): Promise<number>;
|
|
242
|
+
export class Counter {
|
|
243
|
+
constructor(initial?: number);
|
|
244
|
+
increment(): Promise<number>;
|
|
245
|
+
getCount(): Promise<number>;
|
|
246
|
+
[Symbol.dispose](): Promise<void>;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Add more module declarations for subpaths:
|
|
251
|
+
// declare module "export:/utils" {
|
|
252
|
+
// export function myUtil(): Promise<void>;
|
|
253
|
+
// }
|
|
254
|
+
`;
|
|
255
|
+
fs.writeFileSync(envDtsPath, envDtsContent);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Update tsconfig.json to include the type declarations
|
|
259
|
+
const tsconfigPath = path.join(cwd, "tsconfig.json");
|
|
260
|
+
if (fs.existsSync(tsconfigPath)) {
|
|
261
|
+
try {
|
|
262
|
+
const tsconfigContent = fs.readFileSync(tsconfigPath, "utf8");
|
|
263
|
+
const tsconfig = JSON.parse(tsconfigContent);
|
|
264
|
+
|
|
265
|
+
// Add export-env.d.ts to include if not already present
|
|
266
|
+
tsconfig.include = tsconfig.include || [];
|
|
267
|
+
if (!tsconfig.include.includes("export-env.d.ts")) {
|
|
268
|
+
tsconfig.include.push("export-env.d.ts");
|
|
269
|
+
fs.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2) + "\n");
|
|
270
|
+
}
|
|
271
|
+
} catch {
|
|
272
|
+
// Ignore JSON parse errors (might have comments)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
s.stop("Export initialized!");
|
|
278
|
+
|
|
279
|
+
const filesCreated = isTypeScript
|
|
280
|
+
? `${pc.cyan("export/")}
|
|
281
|
+
├── index.${ext} ${pc.dim("# Your server exports")}
|
|
282
|
+
├── package.json ${pc.dim("# Worker configuration")}
|
|
283
|
+
└── .gitignore
|
|
284
|
+
|
|
285
|
+
${pc.cyan("export-env.d.ts")} ${pc.dim("# Type declarations for export:/ imports")}`
|
|
286
|
+
: `${pc.cyan("export/")}
|
|
287
|
+
├── index.${ext} ${pc.dim("# Your server exports")}
|
|
288
|
+
├── package.json ${pc.dim("# Worker configuration")}
|
|
289
|
+
└── .gitignore`;
|
|
290
|
+
|
|
291
|
+
p.note(
|
|
292
|
+
`${filesCreated}
|
|
293
|
+
|
|
294
|
+
${pc.bold("Next steps:")}
|
|
295
|
+
|
|
296
|
+
1. Install dependencies:
|
|
297
|
+
${pc.cyan("cd export && npm install")}
|
|
298
|
+
|
|
299
|
+
2. Start development:
|
|
300
|
+
${pc.cyan("npm run export:dev")} ${pc.dim("# In one terminal")}
|
|
301
|
+
${pc.cyan("npm run dev")} ${pc.dim("# In another terminal")}
|
|
302
|
+
|
|
303
|
+
3. Import in your client code:
|
|
304
|
+
${pc.cyan(`import { hello } from "export:/";`)}
|
|
305
|
+
${pc.cyan(`const message = await hello("World");`)}`,
|
|
306
|
+
"Created"
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
p.outro(`Run ${pc.cyan("cd export && npm install")} to get started!`);
|
|
310
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import mri from "mri";
|
|
4
|
+
import * as p from "@clack/prompts";
|
|
5
|
+
import pc from "picocolors";
|
|
6
|
+
import { init } from "./commands/init.js";
|
|
7
|
+
import { dev } from "./commands/dev.js";
|
|
8
|
+
import { deploy } from "./commands/deploy.js";
|
|
9
|
+
|
|
10
|
+
const argv = mri(process.argv.slice(2), {
|
|
11
|
+
alias: {
|
|
12
|
+
h: "help",
|
|
13
|
+
v: "version",
|
|
14
|
+
},
|
|
15
|
+
boolean: ["help", "version"],
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const command = argv._[0];
|
|
19
|
+
|
|
20
|
+
const HELP = `
|
|
21
|
+
${pc.bold("exportc")} - Add export to existing projects
|
|
22
|
+
|
|
23
|
+
${pc.bold("Usage:")}
|
|
24
|
+
exportc <command> [options]
|
|
25
|
+
|
|
26
|
+
${pc.bold("Commands:")}
|
|
27
|
+
init Initialize export in an existing Vite project
|
|
28
|
+
dev Start development server (Vite + Wrangler)
|
|
29
|
+
deploy Deploy to Cloudflare Workers
|
|
30
|
+
|
|
31
|
+
${pc.bold("Options:")}
|
|
32
|
+
-h, --help Show this help message
|
|
33
|
+
-v, --version Show version number
|
|
34
|
+
|
|
35
|
+
${pc.bold("Examples:")}
|
|
36
|
+
${pc.dim("# Add export to your Vite project")}
|
|
37
|
+
npx exportc init
|
|
38
|
+
|
|
39
|
+
${pc.dim("# Start development")}
|
|
40
|
+
npx exportc dev
|
|
41
|
+
|
|
42
|
+
${pc.dim("# Deploy to Cloudflare")}
|
|
43
|
+
npx exportc deploy
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
if (argv.help || !command) {
|
|
47
|
+
console.log(HELP);
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (argv.version) {
|
|
52
|
+
const pkg = await import("./package.json", { with: { type: "json" } });
|
|
53
|
+
console.log(pkg.default.version);
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const commands = {
|
|
58
|
+
init,
|
|
59
|
+
dev,
|
|
60
|
+
deploy,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const handler = commands[command];
|
|
64
|
+
if (!handler) {
|
|
65
|
+
console.error(pc.red(`Unknown command: ${command}`));
|
|
66
|
+
console.log(HELP);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
await handler(argv);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
p.cancel(err.message);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "exportc",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "CLI to add export to existing projects",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test": "node --test test/*.test.mjs"
|
|
7
|
+
},
|
|
8
|
+
"keywords": [
|
|
9
|
+
"cloudflare",
|
|
10
|
+
"workers",
|
|
11
|
+
"vite",
|
|
12
|
+
"esm",
|
|
13
|
+
"rpc"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/ihasq/export#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/ihasq/export/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/ihasq/export.git",
|
|
22
|
+
"directory": "packages/exportc"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"author": "ihasq",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"bin": {
|
|
28
|
+
"exportc": "./index.js"
|
|
29
|
+
},
|
|
30
|
+
"exports": {
|
|
31
|
+
".": "./index.js",
|
|
32
|
+
"./vite": "./vite-plugin.js"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"index.js",
|
|
36
|
+
"vite-plugin.js",
|
|
37
|
+
"vite-plugin.d.ts",
|
|
38
|
+
"commands"
|
|
39
|
+
],
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@clack/prompts": "^0.8.2",
|
|
42
|
+
"mri": "^1.2.0",
|
|
43
|
+
"picocolors": "^1.1.1"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"vite": "^5.0.0 || ^6.0.0"
|
|
47
|
+
},
|
|
48
|
+
"peerDependenciesMeta": {
|
|
49
|
+
"vite": {
|
|
50
|
+
"optional": true
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
package/vite-plugin.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
|
|
3
|
+
export interface ExportPluginOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Development server URL
|
|
6
|
+
* @default "http://localhost:8787"
|
|
7
|
+
*/
|
|
8
|
+
dev?: string;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Production Worker URL
|
|
12
|
+
* Required for production builds
|
|
13
|
+
* @example "https://my-api.workers.dev"
|
|
14
|
+
*/
|
|
15
|
+
production?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Vite plugin for export integration
|
|
20
|
+
*
|
|
21
|
+
* Allows importing server exports using the "export:/" prefix:
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { hello } from "export:/";
|
|
24
|
+
* import { utils } from "export:/utils";
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // vite.config.ts
|
|
29
|
+
* import { exportPlugin } from "exportc/vite";
|
|
30
|
+
*
|
|
31
|
+
* export default defineConfig({
|
|
32
|
+
* plugins: [
|
|
33
|
+
* exportPlugin({
|
|
34
|
+
* production: "https://my-api.workers.dev"
|
|
35
|
+
* })
|
|
36
|
+
* ]
|
|
37
|
+
* });
|
|
38
|
+
*/
|
|
39
|
+
export function exportPlugin(options?: ExportPluginOptions): Plugin;
|
|
40
|
+
|
|
41
|
+
export default exportPlugin;
|
package/vite-plugin.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite plugin for export integration
|
|
3
|
+
*
|
|
4
|
+
* Allows importing server exports using the "export:/" prefix:
|
|
5
|
+
* import { hello } from "export:/";
|
|
6
|
+
* import { utils } from "export:/utils";
|
|
7
|
+
*
|
|
8
|
+
* In development, proxies to local Wrangler dev server (localhost:8787)
|
|
9
|
+
* In production, resolves to the deployed Worker URL
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const EXPORT_PREFIX = "export:";
|
|
13
|
+
const DEFAULT_DEV_URL = "http://localhost:8787";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {Object} options
|
|
17
|
+
* @param {string} [options.dev] - Development server URL (default: http://localhost:8787)
|
|
18
|
+
* @param {string} [options.production] - Production Worker URL (required for production builds)
|
|
19
|
+
* @returns {import('vite').Plugin}
|
|
20
|
+
*/
|
|
21
|
+
export function exportPlugin(options = {}) {
|
|
22
|
+
const devUrl = options.dev || DEFAULT_DEV_URL;
|
|
23
|
+
const prodUrl = options.production;
|
|
24
|
+
|
|
25
|
+
let isDev = true;
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
name: "vite-plugin-export",
|
|
29
|
+
|
|
30
|
+
config(config, { command }) {
|
|
31
|
+
isDev = command === "serve";
|
|
32
|
+
|
|
33
|
+
// Add proxy for development
|
|
34
|
+
if (isDev) {
|
|
35
|
+
return {
|
|
36
|
+
server: {
|
|
37
|
+
proxy: {
|
|
38
|
+
// Proxy WebSocket connections
|
|
39
|
+
"^/__export_ws__": {
|
|
40
|
+
target: devUrl.replace(/^http/, "ws"),
|
|
41
|
+
ws: true,
|
|
42
|
+
rewrite: (path) => path.replace("/__export_ws__", ""),
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
resolveId(source) {
|
|
51
|
+
if (source.startsWith(EXPORT_PREFIX)) {
|
|
52
|
+
// Mark as external - we'll handle it in load
|
|
53
|
+
return { id: source, external: false };
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
load(id) {
|
|
59
|
+
if (!id.startsWith(EXPORT_PREFIX)) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Extract the path after "export:"
|
|
64
|
+
const exportPath = id.slice(EXPORT_PREFIX.length) || "/";
|
|
65
|
+
|
|
66
|
+
// In development, use the local dev server
|
|
67
|
+
// In production, use the configured production URL
|
|
68
|
+
const baseUrl = isDev ? devUrl : prodUrl;
|
|
69
|
+
|
|
70
|
+
if (!baseUrl) {
|
|
71
|
+
if (!isDev) {
|
|
72
|
+
this.error(
|
|
73
|
+
`[exportc] Production URL not configured. Add { production: "https://your-worker.workers.dev" } to exportPlugin() in vite.config.`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
this.error(`[exportc] Could not resolve base URL for export imports.`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Build the full URL
|
|
80
|
+
const fullUrl = new URL(exportPath, baseUrl).href;
|
|
81
|
+
|
|
82
|
+
// Generate a module that re-exports from the Worker URL
|
|
83
|
+
// For development, we dynamically import from the Worker
|
|
84
|
+
// For production, we use the deployed URL
|
|
85
|
+
const code = `
|
|
86
|
+
// Auto-generated by exportc/vite
|
|
87
|
+
// Importing from: ${fullUrl}
|
|
88
|
+
export * from "${fullUrl}";
|
|
89
|
+
export { default } from "${fullUrl}";
|
|
90
|
+
`;
|
|
91
|
+
|
|
92
|
+
return code;
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
// Transform import statements in the final bundle for production
|
|
96
|
+
transform(code, id) {
|
|
97
|
+
// Skip node_modules and non-JS files
|
|
98
|
+
if (id.includes("node_modules") || !/\.(js|ts|jsx|tsx|vue|svelte)$/.test(id)) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Replace dynamic imports of export: URLs
|
|
103
|
+
if (code.includes('import("export:') || code.includes("import('export:")) {
|
|
104
|
+
const baseUrl = isDev ? devUrl : prodUrl;
|
|
105
|
+
if (!baseUrl && !isDev) {
|
|
106
|
+
this.warn(
|
|
107
|
+
`[exportc] Production URL not configured for dynamic imports in ${id}`
|
|
108
|
+
);
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Replace export:/ with the actual URL
|
|
113
|
+
const transformed = code.replace(
|
|
114
|
+
/import\((['"])export:([^'"]*)\1\)/g,
|
|
115
|
+
(match, quote, path) => {
|
|
116
|
+
const fullUrl = new URL(path || "/", baseUrl).href;
|
|
117
|
+
return `import(${quote}${fullUrl}${quote})`;
|
|
118
|
+
}
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (transformed !== code) {
|
|
122
|
+
return { code: transformed, map: null };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return null;
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
// Generate TypeScript declarations hint
|
|
130
|
+
buildStart() {
|
|
131
|
+
if (isDev) {
|
|
132
|
+
this.info(
|
|
133
|
+
`[exportc] Development mode - proxying to ${devUrl}`
|
|
134
|
+
);
|
|
135
|
+
} else if (prodUrl) {
|
|
136
|
+
this.info(
|
|
137
|
+
`[exportc] Production mode - using ${prodUrl}`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export default exportPlugin;
|