Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions packages/code-infra/bin/code-infra.mjs

This file was deleted.

20 changes: 12 additions & 8 deletions packages/code-infra/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,11 @@
}
},
"bin": {
"code-infra": "./bin/code-infra.mjs"
"code-infra": "./src/bin/code-infra.mjs"
},
"scripts": {
"build": "tsc -p tsconfig.build.json",
"build-tsdown": "node ./src/bin/code-infra.mjs build-new --bundle esm",
"typescript": "tsc -p tsconfig.json",
"test": "pnpm -w test --project @mui/internal-code-infra",
"test:copy": "rm -rf build && node bin/code-infra.mjs copy-files --glob \"src/cli/*.mjs\" --glob \"src/eslint/**/*.mjs:esm\""
Expand Down Expand Up @@ -77,6 +78,7 @@
"@octokit/oauth-methods": "^6.0.2",
"@octokit/rest": "^22.0.1",
"@pnpm/find-workspace-dir": "^1000.1.3",
"@swc/helpers": "^0.5.17",
"babel-plugin-optimize-clsx": "^2.6.2",
"babel-plugin-transform-inline-environment-variables": "^0.4.4",
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
Expand Down Expand Up @@ -107,16 +109,24 @@
"regexp.escape": "^2.0.1",
"resolve-pkg-maps": "^1.0.0",
"semver": "^7.7.3",
"rsbuild-plugin-dts": "^0.16.1",
"stylelint-config-standard": "^39.0.1",
"typescript-eslint": "^8.46.3",
"yargs": "^18.0.0"
},
"peerDependencies": {
"@next/eslint-plugin-next": "*",
"@rslib/core": "^0.16.1",
"eslint": "^9.0.0",
"prettier": "^3.5.3",
"tsdown": "^0.16.4",
"typescript": "^5.0.0"
},
"peerDependenciesMeta": {
"tsdown": {
"optional": true
}
},
"devDependencies": {
"@octokit/types": "^16.0.0",
"@types/babel__core": "^7.20.5",
Expand All @@ -134,15 +144,9 @@
"get-port": "^7.1.0",
"prettier": "^3.6.2",
"serve": "^14.2.5",
"tsdown": "^0.16.4",
"typescript-eslint": "^8.46.3"
},
"files": [
"bin",
"build",
"src",
"README.md",
"LICENSE"
],
"publishConfig": {
"access": "public"
}
Expand Down
3 changes: 3 additions & 0 deletions packages/code-infra/src/bin/code-infra.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { run } from '../cli/index.mjs';

run();
253 changes: 253 additions & 0 deletions packages/code-infra/src/bundlers/tsdown.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
/* eslint-disable no-console */
import { builtinModules } from 'node:module';
import * as fs from 'node:fs/promises';
import { build as tsdown } from 'tsdown';
import {
getTsConfigPath,
getVersionEnvVariables,
generateEntriesFromExports,
writePkgJson,
} from '../utils/build.mjs';

/**
* @TODOs -
* [ ] Handle custom babel plugin transforms
* [ ] Figure out how to pass targets (easy to do if we want to have same target for all output formats)
* [x] Write your own package.json exports (the one built into tsdown doesn't cut it)
* [ ] Side effects from type imports without the "type" identifier. Need eslint rule to enforce this.
* [ ] Figure out how to handle conditional exports. Specifically `react-server` exports.
*
* Difference between d.ts generated by tsdown and tsc
* 1.
*
* ```ts
export { AccordionRoot as Root } from './root/AccordionRoot';
export { AccordionItem as Item } from './item/AccordionItem';
export { AccordionHeader as Header } from './header/AccordionHeader';
export { AccordionTrigger as Trigger } from './trigger/AccordionTrigger';
export { AccordionPanel as Panel } from './panel/AccordionPanel';

and

export * as Accordion from './index.parts';

```
* becomes
* ```ts
import { AccordionRoot } from "./root/AccordionRoot.js";
import { AccordionItem } from "./item/AccordionItem.js";
import { AccordionHeader } from "./header/AccordionHeader.js";
import { AccordionTrigger } from "./trigger/AccordionTrigger.js";
import { AccordionPanel } from "./panel/AccordionPanel.js";

//#region src/accordion/index.parts.d.ts
declare namespace index_parts_d_exports {
export { AccordionHeader as Header, AccordionItem as Item, AccordionPanel as Panel, AccordionRoot as Root, AccordionTrigger as Trigger };
}
//#endregion
export { index_parts_d_exports };

and

import { index_parts_d_exports } from "./index.parts.js";
export { index_parts_d_exports as Accordion };

* So wherever it can, all `export *` are converted to explicit named or namespaced exports.
*/

/**
* @typedef {import('../cli/cmdBuildNew.mjs').BaseArgs} Args
*/

/**
* @typedef {import('../cli/cmdBuildNew.mjs').PackageJson} PackageJson
*/

