UNPKG

@nx/js

Version:

The JS plugin for Nx contains executors and generators that provide the best experience for developing JavaScript and TypeScript projects.

921 lines (920 loc) • 40.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.libraryGenerator = libraryGenerator; exports.libraryGeneratorInternal = libraryGeneratorInternal; exports.addLint = addLint; const devkit_1 = require("@nx/devkit"); const project_name_and_root_utils_1 = require("@nx/devkit/src/generators/project-name-and-root-utils"); const prompt_1 = require("@nx/devkit/src/generators/prompt"); const target_defaults_utils_1 = require("@nx/devkit/src/generators/target-defaults-utils"); const log_show_project_command_1 = require("@nx/devkit/src/utils/log-show-project-command"); const path_1 = require("path"); const generator_prompts_1 = require("../../utils/generator-prompts"); const update_package_json_1 = require("../../utils/package-json/update-package-json"); const add_swc_config_1 = require("../../utils/swc/add-swc-config"); const add_swc_dependencies_1 = require("../../utils/swc/add-swc-dependencies"); const configuration_1 = require("../../utils/typescript/configuration"); const create_ts_config_1 = require("../../utils/typescript/create-ts-config"); const ensure_typescript_1 = require("../../utils/typescript/ensure-typescript"); const plugin_1 = require("../../utils/typescript/plugin"); const ts_config_1 = require("../../utils/typescript/ts-config"); const ts_solution_setup_1 = require("../../utils/typescript/ts-solution-setup"); const versions_1 = require("../../utils/versions"); const init_1 = require("../init/init"); const sort_fields_1 = require("../../utils/package-json/sort-fields"); const add_release_config_1 = require("./utils/add-release-config"); const defaultOutputDirectory = 'dist'; async function libraryGenerator(tree, schema) { return await libraryGeneratorInternal(tree, { addPlugin: false, useProjectJson: true, ...schema, }); } async function libraryGeneratorInternal(tree, schema) { const tasks = []; tasks.push(await (0, init_1.default)(tree, { ...schema, skipFormat: true, tsConfigName: schema.rootProject ? 'tsconfig.json' : 'tsconfig.base.json', addTsConfigBase: true, // In the new setup, Prettier is prompted for and installed during `create-nx-workspace`. formatter: (0, ts_solution_setup_1.isUsingTsSolutionSetup)(tree) ? 'none' : 'prettier', })); const options = await normalizeOptions(tree, schema); createFiles(tree, options); await configureProject(tree, options); if (!options.skipPackageJson) { tasks.push(addProjectDependencies(tree, options)); } // If we are using the new TS solution // We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project if (options.isUsingTsSolutionConfig) { await (0, ts_solution_setup_1.addProjectToTsSolutionWorkspace)(tree, options.projectRoot); } if (options.bundler === 'rollup') { const { configurationGenerator } = (0, devkit_1.ensurePackage)('@nx/rollup', versions_1.nxVersion); await configurationGenerator(tree, { project: options.name, compiler: 'swc', format: options.isUsingTsSolutionConfig ? ['esm'] : ['cjs', 'esm'], skipFormat: true, }); } if (options.bundler === 'vite') { const { viteConfigurationGenerator, createOrEditViteConfig } = (0, devkit_1.ensurePackage)('@nx/vite', versions_1.nxVersion); const viteTask = await viteConfigurationGenerator(tree, { project: options.name, newProject: true, uiFramework: 'none', includeVitest: options.unitTestRunner === 'vitest', includeLib: true, skipFormat: true, testEnvironment: options.testEnvironment, addPlugin: options.addPlugin, }); tasks.push(viteTask); createOrEditViteConfig(tree, { project: options.name, includeLib: true, includeVitest: options.unitTestRunner === 'vitest', testEnvironment: options.testEnvironment, }, false); } if (options.linter !== 'none') { const lintCallback = await addLint(tree, options); tasks.push(lintCallback); } if (options.unitTestRunner === 'jest') { const jestCallback = await addJest(tree, options); tasks.push(jestCallback); if (!options.isUsingTsSolutionConfig && (options.bundler === 'swc' || options.bundler === 'rollup')) { replaceJestConfig(tree, options); } } else if (options.unitTestRunner === 'vitest' && options.bundler !== 'vite' // Test would have been set up already ) { const { vitestGenerator, createOrEditViteConfig } = (0, devkit_1.ensurePackage)('@nx/vite', versions_1.nxVersion); const vitestTask = await vitestGenerator(tree, { project: options.name, uiFramework: 'none', coverageProvider: 'v8', skipFormat: true, testEnvironment: options.testEnvironment, runtimeTsconfigFileName: 'tsconfig.lib.json', compiler: options.compiler === 'swc' ? 'swc' : 'babel', addPlugin: options.addPlugin, }); tasks.push(vitestTask); createOrEditViteConfig(tree, { project: options.name, includeLib: false, includeVitest: true, testEnvironment: options.testEnvironment, }, true); } if (!schema.skipTsConfig && !options.isUsingTsSolutionConfig) { (0, ts_config_1.addTsConfigPath)(tree, options.importPath, [ (0, devkit_1.joinPathFragments)(options.projectRoot, './src', 'index.' + (options.js ? 'js' : 'ts')), ]); } if (options.isUsingTsSolutionConfig && options.unitTestRunner !== 'none') { (0, devkit_1.updateJson)(tree, (0, devkit_1.joinPathFragments)(options.projectRoot, 'tsconfig.spec.json'), (json) => { // add project reference to the runtime tsconfig.lib.json file json.references ??= []; json.references.push({ path: './tsconfig.lib.json' }); const compilerOptions = getCompilerOptions(options); // respect the module and moduleResolution set by the test runner generator if (json.compilerOptions.module) { compilerOptions.module = json.compilerOptions.module; } if (json.compilerOptions.moduleResolution) { compilerOptions.moduleResolution = json.compilerOptions.moduleResolution; } // filter out options already set with the same value in root tsconfig file that we're going to extend from json.compilerOptions = (0, configuration_1.getNeededCompilerOptionOverrides)(tree, { ...json.compilerOptions, ...compilerOptions }, // must have been created by now (0, ts_config_1.getRootTsConfigFileName)(tree)); return json; }); } (0, sort_fields_1.sortPackageJsonFields)(tree, options.projectRoot); if (!options.skipFormat) { await (0, devkit_1.formatFiles)(tree); } if (options.publishable) { tasks.push(await (0, add_release_config_1.releaseTasks)(tree)); } // Always run install to link packages. if (options.isUsingTsSolutionConfig) { tasks.push(() => (0, devkit_1.installPackagesTask)(tree, true)); } tasks.push(() => { (0, log_show_project_command_1.logShowProjectCommand)(options.name); }); return (0, devkit_1.runTasksInSerial)(...tasks); } async function configureProject(tree, options) { if (options.hasPlugin) { const nxJson = (0, devkit_1.readNxJson)(tree); (0, plugin_1.ensureProjectIsIncludedInPluginRegistrations)(nxJson, options.projectRoot, options.bundler === 'none' ? null : 'build'); (0, devkit_1.updateNxJson)(tree, nxJson); } const projectConfiguration = { root: options.projectRoot, sourceRoot: (0, devkit_1.joinPathFragments)(options.projectRoot, 'src'), projectType: 'library', targets: {}, tags: options.parsedTags, }; if (options.config !== 'npm-scripts' && (options.bundler === 'swc' || options.bundler === 'esbuild' || ((!options.isUsingTsSolutionConfig || options.useTscExecutor) && options.bundler === 'tsc'))) { const outputPath = getOutputPath(options); const executor = getBuildExecutor(options.bundler); (0, target_defaults_utils_1.addBuildTargetDefaults)(tree, executor); projectConfiguration.targets.build = { executor, outputs: ['{options.outputPath}'], options: { outputPath, main: `${options.projectRoot}/src/index` + (options.js ? '.js' : '.ts'), tsConfig: `${options.projectRoot}/tsconfig.lib.json`, }, }; if (options.bundler === 'swc' && (options.skipTypeCheck || options.isUsingTsSolutionConfig)) { projectConfiguration.targets.build.options.skipTypeCheck = true; } if (options.isUsingTsSolutionConfig) { if (options.bundler === 'esbuild') { projectConfiguration.targets.build.options.format = ['esm']; projectConfiguration.targets.build.options.declarationRootDir = `${options.projectRoot}/src`; } else if (options.bundler === 'swc') { projectConfiguration.targets.build.options.stripLeadingPaths = true; } } else { projectConfiguration.targets.build.options.assets = []; if (options.bundler === 'esbuild') { projectConfiguration.targets.build.options.format = ['cjs']; projectConfiguration.targets.build.options.generatePackageJson = true; } if (!options.minimal) { projectConfiguration.targets.build.options.assets ??= []; projectConfiguration.targets.build.options.assets.push((0, devkit_1.joinPathFragments)(options.projectRoot, '*.md')); } } } if (options.publishable) { if (options.isUsingTsSolutionConfig) { await (0, add_release_config_1.addReleaseConfigForTsSolution)(tree, options.name, projectConfiguration); } else { await (0, add_release_config_1.addReleaseConfigForNonTsSolution)(tree, options.name, projectConfiguration, defaultOutputDirectory); } } if (!options.useProjectJson) { // we want the package.json as clean as possible, with the bare minimum if (!projectConfiguration.tags?.length) { delete projectConfiguration.tags; } // We want a minimal setup, so unless targets and tags are set, just skip the `nx` property in `package.json`. if (options.isUsingTsSolutionConfig) { delete projectConfiguration.projectType; // SWC executor has logic around sourceRoot and `--strip-leading-paths`. If it is not set then dist will contain the `src` folder rather than being flat. // TODO(leo): Look at how we can remove the dependency on sourceRoot for SWC. if (options.bundler !== 'swc') { delete projectConfiguration.sourceRoot; } } // empty targets are cleaned up automatically by `updateProjectConfiguration` (0, devkit_1.updateProjectConfiguration)(tree, options.name, projectConfiguration); } else if (options.config === 'workspace' || options.config === 'project') { (0, devkit_1.addProjectConfiguration)(tree, options.name, projectConfiguration); } else { (0, devkit_1.addProjectConfiguration)(tree, options.name, { root: projectConfiguration.root, tags: projectConfiguration.tags, targets: {}, }); } } async function addLint(tree, options) { const { lintProjectGenerator } = (0, devkit_1.ensurePackage)('@nx/eslint', versions_1.nxVersion); const projectConfiguration = (0, devkit_1.readProjectConfiguration)(tree, options.name); const task = await lintProjectGenerator(tree, { project: options.name, linter: options.linter, skipFormat: true, tsConfigPaths: [ (0, devkit_1.joinPathFragments)(options.projectRoot, 'tsconfig.lib.json'), ], unitTestRunner: options.unitTestRunner, setParserOptionsProject: options.setParserOptionsProject, rootProject: options.rootProject, addPlugin: options.addPlugin, // Since the build target is inferred now, we need to let the generator know to add @nx/dependency-checks regardless. addPackageJsonDependencyChecks: options.bundler !== 'none', }); const { addOverrideToLintConfig, lintConfigHasOverride, isEslintConfigSupported, updateOverrideInLintConfig, // nx-ignore-next-line } = require('@nx/eslint/src/generators/utils/eslint-file'); // if config is not supported, we don't need to do anything if (!isEslintConfigSupported(tree)) { return task; } // Also update the root ESLint config. The lintProjectGenerator will not generate it for root projects. // But we need to set the package.json checks. if (options.rootProject) { addOverrideToLintConfig(tree, '', { files: ['*.json'], parser: 'jsonc-eslint-parser', rules: { '@nx/dependency-checks': [ 'error', { // With flat configs, we don't want to include imports in the eslint js/cjs/mjs files to be checked ignoredFiles: ['{projectRoot}/eslint.config.{js,cjs,mjs}'], }, ], }, }); } // If project lints package.json with @nx/dependency-checks, then add ignore files for // build configuration files such as vite.config.ts. These config files need to be // ignored, otherwise we will errors on missing dependencies that are for dev only. if (lintConfigHasOverride(tree, projectConfiguration.root, (o) => Array.isArray(o.files) ? o.files.some((f) => f.match(/\.json$/)) : !!o.files?.match(/\.json$/), true)) { updateOverrideInLintConfig(tree, projectConfiguration.root, (o) => o.rules?.['@nx/dependency-checks'], (o) => { const value = o.rules['@nx/dependency-checks']; let ruleSeverity; let ruleOptions; if (Array.isArray(value)) { ruleSeverity = value[0]; ruleOptions = value[1]; } else { ruleSeverity = value; ruleOptions = {}; } const ignoredFiles = new Set(ruleOptions.ignoredFiles ?? []); if (options.bundler === 'vite') { ignoredFiles.add('{projectRoot}/vite.config.{js,ts,mjs,mts}'); } else if (options.bundler === 'rollup') { ignoredFiles.add('{projectRoot}/rollup.config.{js,ts,mjs,mts,cjs,cts}'); } else if (options.bundler === 'esbuild') { ignoredFiles.add('{projectRoot}/esbuild.config.{js,ts,mjs,mts}'); } if (options.unitTestRunner === 'vitest') { ignoredFiles.add('{projectRoot}/vite.config.{js,ts,mjs,mts}'); } if (ignoredFiles.size) { ruleOptions.ignoredFiles = Array.from(ignoredFiles); o.rules['@nx/dependency-checks'] = [ruleSeverity, ruleOptions]; } return o; }); } return task; } function addBabelRc(tree, options) { const filename = '.babelrc'; const babelrc = { presets: [['@nx/js/babel', { useBuiltIns: 'usage' }]], }; (0, devkit_1.writeJson)(tree, (0, path_1.join)(options.projectRoot, filename), babelrc); } function createFiles(tree, options) { const { className, name, propertyName } = (0, devkit_1.names)(options.projectNames.projectFileName); createProjectTsConfigs(tree, options); let fileNameImport = options.fileName; if (options.bundler === 'vite' || options.isUsingTsSolutionConfig) { const tsConfig = (0, ts_config_1.readTsConfigFromTree)(tree, (0, path_1.join)(options.projectRoot, 'tsconfig.lib.json')); const ts = (0, ensure_typescript_1.ensureTypescript)(); if (tsConfig.options.moduleResolution === ts.ModuleResolutionKind.Node16 || tsConfig.options.moduleResolution === ts.ModuleResolutionKind.NodeNext) { // Node16 and NodeNext require explicit file extensions for relative // import paths. Since we generate the file with the `.ts` extension, // we import it from the same file with the `.js` extension. // https://www.typescriptlang.org/docs/handbook/modules/reference.html#file-extension-substitution fileNameImport = `${options.fileName}.js`; } } (0, devkit_1.generateFiles)(tree, (0, path_1.join)(__dirname, './files/lib'), options.projectRoot, { ...options, dot: '.', className, name, propertyName, js: !!options.js, cliCommand: 'nx', strict: undefined, tmpl: '', offsetFromRoot: (0, devkit_1.offsetFromRoot)(options.projectRoot), buildable: options.bundler && options.bundler !== 'none', hasUnitTestRunner: options.unitTestRunner !== 'none', fileNameImport, }); if (!options.rootProject) { (0, devkit_1.generateFiles)(tree, (0, path_1.join)(__dirname, './files/readme'), options.projectRoot, { ...options, dot: '.', className, name, propertyName, js: !!options.js, cliCommand: 'nx', strict: undefined, tmpl: '', offsetFromRoot: (0, devkit_1.offsetFromRoot)(options.projectRoot), buildable: options.bundler && options.bundler !== 'none', hasUnitTestRunner: options.unitTestRunner !== 'none', }); } if (options.bundler === 'swc' || options.bundler === 'rollup') { (0, add_swc_config_1.addSwcConfig)(tree, options.projectRoot, options.bundler === 'swc' && !options.isUsingTsSolutionConfig ? 'commonjs' : 'es6'); } else if (options.includeBabelRc) { addBabelRc(tree, options); } if (options.unitTestRunner === 'none') { tree.delete((0, path_1.join)(options.projectRoot, 'src/lib', `${options.fileName}.spec.ts`)); tree.delete((0, path_1.join)(options.projectRoot, 'src/app', `${options.fileName}.spec.ts`)); } if (options.js) { (0, devkit_1.toJS)(tree); } const packageJsonPath = (0, devkit_1.joinPathFragments)(options.projectRoot, 'package.json'); if (tree.exists(packageJsonPath)) { (0, devkit_1.updateJson)(tree, packageJsonPath, (json) => { json.name = options.importPath; json.version = '0.0.1'; // If the package is publishable or root/standalone, we should remove the private field. if (json.private && (options.publishable || options.rootProject)) { delete json.private; } if (!options.publishable && !options.rootProject) { json.private = true; } if (options.isUsingTsSolutionConfig && options.publishable) { // package.json and README.md are always included by default // https://docs.npmjs.com/cli/v10/configuring-npm/package-json#files json.files = ['dist', '!**/*.tsbuildinfo']; } const updatedPackageJson = { ...json, dependencies: { ...json.dependencies, ...determineDependencies(options), }, ...determineEntryFields(options), }; if (options.isUsingTsSolutionConfig && !['none', 'rollup', 'vite'].includes(options.bundler)) { // the file must exist in the TS solution setup const tsconfigBase = (0, devkit_1.readJson)(tree, 'tsconfig.base.json'); return (0, update_package_json_1.getUpdatedPackageJsonContent)(updatedPackageJson, { main: (0, path_1.join)(options.projectRoot, 'src/index.ts'), outputPath: (0, devkit_1.joinPathFragments)(options.projectRoot, 'dist'), projectRoot: options.projectRoot, rootDir: (0, path_1.join)(options.projectRoot, 'src'), generateExportsField: true, packageJsonPath, format: ['esm'], skipDevelopmentExports: !tsconfigBase.compilerOptions?.customConditions?.includes('development'), }); } return updatedPackageJson; }); } else { let packageJson = { name: options.importPath, version: '0.0.1', dependencies: determineDependencies(options), ...determineEntryFields(options), }; if (!options.publishable && !options.rootProject) { packageJson.private = true; } if (options.isUsingTsSolutionConfig && options.publishable) { // package.json and README.md are always included by default // https://docs.npmjs.com/cli/v10/configuring-npm/package-json#files packageJson.files = ['dist', '!**/*.tsbuildinfo']; } if (options.isUsingTsSolutionConfig && !['none', 'rollup', 'vite'].includes(options.bundler)) { const tsconfigBase = (0, devkit_1.readJson)(tree, 'tsconfig.base.json'); packageJson = (0, update_package_json_1.getUpdatedPackageJsonContent)(packageJson, { main: (0, path_1.join)(options.projectRoot, 'src/index.ts'), outputPath: (0, devkit_1.joinPathFragments)(options.projectRoot, 'dist'), projectRoot: options.projectRoot, rootDir: (0, path_1.join)(options.projectRoot, 'src'), generateExportsField: true, packageJsonPath, format: ['esm'], skipDevelopmentExports: !tsconfigBase.compilerOptions?.customConditions?.includes('development'), }); } if (!options.useProjectJson && options.name !== options.importPath) { packageJson.nx = { name: options.name, }; } (0, devkit_1.writeJson)(tree, packageJsonPath, packageJson); } if (options.config === 'npm-scripts') { (0, devkit_1.updateJson)(tree, packageJsonPath, (json) => { json.scripts = { build: "echo 'implement build'", test: "echo 'implement test'", }; return json; }); } else if (!options.isUsingTsSolutionConfig && options.useProjectJson && (!options.bundler || options.bundler === 'none') && !(options.projectRoot === '.')) { tree.delete(packageJsonPath); } if (options.minimal && !(options.projectRoot === '.')) { tree.delete((0, path_1.join)(options.projectRoot, 'README.md')); } } async function addJest(tree, options) { const { configurationGenerator } = (0, devkit_1.ensurePackage)('@nx/jest', versions_1.nxVersion); return await configurationGenerator(tree, { ...options, project: options.name, setupFile: 'none', supportTsx: false, skipSerializers: true, testEnvironment: options.testEnvironment ?? 'node', skipFormat: true, compiler: options.shouldUseSwcJest ? 'swc' : options.bundler === 'tsc' ? 'tsc' : undefined, runtimeTsconfigFileName: 'tsconfig.lib.json', }); } function replaceJestConfig(tree, options) { const filesDir = (0, path_1.join)(__dirname, './files/jest-config'); // the existing config has to be deleted otherwise the new config won't overwrite it const existingJestConfig = (0, devkit_1.joinPathFragments)(filesDir, `jest.config.${options.js ? 'js' : 'ts'}`); if (tree.exists(existingJestConfig)) { tree.delete(existingJestConfig); } const jestPreset = findRootJestPreset(tree) ?? 'jest.presets.js'; // replace with JS:SWC specific jest config (0, devkit_1.generateFiles)(tree, filesDir, options.projectRoot, { ext: options.js ? 'js' : 'ts', jestPreset, js: !!options.js, project: options.name, offsetFromRoot: (0, devkit_1.offsetFromRoot)(options.projectRoot), projectRoot: options.projectRoot, testEnvironment: options.testEnvironment, }); } async function normalizeOptions(tree, options) { await (0, project_name_and_root_utils_1.ensureRootProjectName)(options, 'library'); const nxJson = (0, devkit_1.readNxJson)(tree); options.addPlugin ??= process.env.NX_ADD_PLUGINS !== 'false' && nxJson.useInferencePlugins !== false; options.linter = await (0, generator_prompts_1.normalizeLinterOption)(tree, options.linter); const hasPlugin = (0, ts_solution_setup_1.isUsingTypeScriptPlugin)(tree); const isUsingTsSolutionConfig = (0, ts_solution_setup_1.isUsingTsSolutionSetup)(tree); if (isUsingTsSolutionConfig) { options.unitTestRunner ??= await (0, prompt_1.promptWhenInteractive)({ type: 'autocomplete', name: 'unitTestRunner', message: `Which unit test runner would you like to use?`, choices: [{ name: 'none' }, { name: 'vitest' }, { name: 'jest' }], initial: 0, }, { unitTestRunner: 'none' }).then(({ unitTestRunner }) => unitTestRunner); } else { options.unitTestRunner ??= await (0, prompt_1.promptWhenInteractive)({ type: 'autocomplete', name: 'unitTestRunner', message: `Which unit test runner would you like to use?`, choices: [{ name: 'jest' }, { name: 'vitest' }, { name: 'none' }], initial: 0, }, { unitTestRunner: undefined }).then(({ unitTestRunner }) => unitTestRunner); if (!options.unitTestRunner && options.bundler === 'vite') { options.unitTestRunner = 'vitest'; } else if (!options.unitTestRunner && options.config !== 'npm-scripts') { options.unitTestRunner = 'jest'; } } /** * We are deprecating the compiler and the buildable options. * However, we want to keep the existing behavior for now. * * So, if the user has not provided a bundler, we will use the compiler option, if any. * * If the user has not provided a bundler and no compiler, but has set buildable to true, * we will use tsc, since that is the compiler the old generator used to default to, if buildable was true * and no compiler was provided. * * If the user has not provided a bundler and no compiler, and has not set buildable to true, then * set the bundler to tsc, to preserve old default behaviour (buildable: true by default). * * If it's publishable, we need to build the code before publishing it, so again * we default to `tsc`. In the previous version of this, it would set `buildable` to true * and that would default to `tsc`. * * In the past, the only way to get a non-buildable library was to set buildable to false. * Now, the only way to get a non-buildble library is to set bundler to none. * By default, with nothing provided, libraries are buildable with `@nx/js:tsc`. */ options.bundler ??= options.compiler ?? 'tsc'; // ensure programmatic runs have an expected default if (!options.config) { options.config = 'project'; } if (options.publishable) { if (!options.importPath) { throw new Error(`For publishable libs you have to provide a proper "--importPath" which needs to be a valid npm package name (e.g. my-awesome-lib or @myorg/my-lib)`); } if (options.bundler === 'none') { throw new Error(`Publishable libraries can't be generated with "--bundler=none". Please select a valid bundler.`); } } // This is to preserve old behavior, buildable: false if (options.publishable === false && options.buildable === false) { options.bundler = 'none'; } if (options.config === 'npm-scripts') { options.unitTestRunner = 'none'; options.linter = 'none'; options.bundler = 'none'; } if ((options.bundler === 'swc' || options.bundler === 'rollup') && (options.skipTypeCheck == null || !isUsingTsSolutionConfig)) { options.skipTypeCheck = false; } const { projectName, names: projectNames, projectRoot, importPath, } = await (0, project_name_and_root_utils_1.determineProjectNameAndRootOptions)(tree, { name: options.name, projectType: 'library', directory: options.directory, importPath: options.importPath, rootProject: options.rootProject, }); options.rootProject = projectRoot === '.'; const fileName = (0, devkit_1.names)(options.simpleName ? projectNames.projectSimpleName : projectNames.projectFileName).fileName; const parsedTags = options.tags ? options.tags.split(',').map((s) => s.trim()) : []; options.minimal ??= false; // We default to generate a project.json file if the new setup is not being used options.useProjectJson ??= !isUsingTsSolutionConfig; const shouldUseSwcJest = options.bundler === 'swc' || options.bundler === 'rollup' || isUsingTsSolutionConfig; return { ...options, fileName, name: isUsingTsSolutionConfig && !options.name ? importPath : projectName, projectNames, projectRoot, parsedTags, importPath, hasPlugin, isUsingTsSolutionConfig, shouldUseSwcJest, }; } function addProjectDependencies(tree, options) { if (options.bundler == 'esbuild') { return (0, devkit_1.addDependenciesToPackageJson)(tree, {}, { '@nx/esbuild': versions_1.nxVersion, '@types/node': versions_1.typesNodeVersion, esbuild: versions_1.esbuildVersion, }); } else if (options.bundler == 'rollup') { const { dependencies, devDependencies } = (0, add_swc_dependencies_1.getSwcDependencies)(); return (0, devkit_1.addDependenciesToPackageJson)(tree, { ...dependencies }, { ...devDependencies, '@nx/rollup': versions_1.nxVersion, '@types/node': versions_1.typesNodeVersion, }); } else if (options.bundler === 'tsc') { return (0, devkit_1.addDependenciesToPackageJson)(tree, {}, { tslib: versions_1.tsLibVersion, '@types/node': versions_1.typesNodeVersion }); } else if (options.bundler === 'swc') { const { dependencies, devDependencies } = (0, add_swc_dependencies_1.getSwcDependencies)(); return (0, devkit_1.addDependenciesToPackageJson)(tree, { ...dependencies }, { ...devDependencies, '@types/node': versions_1.typesNodeVersion }); } else { return (0, devkit_1.addDependenciesToPackageJson)(tree, {}, { '@types/node': versions_1.typesNodeVersion }); } // Vite is being installed in the next step if bundler is vite // noop return () => { }; } function getBuildExecutor(bundler) { switch (bundler) { case 'esbuild': return `@nx/esbuild:esbuild`; case 'rollup': return `@nx/rollup:rollup`; case 'swc': case 'tsc': return `@nx/js:${bundler}`; case 'vite': return `@nx/vite:build`; case 'none': default: return undefined; } } function getOutputPath(options) { if (options.isUsingTsSolutionConfig) { // Executors expect paths relative to workspace root, so we prepend the project root return (0, devkit_1.joinPathFragments)(options.projectRoot, 'dist'); } const parts = [defaultOutputDirectory]; if (options.projectRoot === '.') { parts.push(options.name); } else { parts.push(options.projectRoot); } return (0, devkit_1.joinPathFragments)(...parts); } function createProjectTsConfigs(tree, options) { const rootOffset = (0, devkit_1.offsetFromRoot)(options.projectRoot); let compilerOptionOverrides = getCompilerOptions(options); if (options.isUsingTsSolutionConfig) { // filter out options already set with the same value in root tsconfig file that we're going to extend from compilerOptionOverrides = (0, configuration_1.getNeededCompilerOptionOverrides)(tree, compilerOptionOverrides, // must have been created by now (0, ts_config_1.getRootTsConfigFileName)(tree)); } // tsconfig.lib.json (0, devkit_1.generateFiles)(tree, (0, path_1.join)(__dirname, 'files/tsconfig-lib', options.isUsingTsSolutionConfig ? 'ts-solution' : 'non-ts-solution'), options.projectRoot, { ...options, offsetFromRoot: rootOffset, js: !!options.js, compilerOptions: Object.entries(compilerOptionOverrides) .map(([k, v]) => `${JSON.stringify(k)}: ${JSON.stringify(v)}`) .join(',\n '), tmpl: '', outDir: 'dist', emitDeclarationOnly: options.bundler === 'tsc' ? false : true, }); // tsconfig.json if (options.isUsingTsSolutionConfig) { if (options.rootProject) { // the root tsconfig.json is already created with the expected settings // for the TS plugin, we just need to update it with the project-specific // settings (0, devkit_1.updateJson)(tree, 'tsconfig.json', (json) => { json.references.push({ path: './tsconfig.lib.json', }); return json; }); } else { // create a new tsconfig.json for the project const tsconfig = { extends: (0, ts_config_1.getRelativePathToRootTsConfig)(tree, options.projectRoot), files: [], include: [], references: [{ path: './tsconfig.lib.json' }], }; (0, devkit_1.writeJson)(tree, (0, devkit_1.joinPathFragments)(options.projectRoot, 'tsconfig.json'), tsconfig); // update root project tsconfig.json references with the new lib tsconfig (0, devkit_1.updateJson)(tree, 'tsconfig.json', (json) => { json.references ??= []; json.references.push({ path: options.projectRoot.startsWith('./') ? options.projectRoot : './' + options.projectRoot, }); return json; }); } return; } const tsconfig = { extends: options.rootProject ? undefined : (0, ts_config_1.getRelativePathToRootTsConfig)(tree, options.projectRoot), compilerOptions: { ...(options.rootProject ? create_ts_config_1.tsConfigBaseOptions : {}), ...compilerOptionOverrides, }, files: [], include: [], references: [ { path: './tsconfig.lib.json', }, ], }; (0, devkit_1.writeJson)(tree, (0, devkit_1.joinPathFragments)(options.projectRoot, 'tsconfig.json'), tsconfig); } function getCompilerOptions(options) { return { module: options.isUsingTsSolutionConfig ? options.bundler === 'rollup' ? 'esnext' : 'nodenext' : 'commonjs', ...(options.isUsingTsSolutionConfig ? { moduleResolution: options.bundler === 'rollup' ? 'bundler' : 'nodenext', } : {}), ...(options.js ? { allowJs: true } : {}), ...(options.strict ? { forceConsistentCasingInFileNames: true, strict: true, importHelpers: true, noImplicitOverride: true, noImplicitReturns: true, noFallthroughCasesInSwitch: true, ...(!options.isUsingTsSolutionConfig ? { noPropertyAccessFromIndexSignature: true } : {}), } : {}), }; } function determineDependencies(options) { switch (options.bundler) { case 'tsc': // importHelpers is true by default, so need to add tslib as a dependency. return { tslib: versions_1.tsLibVersion, }; case 'swc': // externalHelpers is true by default, so need to add swc helpers as a dependency. return { '@swc/helpers': versions_1.swcHelpersVersion, }; default: { // In other cases (vite, rollup, esbuild), helpers are bundled so no need to add them as a dependency. return {}; } } } function determineEntryFields(options) { switch (options.bundler) { case 'tsc': case 'swc': if (options.isUsingTsSolutionConfig) { return { type: 'module', main: './dist/index.js', types: './dist/index.d.ts', }; } else { return { type: 'commonjs', main: './src/index.js', types: './src/index.d.ts', }; } case 'rollup': if (options.isUsingTsSolutionConfig) { // the rollup configuration generator already handles this return {}; } else { return { // Since we're publishing both formats, skip the type field. // Bundlers or Node will determine the entry point to use. main: './index.cjs', module: './index.js', }; } case 'vite': if (options.isUsingTsSolutionConfig) { // the vite configuration generator already handle this return {}; } else { return { type: 'module', main: './index.js', types: './index.d.ts', }; } case 'esbuild': if (options.isUsingTsSolutionConfig) { return { type: 'module', main: './dist/index.js', types: './dist/index.d.ts', }; } else { return { type: 'commonjs', main: './index.cjs', types: './index.d.ts', }; } case 'none': { if (options.isUsingTsSolutionConfig) { return { type: 'module', main: options.js ? './src/index.js' : './src/index.ts', types: options.js ? './src/index.js' : './src/index.ts', exports: { '.': options.js ? './src/index.js' : { types: './src/index.ts', import: './src/index.ts', default: './src/index.ts', }, './package.json': './package.json', }, }; } return { // Safest option is to not set a type field. // Allow the user to decide which module format their library is using type: undefined, }; } default: { return {}; } } } function findRootJestPreset(tree) { const ext = ['js', 'cjs', 'mjs'].find((ext) => tree.exists(`jest.preset.${ext}`)); return ext ? `jest.preset.${ext}` : null; } exports.default = libraryGenerator;