UNPKG

@nx/js

Version:

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

209 lines (208 loc) • 9.73 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.isInlineGraphEmpty = isInlineGraphEmpty; exports.handleInliningBuild = handleInliningBuild; exports.postProcessInlinedDependencies = postProcessInlinedDependencies; exports.getRootTsConfigPath = getRootTsConfigPath; const devkit_1 = require("@nx/devkit"); const node_fs_1 = require("node:fs"); const path_1 = require("path"); function isInlineGraphEmpty(inlineGraph) { return Object.keys(inlineGraph.nodes).length === 0; } function handleInliningBuild(context, options, tsConfigPath, projectName = context.projectName) { const tsConfigJson = (0, devkit_1.readJsonFile)(tsConfigPath); const pathAliases = tsConfigJson['compilerOptions']?.['paths'] || readBasePathAliases(context); const inlineGraph = createInlineGraph(context, options, pathAliases, projectName); if (isInlineGraphEmpty(inlineGraph)) { return inlineGraph; } buildInlineGraphExternals(context, inlineGraph, pathAliases); return inlineGraph; } function postProcessInlinedDependencies(outputPath, parentOutputPath, inlineGraph) { if (isInlineGraphEmpty(inlineGraph)) { return; } const parentDistPath = (0, path_1.join)(outputPath, parentOutputPath); const markedForDeletion = new Set(); // move parentOutput movePackage(parentDistPath, outputPath); markedForDeletion.add(parentDistPath); const inlinedDepsDestOutputRecord = {}; // move inlined outputs for (const inlineDependenciesNames of Object.values(inlineGraph.dependencies)) { for (const inlineDependenciesName of inlineDependenciesNames) { const inlineDependency = inlineGraph.nodes[inlineDependenciesName]; const depOutputPath = inlineDependency.buildOutputPath || (0, path_1.join)(outputPath, inlineDependency.root); const destDepOutputPath = (0, path_1.join)(outputPath, inlineDependency.name); const isBuildable = !!inlineDependency.buildOutputPath; if (isBuildable) { (0, node_fs_1.cpSync)(depOutputPath, destDepOutputPath, { recursive: true }); } else { movePackage(depOutputPath, destDepOutputPath); markedForDeletion.add(depOutputPath); } // TODO: hard-coded "src" inlinedDepsDestOutputRecord[inlineDependency.pathAlias] = destDepOutputPath + '/src'; } } markedForDeletion.forEach((path) => (0, node_fs_1.rmSync)(path, { recursive: true, force: true })); updateImports(outputPath, inlinedDepsDestOutputRecord); } function readBasePathAliases(context) { return (0, devkit_1.readJsonFile)(getRootTsConfigPath(context))?.['compilerOptions']['paths']; } function getRootTsConfigPath(context) { for (const tsConfigName of ['tsconfig.base.json', 'tsconfig.json']) { const tsConfigPath = (0, path_1.join)(context.root, tsConfigName); if ((0, node_fs_1.existsSync)(tsConfigPath)) { return tsConfigPath; } } throw new Error('Could not find a root tsconfig.json or tsconfig.base.json file.'); } function emptyInlineGraph() { return { nodes: {}, externals: {}, dependencies: {} }; } function projectNodeToInlineProjectNode(projectNode, pathAlias = '', buildOutputPath = '') { return { name: projectNode.name, root: projectNode.data.root, sourceRoot: projectNode.data.sourceRoot, pathAlias, buildOutputPath, }; } function createInlineGraph(context, options, pathAliases, projectName, inlineGraph = emptyInlineGraph()) { if (options.external == null) return inlineGraph; const projectDependencies = context.projectGraph.dependencies[projectName] || []; if (projectDependencies.length === 0) return inlineGraph; if (!inlineGraph.nodes[projectName]) { inlineGraph.nodes[projectName] = projectNodeToInlineProjectNode(context.projectGraph.nodes[projectName]); } const implicitDependencies = context.projectGraph.nodes[projectName].data.implicitDependencies || []; for (const projectDependency of projectDependencies) { // skip npm packages if (projectDependency.target.startsWith('npm')) { continue; } // skip implicitDependencies if (implicitDependencies.includes(projectDependency.target)) { continue; } const pathAlias = getPathAliasForPackage(context.projectGraph.nodes[projectDependency.target], pathAliases); const buildOutputPath = getBuildOutputPath(projectDependency.target, context, options); const shouldInline = /** * if all buildable libraries are marked as external, * then push the project dependency that doesn't have a build target */ (options.external === 'all' && !buildOutputPath) || /** * if all buildable libraries are marked as internal, * then push every project dependency to be inlined */ options.external === 'none' || /** * if some buildable libraries are marked as external, * then push the project dependency that IS NOT marked as external OR doesn't have a build target */ (Array.isArray(options.external) && options.external.length > 0 && !options.external.includes(projectDependency.target)) || !buildOutputPath; if (shouldInline) { inlineGraph.dependencies[projectName] ??= []; inlineGraph.dependencies[projectName].push(projectDependency.target); } inlineGraph.nodes[projectDependency.target] = projectNodeToInlineProjectNode(context.projectGraph.nodes[projectDependency.target], pathAlias, buildOutputPath); if (context.projectGraph.dependencies[projectDependency.target].length > 0) { inlineGraph = createInlineGraph(context, options, pathAliases, projectDependency.target, inlineGraph); } } return inlineGraph; } function buildInlineGraphExternals(context, inlineProjectGraph, pathAliases) { const allNodes = { ...context.projectGraph.nodes }; for (const [parent, dependencies] of Object.entries(inlineProjectGraph.dependencies)) { if (allNodes[parent]) { delete allNodes[parent]; } for (const dependencyName of dependencies) { const dependencyNode = inlineProjectGraph.nodes[dependencyName]; // buildable is still external even if it is a dependency if (dependencyNode.buildOutputPath) { continue; } if (allNodes[dependencyName]) { delete allNodes[dependencyName]; } } } for (const [projectName, projectNode] of Object.entries(allNodes)) { if (!inlineProjectGraph.externals[projectName]) { inlineProjectGraph.externals[projectName] = projectNodeToInlineProjectNode(projectNode, getPathAliasForPackage(projectNode, pathAliases)); } } } function movePackage(from, to) { if (from === to) return; (0, node_fs_1.cpSync)(from, to, { recursive: true }); } function updateImports(destOutputPath, inlinedDepsDestOutputRecord) { const pathAliases = Object.keys(inlinedDepsDestOutputRecord); if (pathAliases.length == 0) { return; } const importRegex = new RegExp(pathAliases.map((pathAlias) => `["'](${pathAlias})["']`).join('|'), 'g'); recursiveUpdateImport(destOutputPath, importRegex, inlinedDepsDestOutputRecord); } function recursiveUpdateImport(dirPath, importRegex, inlinedDepsDestOutputRecord, rootParentDir) { const files = (0, node_fs_1.readdirSync)(dirPath, { withFileTypes: true }); for (const file of files) { // only check .js and .d.ts files if (file.isFile() && (file.name.endsWith('.js') || file.name.endsWith('.d.ts'))) { const filePath = (0, path_1.join)(dirPath, file.name); const fileContent = (0, node_fs_1.readFileSync)(filePath, 'utf-8'); const updatedContent = fileContent.replace(importRegex, (matched) => { const result = matched.replace(/['"]/g, ''); // If a match is the same as the rootParentDir, we're checking its own files so we return the matched as in no changes. if (result === rootParentDir || !inlinedDepsDestOutputRecord[result]) return matched; const importPath = `"${(0, path_1.relative)(dirPath, inlinedDepsDestOutputRecord[result])}"`; return (0, devkit_1.normalizePath)(importPath); }); (0, node_fs_1.writeFileSync)(filePath, updatedContent); } else if (file.isDirectory()) { recursiveUpdateImport((0, path_1.join)(dirPath, file.name), importRegex, inlinedDepsDestOutputRecord, rootParentDir || file.name); } } } function getPathAliasForPackage(packageNode, pathAliases) { if (!packageNode) return ''; for (const [alias, paths] of Object.entries(pathAliases)) { if (paths.some((path) => path.includes(packageNode.data.root))) { return alias; } } return ''; } function getBuildOutputPath(projectName, context, options) { const projectTargets = context.projectGraph.nodes[projectName]?.data?.targets; if (!projectTargets) return ''; const buildTarget = options.externalBuildTargets.find((buildTarget) => projectTargets[buildTarget]); return buildTarget ? projectTargets[buildTarget].options['outputPath'] : ''; }