@yahoo/uds-create-loader 2.25.0 → 2.26.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/dist/next.js +55 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/next.test.ts +169 -0
- package/src/next.ts +101 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yahoo/uds-create-loader",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.26.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"files": [
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"test:update": "bun test src/ --update-snapshots"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@yahoo/uds-create-config": "2.
|
|
41
|
+
"@yahoo/uds-create-config": "2.26.0",
|
|
42
42
|
"@babel/parser": "^7.29.0",
|
|
43
43
|
"@babel/traverse": "^7.29.0",
|
|
44
44
|
"@babel/types": "^7.29.0",
|
package/src/next.test.ts
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { afterAll, beforeEach, describe, expect, it } from 'bun:test';
|
|
2
|
+
import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { withUds } from './next';
|
|
5
|
+
|
|
6
|
+
// Repo rule: test artifacts under the package's dist/.test-tmp.
|
|
7
|
+
const TMP = path.join(import.meta.dir, '../dist/.test-tmp/next-registry-deps');
|
|
8
|
+
|
|
9
|
+
const REGISTRY_REL = 'src/uds/registry';
|
|
10
|
+
|
|
11
|
+
function writeFixture(args: {
|
|
12
|
+
registryDeps?: Record<string, string>;
|
|
13
|
+
consumerHas?: string[];
|
|
14
|
+
buildOptions?: { componentsDir?: string; registryDir?: string };
|
|
15
|
+
}): string {
|
|
16
|
+
const root = path.join(TMP, `proj-${Math.random().toString(36).slice(2, 8)}`);
|
|
17
|
+
|
|
18
|
+
mkdirSync(path.join(root, '.uds'), { recursive: true });
|
|
19
|
+
writeFileSync(
|
|
20
|
+
path.join(root, '.uds/config.json'),
|
|
21
|
+
JSON.stringify({
|
|
22
|
+
buildOptions: args.buildOptions ?? { componentsDir: 'src/uds' },
|
|
23
|
+
}),
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
if (args.registryDeps) {
|
|
27
|
+
const registryDir = path.join(root, REGISTRY_REL);
|
|
28
|
+
mkdirSync(path.join(registryDir, 'node_modules'), { recursive: true });
|
|
29
|
+
writeFileSync(
|
|
30
|
+
path.join(registryDir, 'package.json'),
|
|
31
|
+
JSON.stringify({
|
|
32
|
+
name: '@yahoo/uds-linked-runtime-deps',
|
|
33
|
+
private: true,
|
|
34
|
+
dependencies: args.registryDeps,
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
37
|
+
for (const name of Object.keys(args.registryDeps)) {
|
|
38
|
+
const pkgDir = path.join(registryDir, 'node_modules', name);
|
|
39
|
+
mkdirSync(pkgDir, { recursive: true });
|
|
40
|
+
writeFileSync(
|
|
41
|
+
path.join(pkgDir, 'package.json'),
|
|
42
|
+
JSON.stringify({ name, version: '1.0.0', main: 'index.js' }),
|
|
43
|
+
);
|
|
44
|
+
writeFileSync(path.join(pkgDir, 'index.js'), 'module.exports = {};\n');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const name of args.consumerHas ?? []) {
|
|
49
|
+
const pkgDir = path.join(root, 'node_modules', name);
|
|
50
|
+
mkdirSync(pkgDir, { recursive: true });
|
|
51
|
+
writeFileSync(
|
|
52
|
+
path.join(pkgDir, 'package.json'),
|
|
53
|
+
JSON.stringify({ name, version: '9.9.9', main: 'index.js' }),
|
|
54
|
+
);
|
|
55
|
+
writeFileSync(path.join(pkgDir, 'index.js'), 'module.exports = {};\n');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return root;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function applyIn<T>(root: string, fn: () => T): T {
|
|
62
|
+
const prev = process.cwd();
|
|
63
|
+
process.chdir(root);
|
|
64
|
+
try {
|
|
65
|
+
return fn();
|
|
66
|
+
} finally {
|
|
67
|
+
process.chdir(prev);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
type WebpackFn = (
|
|
72
|
+
config: Record<string, unknown>,
|
|
73
|
+
options: { dev: boolean },
|
|
74
|
+
) => Record<string, unknown>;
|
|
75
|
+
|
|
76
|
+
beforeEach(() => {
|
|
77
|
+
rmSync(TMP, { recursive: true, force: true });
|
|
78
|
+
mkdirSync(TMP, { recursive: true });
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
afterAll(() => {
|
|
82
|
+
rmSync(TMP, { recursive: true, force: true });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('withUds — linked-registry dependency resolution', () => {
|
|
86
|
+
it('aliases registry deps for Turbopack when the consumer lacks them', () => {
|
|
87
|
+
const root = writeFixture({ registryDeps: { 'date-fns': '^4.1.0' } });
|
|
88
|
+
|
|
89
|
+
const config = applyIn(root, () => withUds({}));
|
|
90
|
+
|
|
91
|
+
const aliases = (config as { turbopack?: { resolveAlias?: unknown } })
|
|
92
|
+
.turbopack?.resolveAlias as Record<string, string>;
|
|
93
|
+
expect(aliases['date-fns']).toBe(`./${REGISTRY_REL}/node_modules/date-fns`);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('prefers the consumer install — no alias when the consumer has the package', () => {
|
|
97
|
+
const root = writeFixture({
|
|
98
|
+
registryDeps: { 'date-fns': '^4.1.0' },
|
|
99
|
+
consumerHas: ['date-fns'],
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const config = applyIn(root, () => withUds({}));
|
|
103
|
+
|
|
104
|
+
const aliases = (config as { turbopack?: { resolveAlias?: unknown } })
|
|
105
|
+
.turbopack?.resolveAlias as Record<string, string> | undefined;
|
|
106
|
+
expect(aliases?.['date-fns']).toBeUndefined();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('appends the registry node_modules to webpack resolve.modules', () => {
|
|
110
|
+
const root = writeFixture({ registryDeps: { 'date-fns': '^4.1.0' } });
|
|
111
|
+
|
|
112
|
+
const config = applyIn(root, () => withUds({}));
|
|
113
|
+
const webpack = (config as { webpack: WebpackFn }).webpack;
|
|
114
|
+
const out = applyIn(root, () =>
|
|
115
|
+
webpack({ module: { rules: [] } }, { dev: false }),
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const modules = (out as { resolve?: { modules?: string[] } }).resolve
|
|
119
|
+
?.modules;
|
|
120
|
+
expect(modules).toBeDefined();
|
|
121
|
+
// Walk-up ('node_modules') stays first so the consumer install wins.
|
|
122
|
+
expect(modules?.[0]).toBe('node_modules');
|
|
123
|
+
expect(modules).toContain(path.join(root, REGISTRY_REL, 'node_modules'));
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('is a no-op without a generated registry manifest', () => {
|
|
127
|
+
const root = writeFixture({});
|
|
128
|
+
|
|
129
|
+
const config = applyIn(root, () => withUds({}));
|
|
130
|
+
|
|
131
|
+
const aliases = (config as { turbopack?: { resolveAlias?: unknown } })
|
|
132
|
+
.turbopack?.resolveAlias as Record<string, string> | undefined;
|
|
133
|
+
expect(aliases ?? {}).toEqual({});
|
|
134
|
+
|
|
135
|
+
const webpack = (config as { webpack: WebpackFn }).webpack;
|
|
136
|
+
const out = applyIn(root, () =>
|
|
137
|
+
webpack({ module: { rules: [] } }, { dev: false }),
|
|
138
|
+
);
|
|
139
|
+
expect(
|
|
140
|
+
(out as { resolve?: { modules?: string[] } }).resolve?.modules,
|
|
141
|
+
).toBeUndefined();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('honors a custom buildOptions.registryDir', () => {
|
|
145
|
+
const root = writeFixture({
|
|
146
|
+
registryDeps: { clsx: '^2.0.0' },
|
|
147
|
+
buildOptions: { registryDir: 'src/uds/registry' },
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const config = applyIn(root, () => withUds({}));
|
|
151
|
+
|
|
152
|
+
const aliases = (config as { turbopack?: { resolveAlias?: unknown } })
|
|
153
|
+
.turbopack?.resolveAlias as Record<string, string>;
|
|
154
|
+
expect(aliases.clsx).toBe('./src/uds/registry/node_modules/clsx');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('preserves caller-provided resolveAlias entries', () => {
|
|
158
|
+
const root = writeFixture({ registryDeps: { 'date-fns': '^4.1.0' } });
|
|
159
|
+
|
|
160
|
+
const config = applyIn(root, () =>
|
|
161
|
+
withUds({ turbopack: { resolveAlias: { lodash: 'lodash-es' } } }),
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const aliases = (config as { turbopack?: { resolveAlias?: unknown } })
|
|
165
|
+
.turbopack?.resolveAlias as Record<string, string>;
|
|
166
|
+
expect(aliases.lodash).toBe('lodash-es');
|
|
167
|
+
expect(aliases['date-fns']).toBe(`./${REGISTRY_REL}/node_modules/date-fns`);
|
|
168
|
+
});
|
|
169
|
+
});
|
package/src/next.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
1
2
|
import path from 'node:path';
|
|
3
|
+
import { resolveRegistryDir } from '@yahoo/uds-create-config';
|
|
2
4
|
import type { NextConfig } from 'next';
|
|
3
5
|
|
|
4
6
|
export interface WithUdsOptions {
|
|
@@ -10,7 +12,82 @@ export interface WithUdsOptions {
|
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
function resolveLoaderPath(): string {
|
|
13
|
-
|
|
15
|
+
try {
|
|
16
|
+
return require.resolve('@yahoo/uds-create-loader/loader');
|
|
17
|
+
} catch {
|
|
18
|
+
// Monorepo source context (tests, pre-build) — dist/ isn't built yet, so
|
|
19
|
+
// the subpath export can't resolve. The sibling source serves the same
|
|
20
|
+
// role; a published install always resolves the dist export above.
|
|
21
|
+
return path.join(import.meta.dirname, 'loader.ts');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The linked registry's install root + dep names, read from the manifest
|
|
26
|
+
* `uds pull` generates. Null when the project has no linked runtime deps. */
|
|
27
|
+
function readRegistryDeps(
|
|
28
|
+
outputDir: string,
|
|
29
|
+
): { registryDirRel: string; nodeModules: string; deps: string[] } | null {
|
|
30
|
+
let buildOptions:
|
|
31
|
+
| { componentsDir?: string; registryDir?: string }
|
|
32
|
+
| undefined;
|
|
33
|
+
try {
|
|
34
|
+
buildOptions = (
|
|
35
|
+
JSON.parse(
|
|
36
|
+
readFileSync(
|
|
37
|
+
path.resolve(process.cwd(), outputDir, 'config.json'),
|
|
38
|
+
'utf-8',
|
|
39
|
+
),
|
|
40
|
+
) as { buildOptions?: typeof buildOptions }
|
|
41
|
+
).buildOptions;
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const registryDirRel = resolveRegistryDir(buildOptions);
|
|
47
|
+
|
|
48
|
+
let deps: string[];
|
|
49
|
+
try {
|
|
50
|
+
const manifest = JSON.parse(
|
|
51
|
+
readFileSync(
|
|
52
|
+
path.resolve(process.cwd(), registryDirRel, 'package.json'),
|
|
53
|
+
'utf-8',
|
|
54
|
+
),
|
|
55
|
+
) as { dependencies?: Record<string, string> };
|
|
56
|
+
deps = Object.keys(manifest.dependencies ?? {});
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (deps.length === 0) return null;
|
|
62
|
+
|
|
63
|
+
const nodeModules = path.resolve(
|
|
64
|
+
process.cwd(),
|
|
65
|
+
registryDirRel,
|
|
66
|
+
'node_modules',
|
|
67
|
+
);
|
|
68
|
+
if (!existsSync(nodeModules)) return null;
|
|
69
|
+
|
|
70
|
+
return { registryDirRel, nodeModules, deps };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Turbopack has no resolve.modules equivalent, so alias each registry dep
|
|
74
|
+
* to its installed path — only when the consumer's own install lacks it
|
|
75
|
+
* (the consumer's version must always win). */
|
|
76
|
+
function buildRegistryAliasMap(
|
|
77
|
+
registry: NonNullable<ReturnType<typeof readRegistryDeps>>,
|
|
78
|
+
): Record<string, string> {
|
|
79
|
+
const aliases: Record<string, string> = {};
|
|
80
|
+
|
|
81
|
+
for (const dep of registry.deps) {
|
|
82
|
+
const consumerCopy = path.resolve(process.cwd(), 'node_modules', dep);
|
|
83
|
+
if (existsSync(consumerCopy)) continue;
|
|
84
|
+
|
|
85
|
+
if (existsSync(path.join(registry.nodeModules, dep))) {
|
|
86
|
+
aliases[dep] = `./${registry.registryDirRel}/node_modules/${dep}`;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return aliases;
|
|
14
91
|
}
|
|
15
92
|
|
|
16
93
|
type NextConfigFn = (
|
|
@@ -55,10 +132,24 @@ function applyUds(
|
|
|
55
132
|
}
|
|
56
133
|
}
|
|
57
134
|
|
|
135
|
+
// Linked-system runtime deps live in `<registryDir>/node_modules` (written
|
|
136
|
+
// by `uds pull`), off the walk-up path of the downleveled `.uds` modules
|
|
137
|
+
// that import them.
|
|
138
|
+
const registry = readRegistryDeps(outputDir);
|
|
139
|
+
const registryAliases = registry ? buildRegistryAliasMap(registry) : {};
|
|
140
|
+
|
|
58
141
|
return {
|
|
59
142
|
...nextConfig,
|
|
60
143
|
turbopack: {
|
|
61
144
|
...turbopack,
|
|
145
|
+
...(Object.keys(registryAliases).length > 0
|
|
146
|
+
? {
|
|
147
|
+
resolveAlias: {
|
|
148
|
+
...registryAliases,
|
|
149
|
+
...turbopack.resolveAlias,
|
|
150
|
+
},
|
|
151
|
+
}
|
|
152
|
+
: {}),
|
|
62
153
|
rules: {
|
|
63
154
|
...existingRules,
|
|
64
155
|
...udsRules,
|
|
@@ -79,6 +170,15 @@ function applyUds(
|
|
|
79
170
|
],
|
|
80
171
|
});
|
|
81
172
|
|
|
173
|
+
if (registry) {
|
|
174
|
+
config.resolve ??= {};
|
|
175
|
+
// 'node_modules' first: standard walk-up wins, registry is fallback.
|
|
176
|
+
config.resolve.modules ??= ['node_modules'];
|
|
177
|
+
if (!config.resolve.modules.includes(registry.nodeModules)) {
|
|
178
|
+
config.resolve.modules.push(registry.nodeModules);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
82
182
|
if (skipWatch) {
|
|
83
183
|
const existing = config.watchOptions?.ignored;
|
|
84
184
|
config.watchOptions = {
|