@unlockable/vite-plugin-unlock 0.1.5 → 0.1.6

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/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  // src/plugin.ts
2
- import path5 from "path";
3
- import fs4 from "fs";
2
+ import path6 from "path";
3
+ import fs5 from "fs";
4
4
 
5
5
  // src/config.ts
6
- import path from "path";
7
- import fs from "fs";
6
+ import path2 from "path";
7
+ import fs2 from "fs";
8
8
  import { createRequire } from "module";
9
9
 
10
10
  // src/constants.ts
@@ -24,6 +24,8 @@ var MAX_SCAN_DEPTH = 20;
24
24
  var PLUGIN_NAME = "vite-plugin-unlock";
25
25
 
26
26
  // src/utils.ts
27
+ import path from "path";
28
+ import fs from "fs";
27
29
  var PREFIX = `[${PLUGIN_NAME}]`;
28
30
  function createLogger(debug) {
29
31
  return {
@@ -49,35 +51,85 @@ function generateAlias(packageName) {
49
51
  const lastPart = parts[parts.length - 1];
50
52
  return `~${lastPart}`;
51
53
  }
54
+ function collectCjsDeps(packageDir) {
55
+ const pkgJsonPath = path.join(packageDir, "package.json");
56
+ if (!fs.existsSync(pkgJsonPath)) return [];
57
+ let pkgJson;
58
+ try {
59
+ pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
60
+ } catch {
61
+ return [];
62
+ }
63
+ const deps = pkgJson.dependencies;
64
+ if (!deps) return [];
65
+ const cjsDeps = [];
66
+ for (const depName of Object.keys(deps)) {
67
+ if (depName.startsWith("@types/")) continue;
68
+ const depParts = depName.startsWith("@") ? depName.split("/") : [depName];
69
+ const candidates = [
70
+ path.join(packageDir, "node_modules", ...depParts, "package.json"),
71
+ path.join(process.cwd(), "node_modules", ...depParts, "package.json")
72
+ ];
73
+ let depPkgJson = null;
74
+ for (const candidate of candidates) {
75
+ if (fs.existsSync(candidate)) {
76
+ try {
77
+ depPkgJson = JSON.parse(fs.readFileSync(candidate, "utf-8"));
78
+ } catch {
79
+ continue;
80
+ }
81
+ break;
82
+ }
83
+ }
84
+ if (!depPkgJson) continue;
85
+ if (depPkgJson.type === "module") continue;
86
+ if (!hasRootEntry(depPkgJson)) continue;
87
+ cjsDeps.push(depName);
88
+ }
89
+ return cjsDeps;
90
+ }
91
+ function hasRootEntry(pkgJson) {
92
+ const exports = pkgJson.exports;
93
+ if (exports !== void 0) {
94
+ if (exports === null) return false;
95
+ if (typeof exports === "string") return true;
96
+ if (typeof exports === "object" && !Array.isArray(exports)) {
97
+ return "." in exports;
98
+ }
99
+ return Array.isArray(exports);
100
+ }
101
+ if (pkgJson.main || pkgJson.module) return true;
102
+ return false;
103
+ }
52
104
 
53
105
  // src/config.ts
54
106
  function findPackageSrcPath(packageName, srcDir) {
55
107
  const cwd = process.cwd();
56
108
  try {
57
- const req = createRequire(path.join(cwd, "package.json"));
58
- const pkgJsonPath = fs.realpathSync(
109
+ const req = createRequire(path2.join(cwd, "package.json"));
110
+ const pkgJsonPath = fs2.realpathSync(
59
111
  req.resolve(`${packageName}/package.json`)
60
112
  );
61
- const pkgRoot = path.dirname(pkgJsonPath);
62
- const srcPath = path.join(pkgRoot, srcDir);
63
- if (fs.existsSync(srcPath)) return srcPath;
113
+ const pkgRoot = path2.dirname(pkgJsonPath);
114
+ const srcPath = path2.join(pkgRoot, srcDir);
115
+ if (fs2.existsSync(srcPath)) return srcPath;
64
116
  return pkgRoot;
65
117
  } catch {
66
118
  }
67
119
  const parts = packageName.startsWith("@") ? packageName.split("/") : [packageName];
68
120
  const candidates = [
69
- path.join(cwd, "node_modules", ...parts, srcDir),
70
- path.join(cwd, ".yalc", ...parts, srcDir)
121
+ path2.join(cwd, "node_modules", ...parts, srcDir),
122
+ path2.join(cwd, ".yalc", ...parts, srcDir)
71
123
  ];
72
124
  for (const dir of candidates) {
73
- if (fs.existsSync(dir)) return fs.realpathSync(dir);
125
+ if (fs2.existsSync(dir)) return fs2.realpathSync(dir);
74
126
  }
75
127
  const rootCandidates = [
76
- path.join(cwd, "node_modules", ...parts),
77
- path.join(cwd, ".yalc", ...parts)
128
+ path2.join(cwd, "node_modules", ...parts),
129
+ path2.join(cwd, ".yalc", ...parts)
78
130
  ];
79
131
  for (const dir of rootCandidates) {
80
- if (fs.existsSync(dir)) return fs.realpathSync(dir);
132
+ if (fs2.existsSync(dir)) return fs2.realpathSync(dir);
81
133
  }
82
134
  return null;
83
135
  }
@@ -86,22 +138,24 @@ function resolveTarget(input) {
86
138
  package: input,
87
139
  alias: generateAlias(input),
88
140
  srcDir: DEFAULT_SRC_DIR,
89
- srcPath: ""
141
+ srcPath: "",
142
+ packageDir: ""
90
143
  } : {
91
144
  package: input.package,
92
145
  alias: input.alias ?? generateAlias(input.package),
93
146
  srcDir: input.srcDir ?? DEFAULT_SRC_DIR,
94
147
  srcPath: "",
148
+ packageDir: "",
95
149
  entryRedirect: input.entryRedirect,
96
150
  hmr: input.hmr
97
151
  };
98
152
  const srcPath = findPackageSrcPath(target.package, target.srcDir);
99
153
  if (!srcPath) return null;
100
154
  target.srcPath = srcPath;
155
+ target.packageDir = srcPath.endsWith(path2.sep + target.srcDir) ? path2.resolve(srcPath, "..") : srcPath;
101
156
  if (target.entryRedirect && target.hmr) {
102
- const pkgDir = path.dirname(srcPath);
103
- const entryFile = path.resolve(pkgDir, target.entryRedirect.to);
104
- if (fs.existsSync(entryFile)) {
157
+ const entryFile = path2.resolve(target.packageDir, target.entryRedirect.to);
158
+ if (fs2.existsSync(entryFile)) {
105
159
  target.entryFilePath = entryFile;
106
160
  }
107
161
  }
@@ -111,8 +165,8 @@ var CONFIG_EXTENSIONS = [".tsx", ".ts", ".jsx", ".js"];
111
165
  function findConfigFile(basename, overrideDirs) {
112
166
  for (const dir of overrideDirs) {
113
167
  for (const ext of CONFIG_EXTENSIONS) {
114
- const p = path.resolve(dir, `${basename}${ext}`);
115
- if (fs.existsSync(p)) return p;
168
+ const p = path2.resolve(dir, `${basename}${ext}`);
169
+ if (fs2.existsSync(p)) return p;
116
170
  }
117
171
  }
118
172
  return null;
@@ -120,7 +174,7 @@ function findConfigFile(basename, overrideDirs) {
120
174
  function resolveOptions(options) {
121
175
  const extensions = options.extensions ?? DEFAULT_EXTENSIONS;
122
176
  const overrideInput = options.overrides ?? DEFAULT_OVERRIDE_DIR;
123
- const overrideDirs = (Array.isArray(overrideInput) ? overrideInput : [overrideInput]).map((dir) => path.resolve(process.cwd(), dir));
177
+ const overrideDirs = (Array.isArray(overrideInput) ? overrideInput : [overrideInput]).map((dir) => path2.resolve(process.cwd(), dir));
124
178
  const targets = [];
125
179
  for (const input of options.targets) {
126
180
  const resolved = resolveTarget(input);
@@ -143,29 +197,33 @@ function resolveOptions(options) {
143
197
  extensions,
144
198
  extensionSet: new Set(extensions),
145
199
  patches,
146
- hmrBoundaries: options.hmrBoundaries ?? []
200
+ hmrBoundaries: options.hmrBoundaries ?? [],
201
+ optimizeDeps: {
202
+ include: options.optimizeDeps?.include ?? []
203
+ },
204
+ autoOptimizeDeps: options.autoOptimizeDeps ?? true
147
205
  };
148
206
  }
149
207
 
150
208
  // src/scanner.ts
151
- import path2 from "path";
152
- import fs2 from "fs";
209
+ import path3 from "path";
210
+ import fs3 from "fs";
153
211
  function collectFiles(dir, extensionSet, depth = 0) {
154
212
  if (depth > MAX_SCAN_DEPTH) return [];
155
213
  const results = [];
156
214
  let entries;
157
215
  try {
158
- entries = fs2.readdirSync(dir, { withFileTypes: true });
216
+ entries = fs3.readdirSync(dir, { withFileTypes: true });
159
217
  } catch {
160
218
  return results;
161
219
  }
162
220
  for (const entry of entries) {
163
221
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
164
- const fullPath = path2.resolve(dir, entry.name);
222
+ const fullPath = path3.resolve(dir, entry.name);
165
223
  if (entry.isDirectory()) {
166
224
  results.push(...collectFiles(fullPath, extensionSet, depth + 1));
167
225
  } else if (entry.isFile()) {
168
- const ext = path2.extname(entry.name);
226
+ const ext = path3.extname(entry.name);
169
227
  if (extensionSet.has(ext)) {
170
228
  results.push(fullPath);
171
229
  }
@@ -183,7 +241,7 @@ function getOverrideKey(filePath, baseDir, match) {
183
241
  if (match === "path") {
184
242
  const normalizedBase = normalizePath(baseDir);
185
243
  const relative = normalizePath(
186
- path2.relative(normalizedBase, normalized)
244
+ path3.relative(normalizedBase, normalized)
187
245
  );
188
246
  return stripExtension(relative);
189
247
  }
@@ -214,7 +272,7 @@ function scanAllTargets(opts, logger) {
214
272
  return combined;
215
273
  }
216
274
  function detectNamespace(filePath, overrideDir) {
217
- const relative = normalizePath(path2.relative(overrideDir, filePath));
275
+ const relative = normalizePath(path3.relative(overrideDir, filePath));
218
276
  const scopedMatch = relative.match(/^(@[^/]+\/[^/]+)\//);
219
277
  if (scopedMatch) return scopedMatch[1];
220
278
  return null;
@@ -224,13 +282,13 @@ function scanOverrides(opts, logger) {
224
282
  const namespaced = /* @__PURE__ */ new Map();
225
283
  const targetPackages = new Set(opts.targets.map((t) => t.package));
226
284
  for (const dir of opts.overrideDirs) {
227
- if (!fs2.existsSync(dir)) continue;
285
+ if (!fs3.existsSync(dir)) continue;
228
286
  for (const fullPath of collectFiles(dir, opts.extensionSet).sort()) {
229
- const relative = normalizePath(path2.relative(dir, fullPath));
287
+ const relative = normalizePath(path3.relative(dir, fullPath));
230
288
  if (relative.split("/").some((part) => part.startsWith("_"))) continue;
231
289
  const ns = detectNamespace(fullPath, dir);
232
290
  if (ns && targetPackages.has(ns)) {
233
- const nsDir = path2.join(dir, ns);
291
+ const nsDir = path3.join(dir, ns);
234
292
  const key = getOverrideKey(fullPath, nsDir, opts.match);
235
293
  if (key && key !== "index") {
236
294
  if (!namespaced.has(ns)) namespaced.set(ns, /* @__PURE__ */ new Map());
@@ -250,8 +308,8 @@ function scanOverrides(opts, logger) {
250
308
  }
251
309
 
252
310
  // src/resolver.ts
253
- import path3 from "path";
254
- import fs3 from "fs";
311
+ import path4 from "path";
312
+ import fs4 from "fs";
255
313
  function findImporterTarget(importer, opts) {
256
314
  const norm = normalizePath(importer);
257
315
  for (const target of opts.targets) {
@@ -259,7 +317,7 @@ function findImporterTarget(importer, opts) {
259
317
  if (norm.startsWith(normSrc + "/") || norm === normSrc) {
260
318
  return target;
261
319
  }
262
- const pkgDir = normalizePath(path3.dirname(target.srcPath));
320
+ const pkgDir = normalizePath(path4.dirname(target.srcPath));
263
321
  if (norm.startsWith(pkgDir + "/")) {
264
322
  return target;
265
323
  }
@@ -272,11 +330,11 @@ function resolveEntryRedirect(resolvedId, opts, logger) {
272
330
  if (target.entryRedirect) {
273
331
  const fromPattern = normalizePath(target.entryRedirect.from);
274
332
  if (norm.endsWith(`/${fromPattern}`) || norm.includes(`/${fromPattern}`)) {
275
- const pkgDir = path3.dirname(target.srcPath);
276
- const srcEntry = path3.join(pkgDir, target.entryRedirect.to);
277
- if (fs3.existsSync(srcEntry)) {
333
+ const pkgDir = path4.dirname(target.srcPath);
334
+ const srcEntry = path4.join(pkgDir, target.entryRedirect.to);
335
+ if (fs4.existsSync(srcEntry)) {
278
336
  logger.info(
279
- `Entry redirect: ${path3.basename(resolvedId)} -> ${target.entryRedirect.to}`
337
+ `Entry redirect: ${path4.basename(resolvedId)} -> ${target.entryRedirect.to}`
280
338
  );
281
339
  return srcEntry;
282
340
  }
@@ -290,7 +348,7 @@ function resolveEntryRedirect(resolvedId, opts, logger) {
290
348
  /\/dist\/app\.(mjs|js)$/,
291
349
  "/src/app.tsx"
292
350
  );
293
- if (fs3.existsSync(srcEntry)) {
351
+ if (fs4.existsSync(srcEntry)) {
294
352
  logger.info(`Entry redirect (auto): dist/app -> src/app.tsx`);
295
353
  return srcEntry;
296
354
  }
@@ -301,11 +359,11 @@ function resolveEntryRedirect(resolvedId, opts, logger) {
301
359
  }
302
360
 
303
361
  // src/watcher.ts
304
- import path4 from "path";
362
+ import path5 from "path";
305
363
  function setupWatcher(server, state, opts, logger) {
306
364
  let debounceTimer = null;
307
365
  const handleStructuralChange = (filePath) => {
308
- const ext = path4.extname(filePath);
366
+ const ext = path5.extname(filePath);
309
367
  if (!opts.extensionSet.has(ext)) return;
310
368
  const normFile = normalizePath(filePath);
311
369
  const isOverrideFile = opts.overrideDirs.some(
@@ -371,14 +429,14 @@ function setupWatcher(server, state, opts, logger) {
371
429
  server.watcher.on("add", handleStructuralChange);
372
430
  server.watcher.on("unlink", handleStructuralChange);
373
431
  server.watcher.on("change", (filePath) => {
374
- const ext = path4.extname(filePath);
432
+ const ext = path5.extname(filePath);
375
433
  if (!opts.extensionSet.has(ext)) return;
376
434
  const normFile = normalizePath(filePath);
377
435
  const isOverrideFile = opts.overrideDirs.some(
378
436
  (dir) => normFile.startsWith(normalizePath(dir) + "/")
379
437
  );
380
438
  if (!isOverrideFile) return;
381
- const basename = stripExtension(path4.basename(filePath));
439
+ const basename = stripExtension(path5.basename(filePath));
382
440
  if (!basename) return;
383
441
  const isTrackedOverride = state.flatOverrides.has(basename) || [...state.namespacedOverrides.values()].some((m) => m.has(basename));
384
442
  if (!isTrackedOverride) return;
@@ -400,7 +458,7 @@ function setupWatcher(server, state, opts, logger) {
400
458
  });
401
459
  if (opts.patches.length > 0) {
402
460
  const handlePatchConfigStructural = (filePath) => {
403
- const basename = stripExtension(path4.basename(filePath));
461
+ const basename = stripExtension(path5.basename(filePath));
404
462
  if (!basename) return;
405
463
  const normFile = normalizePath(filePath);
406
464
  const isOverrideFile = opts.overrideDirs.some(
@@ -448,7 +506,7 @@ function invalidatePatchTarget(patch, server, logger) {
448
506
  if (mod.file && patch.target.test(normalizePath(mod.file))) {
449
507
  moduleGraph.invalidateModule(mod, /* @__PURE__ */ new Set(), timestamp, true);
450
508
  roots.add(mod);
451
- logger.info(`Invalidated patch target: ${path4.basename(mod.file)}`);
509
+ logger.info(`Invalidated patch target: ${path5.basename(mod.file)}`);
452
510
  }
453
511
  }
454
512
  const seen = /* @__PURE__ */ new Set();
@@ -566,7 +624,7 @@ function unlock(userOptions) {
566
624
  config.server.fs = config.server.fs || {};
567
625
  config.server.fs.allow = config.server.fs.allow || [];
568
626
  for (const dir of opts.overrideDirs) {
569
- config.server.fs.allow.push(path5.resolve(dir));
627
+ config.server.fs.allow.push(path6.resolve(dir));
570
628
  }
571
629
  config.optimizeDeps = config.optimizeDeps || {};
572
630
  config.optimizeDeps.exclude = config.optimizeDeps.exclude || [];
@@ -578,6 +636,30 @@ function unlock(userOptions) {
578
636
  );
579
637
  }
580
638
  }
639
+ if (opts.autoOptimizeDeps) {
640
+ const autoDeps = [];
641
+ for (const target of opts.targets) {
642
+ const cjsDeps = collectCjsDeps(target.packageDir);
643
+ for (const dep of cjsDeps) {
644
+ if (opts.targets.some((t) => t.package === dep)) continue;
645
+ if (!autoDeps.includes(dep)) autoDeps.push(dep);
646
+ }
647
+ }
648
+ if (autoDeps.length > 0) {
649
+ config.optimizeDeps.include = config.optimizeDeps.include || [];
650
+ config.optimizeDeps.include.push(...autoDeps);
651
+ logger.info(
652
+ `Auto-included ${autoDeps.length} CJS deps for pre-bundling`
653
+ );
654
+ }
655
+ }
656
+ if (opts.optimizeDeps.include.length > 0) {
657
+ config.optimizeDeps.include = config.optimizeDeps.include || [];
658
+ config.optimizeDeps.include.push(...opts.optimizeDeps.include);
659
+ }
660
+ if (config.optimizeDeps.include) {
661
+ config.optimizeDeps.include = [...new Set(config.optimizeDeps.include)];
662
+ }
581
663
  config.resolve = config.resolve || {};
582
664
  config.resolve.alias = config.resolve.alias || {};
583
665
  for (const target of opts.targets) {
@@ -592,8 +674,8 @@ function unlock(userOptions) {
592
674
  }
593
675
  }
594
676
  for (const target of opts.targets) {
595
- const entryFile = path5.join(target.srcPath, "app.tsx");
596
- if (fs4.existsSync(entryFile)) {
677
+ const entryFile = path6.join(target.srcPath, "app.tsx");
678
+ if (fs5.existsSync(entryFile)) {
597
679
  const existing = config.optimizeDeps.entries;
598
680
  if (Array.isArray(existing)) {
599
681
  existing.push(entryFile);
@@ -620,19 +702,19 @@ function unlock(userOptions) {
620
702
  return null;
621
703
  const target = findImporterTarget(importer, opts);
622
704
  if (target) {
623
- const basename = stripExtension(path5.basename(source));
705
+ const basename = stripExtension(path6.basename(source));
624
706
  if (basename && basename !== "index") {
625
707
  const nsOverrides = state.namespacedOverrides.get(target.package);
626
708
  if (nsOverrides?.has(basename)) {
627
709
  const p = nsOverrides.get(basename);
628
710
  logger.info(
629
- `Override [${target.package}]: ${basename} -> ${path5.basename(p)}`
711
+ `Override [${target.package}]: ${basename} -> ${path6.basename(p)}`
630
712
  );
631
713
  return p;
632
714
  }
633
715
  if (state.flatOverrides.has(basename)) {
634
716
  const p = state.flatOverrides.get(basename);
635
- logger.info(`Override: ${basename} -> ${path5.basename(p)}`);
717
+ logger.info(`Override: ${basename} -> ${path6.basename(p)}`);
636
718
  return p;
637
719
  }
638
720
  }
@@ -667,15 +749,15 @@ function unlock(userOptions) {
667
749
  load(id) {
668
750
  const target = findImporterTarget(id, opts);
669
751
  if (!target) return null;
670
- const basename = stripExtension(path5.basename(id));
752
+ const basename = stripExtension(path6.basename(id));
671
753
  if (!basename || basename === "index") return null;
672
754
  const nsOverrides = state.namespacedOverrides.get(target.package);
673
755
  const overridePath = nsOverrides?.get(basename) ?? state.flatOverrides.get(basename);
674
- if (overridePath && fs4.existsSync(overridePath)) {
756
+ if (overridePath && fs5.existsSync(overridePath)) {
675
757
  this.addWatchFile(overridePath);
676
758
  const normalizedPath = normalizePath(overridePath).replace(/"/g, '\\"');
677
759
  logger.info(
678
- `Load override: ${basename} -> ${path5.basename(overridePath)}`
760
+ `Load override: ${basename} -> ${path6.basename(overridePath)}`
679
761
  );
680
762
  return `export { default } from "${normalizedPath}"
681
763
  export * from "${normalizedPath}"`;
@@ -683,18 +765,18 @@ export * from "${normalizedPath}"`;
683
765
  const normalizedId = normalizePath(id);
684
766
  for (const patch of opts.patches) {
685
767
  if (!patch.target.test(normalizedId)) continue;
686
- if (!patch.configPath || !fs4.existsSync(patch.configPath)) continue;
768
+ if (!patch.configPath || !fs5.existsSync(patch.configPath)) continue;
687
769
  this.addWatchFile(patch.configPath);
688
770
  let original;
689
771
  try {
690
- original = fs4.readFileSync(id, "utf-8");
772
+ original = fs5.readFileSync(id, "utf-8");
691
773
  } catch (err) {
692
774
  logger.error(`Failed to read file for patching: ${id}`);
693
775
  return null;
694
776
  }
695
777
  const patched = patch.apply(original, patch.configPath);
696
778
  logger.info(
697
- `Patch applied: ${path5.basename(id)} via ${patch.configFile}`
779
+ `Patch applied: ${path6.basename(id)} via ${patch.configFile}`
698
780
  );
699
781
  return { code: patched, map: null };
700
782
  }
@@ -733,7 +815,7 @@ export * from "${normalizedPath}"`;
733
815
  );
734
816
  if (needsBoundary) {
735
817
  logger.info(
736
- `HMR boundary injected: ${path5.basename(id)}`
818
+ `HMR boundary injected: ${path6.basename(id)}`
737
819
  );
738
820
  return {
739
821
  code: code + "\nif (import.meta.hot) { import.meta.hot.accept() }",
@@ -748,7 +830,7 @@ export * from "${normalizedPath}"`;
748
830
  const fsConfig = server.config.server?.fs;
749
831
  if (fsConfig && Array.isArray(fsConfig.allow)) {
750
832
  for (const dir of opts.overrideDirs) {
751
- const resolved = path5.resolve(dir);
833
+ const resolved = path6.resolve(dir);
752
834
  if (!fsConfig.allow.includes(resolved)) {
753
835
  fsConfig.allow.push(resolved);
754
836
  }