/**
* @param {Args} args
* @param {PackageJson} pkgJson
* @returns {Promise<void>}
*/
export async function build(args, pkgJson) {
const cwd = process.cwd();

const outDir = /** @type {any} */ (pkgJson.publishConfig)?.directory;
await fs.rm(outDir, {
recursive: true,
force: true,
});

const [exportEntries, nullEntries, binEntries] = await generateEntriesFromExports(
pkgJson.exports ?? {},
pkgJson.bin ?? {},
{ cwd },
);
const externals = new Set([
...Object.keys(pkgJson.dependencies || {}),
...Object.keys(pkgJson.peerDependencies || {}),
]);
/**
* @type {(string|RegExp)[]}
*/
const externalsArray = Array.from(externals);
externalsArray.push(new RegExp(`^(${externalsArray.join('|')})/`));
externalsArray.push(/^node:/);
externalsArray.push(...builtinModules);

const tsconfigPath = args.buildTypes ? await getTsConfigPath(cwd) : null;
const bannerText = `/**
* ${pkgJson.name} v${pkgJson.version}
*
* @license ${pkgJson.license ?? 'MIT'}
* This source code is licensed under the ${pkgJson.license} license found in the
* LICENSE file in the root directory of this source tree.
*/
`;

/**
* @type {import('tsdown').InlineConfig}
*/
const baseOptions = {
watch: args.watch || false,
config: false,
outDir,
unbundle: true,
clean: false,
skipNodeModulesBundle: true,
external: externalsArray,
platform: 'neutral',
ignoreWatch: ['**/node_modules/**', '**/dist/**', '**/build/**'],
define: {
...getVersionEnvVariables(pkgJson.version ?? ''),
},
loader: {
'.js': 'jsx',
},
logLevel: args.verbose ? 'info' : 'silent',
tsconfig: tsconfigPath ?? false,
sourcemap: args.sourceMap || false,
banner: {
js: bannerText,
css: bannerText,
},
minify: 'dce-only',
};

/**
* @type {Promise<void>[]}
*/
const promises = [];

/**
* @type {Record<string, {paths: string[]; isBin?: boolean}>}
*/
const outChunks = {};
/**
* @type {Set<string>}
*/
const exportEntrySet = new Set();
/**
* @type {Set<string>}
*/
const binEntrySet = new Set();

if (Object.keys(exportEntries).length > 0) {
args.bundle.forEach((format) => {
promises.push(
tsdown({
...baseOptions,
entry: exportEntries,
format,
dts: tsconfigPath
? {
cwd,
tsconfig: tsconfigPath,
compilerOptions: {
jsx: 'react-jsx',
outDir,
},
sourcemap: args.sourceMap ?? false,
}
: false,
outputOptions: {
plugins: [
{
name: `get-output-chunks-${format}`,
writeBundle(_ctx, chunks) {
Object.entries(chunks).forEach(([fileName, chunk]) => {
if (chunk.type !== 'chunk' || !chunk.isEntry) {
return;
}
const chunkName = chunk.name.endsWith('.d')
? chunk.name.slice(0, -2)
: chunk.name;
outChunks[chunkName] = outChunks[chunkName] || { paths: [] };
outChunks[chunkName].paths.push(fileName);
exportEntrySet.add(chunkName);
});
},
},
],
},
}),
);
});
}

if (Object.keys(binEntries).length > 0) {
const format = pkgJson.type === 'module' ? 'esm' : 'cjs';
promises.push(
tsdown({
...baseOptions,
tsconfig: undefined,
entry: binEntries,
format,
platform: 'node',
outputOptions: {
plugins: [
{
name: 'bin-shebang',
// eslint-disable-next-line consistent-return
renderChunk(code, chunk) {
if (chunk.isEntry && !code.startsWith('#!')) {
return `#!/usr/bin/env node\n${code}`;
}
},
},
{
name: 'get-output-chunks-bin',
writeBundle(_ctx, chunks) {
Object.entries(chunks).forEach(([fileName, chunk]) => {
if (chunk.type !== 'chunk' || !chunk.isEntry) {
return;
}
outChunks[chunk.name] = outChunks[chunk.name] || { paths: [], isBin: true };
outChunks[chunk.name].paths.push(fileName);
binEntrySet.add(chunk.name);
});
},
},
],
},
}),
);
}
await Promise.all(promises);

if (exportEntrySet.size || binEntrySet.size) {
const messages = [];
if (exportEntrySet.size > 0) {
messages.push(`${exportEntrySet.size} export${exportEntrySet.size > 1 ? 's' : ''}`);
}
if (binEntrySet.size > 0) {
messages.push(`${binEntrySet.size} bin ${binEntrySet.size > 1 ? 'entries' : 'entry'}`);
}
if (messages.length > 0) {
console.log(`+ Added ${messages.join(' and ')} to package.json.`);
}
}

await writePkgJson(pkgJson, outChunks, nullEntries, {
usePkgType: true,
});
}
1 change: 1 addition & 0 deletions packages/code-infra/src/cli/cmdBuild.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
} = args;

const cwd = process.cwd();
performance.mark('build-start');
const pkgJsonPath = path.join(cwd, 'package.json');
const packageJson = JSON.parse(await fs.readFile(pkgJsonPath, { encoding: 'utf8' }));
validatePkgJson(packageJson, { skipMainCheck: args.skipMainCheck });
Expand Down
Loading
Loading