@wyw-in-js/transform 1.0.3 → 1.0.5

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.
Files changed (66) hide show
  1. package/esm/cache.js +47 -6
  2. package/esm/cache.js.map +1 -1
  3. package/esm/module.js +2 -2
  4. package/esm/module.js.map +1 -1
  5. package/esm/options/buildOptions.js +23 -2
  6. package/esm/options/buildOptions.js.map +1 -1
  7. package/esm/options/buildOptions.test.js +97 -0
  8. package/esm/options/buildOptions.test.js.map +1 -1
  9. package/esm/plugins/shaker.js +87 -9
  10. package/esm/plugins/shaker.js.map +1 -1
  11. package/esm/transform/Entrypoint.helpers.js +8 -1
  12. package/esm/transform/Entrypoint.helpers.js.map +1 -1
  13. package/esm/transform/Entrypoint.js +5 -3
  14. package/esm/transform/Entrypoint.js.map +1 -1
  15. package/esm/transform/EvaluatedEntrypoint.js.map +1 -1
  16. package/esm/transform/actions/BaseAction.js +1 -1
  17. package/esm/transform/actions/BaseAction.js.map +1 -1
  18. package/esm/transform/generators/processImports.js +53 -0
  19. package/esm/transform/generators/processImports.js.map +1 -1
  20. package/esm/transform/generators/resolveImports.js +2 -2
  21. package/esm/transform/generators/resolveImports.js.map +1 -1
  22. package/esm/utils/importOverrides.js +60 -0
  23. package/esm/utils/importOverrides.js.map +1 -1
  24. package/esm/vm/createVmContext.js +48 -16
  25. package/esm/vm/createVmContext.js.map +1 -1
  26. package/lib/cache.js +47 -7
  27. package/lib/cache.js.map +1 -1
  28. package/lib/module.js +2 -2
  29. package/lib/module.js.map +1 -1
  30. package/lib/options/buildOptions.js +23 -3
  31. package/lib/options/buildOptions.js.map +1 -1
  32. package/lib/options/buildOptions.test.js +97 -0
  33. package/lib/options/buildOptions.test.js.map +1 -1
  34. package/lib/plugins/shaker.js +88 -9
  35. package/lib/plugins/shaker.js.map +1 -1
  36. package/lib/transform/Entrypoint.helpers.js +8 -1
  37. package/lib/transform/Entrypoint.helpers.js.map +1 -1
  38. package/lib/transform/Entrypoint.js +5 -3
  39. package/lib/transform/Entrypoint.js.map +1 -1
  40. package/lib/transform/EvaluatedEntrypoint.js.map +1 -1
  41. package/lib/transform/actions/BaseAction.js +1 -1
  42. package/lib/transform/actions/BaseAction.js.map +1 -1
  43. package/lib/transform/generators/processImports.js +53 -0
  44. package/lib/transform/generators/processImports.js.map +1 -1
  45. package/lib/transform/generators/resolveImports.js +1 -1
  46. package/lib/transform/generators/resolveImports.js.map +1 -1
  47. package/lib/utils/importOverrides.js +62 -0
  48. package/lib/utils/importOverrides.js.map +1 -1
  49. package/lib/vm/createVmContext.js +48 -16
  50. package/lib/vm/createVmContext.js.map +1 -1
  51. package/package.json +5 -4
  52. package/types/cache.d.ts +2 -1
  53. package/types/cache.js +48 -6
  54. package/types/module.js +1 -1
  55. package/types/options/buildOptions.js +29 -2
  56. package/types/plugins/shaker.js +104 -9
  57. package/types/transform/Entrypoint.helpers.js +10 -1
  58. package/types/transform/Entrypoint.js +5 -3
  59. package/types/transform/EvaluatedEntrypoint.d.ts +2 -0
  60. package/types/transform/EvaluatedEntrypoint.js +1 -0
  61. package/types/transform/actions/BaseAction.js +1 -1
  62. package/types/transform/generators/processImports.js +70 -0
  63. package/types/transform/generators/resolveImports.js +1 -1
  64. package/types/utils/importOverrides.d.ts +2 -1
  65. package/types/utils/importOverrides.js +63 -0
  66. package/types/vm/createVmContext.js +61 -13
