diff --git a/packages/babel-plugin-resolve-imports/index.js b/packages/babel-plugin-resolve-imports/index.js index 68a89c9f7..ab7d62caf 100644 --- a/packages/babel-plugin-resolve-imports/index.js +++ b/packages/babel-plugin-resolve-imports/index.js @@ -40,7 +40,7 @@ function pathToNodeImportSpecifier(importPath) { module.exports = function plugin({ types: t }, { outExtension }) { /** @type {Map} */ const cache = new Map(); - const extensions = ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx']; + const extensions = ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.cjs', '.cts']; const extensionsSet = new Set(extensions); /** @@ -53,7 +53,7 @@ module.exports = function plugin({ types: t }, { outExtension }) { const importExt = nodePath.extname(importedPath); // ignore if the import already has a desired extension or if it is a css import. - if (extensionsSet.has(importExt) || importExt === '.css') { + if ((extensionsSet.has(importExt) || importExt === '.css') && outExtension !== '.cjs') { return; } diff --git a/packages/code-infra/bin/code-infra.mjs b/packages/code-infra/bin/code-infra.mjs deleted file mode 100755 index b3033ecb0..000000000 --- a/packages/code-infra/bin/code-infra.mjs +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env node -import '../src/cli/index.mjs'; diff --git a/packages/code-infra/package.json b/packages/code-infra/package.json index 2c8bc77f7..29f97cd76 100644 --- a/packages/code-infra/package.json +++ b/packages/code-infra/package.json @@ -18,12 +18,13 @@ "./markdownlint": "./src/markdownlint/index.mjs" }, "bin": { - "code-infra": "./bin/code-infra.mjs" + "code-infra": "./src/bin/code-infra.mjs" }, "scripts": { + "build-test": "node src/bin/code-infra.mjs build --flag=bundle-extension --skipBabelRuntimeCheck --ignore=\"**/setupVitest.ts\"", "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\"" + "test:copy": "rm -rf build && node src/bin/code-infra.mjs copy-files --glob \"src/cli/*.mjs\" --glob \"src/eslint/**/*.mjs:esm\"" }, "dependencies": { "@argos-ci/core": "^4.1.2", diff --git a/packages/code-infra/src/bin/code-infra.mjs b/packages/code-infra/src/bin/code-infra.mjs new file mode 100755 index 000000000..b340b89b3 --- /dev/null +++ b/packages/code-infra/src/bin/code-infra.mjs @@ -0,0 +1,2 @@ +#!/usr/bin/env node +import '../cli/index.mjs'; diff --git a/packages/code-infra/src/cli/babel.mjs b/packages/code-infra/src/cli/babel.mjs index 81105854e..755a9e036 100644 --- a/packages/code-infra/src/cli/babel.mjs +++ b/packages/code-infra/src/cli/babel.mjs @@ -8,7 +8,7 @@ import * as path from 'node:path'; import { $ } from 'execa'; import { BASE_IGNORES } from '../utils/build.mjs'; -const TO_TRANSFORM_EXTENSIONS = ['.js', '.ts', '.tsx']; +const TO_TRANSFORM_EXTENSIONS = ['.js', '.ts', '.tsx', '.mjs', '.cjs', '.mts', '.cts']; /** * @param {string} pkgVersion @@ -129,7 +129,14 @@ export async function babelBuild({ ...process.env, ...env, }, - })`babel --config-file ${configFile} --extensions ${TO_TRANSFORM_EXTENSIONS.join(',')} ${sourceDir} --out-dir ${outDir} --ignore ${BASE_IGNORES.concat(ignores).join(',')} --out-file-extension ${outExtension !== '.js' ? outExtension : '.js'} --compact ${hasLargeFiles ? 'false' : 'auto'}`; + })`babel + --config-file ${configFile} + --extensions ${TO_TRANSFORM_EXTENSIONS.join(',')} + ${sourceDir} + --out-dir ${outDir} + --ignore ${BASE_IGNORES.concat(ignores).join(',')} + --out-file-extension ${outExtension !== '.js' ? outExtension : '.js'} + --compact ${hasLargeFiles ? 'false' : 'auto'}`; if (res.stderr) { throw new Error(`Command: '${res.escapedCommand}' failed with \n${res.stderr}`); @@ -141,5 +148,7 @@ export async function babelBuild({ // cjs for reexporting from commons only modules. // If we need to rely more on this we can think about setting up a separate commonjs => commonjs build for .cjs files to .cjs // `--extensions-.cjs --out-file-extension .cjs` - await cjsCopy({ from: sourceDir, to: outDir }); + if (outExtension === '.js') { + await cjsCopy({ from: sourceDir, to: outDir }); + } } diff --git a/packages/code-infra/src/cli/cmdBuild.mjs b/packages/code-infra/src/cli/cmdBuild.mjs index e371ec9b1..21cdb7939 100644 --- a/packages/code-infra/src/cli/cmdBuild.mjs +++ b/packages/code-infra/src/cli/cmdBuild.mjs @@ -3,7 +3,7 @@ import { $ } from 'execa'; import set from 'lodash-es/set.js'; import * as fs from 'node:fs/promises'; import * as path from 'node:path'; -import { getOutExtension, isMjsBuild, validatePkgJson } from '../utils/build.mjs'; +import { getOutExtension, validatePkgJson } from '../utils/build.mjs'; /** * @typedef {Object} Args @@ -18,6 +18,7 @@ import { getOutExtension, isMjsBuild, validatePkgJson } from '../utils/build.mjs * @property {boolean} skipPackageJson - Whether to skip generating the package.json file in the bundle output. * @property {boolean} skipMainCheck - Whether to skip checking for main field in package.json. * @property {string[]} ignore - Globs to be ignored by Babel. + * @property {string[]} [flag] - Flags to alter the build behavior. */ const validBundles = [ @@ -34,9 +35,17 @@ const validBundles = [ * @param {string} options.license - The license of the package. * @param {import('../utils/build.mjs').BundleType} options.bundle * @param {string} options.outputDir + * @param {boolean} [options.useBundleExtension] */ -async function addLicense({ name, version, license, bundle, outputDir }) { - const outExtension = getOutExtension(bundle); +async function addLicense({ + name, + version, + license, + bundle, + outputDir, + useBundleExtension = false, +}) { + const outExtension = getOutExtension(bundle, false, useBundleExtension); const file = path.join(outputDir, `index${outExtension}`); if ( !(await fs.stat(file).then( @@ -128,8 +137,16 @@ async function createExportsFor({ * @param {string} param0.outputDir * @param {string} param0.cwd * @param {boolean} param0.addTypes - Whether to add type declarations for the package. + * @param {boolean} [param0.useBundleExtension] - Whether the build is using bundled extensions. */ -async function writePackageJson({ packageJson, bundles, outputDir, cwd, addTypes = false }) { +async function writePackageJson({ + packageJson, + bundles, + outputDir, + cwd, + addTypes = false, + useBundleExtension = false, +}) { delete packageJson.scripts; delete packageJson.publishConfig?.directory; delete packageJson.devDependencies; @@ -151,8 +168,8 @@ async function writePackageJson({ packageJson, bundles, outputDir, cwd, addTypes await Promise.all( bundles.map(async ({ type, dir }) => { - const outExtension = getOutExtension(type); - const typeOutExtension = getOutExtension(type, true); + const outExtension = getOutExtension(type, false, useBundleExtension); + const typeOutExtension = getOutExtension(type, true, useBundleExtension); const indexFileExists = await fs.stat(path.join(outputDir, dir, `index${outExtension}`)).then( (stats) => stats.isFile(), () => false, @@ -293,11 +310,12 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({ default: false, description: 'Skip generating the package.json file in the bundle output.', }) - .option('skipMainCheck', { - // Currently added only to support @mui/icons-material. To be removed separately. - type: 'boolean', - default: false, - description: 'Skip checking for main field in package.json.', + .option('flag', { + type: 'string', + array: true, + description: 'Flags to alter the build behavior.', + choices: ['bundle-extension'], + default: [], }); }, async handler(args) { @@ -312,7 +330,9 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({ skipTsc, skipBabelRuntimeCheck = false, skipPackageJson = false, + flag: flags = [], } = args; + const useBundleExtension = flags.includes('bundle-extension'); const cwd = process.cwd(); const pkgJsonPath = path.join(cwd, 'package.json'); @@ -356,9 +376,9 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({ // js build start await Promise.all( bundles.map(async (bundle) => { - const outExtension = getOutExtension(bundle); + const outExtension = getOutExtension(bundle, false, useBundleExtension); const relativeOutDir = relativeOutDirs[bundle]; - const outputDir = path.join(buildDir, relativeOutDir); + const outputDir = useBundleExtension ? buildDir : path.join(buildDir, relativeOutDir); await fs.mkdir(outputDir, { recursive: true }); const promises = []; @@ -382,7 +402,7 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({ }), ); - if (buildDir !== outputDir && !skipBundlePackageJson && !isMjsBuild) { + if (buildDir !== outputDir && !skipBundlePackageJson && !useBundleExtension) { // @TODO - Not needed if the output extension is .mjs. Remove this before PR merge. promises.push( fs.writeFile( @@ -402,6 +422,7 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({ name: packageJson.name, version: packageJson.version, outputDir, + useBundleExtension, }); }), ); @@ -422,7 +443,7 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({ srcDir: sourceDir, cwd, skipTsc, - isMjsBuild, + useBundleExtension, buildDir, }); } @@ -441,6 +462,7 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({ })), outputDir: buildDir, addTypes: buildTypes, + useBundleExtension, }); }, }); diff --git a/packages/code-infra/src/cli/typescript.mjs b/packages/code-infra/src/cli/typescript.mjs index 474db16c6..ea05f920e 100644 --- a/packages/code-infra/src/cli/typescript.mjs +++ b/packages/code-infra/src/cli/typescript.mjs @@ -8,6 +8,7 @@ import { globby } from 'globby'; import * as fs from 'node:fs/promises'; import * as os from 'node:os'; import * as path from 'node:path'; +import { getOutExtension } from '../utils/build.mjs'; const $$ = $({ stdio: 'inherit' }); @@ -24,6 +25,7 @@ export async function emitDeclarations(tsconfig, outDir) { --rootDir ${rootDir} --outDir ${outDir} --declaration + --declarationMap false --emitDeclarationOnly --noEmit false --composite false @@ -60,8 +62,9 @@ export async function copyDeclarations(sourceDirectory, destinationDirectory) { * Post-processes TypeScript declaration files. * @param {Object} param0 * @param {string} param0.directory - The directory containing the declaration files. + * @param {string} [param0.outExtension] - The output file extension for the processed files. */ -async function postProcessDeclarations({ directory }) { +async function postProcessDeclarations({ directory, outExtension }) { const dtsFiles = await globby('**/*.d.ts', { absolute: true, cwd: directory, @@ -76,7 +79,7 @@ async function postProcessDeclarations({ directory }) { */ const babelPlugins = [ [pluginTypescriptSyntax, { dts: true }], - [pluginResolveImports], + [pluginResolveImports, outExtension ? { outExtension } : {}], [pluginRemoveImports, { test: /\.css$/ }], ]; @@ -86,9 +89,23 @@ async function postProcessDeclarations({ directory }) { configFile: false, plugins: babelPlugins, }); + let dtsOutExtension = '.ts'; + if (outExtension === '.mjs') { + dtsOutExtension = '.mts'; + } else if (outExtension === '.cjs') { + dtsOutExtension = '.cts'; + } + + console.log({ + dtsOutExtension, + dtsFile, + out: dtsFile.replace(/\.ts$/, dtsOutExtension), + outExtension, + }); if (typeof result?.code === 'string') { - await fs.writeFile(dtsFile, result.code); + await fs.rm(dtsFile, { force: true }); + await fs.writeFile(dtsFile.replace(/\.ts$/, dtsOutExtension), result.code); } else { console.error('failed to transform', dtsFile); } @@ -96,39 +113,27 @@ async function postProcessDeclarations({ directory }) { ); } -/** - * Renames TypeScript declaration files. - * @param {Object} param0 - * @param {string} param0.directory - The directory containing the declaration files. - */ -async function renameDeclarations({ directory }) { - const dtsFiles = await globby('**/*.d.ts', { absolute: true, cwd: directory }); - if (dtsFiles.length === 0) { - return; - } - console.log(`Renaming d.ts files to d.mts in ${directory}`); - await Promise.all( - dtsFiles.map(async (dtsFile) => { - const newFileName = dtsFile.replace(/\.d\.ts$/, '.d.mts'); - await fs.rename(dtsFile, newFileName); - }), - ); -} - /** * Creates TypeScript declaration files for the specified bundles. * Types are first created in a temporary directory and then copied to the appropriate bundle directories parallelly. * After copying, babel transformations are applied to the copied files because they need to be alongside the actual js files for proper resolution. * * @param {Object} param0 - * @param {boolean} [param0.isMjsBuild] - Whether the build is for ESM (ECMAScript Modules). + * @param {boolean} [param0.useBundleExtension=false] - Whether the build is using bundled extensions. * @param {{type: import('../utils/build.mjs').BundleType, dir: string}[]} param0.bundles - The bundles to create declarations for. * @param {string} param0.srcDir - The source directory. * @param {string} param0.buildDir - The build directory. * @param {string} param0.cwd - The current working directory. * @param {boolean} param0.skipTsc - Whether to skip running TypeScript compiler (tsc) for building types. */ -export async function createTypes({ bundles, srcDir, buildDir, cwd, skipTsc, isMjsBuild }) { +export async function createTypes({ + bundles, + srcDir, + buildDir, + cwd, + skipTsc, + useBundleExtension = false, +}) { const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'code-infra-build-tsc-')); try { @@ -152,22 +157,18 @@ export async function createTypes({ bundles, srcDir, buildDir, cwd, skipTsc, isM for (const bundle of bundles) { const { type: bundleType, dir: bundleOutDir } = bundle; - const fullOutDir = path.join(buildDir, bundleOutDir); + const fullOutDir = useBundleExtension ? buildDir : path.join(buildDir, bundleOutDir); // eslint-disable-next-line no-await-in-loop await fs.cp(tmpDir, fullOutDir, { recursive: true, force: false, }); + const outExtension = getOutExtension(bundleType, false, useBundleExtension); // eslint-disable-next-line no-await-in-loop await postProcessDeclarations({ directory: fullOutDir, + outExtension, }); - if (bundleType === 'esm' && isMjsBuild) { - // eslint-disable-next-line no-await-in-loop - await renameDeclarations({ - directory: fullOutDir, - }); - } } } finally { await fs.rm(tmpDir, { recursive: true, force: true }); diff --git a/packages/code-infra/src/utils/build.mjs b/packages/code-infra/src/utils/build.mjs index 7b1b3285e..26e723554 100644 --- a/packages/code-infra/src/utils/build.mjs +++ b/packages/code-infra/src/utils/build.mjs @@ -1,22 +1,21 @@ /** * @typedef {'esm' | 'cjs'} BundleType */ -export const isMjsBuild = !!process.env.MUI_EXPERIMENTAL_MJS; /** * @param {BundleType} bundle */ -export function getOutExtension(bundle, isType = false) { +export function getOutExtension(bundle, isType = false, useBundleExtension = false) { if (isType) { - if (!isMjsBuild) { + if (!useBundleExtension) { return '.d.ts'; } - return bundle === 'esm' ? '.d.mts' : '.d.ts'; + return bundle === 'esm' ? '.d.mts' : '.d.cts'; } - if (!isMjsBuild) { + if (!useBundleExtension) { return '.js'; } - return bundle === 'esm' ? '.mjs' : '.js'; + return bundle === 'esm' ? '.mjs' : '.cjs'; } /** diff --git a/packages/docs-infra/package.json b/packages/docs-infra/package.json index f071b6671..d65653a62 100644 --- a/packages/docs-infra/package.json +++ b/packages/docs-infra/package.json @@ -21,7 +21,7 @@ }, "homepage": "https://github.com/mui/mui-public/tree/master/packages/docs-infra", "scripts": { - "build": "code-infra build --bundle esm && pnpm build:copy-files", + "build": "code-infra build --bundle esm --flag=bundle-extension && pnpm build:copy-files", "build:copy-files": "code-infra copy-files", "test": "exit 0", "typescript": "tsc -p tsconfig.json"