zero-com 1.10.2 → 1.12.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/lib/common.d.ts CHANGED
@@ -8,6 +8,7 @@ export type Replacement = {
8
8
  export type Options = {
9
9
  development?: boolean;
10
10
  target?: 'client' | 'server';
11
+ contextDir?: string;
11
12
  };
12
13
  export type ServerFuncInfo = {
13
14
  funcId: string;
@@ -32,7 +33,9 @@ export declare const getReplacements: (compilationId: string) => {
32
33
  export declare const isFromLibrary: (callExpr: CallExpression, libraryName: string) => boolean;
33
34
  export declare const resolveFilePath: (basePath: string) => string;
34
35
  export declare const createProject: () => Project;
35
- export declare const buildRegistry: (contextDir: string, registry: ServerFuncRegistry) => void;
36
+ export declare const mightNeedTransform: (content: string, filePath: string, registry: ServerFuncRegistry) => boolean;
37
+ export declare const buildRegistry: (contextDir: string, registry: ServerFuncRegistry, project?: Project) => void;
38
+ export declare const updateRegistryForFile: (filePath: string, contextDir: string, registry: ServerFuncRegistry, project?: Project) => void;
36
39
  export declare const getImportedServerFunctions: (sourceFile: SourceFile, registry: ServerFuncRegistry) => Map<string, ServerFuncInfo>;
37
40
  export declare const collectCallSiteReplacements: (sourceFile: SourceFile, importedFuncs: Map<string, ServerFuncInfo>) => Replacement[];
38
41
  export declare const collectHandleCallReplacements: (sourceFile: SourceFile) => Replacement[];
@@ -53,6 +56,6 @@ export type TransformOptions = {
53
56
  development?: boolean;
54
57
  target?: 'client' | 'server';
55
58
  };
56
- export declare const transformSourceFile: (filePath: string, content: string, registry: ServerFuncRegistry, options?: TransformOptions) => TransformResult;
59
+ export declare const transformSourceFile: (filePath: string, content: string, registry: ServerFuncRegistry, options?: TransformOptions, project?: Project) => TransformResult;
57
60
  export declare const emitToJs: (filePath: string, content: string) => string;
58
61
  export declare const applyReplacements: (code: string, compilationId: string) => string;
package/lib/common.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.applyReplacements = exports.emitToJs = exports.transformSourceFile = exports.generateClientStubs = exports.appendRegistryCode = exports.applyReplacementsWithMap = exports.collectFuncCallReplacements = exports.collectSendCallReplacements = exports.collectHandleCallReplacements = exports.collectCallSiteReplacements = exports.getImportedServerFunctions = exports.buildRegistry = exports.createProject = exports.resolveFilePath = exports.isFromLibrary = exports.getReplacements = exports.generateCompilationId = exports.formatFuncIdName = exports.FILE_EXTENSIONS = exports.LIBRARY_NAME = exports.CALL_NAME = exports.HANDLE_NAME = exports.SERVER_FUNCTION_WRAPPER_NAME = exports.ZERO_COM_CONTEXT_STORAGE = exports.ZERO_COM_SERVER_REGISTRY = exports.ZERO_COM_CLIENT_CALL = void 0;
6
+ exports.applyReplacements = exports.emitToJs = exports.transformSourceFile = exports.generateClientStubs = exports.appendRegistryCode = exports.applyReplacementsWithMap = exports.collectFuncCallReplacements = exports.collectSendCallReplacements = exports.collectHandleCallReplacements = exports.collectCallSiteReplacements = exports.getImportedServerFunctions = exports.updateRegistryForFile = exports.buildRegistry = exports.mightNeedTransform = exports.createProject = exports.resolveFilePath = exports.isFromLibrary = exports.getReplacements = exports.generateCompilationId = exports.formatFuncIdName = exports.FILE_EXTENSIONS = exports.LIBRARY_NAME = exports.CALL_NAME = exports.HANDLE_NAME = exports.SERVER_FUNCTION_WRAPPER_NAME = exports.ZERO_COM_CONTEXT_STORAGE = exports.ZERO_COM_SERVER_REGISTRY = exports.ZERO_COM_CLIENT_CALL = void 0;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const magic_string_1 = __importDefault(require("magic-string"));
@@ -81,8 +81,32 @@ const getCalleeFullName = (callExpr) => {
81
81
  return expr.getText();
82
82
  return '';
83
83
  };
84
+ // Fast pre-filter: check if a file might need transformation before doing expensive ts-morph parsing
85
+ const mightNeedTransform = (content, filePath, registry) => {
86
+ var _a;
87
+ if (content.includes(exports.LIBRARY_NAME))
88
+ return true;
89
+ if (registry.has(filePath))
90
+ return true;
91
+ if (registry.size === 0)
92
+ return false;
93
+ // Quick import scan: check if any imported path matches a registered server function file
94
+ const importPattern = /from\s+['"](\.[^'"]+)['"]/g;
95
+ let match;
96
+ while ((match = importPattern.exec(content)) !== null) {
97
+ const importedSegment = (_a = match[1].split('/').pop()) !== null && _a !== void 0 ? _a : '';
98
+ if (!importedSegment)
99
+ continue;
100
+ for (const registeredPath of registry.keys()) {
101
+ if (registeredPath.includes(importedSegment))
102
+ return true;
103
+ }
104
+ }
105
+ return false;
106
+ };
107
+ exports.mightNeedTransform = mightNeedTransform;
84
108
  // Registry building
85
- const buildRegistry = (contextDir, registry) => {
109
+ const buildRegistry = (contextDir, registry, project) => {
86
110
  registry.clear();
87
111
  const scanDirectory = (dir) => {
88
112
  for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
@@ -91,15 +115,30 @@ const buildRegistry = (contextDir, registry) => {
91
115
  scanDirectory(fullPath);
92
116
  }
93
117
  else if (entry.isFile() && exports.FILE_EXTENSIONS.slice(1).includes(path_1.default.extname(entry.name))) {
94
- scanFileForServerFunctions(fullPath, contextDir, registry);
118
+ scanFileForServerFunctions(fullPath, contextDir, registry, project);
95
119
  }
96
120
  }
97
121
  };
98
122
  scanDirectory(contextDir);
99
123
  };
100
124
  exports.buildRegistry = buildRegistry;
101
- const scanFileForServerFunctions = (filePath, contextDir, registry) => {
102
- const sourceFile = (0, exports.createProject)().createSourceFile(filePath, fs_1.default.readFileSync(filePath, 'utf8'), { overwrite: true });
125
+ const updateRegistryForFile = (filePath, contextDir, registry, project) => {
126
+ if (!fs_1.default.existsSync(filePath)) {
127
+ registry.delete(filePath);
128
+ return;
129
+ }
130
+ const ext = path_1.default.extname(filePath);
131
+ if (!exports.FILE_EXTENSIONS.slice(1).includes(ext))
132
+ return;
133
+ scanFileForServerFunctions(filePath, contextDir, registry, project);
134
+ };
135
+ exports.updateRegistryForFile = updateRegistryForFile;
136
+ const scanFileForServerFunctions = (filePath, contextDir, registry, project) => {
137
+ const content = fs_1.default.readFileSync(filePath, 'utf8');
138
+ if (!content.includes(exports.LIBRARY_NAME))
139
+ return;
140
+ const proj = project !== null && project !== void 0 ? project : (0, exports.createProject)();
141
+ const sourceFile = proj.createSourceFile(filePath, content, { overwrite: true });
103
142
  const fileRegistry = new Map();
104
143
  for (const decl of sourceFile.getVariableDeclarations()) {
105
144
  if (!decl.isExported())
@@ -274,10 +313,10 @@ const generateClientStubs = (fileRegistry) => {
274
313
  return stubs;
275
314
  };
276
315
  exports.generateClientStubs = generateClientStubs;
277
- const transformSourceFile = (filePath, content, registry, options = {}) => {
316
+ const transformSourceFile = (filePath, content, registry, options = {}, project) => {
278
317
  const { development = true, target } = options;
279
- const project = (0, exports.createProject)();
280
- const sourceFile = project.createSourceFile(filePath, content, { overwrite: true });
318
+ const proj = project !== null && project !== void 0 ? project : (0, exports.createProject)();
319
+ const sourceFile = proj.createSourceFile(filePath, content, { overwrite: true });
281
320
  const fileRegistry = registry.get(filePath);
282
321
  const isServerFunctionFile = fileRegistry && fileRegistry.size > 0;
283
322
  if (target === 'client' && isServerFunctionFile) {
@@ -308,7 +347,7 @@ const transformSourceFile = (filePath, content, registry, options = {}) => {
308
347
  if (replacements.length > 0) {
309
348
  const { code, map } = (0, exports.applyReplacementsWithMap)(content, replacements, filePath);
310
349
  // Create a new source file from the transformed code to append registry
311
- const transformedSourceFile = project.createSourceFile(filePath + '.transformed', code, { overwrite: true });
350
+ const transformedSourceFile = proj.createSourceFile(filePath + '.transformed', code, { overwrite: true });
312
351
  return { content: (0, exports.appendRegistryCode)(transformedSourceFile, fileRegistry), transformed: true, map };
313
352
  }
314
353
  return { content: (0, exports.appendRegistryCode)(sourceFile, fileRegistry), transformed: true };
package/lib/rollup.js CHANGED
@@ -17,10 +17,12 @@ const fs_1 = __importDefault(require("fs"));
17
17
  const path_1 = __importDefault(require("path"));
18
18
  const common_1 = require("./common");
19
19
  function zeroComRollupPlugin(options = {}) {
20
- const { development = true, target } = options;
20
+ const { development = true, target, contextDir: optContextDir } = options;
21
21
  const compilationId = (0, common_1.generateCompilationId)();
22
22
  const registry = new Map();
23
+ const project = (0, common_1.createProject)();
23
24
  let isVite = false;
25
+ let scanDir = optContextDir !== null && optContextDir !== void 0 ? optContextDir : process.cwd();
24
26
  return {
25
27
  name: 'zero-com-rollup-plugin',
26
28
  // Vite-only hook — detect that we're running inside Vite
@@ -28,13 +30,20 @@ function zeroComRollupPlugin(options = {}) {
28
30
  isVite = true;
29
31
  },
30
32
  buildStart() {
31
- (0, common_1.buildRegistry)(process.cwd(), registry);
33
+ if (!optContextDir)
34
+ scanDir = process.cwd();
35
+ (0, common_1.buildRegistry)(scanDir, registry, project);
32
36
  for (const fileRegistry of registry.values()) {
33
37
  for (const info of fileRegistry.values()) {
34
38
  console.log(`[ZeroComRollupPlugin] ${info.funcId}`);
35
39
  }
36
40
  }
37
41
  },
42
+ watchChange(id) {
43
+ if (!common_1.FILE_EXTENSIONS.slice(1).includes(path_1.default.extname(id)))
44
+ return;
45
+ (0, common_1.updateRegistryForFile)(id, scanDir, registry, project);
46
+ },
38
47
  // Rollup path: resolveId marks files, load transforms them.
39
48
  // Skipped in Vite because meta doesn't propagate from resolveId to load.
40
49
  resolveId(source, importer, opts) {
@@ -61,7 +70,9 @@ function zeroComRollupPlugin(options = {}) {
61
70
  if (!((_b = (_a = this.getModuleInfo(id)) === null || _a === void 0 ? void 0 : _a.meta) === null || _b === void 0 ? void 0 : _b.needsTransform) || !fs_1.default.existsSync(id))
62
71
  return null;
63
72
  const content = fs_1.default.readFileSync(id, 'utf8');
64
- const result = (0, common_1.transformSourceFile)(id, content, registry, { development, target });
73
+ if (!(0, common_1.mightNeedTransform)(content, id, registry))
74
+ return null;
75
+ const result = (0, common_1.transformSourceFile)(id, content, registry, { development, target }, project);
65
76
  if (!result.transformed)
66
77
  return null;
67
78
  console.log(`[ZeroComRollupPlugin] Transformed: ${path_1.default.relative(process.cwd(), id)}`);
@@ -76,12 +87,14 @@ function zeroComRollupPlugin(options = {}) {
76
87
  return null;
77
88
  if (id[0] === '\0' || id.includes('node_modules') || !/\.(ts|tsx|js|jsx|mjs)$/.test(id))
78
89
  return null;
90
+ if (!(0, common_1.mightNeedTransform)(code, id, registry))
91
+ return null;
79
92
  // Derive effective target: explicit option takes precedence, otherwise infer from Vite's ssr flag
80
93
  const ssr = typeof viteOptions === 'object' && viteOptions !== null && 'ssr' in viteOptions
81
94
  ? viteOptions.ssr
82
95
  : undefined;
83
96
  const effectiveTarget = target !== null && target !== void 0 ? target : (ssr === false ? 'client' : ssr === true ? 'server' : undefined);
84
- const result = (0, common_1.transformSourceFile)(id, code, registry, { development, target: effectiveTarget });
97
+ const result = (0, common_1.transformSourceFile)(id, code, registry, { development, target: effectiveTarget }, project);
85
98
  if (!result.transformed)
86
99
  return null;
87
100
  console.log(`[ZeroComRollupPlugin] Transformed: ${path_1.default.relative(process.cwd(), id)}`);
@@ -6,9 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.default = turbopackLoader;
7
7
  const path_1 = __importDefault(require("path"));
8
8
  const common_1 = require("./common");
9
- // Module-level cache: registry is built once and reused across invocations
9
+ // Module-level cache: registry and project are built once and reused across invocations
10
10
  let cachedRegistry = null;
11
11
  let cachedRootDir = null;
12
+ let cachedProject = null;
12
13
  function turbopackLoader(source) {
13
14
  var _a;
14
15
  const options = this.getOptions();
@@ -17,16 +18,21 @@ function turbopackLoader(source) {
17
18
  const development = (_a = options.development) !== null && _a !== void 0 ? _a : true;
18
19
  // Lazily build and cache the registry on first invocation or if rootDir changes
19
20
  if (!cachedRegistry || cachedRootDir !== rootDir) {
21
+ cachedProject = (0, common_1.createProject)();
20
22
  cachedRegistry = new Map();
21
23
  cachedRootDir = rootDir;
22
- (0, common_1.buildRegistry)(rootDir, cachedRegistry);
24
+ (0, common_1.buildRegistry)(rootDir, cachedRegistry, cachedProject);
23
25
  for (const fileRegistry of cachedRegistry.values()) {
24
26
  for (const info of fileRegistry.values()) {
25
27
  console.log(`[TurbopackLoader] ${info.funcId}`);
26
28
  }
27
29
  }
28
30
  }
29
- const result = (0, common_1.transformSourceFile)(filePath, source, cachedRegistry, { development, target: options.target });
31
+ if (!(0, common_1.mightNeedTransform)(source, filePath, cachedRegistry)) {
32
+ this.callback(null, source);
33
+ return;
34
+ }
35
+ const result = (0, common_1.transformSourceFile)(filePath, source, cachedRegistry, { development, target: options.target }, cachedProject !== null && cachedProject !== void 0 ? cachedProject : undefined);
30
36
  if (!result.transformed) {
31
37
  this.callback(null, source);
32
38
  return;
@@ -1,7 +1,9 @@
1
1
  import type { LoaderContext } from 'webpack';
2
+ import { Project } from 'ts-morph';
2
3
  import { ServerFuncRegistry } from './common';
3
4
  export interface ZeroComLoaderOptions {
4
5
  registry: ServerFuncRegistry;
6
+ project?: Project;
5
7
  development: boolean;
6
8
  target?: 'client' | 'server';
7
9
  }
@@ -9,10 +9,14 @@ const common_1 = require("./common");
9
9
  function zeroComLoader(source) {
10
10
  const options = this.getOptions();
11
11
  const filePath = this.resourcePath;
12
+ if (!(0, common_1.mightNeedTransform)(source, filePath, options.registry)) {
13
+ this.callback(null, source);
14
+ return;
15
+ }
12
16
  const result = (0, common_1.transformSourceFile)(filePath, source, options.registry, {
13
17
  development: options.development,
14
18
  target: options.target
15
- });
19
+ }, options.project);
16
20
  if (!result.transformed) {
17
21
  this.callback(null, source);
18
22
  return;
package/lib/webpack.d.ts CHANGED
@@ -4,6 +4,7 @@ export declare class ZeroComWebpackPlugin {
4
4
  private options;
5
5
  private compilationId;
6
6
  private registry;
7
+ private project;
7
8
  constructor(options?: Options);
8
9
  apply(compiler: Compiler): void;
9
10
  }
package/lib/webpack.js CHANGED
@@ -5,21 +5,29 @@ const common_1 = require("./common");
5
5
  class ZeroComWebpackPlugin {
6
6
  constructor(options = {}) {
7
7
  this.registry = new Map();
8
+ this.project = (0, common_1.createProject)();
8
9
  this.options = Object.assign({ development: true }, options);
9
10
  this.compilationId = (0, common_1.generateCompilationId)();
10
11
  }
11
12
  apply(compiler) {
13
+ var _a;
12
14
  const pluginName = ZeroComWebpackPlugin.name;
13
15
  const { webpack } = compiler;
16
+ const contextDir = (_a = this.options.contextDir) !== null && _a !== void 0 ? _a : compiler.context;
14
17
  // Build registry before compilation
15
18
  compiler.hooks.beforeCompile.tap(pluginName, () => {
16
- (0, common_1.buildRegistry)(compiler.context, this.registry);
19
+ (0, common_1.buildRegistry)(contextDir, this.registry, this.project);
17
20
  for (const fileRegistry of this.registry.values()) {
18
21
  for (const info of fileRegistry.values()) {
19
22
  console.log(`[ZeroComWebpackPlugin] ${info.funcId}`);
20
23
  }
21
24
  }
22
25
  });
26
+ // Incrementally update registry when a file changes during watch mode
27
+ compiler.hooks.invalid.tap(pluginName, (fileName) => {
28
+ if (fileName)
29
+ (0, common_1.updateRegistryForFile)(fileName, contextDir, this.registry, this.project);
30
+ });
23
31
  // Add loader rule for TypeScript/JavaScript files
24
32
  const loaderPath = require.resolve('./webpack-loader');
25
33
  const loaderRule = {
@@ -28,7 +36,7 @@ class ZeroComWebpackPlugin {
28
36
  enforce: 'pre',
29
37
  use: [{
30
38
  loader: loaderPath,
31
- options: { registry: this.registry, development: this.options.development, target: this.options.target }
39
+ options: { registry: this.registry, project: this.project, development: this.options.development, target: this.options.target }
32
40
  }]
33
41
  };
34
42
  compiler.options.module.rules.unshift(loaderRule);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zero-com",
3
- "version": "1.10.2",
3
+ "version": "1.12.0",
4
4
  "main": "index.js",
5
5
  "repository": "https://github.com/yosbelms/zero-com",
6
6
  "keywords": [