@@ -1,10 +1,45 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.processImports = processImports;
4
+ const importOverrides_1 = require("../../utils/importOverrides");
5
+ const warnedSlowImportsByServices = new WeakMap();
6
+ function emitWarning(services, message) {
7
+ if (services.emitWarning) {
8
+ services.emitWarning(message);
9
+ return;
10
+ }
11
+ // eslint-disable-next-line no-console
12
+ console.warn(message);
13
+ }
14
+ function getWarnedSlowImports(services) {
15
+ const cached = warnedSlowImportsByServices.get(services);
16
+ if (cached)
17
+ return cached;
18
+ const created = new Set();
19
+ warnedSlowImportsByServices.set(services, created);
20
+ return created;
21
+ }
22
+ function isWarningEnabled(value) {
23
+ return Boolean(value) && value !== '0' && value !== 'false';
24
+ }
4
25
  /**
5
26
  * Creates new entrypoints and emits processEntrypoint for each resolved import
6
27
  */
7
28
  function* processImports() {
29
+ const slowImportWarningsEnabled = isWarningEnabled(process.env.WYW_WARN_SLOW_IMPORTS);
30
+ const slowImportThresholdMs = (() => {
31
+ const raw = process.env.WYW_WARN_SLOW_IMPORTS_MS;
32
+ if (!raw)
33
+ return 50;
34
+ const parsed = Number(raw);
35
+ if (!Number.isFinite(parsed))
36
+ return 50;
37
+ return parsed;
38
+ })();
39
+ const warnedSlowImports = slowImportWarningsEnabled
40
+ ? getWarnedSlowImports(this.services)
41
+ : null;
42
+ const { root } = this.services.options;
8
43
  for (const dependency of this.data.resolved) {
9
44
  const { resolved, only } = dependency;
10
45
  if (!resolved) {
@@ -15,6 +50,41 @@ function* processImports() {
15
50
  if (nextEntrypoint === 'loop' || nextEntrypoint.ignored) {
16
51
  continue;
17
52
  }
53
+ const startedAt = slowImportWarningsEnabled ? performance.now() : 0;
18
54
  yield* this.getNext('processEntrypoint', nextEntrypoint, undefined, null);
55
+ if (slowImportWarningsEnabled &&
56
+ warnedSlowImports &&
57
+ slowImportThresholdMs > 0) {
58
+ const durationMs = performance.now() - startedAt;
59
+ if (durationMs >= slowImportThresholdMs) {
60
+ const { key: importKey } = (0, importOverrides_1.toImportKey)({
61
+ source: dependency.source,
62
+ resolved,
63
+ root,
64
+ });
65
+ const dedupeKey = `${this.entrypoint.name}::${importKey}`;
66
+ if (!warnedSlowImports.has(dedupeKey)) {
67
+ warnedSlowImports.add(dedupeKey);
68
+ const warning = [
69
+ `[wyw-in-js] Slow import during prepare stage`,
70
+ ``,
71
+ `file: ${this.entrypoint.name}`,
72
+ `import: ${dependency.source}`,
73
+ `resolved: ${resolved}`,
74
+ `duration: ${durationMs.toFixed(1)}ms`,
75
+ ``,
76
+ `tip: if this import is runtime-only or heavy, mock it during evaluation via importOverrides:`,
77
+ ` importOverrides: {`,
78
+ ` '${importKey}': { mock: './path/to/mock' },`,
79
+ ` }`,
80
+ ``,
81
+ `note: importOverrides affects only build-time evaluation (it does not change your bundler runtime behavior)`,
82
+ ``,
83
+ `note: configure threshold with WYW_WARN_SLOW_IMPORTS_MS (current: ${slowImportThresholdMs}ms)`,
84
+ ].join('\n');
85
+ emitWarning(this.services, warning);
86
+ }
87
+ }
88
+ }
19
89
  }
20
90
  }
@@ -20,7 +20,7 @@ function applyImportOverrides(services, entrypoint, resolvedImports) {
20
20
  resolved: dependency.resolved,
21
21
  root,
22
22
  });
23
- const override = overrides[key];
23
+ const override = (0, importOverrides_1.getImportOverride)(overrides, key);
24
24
  if (!override) {
25
25
  return dependency;
26
26
  }
@@ -1,4 +1,4 @@
1
- import { type ImportOverride } from '@wyw-in-js/shared';
1
+ import { type ImportOverride, type ImportOverrides } from '@wyw-in-js/shared';
2
2
  export type ImportKeyKind = 'file' | 'package';
3
3
  export type ImportKey = {
4
4
  key: string;
@@ -17,3 +17,4 @@ export declare function resolveMockSpecifier({ importer, mock, root, stack, }: {
17
17
  stack: string[];
18
18
  }): string;
19
19
  export declare function applyImportOverrideToOnly(only: string[], override: ImportOverride | undefined): string[];
20
+ export declare function getImportOverride(importOverrides: ImportOverrides | undefined, key: string): ImportOverride | undefined;
@@ -7,8 +7,10 @@ exports.toCanonicalFileKey = toCanonicalFileKey;
7
7
  exports.toImportKey = toImportKey;
8
8
  exports.resolveMockSpecifier = resolveMockSpecifier;
9
9
  exports.applyImportOverrideToOnly = applyImportOverrideToOnly;
10
+ exports.getImportOverride = getImportOverride;
10
11
  const path_1 = __importDefault(require("path"));
11
12
  const shared_1 = require("@wyw-in-js/shared");
13
+ const minimatch_1 = require("minimatch");
12
14
  function toCanonicalFileKey(resolved, root) {
13
15
  const rootDir = root ? path_1.default.resolve(root) : process.cwd();
14
16
  const normalizedResolved = path_1.default.resolve(resolved);
@@ -38,3 +40,64 @@ function applyImportOverrideToOnly(only, override) {
38
40
  }
39
41
  return only;
40
42
  }
43
+ const compiledImportOverridesCache = new WeakMap();
44
+ const minimatchOptions = {
45
+ dot: true,
46
+ nocomment: true,
47
+ nonegate: true,
48
+ };
49
+ function getPatternSpecificity(pattern) {
50
+ let wildcardCount = 0;
51
+ let escaped = false;
52
+ for (const char of pattern) {
53
+ if (escaped) {
54
+ escaped = false;
55
+ }
56
+ else if (char === '\\') {
57
+ escaped = true;
58
+ }
59
+ else if (char === '*' || char === '?') {
60
+ wildcardCount += 1;
61
+ }
62
+ }
63
+ return pattern.length - wildcardCount * 10;
64
+ }
65
+ function compileImportOverrides(importOverrides) {
66
+ const matchers = Object.entries(importOverrides)
67
+ .map(([pattern, override]) => {
68
+ return {
69
+ matcher: new minimatch_1.Minimatch(pattern, minimatchOptions),
70
+ override,
71
+ pattern,
72
+ specificity: getPatternSpecificity(pattern),
73
+ };
74
+ })
75
+ .sort((a, b) => {
76
+ const bySpecificity = b.specificity - a.specificity;
77
+ if (bySpecificity !== 0)
78
+ return bySpecificity;
79
+ const byLength = b.pattern.length - a.pattern.length;
80
+ if (byLength !== 0)
81
+ return byLength;
82
+ return a.pattern.localeCompare(b.pattern);
83
+ });
84
+ return { matchers };
85
+ }
86
+ function getCompiledImportOverrides(importOverrides) {
87
+ const cached = compiledImportOverridesCache.get(importOverrides);
88
+ if (cached)
89
+ return cached;
90
+ const compiled = compileImportOverrides(importOverrides);
91
+ compiledImportOverridesCache.set(importOverrides, compiled);
92
+ return compiled;
93
+ }
94
+ function getImportOverride(importOverrides, key) {
95
+ if (!importOverrides) {
96
+ return undefined;
97
+ }
98
+ const direct = importOverrides[key];
99
+ if (direct)
100
+ return direct;
101
+ const { matchers } = getCompiledImportOverrides(importOverrides);
102
+ return matchers.find(({ matcher }) => matcher.match(key))?.override;
103
+ }
@@ -39,7 +39,16 @@ const shared_1 = require("@wyw-in-js/shared");
39
39
  const process = __importStar(require("./process"));
40
40
  const NOOP = () => { };
41
41
  const IMPORT_META_ENV = '__wyw_import_meta_env';
42
+ const HAPPY_DOM_REQUIRE_HOOK = '__wyw_requireHappyDom';
42
43
  let importMetaEnvWarned = false;
44
+ let happyDomRequireEsmWarned = false;
45
+ let happyDomUnavailable = false;
46
+ function isErrRequireEsm(error) {
47
+ return (typeof error === 'object' &&
48
+ error !== null &&
49
+ 'code' in error &&
50
+ error.code === 'ERR_REQUIRE_ESM');
51
+ }
43
52
  function createImportMetaEnvProxy() {
44
53
  const target = Object.create(null);
45
54
  const warnOnce = () => {
@@ -84,14 +93,51 @@ function createImportMetaEnvProxy() {
84
93
  },
85
94
  });
86
95
  }
96
+ function requireHappyDom() {
97
+ const hook = globalThis[HAPPY_DOM_REQUIRE_HOOK];
98
+ if (typeof hook === 'function') {
99
+ return hook();
100
+ }
101
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
102
+ return require('happy-dom');
103
+ }
87
104
  function createWindow() {
88
- const { Window, GlobalWindow } = require('happy-dom');
89
- const HappyWindow = GlobalWindow || Window;
90
- const win = new HappyWindow();
91
- // TODO: browser doesn't expose Buffer, but a lot of dependencies use it
92
- win.Buffer = Buffer;
93
- win.Uint8Array = Uint8Array;
94
- return win;
105
+ if (happyDomUnavailable)
106
+ return undefined;
107
+ try {
108
+ const { Window, GlobalWindow } = requireHappyDom();
109
+ const HappyWindow = GlobalWindow || Window;
110
+ const win = new HappyWindow();
111
+ // TODO: browser doesn't expose Buffer, but a lot of dependencies use it
112
+ win.Buffer = Buffer;
113
+ win.Uint8Array = Uint8Array;
114
+ return win;
115
+ }
116
+ catch (error) {
117
+ if (!isErrRequireEsm(error)) {
118
+ throw error;
119
+ }
120
+ const hasCustomRequireHook = typeof globalThis[HAPPY_DOM_REQUIRE_HOOK] ===
121
+ 'function';
122
+ if (!hasCustomRequireHook) {
123
+ happyDomUnavailable = true;
124
+ }
125
+ if (happyDomRequireEsmWarned)
126
+ return undefined;
127
+ happyDomRequireEsmWarned = true;
128
+ // eslint-disable-next-line no-console
129
+ console.warn([
130
+ `[wyw-in-js] DOM emulation is enabled (features.happyDOM), but "happy-dom" could not be loaded in this build-time runtime.`,
131
+ `This usually happens because "happy-dom" is ESM-only and cannot be loaded via require() in a CJS build.`,
132
+ ``,
133
+ `WyW will continue without DOM emulation (as if features.happyDOM:false).`,
134
+ ``,
135
+ `To silence this warning: set features: { happyDOM: false }.`,
136
+ `To get real DOM emulation in Node 20+, WyW needs an ESM-only eval architecture (planned for v2.0.0),`,
137
+ `or a runtime that supports require(ESM) (Node 24+).`,
138
+ ].join('\n'));
139
+ return undefined;
140
+ }
95
141
  }
96
142
  /**
97
143
  * `happy-dom` already has required references, so we don't need to set them.
@@ -125,8 +171,16 @@ function createBaseContext(win, additionalContext) {
125
171
  }
126
172
  return baseContext;
127
173
  }
174
+ function createNothing() {
175
+ return {
176
+ teardown: () => { },
177
+ window: undefined,
178
+ };
179
+ }
128
180
  function createHappyDOMWindow() {
129
181
  const win = createWindow();
182
+ if (!win)
183
+ return createNothing();
130
184
  return {
131
185
  teardown: () => {
132
186
  win.happyDOM.abort();
@@ -134,12 +188,6 @@ function createHappyDOMWindow() {
134
188
  window: win,
135
189
  };
136
190
  }
137
- function createNothing() {
138
- return {
139
- teardown: () => { },
140
- window: undefined,
141
- };
142
- }
143
191
  function createVmContext(filename, features, additionalContext, overrideContext = (i) => i) {
144
192
  const isHappyDOMEnabled = (0, shared_1.isFeatureEnabled)(features, 'happyDOM', filename);
145
193
  const { teardown, window } = isHappyDOMEnabled