exportc 0.0.1 → 0.0.2

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 CHANGED
@@ -8,12 +8,11 @@ Add [export](https://github.com/ihasq/export) to existing Vite projects.
8
8
  # In your existing Vite project
9
9
  npx exportc init
10
10
 
11
- # Install dependencies
12
- cd export && npm install
11
+ # Install export dependencies
12
+ cd export && npm install && cd ..
13
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
14
+ # Start development (Wrangler starts automatically!)
15
+ npm run dev
17
16
  ```
18
17
 
19
18
  ## Usage
@@ -49,7 +48,7 @@ Deploy your exports to Cloudflare Workers.
49
48
 
50
49
  ## Vite Plugin
51
50
 
52
- The `exportPlugin` transforms `export:/` imports:
51
+ The `exportPlugin` automatically starts Wrangler and transforms `export:/` imports:
53
52
 
54
53
  ```typescript
55
54
  // vite.config.ts
@@ -59,15 +58,22 @@ import { exportPlugin } from "exportc/vite";
59
58
  export default defineConfig({
60
59
  plugins: [
61
60
  exportPlugin({
62
- // Development server (default: http://localhost:8787)
63
- dev: "http://localhost:8787",
64
61
  // Production Worker URL (required for production builds)
65
62
  production: "https://my-api.workers.dev",
63
+
64
+ // Optional: customize dev server
65
+ // port: 8787,
66
+ // autoStart: true, // Auto-start Wrangler (default: true)
66
67
  }),
67
68
  ],
68
69
  });
69
70
  ```
70
71
 
72
+ When you run `npm run dev`, the plugin:
73
+ 1. Automatically starts Wrangler dev server
74
+ 2. Waits for it to be ready
75
+ 3. Transforms `export:/` imports to point to the local server
76
+
71
77
  ## Project Structure
72
78
 
73
79
  After running `exportc init`:
package/commands/init.js CHANGED
@@ -157,13 +157,15 @@ export class Counter {
157
157
 
158
158
  fs.writeFileSync(exportPkgPath, JSON.stringify(exportPkg, null, 2) + "\n");
159
159
 
160
- // Update main package.json scripts
160
+ // Update main package.json
161
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";
162
+
163
+ // Add deploy script for export
164
+ if (!pkg.scripts["deploy"]) {
165
+ pkg.scripts["deploy"] = "cd export && npm run deploy";
166
+ } else if (!pkg.scripts["deploy"].includes("export")) {
167
+ // Add export deploy to existing deploy script
168
+ pkg.scripts["deploy:export"] = "cd export && npm run deploy";
167
169
  }
168
170
 
169
171
  // Add exportc to devDependencies
@@ -293,16 +295,18 @@ ${pc.cyan("export-env.d.ts")} ${pc.dim("# Type declarations for export:/ imp
293
295
 
294
296
  ${pc.bold("Next steps:")}
295
297
 
296
- 1. Install dependencies:
297
- ${pc.cyan("cd export && npm install")}
298
+ 1. Install export dependencies:
299
+ ${pc.cyan("cd export && npm install && cd ..")}
298
300
 
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")}
301
+ 2. Start development (Vite + Wrangler auto-start):
302
+ ${pc.cyan("npm run dev")}
302
303
 
303
304
  3. Import in your client code:
304
305
  ${pc.cyan(`import { hello } from "export:/";`)}
305
- ${pc.cyan(`const message = await hello("World");`)}`,
306
+ ${pc.cyan(`const message = await hello("World");`)}
307
+
308
+ 4. Deploy to Cloudflare:
309
+ ${pc.cyan("npm run deploy")}`,
306
310
  "Created"
307
311
  );
308
312
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "exportc",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "CLI to add export to existing projects",
5
5
  "scripts": {
6
6
  "test": "node --test test/*.test.mjs"
package/vite-plugin.d.ts CHANGED
@@ -7,18 +7,38 @@ export interface ExportPluginOptions {
7
7
  */
8
8
  dev?: string;
9
9
 
10
+ /**
11
+ * Development server port
12
+ * @default 8787
13
+ */
14
+ port?: number;
15
+
10
16
  /**
11
17
  * Production Worker URL
12
18
  * Required for production builds
13
19
  * @example "https://my-api.workers.dev"
14
20
  */
15
21
  production?: string;
22
+
23
+ /**
24
+ * Export directory path (relative to project root)
25
+ * @default "./export"
26
+ */
27
+ exportDir?: string;
28
+
29
+ /**
30
+ * Auto-start Wrangler dev server when running Vite in dev mode
31
+ * @default true
32
+ */
33
+ autoStart?: boolean;
16
34
  }
17
35
 
18
36
  /**
19
37
  * Vite plugin for export integration
20
38
  *
21
- * Allows importing server exports using the "export:/" prefix:
39
+ * Automatically starts Wrangler when you run `npm run dev` and allows
40
+ * importing server exports using the "export:/" prefix:
41
+ *
22
42
  * ```ts
23
43
  * import { hello } from "export:/";
24
44
  * import { utils } from "export:/utils";
@@ -31,6 +51,7 @@ export interface ExportPluginOptions {
31
51
  * export default defineConfig({
32
52
  * plugins: [
33
53
  * exportPlugin({
54
+ * // Required for production builds
34
55
  * production: "https://my-api.workers.dev"
35
56
  * })
36
57
  * ]
package/vite-plugin.js CHANGED
@@ -5,51 +5,175 @@
5
5
  * import { hello } from "export:/";
6
6
  * import { utils } from "export:/utils";
7
7
  *
8
- * In development, proxies to local Wrangler dev server (localhost:8787)
9
- * In production, resolves to the deployed Worker URL
8
+ * In development:
9
+ * - Automatically starts Wrangler dev server
10
+ * - Proxies to local Wrangler (localhost:8787)
11
+ *
12
+ * In production:
13
+ * - Resolves to the deployed Worker URL
10
14
  */
11
15
 
16
+ import { spawn } from "node:child_process";
17
+ import { existsSync } from "node:fs";
18
+ import { resolve } from "node:path";
19
+
12
20
  const EXPORT_PREFIX = "export:";
13
- const DEFAULT_DEV_URL = "http://localhost:8787";
21
+ const DEFAULT_DEV_PORT = 8787;
14
22
 
15
23
  /**
16
24
  * @param {Object} options
17
25
  * @param {string} [options.dev] - Development server URL (default: http://localhost:8787)
18
26
  * @param {string} [options.production] - Production Worker URL (required for production builds)
27
+ * @param {string} [options.exportDir] - Export directory (default: ./export)
28
+ * @param {boolean} [options.autoStart] - Auto-start Wrangler in dev mode (default: true)
19
29
  * @returns {import('vite').Plugin}
20
30
  */
21
31
  export function exportPlugin(options = {}) {
22
- const devUrl = options.dev || DEFAULT_DEV_URL;
32
+ const devPort = options.port || DEFAULT_DEV_PORT;
33
+ const devUrl = options.dev || `http://localhost:${devPort}`;
23
34
  const prodUrl = options.production;
35
+ const exportDir = options.exportDir || "./export";
36
+ const autoStart = options.autoStart !== false;
24
37
 
25
38
  let isDev = true;
39
+ let wranglerProcess = null;
40
+ let wranglerReady = false;
41
+ let wranglerReadyPromise = null;
42
+
43
+ const startWrangler = (root) => {
44
+ const exportPath = resolve(root, exportDir);
45
+
46
+ if (!existsSync(exportPath)) {
47
+ console.warn(`[exportc] Export directory not found: ${exportPath}`);
48
+ console.warn(`[exportc] Run 'npx exportc init' to initialize.`);
49
+ return Promise.resolve();
50
+ }
51
+
52
+ const nodeModulesPath = resolve(exportPath, "node_modules");
53
+ if (!existsSync(nodeModulesPath)) {
54
+ console.warn(`[exportc] Dependencies not installed in ${exportPath}`);
55
+ console.warn(`[exportc] Run 'cd ${exportDir} && npm install' first.`);
56
+ return Promise.resolve();
57
+ }
58
+
59
+ return new Promise((resolveReady) => {
60
+ console.log(`[exportc] Starting Wrangler dev server...`);
61
+
62
+ // Generate types first, then start wrangler
63
+ const generateTypes = spawn("npm", ["run", "dev"], {
64
+ cwd: exportPath,
65
+ stdio: ["ignore", "pipe", "pipe"],
66
+ shell: true,
67
+ });
68
+
69
+ wranglerProcess = generateTypes;
70
+
71
+ generateTypes.stdout.on("data", (data) => {
72
+ const output = data.toString();
73
+ process.stdout.write(`[export] ${output}`);
74
+
75
+ // Detect when wrangler is ready
76
+ if (output.includes("Ready on") || output.includes("Listening on") || output.includes("localhost")) {
77
+ if (!wranglerReady) {
78
+ wranglerReady = true;
79
+ console.log(`[exportc] Wrangler ready at ${devUrl}`);
80
+ resolveReady();
81
+ }
82
+ }
83
+ });
84
+
85
+ generateTypes.stderr.on("data", (data) => {
86
+ const output = data.toString();
87
+ // Filter out noisy warnings
88
+ if (!output.includes("ExperimentalWarning")) {
89
+ process.stderr.write(`[export] ${output}`);
90
+ }
91
+ // Also check stderr for ready message
92
+ if (output.includes("Ready on") || output.includes("Listening on") || output.includes("localhost:8787")) {
93
+ if (!wranglerReady) {
94
+ wranglerReady = true;
95
+ console.log(`[exportc] Wrangler ready at ${devUrl}`);
96
+ resolveReady();
97
+ }
98
+ }
99
+ });
100
+
101
+ generateTypes.on("error", (err) => {
102
+ console.error(`[exportc] Failed to start Wrangler:`, err.message);
103
+ resolveReady();
104
+ });
105
+
106
+ generateTypes.on("close", (code) => {
107
+ if (code !== 0 && code !== null) {
108
+ console.error(`[exportc] Wrangler exited with code ${code}`);
109
+ }
110
+ wranglerProcess = null;
111
+ resolveReady();
112
+ });
113
+
114
+ // Timeout after 30 seconds
115
+ setTimeout(() => {
116
+ if (!wranglerReady) {
117
+ console.warn(`[exportc] Wrangler startup timeout, continuing anyway...`);
118
+ wranglerReady = true;
119
+ resolveReady();
120
+ }
121
+ }, 30000);
122
+ });
123
+ };
124
+
125
+ const stopWrangler = () => {
126
+ if (wranglerProcess) {
127
+ console.log(`[exportc] Stopping Wrangler...`);
128
+ wranglerProcess.kill("SIGTERM");
129
+ wranglerProcess = null;
130
+ wranglerReady = false;
131
+ }
132
+ };
26
133
 
27
134
  return {
28
135
  name: "vite-plugin-export",
29
136
 
30
137
  config(config, { command }) {
31
138
  isDev = command === "serve";
139
+ return {};
140
+ },
141
+
142
+ configureServer(server) {
143
+ if (isDev && autoStart) {
144
+ const root = server.config.root || process.cwd();
145
+ wranglerReadyPromise = startWrangler(root);
146
+
147
+ // Cleanup on server close
148
+ server.httpServer?.on("close", stopWrangler);
149
+ }
150
+ },
32
151
 
33
- // Add proxy for development
152
+ async buildStart() {
34
153
  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
- };
154
+ if (autoStart && wranglerReadyPromise) {
155
+ await wranglerReadyPromise;
156
+ }
157
+ this.info(`Development mode - using ${devUrl}`);
158
+ } else if (prodUrl) {
159
+ this.info(`Production mode - using ${prodUrl}`);
160
+ } else {
161
+ this.warn(`Production URL not configured. Add 'production' option to exportPlugin().`);
47
162
  }
48
163
  },
49
164
 
165
+ buildEnd() {
166
+ if (!isDev) {
167
+ stopWrangler();
168
+ }
169
+ },
170
+
171
+ closeBundle() {
172
+ stopWrangler();
173
+ },
174
+
50
175
  resolveId(source) {
51
176
  if (source.startsWith(EXPORT_PREFIX)) {
52
- // Mark as external - we'll handle it in load
53
177
  return { id: source, external: false };
54
178
  }
55
179
  return null;
@@ -60,28 +184,20 @@ export function exportPlugin(options = {}) {
60
184
  return null;
61
185
  }
62
186
 
63
- // Extract the path after "export:"
64
187
  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
188
  const baseUrl = isDev ? devUrl : prodUrl;
69
189
 
70
190
  if (!baseUrl) {
71
191
  if (!isDev) {
72
192
  this.error(
73
- `[exportc] Production URL not configured. Add { production: "https://your-worker.workers.dev" } to exportPlugin() in vite.config.`
193
+ `[exportc] Production URL not configured. Add { production: "https://your-worker.workers.dev" } to exportPlugin().`
74
194
  );
75
195
  }
76
196
  this.error(`[exportc] Could not resolve base URL for export imports.`);
77
197
  }
78
198
 
79
- // Build the full URL
80
199
  const fullUrl = new URL(exportPath, baseUrl).href;
81
200
 
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
201
  const code = `
86
202
  // Auto-generated by exportc/vite
87
203
  // Importing from: ${fullUrl}
@@ -92,24 +208,18 @@ export { default } from "${fullUrl}";
92
208
  return code;
93
209
  },
94
210
 
95
- // Transform import statements in the final bundle for production
96
211
  transform(code, id) {
97
- // Skip node_modules and non-JS files
98
212
  if (id.includes("node_modules") || !/\.(js|ts|jsx|tsx|vue|svelte)$/.test(id)) {
99
213
  return null;
100
214
  }
101
215
 
102
- // Replace dynamic imports of export: URLs
103
216
  if (code.includes('import("export:') || code.includes("import('export:")) {
104
217
  const baseUrl = isDev ? devUrl : prodUrl;
105
218
  if (!baseUrl && !isDev) {
106
- this.warn(
107
- `[exportc] Production URL not configured for dynamic imports in ${id}`
108
- );
219
+ this.warn(`[exportc] Production URL not configured for dynamic imports in ${id}`);
109
220
  return null;
110
221
  }
111
222
 
112
- // Replace export:/ with the actual URL
113
223
  const transformed = code.replace(
114
224
  /import\((['"])export:([^'"]*)\1\)/g,
115
225
  (match, quote, path) => {
@@ -125,19 +235,6 @@ export { default } from "${fullUrl}";
125
235
 
126
236
  return null;
127
237
  },
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
238
  };
142
239
  }
143
240