Skip to content

Commit

Permalink
chore: use rollup instead of tsup for a cleaner dist (#255)
Browse files Browse the repository at this point in the history
* update tsup to 8.2.0 (no change to dist)

* add rollup config to generate dist

* fix browser build config

* browser umd bundle sourcemap should be published

* browser bundle is iife, not umd, despite folder name

* rollup: don't use manualChunks to keep the config a tad simpler

* prepack: use rollup instead of tsup

* remove browser entrypoint from dist

* remove comments in .js and .mjs outputs

* remove src/browser.ts, use equivalent Rollup config

* Restore original .npmignore

* rollup config: rename umdBuildConfig to browserBuildConfig

* rollup: use package.json#exports for esm entrypoints

* chore: package.json

* rollup config

* sort

* lint

* prettier

---------

Co-authored-by: Sojin Park <raon0211@toss.im>
  • Loading branch information
fvsch and raon0211 authored Jul 24, 2024
1 parent 3b99a74 commit 7b2aa1a
Show file tree
Hide file tree
Showing 7 changed files with 524 additions and 803 deletions.
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
},
"files": [
"dist",
"umd",
"*.d.ts"
],
"publishConfig": {
Expand Down Expand Up @@ -137,6 +138,8 @@
"@babel/preset-typescript": "^7.24.1",
"@changesets/changelog-github": "^0.5.0",
"@changesets/cli": "^2.27.1",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^11.1.6",
"@types/babel__core": "^7",
"@types/babel__preset-env": "^7",
"@types/broken-link-checker": "^0",
Expand All @@ -151,15 +154,18 @@
"eslint-plugin-jsdoc": "^48.5.0",
"execa": "^9.3.0",
"prettier": "^3.2.5",
"rollup": "^4.19.0",
"rollup-plugin-dts": "^6.1.1",
"tar": "^6",
"tslib": "^2.6.3",
"tsup": "^8.1.0",
"typescript": "^5.4.5",
"vitest": "^1.5.2"
},
"sideEffects": false,
"scripts": {
"prepack": "yarn build",
"build": "tsup && ./.scripts/postbuild.sh",
"build": "rollup -c rollup.config.mjs && ./.scripts/postbuild.sh",
"test": "vitest run --coverage --typecheck",
"bench": "vitest bench",
"lint": "eslint ./src --ext .ts",
Expand Down
174 changes: 174 additions & 0 deletions rollup.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// @ts-check

import fs from 'node:fs';
import { createRequire } from 'node:module';
import path from 'node:path';
import { dirname } from 'node:path';
import terserPlugin from '@rollup/plugin-terser';
import tsPlugin from '@rollup/plugin-typescript';
import dtsPlugin from 'rollup-plugin-dts';
import { fileURLToPath } from 'node:url';

// eslint-disable-next-line @typescript-eslint/naming-convention
const __dirname = dirname(fileURLToPath(import.meta.url));

/**
* @type {{
* exports: Record<string, string>;
* publishConfig: { browser: string };
* }}
*/
const packageJson = createRequire(import.meta.url)('./package.json');

const testPatterns = ['**/*.bench.ts', '**/*.spec.ts', '**/*.test.ts'];

export default () => {
clearDir('dist');
clearDir('umd');

const entrypoints = Object.values(packageJson.exports).filter(f => /^(\.\/)?src\//.test(f) && f.endsWith('.ts'));

return [
libBuildOptions({
format: 'esm',
extension: 'mjs',
entrypoints,
outDir: 'dist',
}),
libBuildOptions({
format: 'cjs',
extension: 'js',
entrypoints,
outDir: 'dist',
}),
declarationOptions({
entrypoints,
outDir: 'dist',
}),
browserBuildConfig({
inputFile: './src/compat/index.ts',
outFile: packageJson.publishConfig.browser,
name: '_',
}),
];
};

/**
* @type {(options: {
* entrypoints: string[];
* format: 'esm' | 'cjs';
* extension: 'js' | 'cjs' | 'mjs';
* outDir: string;
* }) => import('rollup').RollupOptions}
*/
function libBuildOptions({ entrypoints, extension, format, outDir }) {
const isESM = format === 'esm';

return {
input: mapInputs(entrypoints),
plugins: [
tsPlugin({
exclude: [...testPatterns],
compilerOptions: {
sourceMap: true,
inlineSources: true,
declaration: false,
removeComments: true,
},
}),
],
output: {
format,
dir: outDir,
...fileNames(extension),
// Using preserveModules disables bundling and the creation of chunks,
// leading to a result that is a mirror of the input module graph.
preserveModules: isESM,
sourcemap: true,
generatedCode: 'es2015',
// Hoisting transitive imports adds bare imports in modules,
// which can make imports by JS runtimes slightly faster,
// but makes the generated code harder to follow.
hoistTransitiveImports: false,
},
};
}

/**
* @type {(options: {inputFile: string; outFile: string; name: string}) => import('rollup').RollupOptions}
*/
function browserBuildConfig({ inputFile, outFile, name }) {
return {
input: inputFile,
plugins: [
tsPlugin({
exclude: [...testPatterns],
compilerOptions: {
sourceMap: true,
inlineSources: true,
removeComments: true,
declaration: false,
},
}),
],
output: {
plugins: [terserPlugin()],
format: 'iife',
name,
file: outFile,
sourcemap: true,
generatedCode: 'es2015',
},
};
}

/**
* @type {(options: {entrypoints: string[]; outDir: string}) => import('rollup').RollupOptions}
*/
function declarationOptions({ entrypoints, outDir }) {
return {
plugins: [dtsPlugin()],
input: mapInputs(entrypoints),
output: [
{
format: 'esm',
dir: outDir,
generatedCode: 'es2015',
...fileNames('d.mts'),
preserveModules: true,
preserveModulesRoot: 'src',
},
{
format: 'cjs',
dir: outDir,
generatedCode: 'es2015',
...fileNames('d.ts'),
preserveModules: true,
preserveModulesRoot: 'src',
},
],
};
}

/** @type {(srcFiles: string[]) => Record<string, string>} */
function mapInputs(srcFiles) {
return Object.fromEntries(
srcFiles.map(file => [file.replace(/^(\.\/)?src\//, '').replace(/\.[cm]?(js|ts)$/, ''), path.join(__dirname, file)])
);
}

function fileNames(extension = 'js') {
return {
entryFileNames: `[name].${extension}`,
chunkFileNames: `_chunk/[name]-[hash:6].${extension}`,
};
}

/** @type {(dir: string) => void} */
function clearDir(dir) {
const dirPath = path.join(__dirname, dir);
if (dir && fs.existsSync(dirPath)) {
fs.rmSync(dirPath, { recursive: true, force: true });
console.log(`cleared: ${dir}`);
}
}
8 changes: 0 additions & 8 deletions src/browser.ts

This file was deleted.

98 changes: 54 additions & 44 deletions tests/check-dist.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,59 +32,69 @@ const ENTRYPOINTS = [
'./promise',
'./string',
'./compat',
]
];

describe(`es-toolkit's package tarball`, () => {
it('configures all entrypoints correctly', async () => {
const packageJson = await getPackageJsonOfTarball();
const entrypoints = Object.keys(packageJson.exports);

expect(entrypoints).toEqual([
...ENTRYPOINTS,
'./package.json'
]);
}, { timeout: 30_000 });

it('exports identical functions in CJS and ESM', async () => {
const tarball = await createPackageTarball();
const tmpdir = await createTmpDir();

const packageJson = {
"dependencies": {
"es-toolkit": tarball.path,
}
}

await fs.promises.writeFile(path.join(tmpdir, 'package.json'), JSON.stringify(packageJson, null, 2));
await execa('npm', ['install'], { cwd: tmpdir });

for (const entrypoint of ENTRYPOINTS) {
const cjsScript = `
const toolkit = require("${path.join("es-toolkit", entrypoint)}");
const exported = Object.entries(toolkit).map(([k, v]) => [k, typeof v]);
it(
'configures all entrypoints correctly',
async () => {
const packageJson = await getPackageJsonOfTarball();
const entrypoints = Object.keys(packageJson.exports);

expect(entrypoints).toEqual([...ENTRYPOINTS, './package.json']);
},
{ timeout: 30_000 }
);

it(
'exports identical functions in CJS and ESM',
async () => {
const tarball = await createPackageTarball();
const tmpdir = await createTmpDir();

const packageJson = {
dependencies: {
'es-toolkit': tarball.path,
},
};

await fs.promises.writeFile(path.join(tmpdir, 'package.json'), JSON.stringify(packageJson, null, 2));
await execa('npm', ['install'], { cwd: tmpdir });

for (const entrypoint of ENTRYPOINTS) {
const cjsScript = `
const toolkit = require("${path.join('es-toolkit', entrypoint)}");
const exported = Object.entries(toolkit)
.map(([k, v]) => [k, typeof v])
.sort((x, y) => x[0].localeCompare(y[0]));
console.log("${path.join('es-toolkit', entrypoint)}");
console.log(exported);
`.trim();
const cjsScriptPath = path.join(tmpdir, 'script.cjs');
const cjsScriptPath = path.join(tmpdir, 'script.cjs');

const esmScript = `
const toolkit = await import("${path.join("es-toolkit", entrypoint)}");
const esmScript = `
const toolkit = await import("${path.join('es-toolkit', entrypoint)}");
const exported = Object.entries(toolkit).map(([k, v]) => [k, typeof v]);
const exported = Object.entries(toolkit)
.map(([k, v]) => [k, typeof v])
.sort((x, y) => x[0].localeCompare(y[0]));
console.log("${path.join('es-toolkit', entrypoint)}");
console.log(exported);
`.trim();
const esmScriptPath = path.join(tmpdir, 'script.mjs');

await fs.promises.writeFile(cjsScriptPath, cjsScript);
await fs.promises.writeFile(esmScriptPath, esmScript);
const esmScriptPath = path.join(tmpdir, 'script.mjs');

const cjsResult = await execa('node', [cjsScriptPath])
const esmResult = await execa('node', [esmScriptPath])
await fs.promises.writeFile(cjsScriptPath, cjsScript);
await fs.promises.writeFile(esmScriptPath, esmScript);

expect(cjsResult.stdout).toEqual(esmResult.stdout);
}
const cjsResult = await execa('node', [cjsScriptPath]);
const esmResult = await execa('node', [esmScriptPath]);

}, { timeout: 60_000 });
})
expect(cjsResult.stdout).toEqual(esmResult.stdout);
}
},
{ timeout: 60_000 }
);
});
20 changes: 0 additions & 20 deletions tsup.config.ts

This file was deleted.

1 change: 0 additions & 1 deletion vitest.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export default defineConfig({
coverage: {
provider: 'istanbul',
include: ['src/**/*'],
exclude: ['src/browser.ts'],
},
},
});
Loading

0 comments on commit 7b2aa1a

Please sign in to comment.