@pob/rollup 8.3.0 → 8.4.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/index.js +84 -0
- package/lib/index.test.js +111 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { setTimeout } from "node:timers/promises";
|
|
4
5
|
|
|
5
6
|
export const nodeFormatToExt = (format, pkgType) => {
|
|
6
7
|
if (format === "cjs" && pkgType === "module") return ".cjs";
|
|
@@ -86,3 +87,86 @@ export const createSourceWorkspaceDependencies = (cwd, pkg) => {
|
|
|
86
87
|
},
|
|
87
88
|
};
|
|
88
89
|
};
|
|
90
|
+
|
|
91
|
+
const nodeImportConditions = new Set(["node", "import", "default"]);
|
|
92
|
+
|
|
93
|
+
const resolveExportTarget = (target) => {
|
|
94
|
+
if (typeof target === "string") return target;
|
|
95
|
+
if (!target || typeof target !== "object" || Array.isArray(target)) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
for (const [condition, value] of Object.entries(target)) {
|
|
99
|
+
if (nodeImportConditions.has(condition)) {
|
|
100
|
+
const resolved = resolveExportTarget(value);
|
|
101
|
+
if (resolved) return resolved;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return undefined;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/** the file node imports for the package's main entry */
|
|
108
|
+
const getMainEntry = (pkg) => {
|
|
109
|
+
const { exports } = pkg;
|
|
110
|
+
if (exports === undefined) return pkg.module ?? pkg.main;
|
|
111
|
+
if (typeof exports === "string") return exports;
|
|
112
|
+
const isSubpathMap = Object.keys(exports).some((key) => key.startsWith("."));
|
|
113
|
+
return resolveExportTarget(isSubpathMap ? exports["."] : exports);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const dependencyPollIntervalMs = 100;
|
|
117
|
+
const dependencySlowAfterMs = 5000;
|
|
118
|
+
|
|
119
|
+
const waitForFile = async (file, onSlow) => {
|
|
120
|
+
let waitedMs = 0;
|
|
121
|
+
while (!fs.existsSync(file)) {
|
|
122
|
+
if (waitedMs === dependencySlowAfterMs) onSlow();
|
|
123
|
+
await setTimeout(dependencyPollIntervalMs);
|
|
124
|
+
waitedMs += dependencyPollIntervalMs;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const findDependencyPackageJson = (cwd, name) => {
|
|
129
|
+
let directory = cwd;
|
|
130
|
+
while (true) {
|
|
131
|
+
const pkgPath = path.join(directory, "node_modules", name, "package.json");
|
|
132
|
+
if (fs.existsSync(pkgPath)) return fs.realpathSync(pkgPath);
|
|
133
|
+
if (directory === path.dirname(directory)) return undefined;
|
|
134
|
+
directory = path.dirname(directory);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Workspace dependencies imported from their build stay external, and node
|
|
140
|
+
* loads them once: the process started by the run plugin would keep running a
|
|
141
|
+
* stale build of them. Watching their entry rebuilds, and so restarts it, each
|
|
142
|
+
* time one of them is rebuilt. A watched file that gets deleted, by the
|
|
143
|
+
* `clean:build` of its package, is no longer watched once recreated: the
|
|
144
|
+
* rebuild its deletion triggers waits for it to come back, and watches the new
|
|
145
|
+
* one.
|
|
146
|
+
*/
|
|
147
|
+
export const createWatchWorkspaceDependencies = (cwd, pkg) => {
|
|
148
|
+
const builtEntries = Object.entries(pkg.dependencies || {})
|
|
149
|
+
.filter(([, version]) => version.startsWith("workspace:"))
|
|
150
|
+
.flatMap(([name]) => {
|
|
151
|
+
const dependencyPkgPath = findDependencyPackageJson(cwd, name);
|
|
152
|
+
if (!dependencyPkgPath) return [];
|
|
153
|
+
const entry = getMainEntry(
|
|
154
|
+
JSON.parse(fs.readFileSync(dependencyPkgPath)),
|
|
155
|
+
);
|
|
156
|
+
if (!entry || sourceExtensionRegExp.test(entry)) return [];
|
|
157
|
+
return [path.resolve(path.dirname(dependencyPkgPath), entry)];
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
name: "@pob/rollup/watch-workspace-dependencies",
|
|
162
|
+
async buildStart() {
|
|
163
|
+
if (!this.meta.watchMode) return;
|
|
164
|
+
for (const entry of builtEntries) {
|
|
165
|
+
await waitForFile(entry, () => {
|
|
166
|
+
this.warn(`Waiting for ${entry} to be built`);
|
|
167
|
+
});
|
|
168
|
+
this.addWatchFile(entry);
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { createWatchWorkspaceDependencies } from "./index.js";
|
|
6
|
+
|
|
7
|
+
const writeJson = (filePath, value) => {
|
|
8
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
9
|
+
fs.writeFileSync(filePath, JSON.stringify(value));
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
describe("createWatchWorkspaceDependencies", () => {
|
|
13
|
+
let root;
|
|
14
|
+
let appDirectory;
|
|
15
|
+
|
|
16
|
+
/** a workspace package, linked in the root node_modules as pnpm hoists it */
|
|
17
|
+
const addWorkspacePackage = (name, pkg) => {
|
|
18
|
+
const directory = path.join(root, "packages", name);
|
|
19
|
+
writeJson(path.join(directory, "package.json"), { name, ...pkg });
|
|
20
|
+
fs.mkdirSync(path.join(root, "node_modules"), { recursive: true });
|
|
21
|
+
fs.symlinkSync(directory, path.join(root, "node_modules", name), "dir");
|
|
22
|
+
return fs.realpathSync(directory);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const watchedFiles = async (pkg, { watchMode = true } = {}) => {
|
|
26
|
+
const addWatchFile = vi.fn();
|
|
27
|
+
await createWatchWorkspaceDependencies(appDirectory, pkg).buildStart.call({
|
|
28
|
+
meta: { watchMode },
|
|
29
|
+
addWatchFile,
|
|
30
|
+
warn: vi.fn(),
|
|
31
|
+
});
|
|
32
|
+
return addWatchFile.mock.calls.map(([file]) => file);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
root = fs.mkdtempSync(path.join(os.tmpdir(), "pob-rollup-"));
|
|
37
|
+
appDirectory = path.join(root, "packages", "app");
|
|
38
|
+
fs.mkdirSync(appDirectory, { recursive: true });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("watches the build node imports of each workspace dependency", async () => {
|
|
46
|
+
const core = addWorkspacePackage("core", {
|
|
47
|
+
exports: {
|
|
48
|
+
"./package.json": "./package.json",
|
|
49
|
+
".": {
|
|
50
|
+
types: "./src/index.ts",
|
|
51
|
+
node: { import: "./build/index-node.mjs" },
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
const legacy = addWorkspacePackage("legacy", { main: "./dist/index.js" });
|
|
56
|
+
const builtFiles = [
|
|
57
|
+
path.join(core, "build/index-node.mjs"),
|
|
58
|
+
path.join(legacy, "dist/index.js"),
|
|
59
|
+
];
|
|
60
|
+
builtFiles.forEach((builtFile) => {
|
|
61
|
+
fs.mkdirSync(path.dirname(builtFile));
|
|
62
|
+
fs.writeFileSync(builtFile, "export {};");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(
|
|
66
|
+
await watchedFiles({
|
|
67
|
+
dependencies: { core: "workspace:*", legacy: "workspace:^" },
|
|
68
|
+
}),
|
|
69
|
+
).toEqual(builtFiles);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("leaves out sources, registry dependencies and unknown packages", async () => {
|
|
73
|
+
addWorkspacePackage("untranspiled", { exports: "./src/index.ts" });
|
|
74
|
+
addWorkspacePackage("published", { main: "./index.js" });
|
|
75
|
+
|
|
76
|
+
expect(
|
|
77
|
+
await watchedFiles({
|
|
78
|
+
dependencies: {
|
|
79
|
+
untranspiled: "workspace:*",
|
|
80
|
+
published: "1.0.0",
|
|
81
|
+
missing: "workspace:*",
|
|
82
|
+
},
|
|
83
|
+
}),
|
|
84
|
+
).toEqual([]);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("watches nothing outside watch mode", async () => {
|
|
88
|
+
addWorkspacePackage("core", { main: "./build/index.js" });
|
|
89
|
+
|
|
90
|
+
expect(
|
|
91
|
+
await watchedFiles(
|
|
92
|
+
{ dependencies: { core: "workspace:*" } },
|
|
93
|
+
{ watchMode: false },
|
|
94
|
+
),
|
|
95
|
+
).toEqual([]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("waits for a dependency being rebuilt before watching it", async () => {
|
|
99
|
+
const core = addWorkspacePackage("core", { main: "./build/index.js" });
|
|
100
|
+
const builtFile = path.join(core, "build/index.js");
|
|
101
|
+
setTimeout(() => {
|
|
102
|
+
fs.mkdirSync(path.dirname(builtFile));
|
|
103
|
+
fs.writeFileSync(builtFile, "export {};");
|
|
104
|
+
}, 150);
|
|
105
|
+
|
|
106
|
+
expect(
|
|
107
|
+
await watchedFiles({ dependencies: { core: "workspace:*" } }),
|
|
108
|
+
).toEqual([builtFile]);
|
|
109
|
+
expect(fs.existsSync(builtFile)).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
});
|