Skip to content

Commit 46a618c

Browse files
committed
feat: generate pkg.exports
1 parent b0d19eb commit 46a618c

File tree

9 files changed

+343
-24
lines changed

9 files changed

+343
-24
lines changed

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
"version": "1.0.0-alpha.2",
44
"description": "an example package published from template for typescript libraries",
55
"license": "MIT",
6-
"main": "dist/index.js",
7-
"module": "dist/es/index.js",
8-
"types": "dist/index.d.ts",
96
"keywords": [
107
"tlib",
118
"typescript",
@@ -93,5 +90,8 @@
9390
"publishConfig": {
9491
"access": "public"
9592
},
93+
"workspaces": [
94+
"test/built-pkg/jest-ignore"
95+
],
9696
"packageManager": "[email protected]"
9797
}

src/rollup/gen-pkg.ts

Lines changed: 169 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,99 @@
1-
function trimDist(p: string): string {
2-
if (!p.startsWith("dist/")) {
3-
throw new Error("Path should starts with 'dist/'.");
1+
import { InputOption } from "rollup";
2+
import { deepTrimStartDir, joinPath, trimStartDir } from "../util/path";
3+
import { posix as pp } from "path";
4+
5+
export interface GetEntryPointsFormatOptions {
6+
baseDir?: string;
7+
}
8+
9+
export interface GetEntryPointsOptions {
10+
input: InputOption;
11+
es?: boolean | GetEntryPointsFormatOptions;
12+
cjs?: boolean | GetEntryPointsFormatOptions;
13+
}
14+
15+
function normFormatOptions(
16+
opts: undefined | boolean | GetEntryPointsFormatOptions,
17+
): undefined | Required<GetEntryPointsFormatOptions> {
18+
return opts
19+
? opts === true
20+
? { baseDir: "." }
21+
: { ...opts, baseDir: opts.baseDir ?? "." }
22+
: undefined;
23+
}
24+
25+
export function getEntryPointsFromRollup({
26+
input,
27+
es,
28+
cjs,
29+
}: GetEntryPointsOptions): GenPkgExportsOptions {
30+
const formats = {
31+
es: normFormatOptions(es),
32+
cjs: normFormatOptions(cjs),
33+
};
34+
35+
if (typeof input === "string") {
36+
return {
37+
importEntryPoints: formats.es
38+
? { index: joinPath(formats.es.baseDir, "index.js") }
39+
: undefined,
40+
requireEntryPoints: formats.cjs
41+
? { index: joinPath(formats.cjs.baseDir, "index.js") }
42+
: undefined,
43+
};
44+
} else if (Array.isArray(input)) {
45+
throw new Error(
46+
"getEntryPointsFromRollupOptions currently doesn't support array of inputs",
47+
);
48+
} else {
49+
const importEntryPoints: Record<string, string> = {};
50+
const requireEntryPoints: Record<string, string> = {};
51+
52+
for (const ep of Object.keys(input)) {
53+
if (formats.es) {
54+
importEntryPoints[ep] = joinPath(formats.es.baseDir, `${ep}.js`);
55+
}
56+
if (formats.cjs) {
57+
requireEntryPoints[ep] = joinPath(formats.cjs.baseDir, `${ep}.js`);
58+
}
59+
}
60+
61+
return {
62+
importEntryPoints,
63+
requireEntryPoints,
64+
};
465
}
5-
return p.slice(5);
66+
}
67+
68+
export interface GenPkgExportsOptions {
69+
importEntryPoints?: Record<string, string>;
70+
requireEntryPoints?: Record<string, string>;
71+
}
72+
73+
export interface GetPkgJsonBaseContentsOptions {
74+
/**
75+
* For the `main` `module` or `types` fields of `package.json`,
76+
* if the value of the field is in the directory specified by this option,
77+
* then it will be trimmed.
78+
*
79+
* For example:
80+
*
81+
* ```
82+
* // package.json
83+
* { "main": "dist/index.js", types: "types/index.d.ts" }
84+
* // options
85+
* { trimEntryPointsFromPkg: "dist" }
86+
* // generated package.json
87+
* { "main": "index.js", types: "types/index.d.ts" }
88+
* ```
89+
*/
90+
trimPkgEntryPoints?: string;
91+
genExports?: GenPkgExportsOptions;
92+
}
93+
94+
interface ExportsEntry {
95+
import: string | null;
96+
require: string | null;
697
}
798

899
/**
@@ -12,6 +103,10 @@ function trimDist(p: string): string {
12103
*/
13104
export function getPkgJsonBaseContents(
14105
pkg: Record<string, unknown>,
106+
{
107+
trimPkgEntryPoints,
108+
genExports: { importEntryPoints, requireEntryPoints } = {},
109+
}: GetPkgJsonBaseContentsOptions = {},
15110
): Record<string, unknown> {
16111
const reserved = [
17112
"name",
@@ -23,32 +118,89 @@ export function getPkgJsonBaseContents(
23118
"repository",
24119
"publishConfig",
25120
];
26-
const pkgEntries = ["main", "module", "types"];
27121

28122
const contents: Record<string, unknown> = {};
29123

30124
for (const e of reserved) {
31-
contents[e] = pkg[e];
125+
const value = pkg[e];
126+
if (value === undefined) continue;
127+
contents[e] = value;
32128
}
33129

130+
const pkgEntries = ["main", "module", "types"];
34131
for (const e of pkgEntries) {
35132
const value = pkg[e];
36133
if (value === undefined) continue;
37134

38-
if (typeof value !== "string") {
39-
throw new Error(
40-
`package.json "${e}" field must be a string if specified, but received ${String(
41-
value,
42-
)}`,
43-
);
135+
if (trimPkgEntryPoints) {
136+
if (typeof value !== "string") {
137+
throw new Error(
138+
`package.json "${e}" field must be a string if specified, but received ${String(
139+
value,
140+
)}`,
141+
);
142+
}
143+
contents[e] = trimStartDir(value, trimPkgEntryPoints);
144+
} else {
145+
contents[e] = value;
44146
}
45-
contents[e] = trimDist(value);
46147
}
47148

48-
contents.exports = {
49-
".": "./es/index.js",
50-
"./*": "./es/*.js",
51-
};
149+
if (trimPkgEntryPoints && contents.exports !== undefined) {
150+
contents.exports = deepTrimStartDir(
151+
contents.exports as never,
152+
trimPkgEntryPoints,
153+
);
154+
}
155+
156+
if (importEntryPoints || requireEntryPoints) {
157+
const res: Record<string, ExportsEntry> = {};
158+
159+
if (importEntryPoints) {
160+
for (const [ep, file] of flatIndexEntryPoints(importEntryPoints)) {
161+
const n = normalizeEntryPoint(ep);
162+
const v = normalizeEntryPoint(file);
163+
const obj = res[n] || (res[n] = { import: null, require: null });
164+
165+
obj["import"] = v;
166+
}
167+
}
168+
169+
if (requireEntryPoints) {
170+
for (const [ep, file] of flatIndexEntryPoints(requireEntryPoints)) {
171+
const n = normalizeEntryPoint(ep);
172+
const v = normalizeEntryPoint(file);
173+
const obj = res[n] || (res[n] = { import: null, require: null });
174+
175+
obj["require"] = v;
176+
}
177+
}
178+
179+
contents.exports = res;
180+
}
52181

53182
return contents;
54183
}
184+
185+
function flatIndexEntryPoints<T>(ep: Record<string, T>): [string, T][] {
186+
return Object.entries(ep)
187+
.map(([e, file]) => {
188+
const entries: ([string, T] | null)[] = [
189+
[`./${e}`, file],
190+
e === "index" ? [".", file] : null,
191+
e.endsWith("/index") ? [`./${e.slice(0, e.length - 6)}`, file] : null,
192+
];
193+
194+
return entries.filter(<T>(v: T): v is NonNullable<T> => !!v);
195+
})
196+
.flat(1);
197+
}
198+
199+
function normalizeEntryPoint(ep: string) {
200+
const n = pp.normalize(ep);
201+
202+
if (n === ".") return ".";
203+
204+
if (n.startsWith("../")) return n;
205+
return `./${n}`;
206+
}

src/rollup/node.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import externals from "rollup-plugin-node-externals";
22
import generatePackageJson from "rollup-plugin-generate-package-json";
33
import { compilePlugins } from "./common-plugins";
4-
import { getPkgJsonBaseContents } from "./gen-pkg";
4+
import { getEntryPointsFromRollup, getPkgJsonBaseContents } from "./gen-pkg";
55
import { chunkFileNames } from "../util/common";
6-
import type { InputOption, OutputOptions, RollupOptions } from "rollup";
6+
import type {
7+
InputOption,
8+
OutputOptions,
9+
PluginImpl,
10+
RollupOptions,
11+
} from "rollup";
712
import { getEntryFiles, MatchFilesPatterns } from "./entry";
813
import { Resolvable, resolve } from "../util/resolvable";
914
import { genOutputOptions, GenOutputOptions } from "./output";
@@ -27,6 +32,25 @@ export interface RollupNodeOptions {
2732
outputRootDir?: string;
2833
}
2934

35+
const pkgModuleAfterBuild: PluginImpl = () => {
36+
return {
37+
name: "pkg-module-after-build",
38+
async writeBundle(options) {
39+
if (options.format !== "es") return;
40+
41+
if (!options.dir) return;
42+
43+
const fs = await import("fs");
44+
const fsp = fs.promises;
45+
await fsp.writeFile(
46+
joinPath(options.dir, "package.json"),
47+
JSON.stringify({ type: "module" }),
48+
);
49+
return undefined;
50+
},
51+
};
52+
};
53+
3054
export async function rollupNode({
3155
inputBaseDir = "src",
3256
inputPatterns,
@@ -66,9 +90,18 @@ export async function rollupNode({
6690
...compilePlugins(),
6791
// https://github.com/vladshcherbin/rollup-plugin-generate-package-json
6892
generatePackageJson({
69-
baseContents: getPkgJsonBaseContents,
93+
baseContents: (v: Record<string, unknown>) =>
94+
getPkgJsonBaseContents(v, {
95+
trimPkgEntryPoints: outputRootDir,
96+
genExports: getEntryPointsFromRollup({
97+
input: inputFiles,
98+
es: { baseDir: "es" },
99+
cjs: true,
100+
}),
101+
}),
70102
outputFolder: outputRootDir,
71103
}),
104+
pkgModuleAfterBuild({ baseDir: joinPath(outputRootDir, "es") }),
72105
],
73106
};
74107
}

src/util/common.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
export const chunkFileNames = "_chunks/[name]-[hash].js";
2-
export const typescriptDeclarationDir = "_tmp_dts_files";
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import * as rollup from "tlibjs-dist/rollup";
2+
import * as rollupBundle from "tlibjs-dist/rollup/bundle";
3+
import * as utilPath from "tlibjs-dist/util/path";
4+
5+
console.log(
6+
JSON.stringify(
7+
{
8+
rollup,
9+
rollupBundle,
10+
utilPath,
11+
},
12+
(k, v) => (typeof v === "function" ? `Function ${v.name}` : v),
13+
),
14+
);
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"dependencies": {
3+
"tlibjs-dist": "link:../../../dist"
4+
}
5+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const rollup = require("tlibjs-dist/rollup");
2+
const rollupBundle = require("tlibjs-dist/rollup/bundle");
3+
const utilPath = require("tlibjs-dist/util/path");
4+
5+
console.log(
6+
JSON.stringify(
7+
{
8+
rollup,
9+
rollupBundle,
10+
utilPath,
11+
},
12+
(k, v) => (typeof v === "function" ? `Function ${v.name}` : v),
13+
),
14+
);

0 commit comments

Comments
 (0)