feat(js): update the setup-build generator to support the new ts setup (#28446)
Update the `@nx/js:setup-build` and the generators it depends on to support the new TS setup with project references. <!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> <!-- If this is a particularly complex change or feature addition, you can request a dedicated Nx release for this pull request branch. Mention someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they will confirm if the PR warrants its own release for testing purposes, and generate it for you if appropriate. --> ## Current Behavior <!-- This is the behavior we have today --> ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
This commit is contained in:
parent
1fec637514
commit
f357b4ed53
@ -61,6 +61,12 @@
|
||||
"description": "The build target to add.",
|
||||
"type": "string",
|
||||
"default": "build"
|
||||
},
|
||||
"format": {
|
||||
"description": "The format to build the library (esm or cjs).",
|
||||
"type": "array",
|
||||
"items": { "type": "string", "enum": ["esm", "cjs"] },
|
||||
"default": ["esm"]
|
||||
}
|
||||
},
|
||||
"required": [],
|
||||
|
||||
@ -130,13 +130,13 @@
|
||||
"description": "Generate a lockfile (e.g. package-lock.json) that matches the workspace lockfile to ensure package versions match.",
|
||||
"default": false,
|
||||
"x-priority": "internal"
|
||||
},
|
||||
"stripLeadingPaths": {
|
||||
"type": "boolean",
|
||||
"description": "Remove leading directory from output (e.g. src). See: https://swc.rs/docs/usage/cli#--strip-leading-paths",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"stripLeadingPaths": {
|
||||
"type": "boolean",
|
||||
"description": "Remove leading directory from output (e.g. src). See: https://swc.rs/docs/usage/cli#--strip-leading-paths",
|
||||
"default": false
|
||||
},
|
||||
"required": ["main", "outputPath", "tsConfig"],
|
||||
"definitions": {
|
||||
"assetPattern": {
|
||||
|
||||
@ -26,13 +26,13 @@
|
||||
},
|
||||
"main": {
|
||||
"type": "string",
|
||||
"description": "Path relative to the workspace root for the main entry file. Defaults to '<projectRoot>/src/main.ts'.",
|
||||
"description": "Path relative to the workspace root for the main entry file. Defaults to '<projectRoot>/src/index.ts'.",
|
||||
"alias": "entryFile",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"tsConfig": {
|
||||
"type": "string",
|
||||
"description": "Path relative to the workspace root for the tsconfig file to build with. Defaults to '<projectRoot>/tsconfig.app.json'.",
|
||||
"description": "Path relative to the workspace root for the tsconfig file to build with. Defaults to '<projectRoot>/tsconfig.lib.json'.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"skipFormat": {
|
||||
|
||||
@ -214,14 +214,20 @@ describe('packaging libs', () => {
|
||||
|
||||
expect(readJson(`dist/libs/${tscLib}/package.json`).exports).toEqual({
|
||||
'./package.json': './package.json',
|
||||
'.': './src/index.js',
|
||||
'.': {
|
||||
default: './src/index.js',
|
||||
types: './src/index.d.ts',
|
||||
},
|
||||
'./foo/bar': './src/foo/bar.js',
|
||||
'./foo/faz': './src/foo/faz.js',
|
||||
});
|
||||
|
||||
expect(readJson(`dist/libs/${swcLib}/package.json`).exports).toEqual({
|
||||
'./package.json': './package.json',
|
||||
'.': './src/index.js',
|
||||
'.': {
|
||||
default: './src/index.js',
|
||||
types: './src/index.d.ts',
|
||||
},
|
||||
'./foo/bar': './src/foo/bar.js',
|
||||
'./foo/faz': './src/foo/faz.js',
|
||||
});
|
||||
|
||||
@ -18,7 +18,10 @@ import {
|
||||
import * as esbuild from 'esbuild';
|
||||
import { normalizeOptions } from './lib/normalize';
|
||||
|
||||
import { EsBuildExecutorOptions } from './schema';
|
||||
import {
|
||||
EsBuildExecutorOptions,
|
||||
NormalizedEsBuildExecutorOptions,
|
||||
} from './schema';
|
||||
import { createAsyncIterable } from '@nx/devkit/src/utils/async-iterable';
|
||||
import {
|
||||
buildEsbuildOptions,
|
||||
@ -213,7 +216,7 @@ export async function* esbuildExecutor(
|
||||
}
|
||||
|
||||
function getTypeCheckOptions(
|
||||
options: EsBuildExecutorOptions,
|
||||
options: NormalizedEsBuildExecutorOptions,
|
||||
context: ExecutorContext
|
||||
) {
|
||||
const { watch, tsConfig, outputPath } = options;
|
||||
@ -243,7 +246,7 @@ function getTypeCheckOptions(
|
||||
}
|
||||
|
||||
async function runTypeCheck(
|
||||
options: EsBuildExecutorOptions,
|
||||
options: NormalizedEsBuildExecutorOptions,
|
||||
context: ExecutorContext
|
||||
) {
|
||||
const { errors, warnings } = await _runTypeCheck(
|
||||
|
||||
@ -125,7 +125,7 @@ export function buildEsbuildOptions(
|
||||
|
||||
export function getOutExtension(
|
||||
format: 'cjs' | 'esm',
|
||||
options: NormalizedEsBuildExecutorOptions
|
||||
options: Pick<NormalizedEsBuildExecutorOptions, 'userDefinedBuildOptions'>
|
||||
): '.cjs' | '.mjs' | '.js' {
|
||||
const userDefinedExt = options.userDefinedBuildOptions?.outExtension?.['.js'];
|
||||
// Allow users to change the output extensions from default CJS and ESM extensions.
|
||||
|
||||
@ -17,7 +17,7 @@ export function normalizeOptions(
|
||||
const isTsSolutionSetup = isUsingTsSolutionSetup();
|
||||
if (isTsSolutionSetup && options.generatePackageJson) {
|
||||
throw new Error(
|
||||
`Setting 'generatePackageJson: true' is not allowed with the current TypeScript setup. Please update the 'package.json' file at the project root as needed and don't set the 'generatePackageJson' option.`
|
||||
`Setting 'generatePackageJson: true' is not supported with the current TypeScript setup. Update the 'package.json' file at the project root as needed and unset the 'generatePackageJson' option.`
|
||||
);
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ export function normalizeOptions(
|
||||
// If we're not generating package.json file, then copy it as-is as an asset when not using ts solution setup.
|
||||
const assets =
|
||||
options.generatePackageJson || isTsSolutionSetup
|
||||
? options.assets
|
||||
? options.assets ?? []
|
||||
: [
|
||||
...options.assets,
|
||||
joinPathFragments(
|
||||
|
||||
@ -5,7 +5,7 @@ type Compiler = 'babel' | 'swc';
|
||||
|
||||
export interface EsBuildExecutorOptions {
|
||||
additionalEntryPoints?: string[];
|
||||
assets: (AssetGlob | string)[];
|
||||
assets?: (AssetGlob | string)[];
|
||||
bundle?: boolean;
|
||||
declaration?: boolean;
|
||||
declarationRootDir?: string;
|
||||
@ -32,7 +32,8 @@ export interface EsBuildExecutorOptions {
|
||||
|
||||
export interface NormalizedEsBuildExecutorOptions
|
||||
extends Omit<EsBuildExecutorOptions, 'esbuildOptions' | 'esbuildConfig'> {
|
||||
assets: (AssetGlob | string)[];
|
||||
singleEntry: boolean;
|
||||
external: string[];
|
||||
userDefinedBuildOptions: esbuild.BuildOptions;
|
||||
userDefinedBuildOptions: esbuild.BuildOptions | undefined;
|
||||
}
|
||||
|
||||
@ -1,33 +1,38 @@
|
||||
import {
|
||||
formatFiles,
|
||||
joinPathFragments,
|
||||
readJson,
|
||||
readNxJson,
|
||||
readProjectConfiguration,
|
||||
Tree,
|
||||
updateProjectConfiguration,
|
||||
writeJson,
|
||||
} from '@nx/devkit';
|
||||
|
||||
import { getImportPath } from '@nx/js/src/utils/get-import-path';
|
||||
import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
|
||||
|
||||
import { esbuildInitGenerator } from '../init/init';
|
||||
import { EsBuildExecutorOptions } from '../../executors/esbuild/schema';
|
||||
import { EsBuildProjectSchema } from './schema';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
import { getOutputDir, getUpdatedPackageJsonContent } from '@nx/js';
|
||||
import { getImportPath } from '@nx/js/src/utils/get-import-path';
|
||||
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
|
||||
import { basename, dirname, join } from 'node:path/posix';
|
||||
import { mergeTargetConfigurations } from 'nx/src/devkit-internals';
|
||||
import { PackageJson } from 'nx/src/utils/package-json';
|
||||
import { getOutExtension } from '../../executors/esbuild/lib/build-esbuild-options';
|
||||
import { EsBuildExecutorOptions } from '../../executors/esbuild/schema';
|
||||
import { esbuildInitGenerator } from '../init/init';
|
||||
import { EsBuildProjectSchema } from './schema';
|
||||
|
||||
export async function configurationGenerator(
|
||||
tree: Tree,
|
||||
options: EsBuildProjectSchema
|
||||
) {
|
||||
assertNotUsingTsSolutionSetup(tree, 'esbuild', 'configuration');
|
||||
|
||||
const task = await esbuildInitGenerator(tree, {
|
||||
...options,
|
||||
skipFormat: true,
|
||||
});
|
||||
options.buildTarget ??= 'build';
|
||||
const isTsSolutionSetup = isUsingTsSolutionSetup(tree);
|
||||
checkForTargetConflicts(tree, options);
|
||||
addBuildTarget(tree, options);
|
||||
addBuildTarget(tree, options, isTsSolutionSetup);
|
||||
updatePackageJson(tree, options, isTsSolutionSetup);
|
||||
await formatFiles(tree);
|
||||
return task;
|
||||
}
|
||||
@ -42,53 +47,58 @@ function checkForTargetConflicts(tree: Tree, options: EsBuildProjectSchema) {
|
||||
}
|
||||
}
|
||||
|
||||
function addBuildTarget(tree: Tree, options: EsBuildProjectSchema) {
|
||||
function addBuildTarget(
|
||||
tree: Tree,
|
||||
options: EsBuildProjectSchema,
|
||||
isTsSolutionSetup: boolean
|
||||
) {
|
||||
addBuildTargetDefaults(tree, '@nx/esbuild:esbuild', options.buildTarget);
|
||||
const project = readProjectConfiguration(tree, options.project);
|
||||
const packageJsonPath = joinPathFragments(project.root, 'package.json');
|
||||
|
||||
if (!tree.exists(packageJsonPath)) {
|
||||
const importPath =
|
||||
options.importPath || getImportPath(tree, options.project);
|
||||
writeJson(tree, packageJsonPath, {
|
||||
name: importPath,
|
||||
version: '0.0.1',
|
||||
});
|
||||
}
|
||||
|
||||
const prevBuildOptions = project.targets?.[options.buildTarget]?.options;
|
||||
|
||||
const tsConfig = prevBuildOptions?.tsConfig ?? getTsConfigFile(tree, options);
|
||||
|
||||
let outputPath = prevBuildOptions?.outputPath;
|
||||
if (!outputPath) {
|
||||
outputPath = isTsSolutionSetup
|
||||
? joinPathFragments(project.root, 'dist')
|
||||
: joinPathFragments(
|
||||
'dist',
|
||||
project.root === '.' ? options.project : project.root
|
||||
);
|
||||
}
|
||||
|
||||
const buildOptions: EsBuildExecutorOptions = {
|
||||
main: prevBuildOptions?.main ?? getMainFile(tree, options),
|
||||
outputPath:
|
||||
prevBuildOptions?.outputPath ??
|
||||
joinPathFragments(
|
||||
'dist',
|
||||
project.root === '.' ? options.project : project.root
|
||||
),
|
||||
outputPath,
|
||||
outputFileName: 'main.js',
|
||||
tsConfig,
|
||||
assets: [],
|
||||
platform: options.platform,
|
||||
format: options.format,
|
||||
};
|
||||
|
||||
if (isTsSolutionSetup) {
|
||||
buildOptions.declarationRootDir =
|
||||
project.sourceRoot ?? tree.exists(`${project.root}/src`)
|
||||
? `${project.root}/src`
|
||||
: project.root;
|
||||
} else {
|
||||
buildOptions.assets = [];
|
||||
|
||||
if (tree.exists(joinPathFragments(project.root, 'README.md'))) {
|
||||
buildOptions.assets.push({
|
||||
glob: `${project.root}/README.md`,
|
||||
input: '.',
|
||||
output: '.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (options.platform === 'browser') {
|
||||
buildOptions.outputHashing = 'all';
|
||||
buildOptions.minify = true;
|
||||
}
|
||||
|
||||
if (tree.exists(joinPathFragments(project.root, 'README.md'))) {
|
||||
buildOptions.assets = [
|
||||
{
|
||||
glob: `${project.root}/README.md`,
|
||||
input: '.',
|
||||
output: '.',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
updateProjectConfiguration(tree, options.project, {
|
||||
...project,
|
||||
targets: {
|
||||
@ -111,6 +121,89 @@ function addBuildTarget(tree: Tree, options: EsBuildProjectSchema) {
|
||||
});
|
||||
}
|
||||
|
||||
function updatePackageJson(
|
||||
tree: Tree,
|
||||
options: EsBuildProjectSchema,
|
||||
isTsSolutionSetup: boolean
|
||||
) {
|
||||
const project = readProjectConfiguration(tree, options.project);
|
||||
|
||||
const packageJsonPath = join(project.root, 'package.json');
|
||||
let packageJson: PackageJson;
|
||||
if (tree.exists(packageJsonPath)) {
|
||||
if (!isTsSolutionSetup) {
|
||||
return;
|
||||
}
|
||||
|
||||
packageJson = readJson(tree, packageJsonPath);
|
||||
} else {
|
||||
packageJson = {
|
||||
name: getImportPath(tree, options.project),
|
||||
version: '0.0.1',
|
||||
};
|
||||
}
|
||||
|
||||
if (isTsSolutionSetup) {
|
||||
const nxJson = readNxJson(tree);
|
||||
const projectTarget = project.targets[options.buildTarget];
|
||||
const mergedTarget = mergeTargetConfigurations(
|
||||
projectTarget,
|
||||
(projectTarget.executor
|
||||
? nxJson.targetDefaults?.[projectTarget.executor]
|
||||
: undefined) ?? nxJson.targetDefaults?.[options.buildTarget]
|
||||
);
|
||||
|
||||
const {
|
||||
declarationRootDir = '.',
|
||||
main,
|
||||
outputPath,
|
||||
outputFileName,
|
||||
// the executor option defaults to [esm]
|
||||
format = ['esm'],
|
||||
esbuildOptions,
|
||||
} = mergedTarget.options;
|
||||
|
||||
// can't use the declarationRootDir as rootDir because it only affects the typings,
|
||||
// not the runtime entry point
|
||||
packageJson = getUpdatedPackageJsonContent(packageJson, {
|
||||
main,
|
||||
outputPath,
|
||||
projectRoot: project.root,
|
||||
generateExportsField: true,
|
||||
packageJsonPath,
|
||||
format,
|
||||
outputFileName,
|
||||
outputFileExtensionForCjs: getOutExtension('cjs', {
|
||||
// there's very little chance that the user would have defined a custom esbuild config
|
||||
// since that's an Nx specific file that we're not generating here and we're setting up
|
||||
// the build for esbuild now
|
||||
userDefinedBuildOptions: esbuildOptions,
|
||||
}),
|
||||
outputFileExtensionForEsm: getOutExtension('esm', {
|
||||
userDefinedBuildOptions: esbuildOptions,
|
||||
}),
|
||||
});
|
||||
|
||||
if (declarationRootDir !== dirname(main)) {
|
||||
// the declaration file entry point will be output to a location
|
||||
// different than the runtime entry point, adjust accodingly
|
||||
const outputDir = getOutputDir({
|
||||
main,
|
||||
outputPath,
|
||||
projectRoot: project.root,
|
||||
packageJsonPath,
|
||||
rootDir: declarationRootDir,
|
||||
});
|
||||
const mainFile = basename(options.main).replace(/\.[tj]s$/, '');
|
||||
const typingsFile = `${outputDir}${mainFile}.d.ts`;
|
||||
packageJson.types = typingsFile;
|
||||
packageJson.exports['.'].types = typingsFile;
|
||||
}
|
||||
}
|
||||
|
||||
writeJson(tree, packageJsonPath, packageJson);
|
||||
}
|
||||
|
||||
function getMainFile(tree: Tree, options: EsBuildProjectSchema) {
|
||||
const project = readProjectConfiguration(tree, options.project);
|
||||
const candidates = [
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import type { SupportedFormat } from '@nx/js';
|
||||
|
||||
export interface EsBuildProjectSchema {
|
||||
project: string;
|
||||
main?: string;
|
||||
@ -10,4 +12,5 @@ export interface EsBuildProjectSchema {
|
||||
esbuildConfig?: string;
|
||||
platform?: 'node' | 'browser' | 'neutral';
|
||||
buildTarget?: string;
|
||||
format?: SupportedFormat[];
|
||||
}
|
||||
|
||||
@ -60,6 +60,15 @@
|
||||
"description": "The build target to add.",
|
||||
"type": "string",
|
||||
"default": "build"
|
||||
},
|
||||
"format": {
|
||||
"description": "The format to build the library (esm or cjs).",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["esm", "cjs"]
|
||||
},
|
||||
"default": ["esm"]
|
||||
}
|
||||
},
|
||||
"required": [],
|
||||
|
||||
@ -4,14 +4,11 @@ import {
|
||||
GeneratorCallback,
|
||||
Tree,
|
||||
} from '@nx/devkit';
|
||||
import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
|
||||
import { esbuildVersion } from '@nx/js/src/utils/versions';
|
||||
import { nxVersion } from '../../utils/versions';
|
||||
import { Schema } from './schema';
|
||||
|
||||
export async function esbuildInitGenerator(tree: Tree, schema: Schema) {
|
||||
assertNotUsingTsSolutionSetup(tree, 'esbuild', 'init');
|
||||
|
||||
let installTask: GeneratorCallback = () => {};
|
||||
if (!schema.skipPackageJson) {
|
||||
installTask = addDependenciesToPackageJson(
|
||||
|
||||
@ -73,13 +73,13 @@ export async function expoLibraryGeneratorInternal(
|
||||
}
|
||||
initRootBabelConfig(host);
|
||||
|
||||
createFiles(host, options);
|
||||
|
||||
const addProjectTask = await addProject(host, options);
|
||||
if (addProjectTask) {
|
||||
tasks.push(addProjectTask);
|
||||
}
|
||||
|
||||
createFiles(host, options);
|
||||
|
||||
const lintTask = await addLinting(host, {
|
||||
...options,
|
||||
projectName: options.name,
|
||||
|
||||
@ -113,13 +113,13 @@
|
||||
"description": "Generate a lockfile (e.g. package-lock.json) that matches the workspace lockfile to ensure package versions match.",
|
||||
"default": false,
|
||||
"x-priority": "internal"
|
||||
},
|
||||
"stripLeadingPaths": {
|
||||
"type": "boolean",
|
||||
"description": "Remove leading directory from output (e.g. src). See: https://swc.rs/docs/usage/cli#--strip-leading-paths",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"stripLeadingPaths": {
|
||||
"type": "boolean",
|
||||
"description": "Remove leading directory from output (e.g. src). See: https://swc.rs/docs/usage/cli#--strip-leading-paths",
|
||||
"default": false
|
||||
},
|
||||
"required": ["main", "outputPath", "tsConfig"],
|
||||
"definitions": {
|
||||
"assetPattern": {
|
||||
|
||||
@ -1,6 +1,12 @@
|
||||
import 'nx/src/internal-testing-utils/mock-project-graph';
|
||||
|
||||
import { readProjectConfiguration, Tree } from '@nx/devkit';
|
||||
import {
|
||||
addProjectConfiguration,
|
||||
readJson,
|
||||
readProjectConfiguration,
|
||||
Tree,
|
||||
writeJson,
|
||||
} from '@nx/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import { join } from 'path';
|
||||
import { LibraryGeneratorSchema } from '../library/schema';
|
||||
@ -23,10 +29,8 @@ describe('convert to swc', () => {
|
||||
bundler: 'tsc',
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
beforeEach(() => {
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
tree.write('/.gitignore', '');
|
||||
tree.write('/.gitignore', '');
|
||||
});
|
||||
|
||||
it('should convert tsc to swc', async () => {
|
||||
@ -50,9 +54,33 @@ describe('convert to swc', () => {
|
||||
join(readProjectConfiguration(tree, 'tsc-lib').root, '.swcrc')
|
||||
)
|
||||
).toEqual(true);
|
||||
expect(tree.read('package.json', 'utf-8')).toContain('@swc/core');
|
||||
expect(tree.read('tsc-lib/package.json', 'utf-8')).toContain(
|
||||
'@swc/helpers'
|
||||
);
|
||||
expect(
|
||||
readJson(tree, 'package.json').devDependencies['@swc/core']
|
||||
).toBeDefined();
|
||||
expect(
|
||||
readJson(tree, 'tsc-lib/package.json').dependencies['@swc/helpers']
|
||||
).toBeDefined();
|
||||
});
|
||||
|
||||
it('should handle project configuration without targets', async () => {
|
||||
addProjectConfiguration(tree, 'lib1', { root: 'lib1' });
|
||||
|
||||
await expect(
|
||||
convertToSwcGenerator(tree, { project: 'lib1' })
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it('should not add swc dependencies when no target was updated', async () => {
|
||||
addProjectConfiguration(tree, 'lib1', { root: 'lib1' });
|
||||
writeJson(tree, 'lib1/package.json', { dependencies: {} });
|
||||
|
||||
await convertToSwcGenerator(tree, { project: 'lib1' });
|
||||
|
||||
expect(
|
||||
readJson(tree, 'package.json').devDependencies['@swc/core']
|
||||
).not.toBeDefined();
|
||||
expect(
|
||||
readJson(tree, 'lib1/package.json').dependencies['@swc/helpers']
|
||||
).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@ -20,13 +20,14 @@ export async function convertToSwcGenerator(
|
||||
const options = normalizeOptions(schema);
|
||||
const projectConfiguration = readProjectConfiguration(tree, options.project);
|
||||
|
||||
updateProjectBuildTargets(
|
||||
const updated = updateProjectBuildTargets(
|
||||
tree,
|
||||
projectConfiguration,
|
||||
options.project,
|
||||
options.targets
|
||||
);
|
||||
return checkSwcDependencies(tree, projectConfiguration);
|
||||
|
||||
return updated ? checkSwcDependencies(tree, projectConfiguration) : () => {};
|
||||
}
|
||||
|
||||
function normalizeOptions(
|
||||
@ -47,8 +48,9 @@ function updateProjectBuildTargets(
|
||||
projectName: string,
|
||||
projectTargets: string[]
|
||||
) {
|
||||
let updated = false;
|
||||
for (const target of projectTargets) {
|
||||
const targetConfiguration = projectConfiguration.targets[target];
|
||||
const targetConfiguration = projectConfiguration.targets?.[target];
|
||||
if (
|
||||
!targetConfiguration ||
|
||||
(targetConfiguration.executor !== '@nx/js:tsc' &&
|
||||
@ -56,9 +58,14 @@ function updateProjectBuildTargets(
|
||||
)
|
||||
continue;
|
||||
targetConfiguration.executor = '@nx/js:swc';
|
||||
updated = true;
|
||||
}
|
||||
|
||||
updateProjectConfiguration(tree, projectName, projectConfiguration);
|
||||
if (updated) {
|
||||
updateProjectConfiguration(tree, projectName, projectConfiguration);
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
function checkSwcDependencies(
|
||||
|
||||
@ -67,7 +67,7 @@ import { getProjectPackageManagerWorkspaceStateWarningTask } from './utils/packa
|
||||
import {
|
||||
ensureProjectIsExcludedFromPluginRegistrations,
|
||||
ensureProjectIsIncludedInPluginRegistrations,
|
||||
} from './utils/plugin-registrations';
|
||||
} from '../../utils/typescript/plugin';
|
||||
|
||||
const defaultOutputDirectory = 'dist';
|
||||
|
||||
|
||||
@ -1,18 +1,37 @@
|
||||
import {
|
||||
ensurePackage,
|
||||
formatFiles,
|
||||
type GeneratorCallback,
|
||||
joinPathFragments,
|
||||
readJson,
|
||||
readNxJson,
|
||||
readProjectConfiguration,
|
||||
runTasksInSerial,
|
||||
type Tree,
|
||||
updateNxJson,
|
||||
updateProjectConfiguration,
|
||||
writeJson,
|
||||
type GeneratorCallback,
|
||||
type ProjectConfiguration,
|
||||
type Tree,
|
||||
} from '@nx/devkit';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
import { basename, dirname, join } from 'node:path/posix';
|
||||
import { mergeTargetConfigurations } from 'nx/src/devkit-internals';
|
||||
import type { PackageJson } from 'nx/src/utils/package-json';
|
||||
import { ensureProjectIsIncludedInPluginRegistrations } from '../..//utils/typescript/plugin';
|
||||
import { getImportPath } from '../../utils/get-import-path';
|
||||
import {
|
||||
getUpdatedPackageJsonContent,
|
||||
type SupportedFormat,
|
||||
} from '../../utils/package-json/update-package-json';
|
||||
import { addSwcConfig } from '../../utils/swc/add-swc-config';
|
||||
import { addSwcDependencies } from '../../utils/swc/add-swc-dependencies';
|
||||
import { ensureTypescript } from '../../utils/typescript/ensure-typescript';
|
||||
import { readTsConfig } from '../../utils/typescript/ts-config';
|
||||
import { isUsingTsSolutionSetup } from '../../utils/typescript/ts-solution-setup';
|
||||
import { nxVersion } from '../../utils/versions';
|
||||
import { SetupBuildGeneratorSchema } from './schema';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
|
||||
let ts: typeof import('typescript');
|
||||
|
||||
export async function setupBuildGenerator(
|
||||
tree: Tree,
|
||||
@ -20,8 +39,8 @@ export async function setupBuildGenerator(
|
||||
): Promise<GeneratorCallback> {
|
||||
const tasks: GeneratorCallback[] = [];
|
||||
const project = readProjectConfiguration(tree, options.project);
|
||||
const buildTarget = options.buildTarget ?? 'build';
|
||||
const prevBuildOptions = project.targets?.[buildTarget]?.options;
|
||||
options.buildTarget ??= 'build';
|
||||
const prevBuildOptions = project.targets?.[options.buildTarget]?.options;
|
||||
|
||||
project.targets ??= {};
|
||||
|
||||
@ -49,6 +68,7 @@ export async function setupBuildGenerator(
|
||||
`Cannot locate a main file for ${options.project}. Please specify one using --main=<file-path>.`
|
||||
);
|
||||
}
|
||||
options.main = mainFile;
|
||||
|
||||
let tsConfigFile: string;
|
||||
if (prevBuildOptions?.tsConfig) {
|
||||
@ -73,6 +93,13 @@ export async function setupBuildGenerator(
|
||||
`Cannot locate a tsConfig file for ${options.project}. Please specify one using --tsConfig=<file-path>.`
|
||||
);
|
||||
}
|
||||
options.tsConfig = tsConfigFile;
|
||||
|
||||
const isTsSolutionSetup = isUsingTsSolutionSetup(tree);
|
||||
const nxJson = readNxJson(tree);
|
||||
const addPlugin =
|
||||
process.env.NX_ADD_PLUGINS !== 'false' &&
|
||||
nxJson.useInferencePlugins !== false;
|
||||
|
||||
switch (options.bundler) {
|
||||
case 'vite': {
|
||||
@ -83,10 +110,11 @@ export async function setupBuildGenerator(
|
||||
const task = await viteConfigurationGenerator(tree, {
|
||||
buildTarget: options.buildTarget,
|
||||
project: options.project,
|
||||
newProject: true,
|
||||
newProject: false,
|
||||
uiFramework: 'none',
|
||||
includeVitest: false,
|
||||
includeLib: true,
|
||||
addPlugin,
|
||||
skipFormat: true,
|
||||
});
|
||||
tasks.push(task);
|
||||
@ -103,6 +131,7 @@ export async function setupBuildGenerator(
|
||||
project: options.project,
|
||||
skipFormat: true,
|
||||
skipValidation: true,
|
||||
format: ['cjs'],
|
||||
});
|
||||
tasks.push(task);
|
||||
break;
|
||||
@ -116,6 +145,7 @@ export async function setupBuildGenerator(
|
||||
project: options.project,
|
||||
compiler: 'tsc',
|
||||
format: ['cjs', 'esm'],
|
||||
addPlugin,
|
||||
skipFormat: true,
|
||||
skipValidation: true,
|
||||
});
|
||||
@ -123,39 +153,61 @@ export async function setupBuildGenerator(
|
||||
break;
|
||||
}
|
||||
case 'tsc': {
|
||||
addBuildTargetDefaults(tree, '@nx/js:tsc');
|
||||
if (isTsSolutionSetup) {
|
||||
const nxJson = readNxJson(tree);
|
||||
ensureProjectIsIncludedInPluginRegistrations(
|
||||
nxJson,
|
||||
project.root,
|
||||
options.buildTarget
|
||||
);
|
||||
updateNxJson(tree, nxJson);
|
||||
updatePackageJsonForTsc(tree, options, project);
|
||||
} else {
|
||||
addBuildTargetDefaults(tree, '@nx/js:tsc');
|
||||
|
||||
const outputPath = joinPathFragments('dist', project.root);
|
||||
project.targets[buildTarget] = {
|
||||
executor: `@nx/js:tsc`,
|
||||
outputs: ['{options.outputPath}'],
|
||||
options: {
|
||||
outputPath,
|
||||
main: mainFile,
|
||||
tsConfig: tsConfigFile,
|
||||
assets: [],
|
||||
},
|
||||
};
|
||||
updateProjectConfiguration(tree, options.project, project);
|
||||
const outputPath = joinPathFragments('dist', project.root);
|
||||
project.targets[options.buildTarget] = {
|
||||
executor: `@nx/js:tsc`,
|
||||
outputs: ['{options.outputPath}'],
|
||||
options: {
|
||||
outputPath,
|
||||
main: mainFile,
|
||||
tsConfig: tsConfigFile,
|
||||
assets: [],
|
||||
},
|
||||
};
|
||||
updateProjectConfiguration(tree, options.project, project);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'swc': {
|
||||
addBuildTargetDefaults(tree, '@nx/js:swc');
|
||||
|
||||
const outputPath = joinPathFragments('dist', project.root);
|
||||
project.targets[buildTarget] = {
|
||||
const outputPath = isTsSolutionSetup
|
||||
? joinPathFragments(project.root, 'dist')
|
||||
: joinPathFragments('dist', project.root);
|
||||
project.targets[options.buildTarget] = {
|
||||
executor: `@nx/js:swc`,
|
||||
outputs: ['{options.outputPath}'],
|
||||
options: {
|
||||
outputPath,
|
||||
main: mainFile,
|
||||
tsConfig: tsConfigFile,
|
||||
assets: [],
|
||||
},
|
||||
};
|
||||
|
||||
if (isTsSolutionSetup) {
|
||||
project.targets[options.buildTarget].options.stripLeadingPaths = true;
|
||||
} else {
|
||||
project.targets[options.buildTarget].options.assets = [];
|
||||
}
|
||||
|
||||
updateProjectConfiguration(tree, options.project, project);
|
||||
addSwcDependencies(tree);
|
||||
addSwcConfig(tree, project.root, 'es6');
|
||||
tasks.push(addSwcDependencies(tree));
|
||||
addSwcConfig(tree, project.root, 'commonjs');
|
||||
if (isTsSolutionSetup) {
|
||||
updatePackageJsonForSwc(tree, options, project);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -165,3 +217,138 @@ export async function setupBuildGenerator(
|
||||
}
|
||||
|
||||
export default setupBuildGenerator;
|
||||
|
||||
function updatePackageJsonForTsc(
|
||||
tree: Tree,
|
||||
options: SetupBuildGeneratorSchema,
|
||||
project: ProjectConfiguration
|
||||
) {
|
||||
if (!ts) {
|
||||
ts = ensureTypescript();
|
||||
}
|
||||
|
||||
const tsconfig = readTsConfig(options.tsConfig, {
|
||||
...ts.sys,
|
||||
readFile: (p) => tree.read(p, 'utf-8'),
|
||||
fileExists: (p) => tree.exists(p),
|
||||
});
|
||||
|
||||
let main: string;
|
||||
let rootDir: string;
|
||||
let outputPath: string;
|
||||
if (project.targets?.[options.buildTarget]) {
|
||||
const mergedTarget = mergeTargetDefaults(
|
||||
tree,
|
||||
project,
|
||||
options.buildTarget
|
||||
);
|
||||
({ main, rootDir, outputPath } = mergedTarget.options);
|
||||
} else {
|
||||
main = options.main;
|
||||
|
||||
({ rootDir = project.root, outDir: outputPath } = tsconfig.options);
|
||||
const tsOutFile = tsconfig.options.outFile;
|
||||
|
||||
if (tsOutFile) {
|
||||
main = join(project.root, basename(tsOutFile));
|
||||
outputPath = dirname(tsOutFile);
|
||||
}
|
||||
|
||||
if (!outputPath) {
|
||||
outputPath = project.root;
|
||||
}
|
||||
}
|
||||
|
||||
const module = Object.keys(ts.ModuleKind).find(
|
||||
(m) => ts.ModuleKind[m] === tsconfig.options.module
|
||||
);
|
||||
const format: SupportedFormat[] = module.toLowerCase().startsWith('es')
|
||||
? ['esm']
|
||||
: ['cjs'];
|
||||
|
||||
updatePackageJson(
|
||||
tree,
|
||||
options.project,
|
||||
project.root,
|
||||
main,
|
||||
outputPath,
|
||||
rootDir,
|
||||
format
|
||||
);
|
||||
}
|
||||
|
||||
function updatePackageJsonForSwc(
|
||||
tree: Tree,
|
||||
options: SetupBuildGeneratorSchema,
|
||||
project: ProjectConfiguration
|
||||
) {
|
||||
const mergedTarget = mergeTargetDefaults(tree, project, options.buildTarget);
|
||||
const {
|
||||
main,
|
||||
outputPath,
|
||||
swcrc: swcrcPath = join(project.root, '.swcrc'),
|
||||
} = mergedTarget.options;
|
||||
|
||||
const swcrc = readJson(tree, swcrcPath);
|
||||
const format: SupportedFormat[] = swcrc.module?.type?.startsWith('es')
|
||||
? ['esm']
|
||||
: ['cjs'];
|
||||
|
||||
updatePackageJson(
|
||||
tree,
|
||||
options.project,
|
||||
project.root,
|
||||
main,
|
||||
outputPath,
|
||||
// we set the `stripLeadingPaths` option, so the rootDir would match the dirname of the entry point
|
||||
dirname(main),
|
||||
format
|
||||
);
|
||||
}
|
||||
|
||||
function updatePackageJson(
|
||||
tree: Tree,
|
||||
projectName: string,
|
||||
projectRoot: string,
|
||||
main: string,
|
||||
outputPath: string,
|
||||
rootDir: string,
|
||||
format?: SupportedFormat[]
|
||||
) {
|
||||
const packageJsonPath = join(projectRoot, 'package.json');
|
||||
let packageJson: PackageJson;
|
||||
if (tree.exists(packageJsonPath)) {
|
||||
packageJson = readJson(tree, packageJsonPath);
|
||||
} else {
|
||||
packageJson = {
|
||||
name: getImportPath(tree, projectName),
|
||||
version: '0.0.1',
|
||||
};
|
||||
}
|
||||
packageJson = getUpdatedPackageJsonContent(packageJson, {
|
||||
main,
|
||||
outputPath,
|
||||
projectRoot,
|
||||
generateExportsField: true,
|
||||
packageJsonPath,
|
||||
rootDir,
|
||||
format,
|
||||
});
|
||||
writeJson(tree, packageJsonPath, packageJson);
|
||||
}
|
||||
|
||||
function mergeTargetDefaults(
|
||||
tree: Tree,
|
||||
project: ProjectConfiguration,
|
||||
buildTarget: string
|
||||
) {
|
||||
const nxJson = readNxJson(tree);
|
||||
const projectTarget = project.targets[buildTarget];
|
||||
|
||||
return mergeTargetConfigurations(
|
||||
projectTarget,
|
||||
(projectTarget.executor
|
||||
? nxJson.targetDefaults?.[projectTarget.executor]
|
||||
: undefined) ?? nxJson.targetDefaults?.[buildTarget]
|
||||
);
|
||||
}
|
||||
|
||||
@ -156,7 +156,10 @@ describe('getUpdatedPackageJsonContent', () => {
|
||||
types: './src/index.d.ts',
|
||||
version: '0.0.1',
|
||||
exports: {
|
||||
'.': './src/index.js',
|
||||
'.': {
|
||||
import: './src/index.js',
|
||||
types: './src/index.d.ts',
|
||||
},
|
||||
'./package.json': './package.json',
|
||||
},
|
||||
});
|
||||
@ -185,7 +188,10 @@ describe('getUpdatedPackageJsonContent', () => {
|
||||
version: '0.0.1',
|
||||
type: 'commonjs',
|
||||
exports: {
|
||||
'.': './src/index.cjs',
|
||||
'.': {
|
||||
default: './src/index.cjs',
|
||||
types: './src/index.d.ts',
|
||||
},
|
||||
'./package.json': './package.json',
|
||||
},
|
||||
});
|
||||
@ -220,7 +226,10 @@ describe('getUpdatedPackageJsonContent', () => {
|
||||
types: './src/index.d.ts',
|
||||
version: '0.0.1',
|
||||
exports: {
|
||||
'.': './src/index.js',
|
||||
'.': {
|
||||
default: './src/index.js',
|
||||
types: './src/index.d.ts',
|
||||
},
|
||||
'./foo': './src/foo.js',
|
||||
'./bar': './src/bar.js',
|
||||
'./package.json': './package.json',
|
||||
@ -258,7 +267,10 @@ describe('getUpdatedPackageJsonContent', () => {
|
||||
types: './src/index.d.ts',
|
||||
version: '0.0.1',
|
||||
exports: {
|
||||
'.': './src/index.js',
|
||||
'.': {
|
||||
import: './src/index.js',
|
||||
types: './src/index.d.ts',
|
||||
},
|
||||
'./foo': './src/foo.js',
|
||||
'./bar': './src/bar.js',
|
||||
'./package.json': './package.json',
|
||||
@ -298,6 +310,7 @@ describe('getUpdatedPackageJsonContent', () => {
|
||||
'.': {
|
||||
import: './src/index.js',
|
||||
default: './src/index.cjs',
|
||||
types: './src/index.d.ts',
|
||||
},
|
||||
'./foo': {
|
||||
import: './src/foo.js',
|
||||
@ -351,6 +364,7 @@ describe('getUpdatedPackageJsonContent', () => {
|
||||
'.': {
|
||||
import: './src/index.js',
|
||||
default: './src/index.cjs',
|
||||
types: './src/index.d.ts',
|
||||
},
|
||||
'./package.json': './package.json',
|
||||
'./custom': './custom.js',
|
||||
@ -383,7 +397,10 @@ describe('getUpdatedPackageJsonContent', () => {
|
||||
version: '0.0.1',
|
||||
type: 'module',
|
||||
exports: {
|
||||
'.': './src/index.cjs',
|
||||
'.': {
|
||||
default: './src/index.cjs',
|
||||
types: './src/index.d.ts',
|
||||
},
|
||||
'./package.json': './package.json',
|
||||
},
|
||||
});
|
||||
|
||||
@ -22,7 +22,7 @@ import {
|
||||
} from '@nx/devkit';
|
||||
import { DependentBuildableProjectNode } from '../buildable-libs-utils';
|
||||
import { existsSync, writeFileSync } from 'node:fs';
|
||||
import { basename, join, parse } from 'path';
|
||||
import { basename, dirname, join, parse, relative } from 'path';
|
||||
import { fileExists } from 'nx/src/utils/fileutils';
|
||||
import type { PackageJson } from 'nx/src/utils/package-json';
|
||||
import { readFileMapCache } from 'nx/src/project-graph/nx-deps-cache';
|
||||
@ -47,6 +47,7 @@ export interface UpdatePackageJsonOption {
|
||||
updateBuildableProjectDepsInPackageJson?: boolean;
|
||||
buildableProjectDepsInPackageJsonType?: 'dependencies' | 'peerDependencies';
|
||||
generateLockfile?: boolean;
|
||||
packageJsonPath?: string;
|
||||
}
|
||||
|
||||
export function updatePackageJson(
|
||||
@ -233,21 +234,18 @@ export function getExports(
|
||||
| 'projectRoot'
|
||||
| 'outputFileName'
|
||||
| 'additionalEntryPoints'
|
||||
| 'outputPath'
|
||||
| 'packageJsonPath'
|
||||
> & {
|
||||
fileExt: string;
|
||||
}
|
||||
): Exports {
|
||||
const outputDir = getOutputDir(options);
|
||||
const mainFile = options.outputFileName
|
||||
? options.outputFileName.replace(/\.[tj]s$/, '')
|
||||
: basename(options.main).replace(/\.[tj]s$/, '');
|
||||
const relativeMainFileDir = options.outputFileName
|
||||
? './'
|
||||
: getRelativeDirectoryToProjectRoot(
|
||||
options.main,
|
||||
options.rootDir ?? options.projectRoot
|
||||
);
|
||||
const exports: Exports = {
|
||||
'.': relativeMainFileDir + mainFile + options.fileExt,
|
||||
'.': outputDir + mainFile + options.fileExt,
|
||||
};
|
||||
|
||||
if (options.additionalEntryPoints) {
|
||||
@ -289,6 +287,24 @@ export function getUpdatedPackageJsonContent(
|
||||
packageJson.exports['./package.json'] ??= './package.json';
|
||||
}
|
||||
|
||||
if (!options.skipTypings) {
|
||||
const mainFile = basename(options.main).replace(/\.[tj]s$/, '');
|
||||
const outputDir = getOutputDir(options);
|
||||
const typingsFile = `${outputDir}${mainFile}.d.ts`;
|
||||
packageJson.types ??= typingsFile;
|
||||
|
||||
if (options.generateExportsField) {
|
||||
if (!packageJson.exports['.']) {
|
||||
packageJson.exports['.'] = { types: typingsFile };
|
||||
} else if (
|
||||
typeof packageJson.exports['.'] === 'object' &&
|
||||
!packageJson.exports['.'].types
|
||||
) {
|
||||
packageJson.exports['.'].types = typingsFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasEsmFormat) {
|
||||
const esmExports = getExports({
|
||||
...options,
|
||||
@ -304,9 +320,13 @@ export function getUpdatedPackageJsonContent(
|
||||
|
||||
if (options.generateExportsField) {
|
||||
for (const [exportEntry, filePath] of Object.entries(esmExports)) {
|
||||
packageJson.exports[exportEntry] ??= hasCjsFormat
|
||||
? { import: filePath }
|
||||
: filePath;
|
||||
if (!packageJson.exports[exportEntry]) {
|
||||
packageJson.exports[exportEntry] ??= hasCjsFormat
|
||||
? { import: filePath }
|
||||
: filePath;
|
||||
} else if (typeof packageJson.exports[exportEntry] === 'object') {
|
||||
packageJson.exports[exportEntry].import ??= filePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -327,24 +347,39 @@ export function getUpdatedPackageJsonContent(
|
||||
|
||||
if (options.generateExportsField) {
|
||||
for (const [exportEntry, filePath] of Object.entries(cjsExports)) {
|
||||
if (hasEsmFormat) {
|
||||
packageJson.exports[exportEntry]['default'] ??= filePath;
|
||||
} else {
|
||||
packageJson.exports[exportEntry] ??= filePath;
|
||||
if (!packageJson.exports[exportEntry]) {
|
||||
packageJson.exports[exportEntry] ??= hasEsmFormat
|
||||
? { default: filePath }
|
||||
: filePath;
|
||||
} else if (typeof packageJson.exports[exportEntry] === 'object') {
|
||||
packageJson.exports[exportEntry].default ??= filePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!options.skipTypings) {
|
||||
const mainFile = basename(options.main).replace(/\.[tj]s$/, '');
|
||||
const relativeMainFileDir = getRelativeDirectoryToProjectRoot(
|
||||
options.main,
|
||||
options.projectRoot
|
||||
);
|
||||
const typingsFile = `${relativeMainFileDir}${mainFile}.d.ts`;
|
||||
packageJson.types ??= typingsFile;
|
||||
}
|
||||
|
||||
return packageJson;
|
||||
}
|
||||
|
||||
export function getOutputDir(
|
||||
options: Pick<
|
||||
UpdatePackageJsonOption,
|
||||
| 'main'
|
||||
| 'rootDir'
|
||||
| 'projectRoot'
|
||||
| 'outputFileName'
|
||||
| 'outputPath'
|
||||
| 'packageJsonPath'
|
||||
>
|
||||
): string {
|
||||
const packageJsonDir = options.packageJsonPath
|
||||
? dirname(options.packageJsonPath)
|
||||
: options.outputPath;
|
||||
const relativeOutputPath = relative(packageJsonDir, options.outputPath);
|
||||
const relativeMainDir = options.outputFileName
|
||||
? ''
|
||||
: relative(options.rootDir ?? options.projectRoot, dirname(options.main));
|
||||
const outputDir = join(relativeOutputPath, relativeMainDir);
|
||||
|
||||
return outputDir === '.' ? `./` : `./${outputDir}/`;
|
||||
}
|
||||
|
||||
@ -2,33 +2,9 @@ import type { NxJsonConfiguration } from '@nx/devkit';
|
||||
import {
|
||||
ensureProjectIsExcludedFromPluginRegistrations,
|
||||
ensureProjectIsIncludedInPluginRegistrations,
|
||||
} from './plugin-registrations';
|
||||
} from './plugin';
|
||||
|
||||
describe('ensureProjectIsIncludedInPluginRegistrations', () => {
|
||||
it('should do nothing when there is no `plugin` entry', () => {
|
||||
const nxJson: NxJsonConfiguration = {};
|
||||
|
||||
ensureProjectIsIncludedInPluginRegistrations(nxJson, 'packages/pkg1');
|
||||
|
||||
expect(nxJson).toStrictEqual({});
|
||||
});
|
||||
|
||||
it('should do nothing when the there are no plugins', () => {
|
||||
const nxJson: NxJsonConfiguration = { plugins: [] };
|
||||
|
||||
ensureProjectIsIncludedInPluginRegistrations(nxJson, 'packages/pkg1');
|
||||
|
||||
expect(nxJson).toStrictEqual({ plugins: [] });
|
||||
});
|
||||
|
||||
it('should do nothing when the are no registrations for the `@nx/js/typescript` plugin', () => {
|
||||
const nxJson: NxJsonConfiguration = { plugins: ['@foo/bar/plugin'] };
|
||||
|
||||
ensureProjectIsIncludedInPluginRegistrations(nxJson, 'packages/pkg1');
|
||||
|
||||
expect(nxJson).toStrictEqual({ plugins: ['@foo/bar/plugin'] });
|
||||
});
|
||||
|
||||
it('should do nothing when `include`/`exclude` are not set in a plugin registration that infers both targets', () => {
|
||||
const originalNxJson: NxJsonConfiguration = {
|
||||
plugins: [
|
||||
@ -203,6 +179,44 @@ describe('ensureProjectIsIncludedInPluginRegistrations', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should exclude a project from a plugin registration with a different build target nama and add a new plugin registration that includes it', () => {
|
||||
const nxJson: NxJsonConfiguration = {
|
||||
plugins: [
|
||||
{
|
||||
plugin: '@nx/js/typescript',
|
||||
options: { typecheck: { targetName: 'typecheck' } },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
ensureProjectIsIncludedInPluginRegistrations(
|
||||
nxJson,
|
||||
'packages/pkg1',
|
||||
'build-tsc'
|
||||
);
|
||||
|
||||
expect(nxJson).toStrictEqual({
|
||||
plugins: [
|
||||
{
|
||||
plugin: '@nx/js/typescript',
|
||||
exclude: ['packages/pkg1/*'],
|
||||
options: { typecheck: { targetName: 'typecheck' } },
|
||||
},
|
||||
{
|
||||
plugin: '@nx/js/typescript',
|
||||
include: ['packages/pkg1/*'],
|
||||
options: {
|
||||
typecheck: { targetName: 'typecheck' },
|
||||
build: {
|
||||
targetName: 'build-tsc',
|
||||
configName: 'tsconfig.lib.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should include a project in a plugin registration that infers both targets and with `include` set but not including the project', () => {
|
||||
const nxJson: NxJsonConfiguration = {
|
||||
plugins: [
|
||||
@ -239,6 +253,57 @@ describe('ensureProjectIsIncludedInPluginRegistrations', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should not include a project in a plugin registration that infers both targets with a different build target name and with `include` set but not including the project', () => {
|
||||
const nxJson: NxJsonConfiguration = {
|
||||
plugins: [
|
||||
{
|
||||
plugin: '@nx/js/typescript',
|
||||
include: ['packages/pkg1/*'],
|
||||
options: {
|
||||
typecheck: { targetName: 'typecheck' },
|
||||
build: {
|
||||
targetName: 'build',
|
||||
configName: 'tsconfig.lib.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
ensureProjectIsIncludedInPluginRegistrations(
|
||||
nxJson,
|
||||
'packages/pkg2',
|
||||
'build-tsc'
|
||||
);
|
||||
|
||||
expect(nxJson).toStrictEqual({
|
||||
plugins: [
|
||||
{
|
||||
plugin: '@nx/js/typescript',
|
||||
include: ['packages/pkg1/*'],
|
||||
options: {
|
||||
typecheck: { targetName: 'typecheck' },
|
||||
build: {
|
||||
targetName: 'build',
|
||||
configName: 'tsconfig.lib.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
plugin: '@nx/js/typescript',
|
||||
include: ['packages/pkg2/*'],
|
||||
options: {
|
||||
typecheck: { targetName: 'typecheck' },
|
||||
build: {
|
||||
targetName: 'build-tsc',
|
||||
configName: 'tsconfig.lib.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should add a new plugin registration including the project when there is an existing plugin registration that infers both targets and with `exclude` set excluding the project', () => {
|
||||
const nxJson: NxJsonConfiguration = {
|
||||
plugins: [
|
||||
@ -285,6 +350,42 @@ describe('ensureProjectIsIncludedInPluginRegistrations', () => {
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove glob pattern from `exclude` when it matches exactly the project root glob pattern', () => {
|
||||
const nxJson: NxJsonConfiguration = {
|
||||
plugins: [
|
||||
{
|
||||
plugin: '@nx/js/typescript',
|
||||
exclude: ['packages/pkg1/*', 'packages/pkg2/*'],
|
||||
options: {
|
||||
typecheck: { targetName: 'typecheck' },
|
||||
build: {
|
||||
targetName: 'build',
|
||||
configName: 'tsconfig.lib.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
ensureProjectIsIncludedInPluginRegistrations(nxJson, 'packages/pkg1');
|
||||
|
||||
expect(nxJson).toStrictEqual({
|
||||
plugins: [
|
||||
{
|
||||
plugin: '@nx/js/typescript',
|
||||
exclude: ['packages/pkg2/*'],
|
||||
options: {
|
||||
typecheck: { targetName: 'typecheck' },
|
||||
build: {
|
||||
targetName: 'build',
|
||||
configName: 'tsconfig.lib.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ensureProjectIsExcludedFromPluginRegistrations', () => {
|
||||
@ -3,19 +3,14 @@ import type {
|
||||
NxJsonConfiguration,
|
||||
} from '@nx/devkit';
|
||||
import { findMatchingConfigFiles } from 'nx/src/devkit-internals';
|
||||
import type { TscPluginOptions } from '../../../plugins/typescript/plugin';
|
||||
import type { TscPluginOptions } from '../../plugins/typescript/plugin';
|
||||
|
||||
export function ensureProjectIsIncludedInPluginRegistrations(
|
||||
nxJson: NxJsonConfiguration,
|
||||
projectRoot: string
|
||||
projectRoot: string,
|
||||
buildTargetName: string = 'build'
|
||||
): void {
|
||||
if (
|
||||
!nxJson.plugins?.length ||
|
||||
!nxJson.plugins.some(isTypeScriptPluginRegistration)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
nxJson.plugins ??= [];
|
||||
let isIncluded = false;
|
||||
let index = 0;
|
||||
for (const registration of nxJson.plugins) {
|
||||
@ -26,7 +21,7 @@ export function ensureProjectIsIncludedInPluginRegistrations(
|
||||
|
||||
if (typeof registration === 'string') {
|
||||
// if it's a string all projects are included but the are no user-specified options
|
||||
// and the `build` task is not inferred by default, so we need to exclude it
|
||||
// and the build task is not inferred by default, so we need to exclude it
|
||||
nxJson.plugins[index] = {
|
||||
plugin: '@nx/js/typescript',
|
||||
exclude: [`${projectRoot}/*`],
|
||||
@ -41,43 +36,58 @@ export function ensureProjectIsIncludedInPluginRegistrations(
|
||||
);
|
||||
if (matchingConfigFiles.length) {
|
||||
// it's included by the plugin registration, check if the user-specified options would result
|
||||
// in a `build` task being inferred, if not, we need to exclude it
|
||||
if (registration.options?.typecheck && registration.options?.build) {
|
||||
// it has the desired options, do nothing
|
||||
// in the appropriate build task being inferred, if not, we need to exclude it
|
||||
if (
|
||||
registration.options?.typecheck !== false &&
|
||||
matchesBuildTarget(registration.options?.build, buildTargetName)
|
||||
) {
|
||||
// it has the desired options, do nothing, but continue processing
|
||||
// other registrations to exclude as needed
|
||||
isIncluded = true;
|
||||
} else {
|
||||
// it would not have the `build` task inferred, so we need to exclude it
|
||||
// it would not have the typecheck or build task inferred, so we need to exclude it
|
||||
registration.exclude ??= [];
|
||||
registration.exclude.push(`${projectRoot}/*`);
|
||||
}
|
||||
} else if (
|
||||
registration.options?.typecheck &&
|
||||
registration.options?.build &&
|
||||
!registration.exclude?.length
|
||||
!isIncluded &&
|
||||
registration.options?.typecheck !== false &&
|
||||
matchesBuildTarget(registration.options?.build, buildTargetName)
|
||||
) {
|
||||
// negative pattern are not supported by the `exclude` option so we
|
||||
// can't update it to not exclude the project, so we only update the
|
||||
// plugin registration if there's no `exclude` option, in which case
|
||||
// the plugin registration should have an `include` options that doesn't
|
||||
// include the project
|
||||
isIncluded = true;
|
||||
registration.include ??= [];
|
||||
registration.include.push(`${projectRoot}/*`);
|
||||
if (!registration.exclude?.length) {
|
||||
// negative pattern are not supported by the `exclude` option so we
|
||||
// can't update it to not exclude the project, so we only update the
|
||||
// plugin registration if there's no `exclude` option, in which case
|
||||
// the plugin registration should have an `include` options that doesn't
|
||||
// include the project
|
||||
isIncluded = true;
|
||||
registration.include ??= [];
|
||||
registration.include.push(`${projectRoot}/*`);
|
||||
} else if (registration.exclude?.includes(`${projectRoot}/*`)) {
|
||||
isIncluded = true;
|
||||
registration.exclude = registration.exclude.filter(
|
||||
(e) => e !== `${projectRoot}/*`
|
||||
);
|
||||
if (!registration.exclude.length) {
|
||||
// if there's no `exclude` option left, we can remove the exclude option
|
||||
delete registration.exclude;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
if (!isIncluded) {
|
||||
// the project is not included by any plugin registration with an inferred `build` task,
|
||||
// so we create a new plugin registration for it
|
||||
// the project is not included by any plugin registration with an inferred build task
|
||||
// with the given name, so we create a new plugin registration for it
|
||||
nxJson.plugins.push({
|
||||
plugin: '@nx/js/typescript',
|
||||
include: [`${projectRoot}/*`],
|
||||
options: {
|
||||
typecheck: { targetName: 'typecheck' },
|
||||
build: {
|
||||
targetName: 'build',
|
||||
targetName: buildTargetName,
|
||||
configName: 'tsconfig.lib.json',
|
||||
},
|
||||
},
|
||||
@ -139,3 +149,21 @@ function isTypeScriptPluginRegistration(
|
||||
(typeof plugin !== 'string' && plugin.plugin === '@nx/js/typescript')
|
||||
);
|
||||
}
|
||||
|
||||
function matchesBuildTarget(
|
||||
buildOptions: TscPluginOptions['build'],
|
||||
buildTargetName: string
|
||||
): boolean {
|
||||
if (buildOptions === undefined || buildOptions === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buildOptions === true && buildTargetName === 'build') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
typeof buildOptions === 'object' &&
|
||||
buildOptions.targetName === buildTargetName
|
||||
);
|
||||
}
|
||||
@ -1,22 +1,25 @@
|
||||
import { offsetFromRoot, Tree, updateJson, workspaceRoot } from '@nx/devkit';
|
||||
import { existsSync } from 'fs';
|
||||
import { dirname, join } from 'path';
|
||||
import * as ts from 'typescript';
|
||||
import type * as ts from 'typescript';
|
||||
import { ensureTypescript } from './ensure-typescript';
|
||||
|
||||
let tsModule: typeof import('typescript');
|
||||
|
||||
export function readTsConfig(tsConfigPath: string): ts.ParsedCommandLine {
|
||||
export function readTsConfig(
|
||||
tsConfigPath: string,
|
||||
sys?: ts.System
|
||||
): ts.ParsedCommandLine {
|
||||
if (!tsModule) {
|
||||
tsModule = require('typescript');
|
||||
}
|
||||
const readResult = tsModule.readConfigFile(
|
||||
tsConfigPath,
|
||||
tsModule.sys.readFile
|
||||
);
|
||||
|
||||
sys ??= tsModule.sys;
|
||||
|
||||
const readResult = tsModule.readConfigFile(tsConfigPath, sys.readFile);
|
||||
return tsModule.parseJsonConfigFileContent(
|
||||
readResult.config,
|
||||
tsModule.sys,
|
||||
sys,
|
||||
dirname(tsConfigPath)
|
||||
);
|
||||
}
|
||||
|
||||
@ -71,13 +71,13 @@ export async function reactNativeLibraryGeneratorInternal(
|
||||
tasks.push(ensureDependencies(host));
|
||||
}
|
||||
|
||||
createFiles(host, options);
|
||||
|
||||
const addProjectTask = await addProject(host, options);
|
||||
if (addProjectTask) {
|
||||
tasks.push(addProjectTask);
|
||||
}
|
||||
|
||||
createFiles(host, options);
|
||||
|
||||
const lintTask = await addLinting(host, {
|
||||
...options,
|
||||
projectName: options.name,
|
||||
|
||||
@ -56,7 +56,7 @@ describe('configurationGenerator', () => {
|
||||
it('should support --main option', async () => {
|
||||
await configurationGenerator(tree, {
|
||||
project: 'mypkg',
|
||||
main: './src/index.ts',
|
||||
main: './libs/mypkg/src/index.ts',
|
||||
});
|
||||
|
||||
const rollupConfig = tree.read('libs/mypkg/rollup.config.js', 'utf-8');
|
||||
@ -85,7 +85,7 @@ module.exports = withNx(
|
||||
it('should support --tsConfig option', async () => {
|
||||
await configurationGenerator(tree, {
|
||||
project: 'mypkg',
|
||||
tsConfig: './tsconfig.custom.json',
|
||||
tsConfig: 'libs/mypkg/tsconfig.custom.json',
|
||||
});
|
||||
|
||||
const rollupConfig = tree.read('libs/mypkg/rollup.config.js', 'utf-8');
|
||||
|
||||
@ -3,24 +3,31 @@ import {
|
||||
GeneratorCallback,
|
||||
joinPathFragments,
|
||||
offsetFromRoot,
|
||||
readJson,
|
||||
readNxJson,
|
||||
readProjectConfiguration,
|
||||
runTasksInSerial,
|
||||
stripIndents,
|
||||
Tree,
|
||||
updateJson,
|
||||
updateProjectConfiguration,
|
||||
writeJson,
|
||||
} from '@nx/devkit';
|
||||
import { getImportPath } from '@nx/js/src/utils/get-import-path';
|
||||
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
|
||||
|
||||
import { rollupInitGenerator } from '../init/init';
|
||||
import { RollupExecutorOptions } from '../../executors/rollup/schema';
|
||||
import { RollupProjectSchema } from './schema';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
import { getUpdatedPackageJsonContent, readTsConfig } from '@nx/js';
|
||||
import { getImportPath } from '@nx/js/src/utils/get-import-path';
|
||||
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
|
||||
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
|
||||
import { dirname, join, relative } from 'node:path/posix';
|
||||
import { mergeTargetConfigurations } from 'nx/src/devkit-internals';
|
||||
import type { PackageJson } from 'nx/src/utils/package-json';
|
||||
import { RollupExecutorOptions } from '../../executors/rollup/schema';
|
||||
import { RollupWithNxPluginOptions } from '../../plugins/with-nx/with-nx-options';
|
||||
import { ensureDependencies } from '../../utils/ensure-dependencies';
|
||||
import { hasPlugin } from '../../utils/has-plugin';
|
||||
import { RollupWithNxPluginOptions } from '../../plugins/with-nx/with-nx-options';
|
||||
import { rollupInitGenerator } from '../init/init';
|
||||
import { RollupProjectSchema } from './schema';
|
||||
|
||||
let ts: typeof import('typescript');
|
||||
|
||||
export async function configurationGenerator(
|
||||
tree: Tree,
|
||||
@ -39,15 +46,20 @@ export async function configurationGenerator(
|
||||
tasks.push(ensureDependencies(tree, options));
|
||||
}
|
||||
|
||||
const isTsSolutionSetup = isUsingTsSolutionSetup(tree);
|
||||
let outputConfig: OutputConfig | undefined;
|
||||
if (hasPlugin(tree)) {
|
||||
createRollupConfig(tree, options);
|
||||
outputConfig = createRollupConfig(tree, options, isTsSolutionSetup);
|
||||
} else {
|
||||
options.buildTarget ??= 'build';
|
||||
checkForTargetConflicts(tree, options);
|
||||
addBuildTarget(tree, options);
|
||||
addBuildTarget(tree, options, isTsSolutionSetup);
|
||||
}
|
||||
|
||||
addPackageJson(tree, options);
|
||||
updatePackageJson(tree, options, outputConfig, isTsSolutionSetup);
|
||||
if (isTsSolutionSetup) {
|
||||
updateTsConfig(tree, options);
|
||||
}
|
||||
|
||||
if (!options.skipFormat) {
|
||||
await formatFiles(tree);
|
||||
@ -56,20 +68,34 @@ export async function configurationGenerator(
|
||||
return runTasksInSerial(...tasks);
|
||||
}
|
||||
|
||||
function createRollupConfig(tree: Tree, options: RollupProjectSchema) {
|
||||
const isUsingTsPlugin = isUsingTsSolutionSetup(tree);
|
||||
type OutputConfig = {
|
||||
main: string;
|
||||
outputPath: string;
|
||||
};
|
||||
function createRollupConfig(
|
||||
tree: Tree,
|
||||
options: RollupProjectSchema,
|
||||
isTsSolutionSetup: boolean
|
||||
): OutputConfig {
|
||||
const project = readProjectConfiguration(tree, options.project);
|
||||
const main = options.main
|
||||
? `./${relative(project.root, options.main)}`
|
||||
: './src/index.ts';
|
||||
const outputPath = isTsSolutionSetup
|
||||
? './dist'
|
||||
: joinPathFragments(
|
||||
offsetFromRoot(project.root),
|
||||
'dist',
|
||||
project.root === '.' ? project.name : project.root
|
||||
);
|
||||
|
||||
const buildOptions: RollupWithNxPluginOptions = {
|
||||
outputPath: isUsingTsPlugin
|
||||
? './dist'
|
||||
: joinPathFragments(
|
||||
offsetFromRoot(project.root),
|
||||
'dist',
|
||||
project.root === '.' ? project.name : project.root
|
||||
),
|
||||
outputPath,
|
||||
compiler: options.compiler ?? 'babel',
|
||||
main: options.main ?? './src/index.ts',
|
||||
tsConfig: options.tsConfig ?? './tsconfig.lib.json',
|
||||
main,
|
||||
tsConfig: options.tsConfig
|
||||
? `./${relative(project.root, options.tsConfig)}`
|
||||
: './tsconfig.lib.json',
|
||||
};
|
||||
|
||||
tree.write(
|
||||
@ -82,8 +108,12 @@ module.exports = withNx(
|
||||
outputPath: '${buildOptions.outputPath}',
|
||||
tsConfig: '${buildOptions.tsConfig}',
|
||||
compiler: '${buildOptions.compiler}',
|
||||
format: ${JSON.stringify(options.format ?? ['esm'])},
|
||||
assets: [{ input: '.', output: '.', glob:'*.md' }],
|
||||
format: ${JSON.stringify(options.format ?? ['esm'])},${
|
||||
!isTsSolutionSetup
|
||||
? `
|
||||
assets: [{ input: '.', output: '.', glob:'*.md' }],`
|
||||
: ''
|
||||
}
|
||||
},
|
||||
{
|
||||
// Provide additional rollup configuration here. See: https://rollupjs.org/configuration-options
|
||||
@ -93,6 +123,11 @@ module.exports = withNx(
|
||||
);
|
||||
`
|
||||
);
|
||||
|
||||
return {
|
||||
main: joinPathFragments(project.root, main),
|
||||
outputPath: joinPathFragments(project.root, outputPath),
|
||||
};
|
||||
}
|
||||
|
||||
function checkForTargetConflicts(tree: Tree, options: RollupProjectSchema) {
|
||||
@ -105,60 +140,129 @@ function checkForTargetConflicts(tree: Tree, options: RollupProjectSchema) {
|
||||
}
|
||||
}
|
||||
|
||||
function addPackageJson(tree: Tree, options: RollupProjectSchema) {
|
||||
function updatePackageJson(
|
||||
tree: Tree,
|
||||
options: RollupProjectSchema,
|
||||
outputConfig: OutputConfig | undefined,
|
||||
isTsSolutionSetup: boolean
|
||||
) {
|
||||
const project = readProjectConfiguration(tree, options.project);
|
||||
const packageJsonPath = joinPathFragments(project.root, 'package.json');
|
||||
|
||||
if (!tree.exists(packageJsonPath)) {
|
||||
const importPath =
|
||||
options.importPath || getImportPath(tree, options.project);
|
||||
writeJson(tree, packageJsonPath, {
|
||||
name: importPath,
|
||||
const packageJsonPath = join(project.root, 'package.json');
|
||||
let packageJson: PackageJson;
|
||||
if (tree.exists(packageJsonPath)) {
|
||||
if (!isTsSolutionSetup) {
|
||||
return;
|
||||
}
|
||||
|
||||
packageJson = readJson(tree, packageJsonPath);
|
||||
} else {
|
||||
packageJson = {
|
||||
name: options.importPath || getImportPath(tree, options.project),
|
||||
version: '0.0.1',
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
if (isTsSolutionSetup) {
|
||||
let main: string;
|
||||
let outputPath: string;
|
||||
if (outputConfig) {
|
||||
({ main, outputPath } = outputConfig);
|
||||
} else {
|
||||
// target must exist if we don't receive an outputConfig
|
||||
const projectTarget = project.targets[options.buildTarget];
|
||||
const nxJson = readNxJson(tree);
|
||||
const mergedTarget = mergeTargetConfigurations(
|
||||
projectTarget,
|
||||
(projectTarget.executor
|
||||
? nxJson.targetDefaults?.[projectTarget.executor]
|
||||
: undefined) ?? nxJson.targetDefaults?.[options.buildTarget]
|
||||
);
|
||||
({ main, outputPath } = mergedTarget.options);
|
||||
}
|
||||
|
||||
packageJson = getUpdatedPackageJsonContent(packageJson, {
|
||||
main,
|
||||
outputPath,
|
||||
projectRoot: project.root,
|
||||
rootDir: dirname(main),
|
||||
generateExportsField: true,
|
||||
packageJsonPath,
|
||||
format: options.format ?? ['esm'],
|
||||
outputFileExtensionForCjs: '.cjs.js',
|
||||
outputFileExtensionForEsm: '.esm.js',
|
||||
});
|
||||
|
||||
// rollup has a specific declaration file generation not handled by the util above,
|
||||
// adjust accordingly
|
||||
const typingsFile = (packageJson.module ?? packageJson.main).replace(
|
||||
/\.js$/,
|
||||
'.d.ts'
|
||||
);
|
||||
packageJson.types = typingsFile;
|
||||
packageJson.exports['.'].types = typingsFile;
|
||||
}
|
||||
|
||||
writeJson(tree, packageJsonPath, packageJson);
|
||||
}
|
||||
|
||||
function addBuildTarget(tree: Tree, options: RollupProjectSchema) {
|
||||
function addBuildTarget(
|
||||
tree: Tree,
|
||||
options: RollupProjectSchema,
|
||||
isTsSolutionSetup: boolean
|
||||
) {
|
||||
addBuildTargetDefaults(tree, '@nx/rollup:rollup', options.buildTarget);
|
||||
const project = readProjectConfiguration(tree, options.project);
|
||||
const prevBuildOptions = project.targets?.[options.buildTarget]?.options;
|
||||
|
||||
options.tsConfig ??=
|
||||
prevBuildOptions?.tsConfig ??
|
||||
joinPathFragments(project.root, 'tsconfig.lib.json');
|
||||
|
||||
let outputPath = prevBuildOptions?.outputPath;
|
||||
if (!outputPath) {
|
||||
outputPath = isTsSolutionSetup
|
||||
? joinPathFragments(project.root, 'dist')
|
||||
: joinPathFragments(
|
||||
'dist',
|
||||
project.root === '.' ? project.name : project.root
|
||||
);
|
||||
}
|
||||
|
||||
const buildOptions: RollupExecutorOptions = {
|
||||
main:
|
||||
options.main ??
|
||||
prevBuildOptions?.main ??
|
||||
joinPathFragments(project.root, 'src/index.ts'),
|
||||
outputPath:
|
||||
prevBuildOptions?.outputPath ??
|
||||
joinPathFragments(
|
||||
'dist',
|
||||
project.root === '.' ? project.name : project.root
|
||||
),
|
||||
tsConfig:
|
||||
options.tsConfig ??
|
||||
prevBuildOptions?.tsConfig ??
|
||||
joinPathFragments(project.root, 'tsconfig.lib.json'),
|
||||
additionalEntryPoints: prevBuildOptions?.additionalEntryPoints,
|
||||
generateExportsField: prevBuildOptions?.generateExportsField,
|
||||
outputPath,
|
||||
tsConfig: options.tsConfig,
|
||||
// TODO(leo): see if we can use this when updating the package.json for the new setup
|
||||
// additionalEntryPoints: prevBuildOptions?.additionalEntryPoints,
|
||||
// generateExportsField: prevBuildOptions?.generateExportsField,
|
||||
compiler: options.compiler ?? 'babel',
|
||||
project: `${project.root}/package.json`,
|
||||
external: options.external,
|
||||
format: options.format,
|
||||
format: options.format ?? isTsSolutionSetup ? ['esm'] : undefined,
|
||||
};
|
||||
|
||||
if (options.rollupConfig) {
|
||||
buildOptions.rollupConfig = options.rollupConfig;
|
||||
}
|
||||
|
||||
if (tree.exists(joinPathFragments(project.root, 'README.md'))) {
|
||||
buildOptions.assets = [
|
||||
{
|
||||
glob: `${project.root}/README.md`,
|
||||
input: '.',
|
||||
output: '.',
|
||||
},
|
||||
];
|
||||
if (!isTsSolutionSetup) {
|
||||
buildOptions.additionalEntryPoints =
|
||||
prevBuildOptions?.additionalEntryPoints;
|
||||
buildOptions.generateExportsField = prevBuildOptions?.generateExportsField;
|
||||
|
||||
if (tree.exists(joinPathFragments(project.root, 'README.md'))) {
|
||||
buildOptions.assets = [
|
||||
{
|
||||
glob: `${project.root}/README.md`,
|
||||
input: '.',
|
||||
output: '.',
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
updateProjectConfiguration(tree, options.project, {
|
||||
@ -174,4 +278,35 @@ function addBuildTarget(tree: Tree, options: RollupProjectSchema) {
|
||||
});
|
||||
}
|
||||
|
||||
function updateTsConfig(tree: Tree, options: RollupProjectSchema): void {
|
||||
const project = readProjectConfiguration(tree, options.project);
|
||||
const tsconfigPath =
|
||||
options.tsConfig ?? joinPathFragments(project.root, 'tsconfig.lib.json');
|
||||
if (!tree.exists(tsconfigPath)) {
|
||||
throw new Error(
|
||||
`The '${tsconfigPath}' file doesn't exist. Provide the 'tsConfig' option with the correct path pointing to the tsconfig file to use for builds.`
|
||||
);
|
||||
}
|
||||
|
||||
if (!ts) {
|
||||
ts = ensureTypescript();
|
||||
}
|
||||
|
||||
const parsedTsConfig = readTsConfig(tsconfigPath, {
|
||||
...ts.sys,
|
||||
readFile: (p) => tree.read(p, 'utf-8'),
|
||||
fileExists: (p) => tree.exists(p),
|
||||
});
|
||||
|
||||
updateJson(tree, tsconfigPath, (json) => {
|
||||
if (parsedTsConfig.options.module === ts.ModuleKind.NodeNext) {
|
||||
json.compilerOptions ??= {};
|
||||
json.compilerOptions.module = 'esnext';
|
||||
json.compilerOptions.moduleResolution = 'bundler';
|
||||
}
|
||||
|
||||
return json;
|
||||
});
|
||||
}
|
||||
|
||||
export default configurationGenerator;
|
||||
|
||||
@ -25,13 +25,13 @@
|
||||
},
|
||||
"main": {
|
||||
"type": "string",
|
||||
"description": "Path relative to the workspace root for the main entry file. Defaults to '<projectRoot>/src/main.ts'.",
|
||||
"description": "Path relative to the workspace root for the main entry file. Defaults to '<projectRoot>/src/index.ts'.",
|
||||
"alias": "entryFile",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"tsConfig": {
|
||||
"type": "string",
|
||||
"description": "Path relative to the workspace root for the tsconfig file to build with. Defaults to '<projectRoot>/tsconfig.app.json'.",
|
||||
"description": "Path relative to the workspace root for the tsconfig file to build with. Defaults to '<projectRoot>/tsconfig.lib.json'.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"skipFormat": {
|
||||
|
||||
@ -76,6 +76,12 @@ export interface RollupWithNxPluginOptions {
|
||||
* The path to tsconfig file.
|
||||
*/
|
||||
tsConfig: string;
|
||||
/**
|
||||
* Whether to generate a package.json file in the output path. It's not supported when the workspace is
|
||||
* set up with TypeScript Project References along with the package managers' Workspaces feature. Otherwise,
|
||||
* it defaults to `true`.
|
||||
*/
|
||||
generatePackageJson?: boolean;
|
||||
}
|
||||
|
||||
export interface AssetGlobPattern {
|
||||
|
||||
@ -28,6 +28,7 @@ import { deleteOutput } from '../delete-output';
|
||||
import { AssetGlobPattern, RollupWithNxPluginOptions } from './with-nx-options';
|
||||
import { normalizeOptions } from './normalize-options';
|
||||
import { PackageJson } from 'nx/src/utils/package-json';
|
||||
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
|
||||
|
||||
// These use require because the ES import isn't correct.
|
||||
const commonjs = require('@rollup/plugin-commonjs');
|
||||
@ -182,6 +183,22 @@ export function withNx(
|
||||
}
|
||||
|
||||
if (!global.NX_GRAPH_CREATION) {
|
||||
const isTsSolutionSetup = isUsingTsSolutionSetup();
|
||||
if (isTsSolutionSetup) {
|
||||
if (options.generatePackageJson) {
|
||||
throw new Error(
|
||||
`Setting 'generatePackageJson: true' is not supported with the current TypeScript setup. Update the 'package.json' file at the project root as needed and unset the 'generatePackageJson' option.`
|
||||
);
|
||||
}
|
||||
if (options.generateExportsField) {
|
||||
throw new Error(
|
||||
`Setting 'generateExportsField: true' is not supported with the current TypeScript setup. Set 'exports' field in the 'package.json' file at the project root and unset the 'generateExportsField' option.`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
options.generatePackageJson ??= true;
|
||||
}
|
||||
|
||||
finalConfig.plugins = [
|
||||
copy({
|
||||
targets: convertCopyAssetsToRollupOptions(
|
||||
@ -247,7 +264,7 @@ export function withNx(
|
||||
}),
|
||||
commonjs(),
|
||||
analyze(),
|
||||
generatePackageJson(options, packageJson),
|
||||
options.generatePackageJson && generatePackageJson(options, packageJson),
|
||||
];
|
||||
if (Array.isArray(rollupConfig.plugins)) {
|
||||
finalConfig.plugins.push(...rollupConfig.plugins);
|
||||
|
||||
@ -285,6 +285,7 @@ export default defineConfig({
|
||||
exports[`@nx/vite:configuration library mode should set up non buildable library which already has vite.config.ts correctly 1`] = `
|
||||
"import dts from 'vite-plugin-dts';
|
||||
import * as path from 'path';
|
||||
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
@ -395,7 +396,6 @@ exports[`@nx/vite:configuration transform React app to use Vite should move inde
|
||||
exports[`@nx/vite:configuration transform Web app to use Vite should create vite.config file at the root of the app 1`] = `
|
||||
"/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
|
||||
|
||||
|
||||
@ -2,27 +2,34 @@ import {
|
||||
formatFiles,
|
||||
GeneratorCallback,
|
||||
joinPathFragments,
|
||||
readJson,
|
||||
readNxJson,
|
||||
readProjectConfiguration,
|
||||
runTasksInSerial,
|
||||
Tree,
|
||||
updateJson,
|
||||
writeJson,
|
||||
} from '@nx/devkit';
|
||||
import { initGenerator as jsInitGenerator } from '@nx/js';
|
||||
|
||||
import {
|
||||
getUpdatedPackageJsonContent,
|
||||
initGenerator as jsInitGenerator,
|
||||
} from '@nx/js';
|
||||
import { getImportPath } from '@nx/js/src/utils/get-import-path';
|
||||
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
|
||||
import { join } from 'node:path/posix';
|
||||
import type { PackageJson } from 'nx/src/utils/package-json';
|
||||
import { ensureDependencies } from '../../utils/ensure-dependencies';
|
||||
import {
|
||||
addBuildTarget,
|
||||
addServeTarget,
|
||||
addPreviewTarget,
|
||||
addServeTarget,
|
||||
createOrEditViteConfig,
|
||||
TargetFlags,
|
||||
} from '../../utils/generator-utils';
|
||||
|
||||
import initGenerator from '../init/init';
|
||||
import vitestGenerator from '../vitest/vitest-generator';
|
||||
import { ViteConfigurationGeneratorSchema } from './schema';
|
||||
import { ensureDependencies } from '../../utils/ensure-dependencies';
|
||||
import { convertNonVite } from './lib/convert-non-vite';
|
||||
import { ViteConfigurationGeneratorSchema } from './schema';
|
||||
|
||||
export function viteConfigurationGenerator(
|
||||
host: Tree,
|
||||
@ -103,20 +110,10 @@ export async function viteConfigurationGeneratorInternal(
|
||||
tree,
|
||||
joinPathFragments(projectRoot, 'tsconfig.lib.json'),
|
||||
(json) => {
|
||||
if (!json.compilerOptions) {
|
||||
json.compilerOptions = {};
|
||||
}
|
||||
if (!json.compilerOptions.types) {
|
||||
json.compilerOptions.types = [];
|
||||
}
|
||||
json.compilerOptions ??= {};
|
||||
json.compilerOptions.types ??= [];
|
||||
if (!json.compilerOptions.types.includes('vite/client')) {
|
||||
return {
|
||||
...json,
|
||||
compilerOptions: {
|
||||
...json.compilerOptions,
|
||||
types: [...json.compilerOptions.types, 'vite/client'],
|
||||
},
|
||||
};
|
||||
json.compilerOptions.types.push('vite/client');
|
||||
}
|
||||
return json;
|
||||
}
|
||||
@ -168,6 +165,10 @@ export async function viteConfigurationGeneratorInternal(
|
||||
tasks.push(vitestTask);
|
||||
}
|
||||
|
||||
if (isUsingTsSolutionSetup(tree)) {
|
||||
updatePackageJson(tree, schema);
|
||||
}
|
||||
|
||||
if (!schema.skipFormat) {
|
||||
await formatFiles(tree);
|
||||
}
|
||||
@ -176,3 +177,44 @@ export async function viteConfigurationGeneratorInternal(
|
||||
}
|
||||
|
||||
export default viteConfigurationGenerator;
|
||||
|
||||
function updatePackageJson(
|
||||
tree: Tree,
|
||||
options: ViteConfigurationGeneratorSchema
|
||||
) {
|
||||
const project = readProjectConfiguration(tree, options.project);
|
||||
|
||||
const packageJsonPath = join(project.root, 'package.json');
|
||||
let packageJson: PackageJson;
|
||||
if (tree.exists(packageJsonPath)) {
|
||||
packageJson = readJson(tree, packageJsonPath);
|
||||
} else {
|
||||
packageJson = {
|
||||
name: getImportPath(tree, options.project),
|
||||
version: '0.0.1',
|
||||
};
|
||||
}
|
||||
|
||||
// we always write/override the vite and project config with some set values,
|
||||
// so we can rely on them
|
||||
const main = join(project.root, 'src/index.ts');
|
||||
// we configure the dts plugin with the entryRoot set to `src`
|
||||
const rootDir = join(project.root, 'src');
|
||||
const outputPath = joinPathFragments(project.root, 'dist');
|
||||
|
||||
packageJson = getUpdatedPackageJsonContent(packageJson, {
|
||||
main,
|
||||
outputPath,
|
||||
projectRoot: project.root,
|
||||
rootDir,
|
||||
generateExportsField: true,
|
||||
packageJsonPath,
|
||||
format: ['esm', 'cjs'],
|
||||
// when building both formats, we don't set the package.json "type" field, so
|
||||
// we need to set the esm extension to ".mjs" to match vite output
|
||||
// see the "File Extensions" callout in https://vite.dev/guide/build.html#library-mode
|
||||
outputFileExtensionForEsm: '.mjs',
|
||||
});
|
||||
|
||||
writeJson(tree, packageJsonPath, packageJson);
|
||||
}
|
||||
|
||||
@ -215,7 +215,6 @@ describe('generator utils', () => {
|
||||
.toMatchInlineSnapshot(`
|
||||
"/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
|
||||
|
||||
|
||||
@ -118,11 +118,15 @@ export function addBuildTarget(
|
||||
) {
|
||||
addBuildTargetDefaults(tree, '@nx/vite:build');
|
||||
const project = readProjectConfiguration(tree, options.project);
|
||||
|
||||
const isTsSolutionSetup = isUsingTsSolutionSetup(tree);
|
||||
const buildOptions: ViteBuildExecutorOptions = {
|
||||
outputPath: joinPathFragments(
|
||||
'dist',
|
||||
project.root != '.' ? project.root : options.project
|
||||
),
|
||||
outputPath: isTsSolutionSetup
|
||||
? joinPathFragments(project.root, 'dist')
|
||||
: joinPathFragments(
|
||||
'dist',
|
||||
project.root != '.' ? project.root : options.project
|
||||
),
|
||||
};
|
||||
project.targets ??= {};
|
||||
project.targets[target] = {
|
||||
@ -228,7 +232,15 @@ export function editTsConfig(
|
||||
) {
|
||||
const projectConfig = readProjectConfiguration(tree, options.project);
|
||||
|
||||
const config = readJson(tree, `${projectConfig.root}/tsconfig.json`);
|
||||
let tsconfigPath = joinPathFragments(projectConfig.root, 'tsconfig.json');
|
||||
const isTsSolutionSetup = isUsingTsSolutionSetup(tree);
|
||||
if (isTsSolutionSetup) {
|
||||
tsconfigPath = [
|
||||
joinPathFragments(projectConfig.root, 'tsconfig.app.json'),
|
||||
joinPathFragments(projectConfig.root, 'tsconfig.lib.json'),
|
||||
].find((p) => tree.exists(p));
|
||||
}
|
||||
const config = readJson(tree, tsconfigPath);
|
||||
|
||||
switch (options.uiFramework) {
|
||||
case 'react':
|
||||
@ -241,21 +253,23 @@ export function editTsConfig(
|
||||
};
|
||||
break;
|
||||
case 'none':
|
||||
config.compilerOptions = {
|
||||
module: 'commonjs',
|
||||
forceConsistentCasingInFileNames: true,
|
||||
strict: true,
|
||||
noImplicitOverride: true,
|
||||
noPropertyAccessFromIndexSignature: true,
|
||||
noImplicitReturns: true,
|
||||
noFallthroughCasesInSwitch: true,
|
||||
};
|
||||
if (!isTsSolutionSetup) {
|
||||
config.compilerOptions = {
|
||||
module: 'commonjs',
|
||||
forceConsistentCasingInFileNames: true,
|
||||
strict: true,
|
||||
noImplicitOverride: true,
|
||||
noPropertyAccessFromIndexSignature: true,
|
||||
noImplicitReturns: true,
|
||||
noFallthroughCasesInSwitch: true,
|
||||
};
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
writeJson(tree, `${projectConfig.root}/tsconfig.json`, config);
|
||||
writeJson(tree, tsconfigPath, config);
|
||||
}
|
||||
|
||||
export function deleteWebpackConfig(
|
||||
@ -405,7 +419,8 @@ export function createOrEditViteConfig(
|
||||
},
|
||||
},`;
|
||||
|
||||
const imports: string[] = options.imports ? options.imports : [];
|
||||
const imports: string[] = options.imports ? [...options.imports] : [];
|
||||
const plugins: string[] = options.plugins ? [...options.plugins] : [];
|
||||
|
||||
if (!onlyVitest && options.includeLib) {
|
||||
imports.push(
|
||||
@ -414,11 +429,13 @@ export function createOrEditViteConfig(
|
||||
);
|
||||
}
|
||||
|
||||
let viteConfigContent = '';
|
||||
|
||||
const plugins = options.plugins
|
||||
? [...options.plugins, `nxViteTsPaths()`, `nxCopyAssetsPlugin(['*.md'])`]
|
||||
: [`nxViteTsPaths()`, `nxCopyAssetsPlugin(['*.md'])`];
|
||||
if (!isUsingTsPlugin) {
|
||||
imports.push(
|
||||
`import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'`,
|
||||
`import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'`
|
||||
);
|
||||
plugins.push(`nxViteTsPaths()`, `nxCopyAssetsPlugin(['*.md'])`);
|
||||
}
|
||||
|
||||
if (!onlyVitest && options.includeLib) {
|
||||
plugins.push(
|
||||
@ -507,11 +524,9 @@ export function createOrEditViteConfig(
|
||||
return;
|
||||
}
|
||||
|
||||
viteConfigContent = `/// <reference types='vitest' />
|
||||
const viteConfigContent = `/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite';
|
||||
${imports.join(';\n')}${imports.length ? ';' : ''}
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
|
||||
|
||||
export default defineConfig({
|
||||
root: __dirname,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user