@nx/js
Version:
921 lines (920 loc) • 40.7 kB
JavaScript
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;
;