UNPKG

@nx/react

Version:

The React plugin for Nx contains executors and generators for managing React applications and libraries within an Nx workspace. It provides: - Integration with libraries such as Jest, Vitest, Playwright, Cypress, and Storybook. - Generators for applica

129 lines (128 loc) 7.01 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = removeNxReactWebpackPluginImport; const devkit_1 = require("@nx/devkit"); const tsquery_1 = require("@phenomnomnominal/tsquery"); const DEPRECATED_SYMBOL = 'NxReactWebpackPlugin'; const DEPRECATED_PACKAGE = '@nx/react'; const NEW_PACKAGE = '@nx/react/webpack-plugin'; // ES module import: import { NxReactWebpackPlugin[, ...] } from '@nx/react' const ES_IMPORT_SELECTOR = `ImportDeclaration:has(StringLiteral[value=${DEPRECATED_PACKAGE}]):has(ImportClause ImportSpecifier > Identifier[name=${DEPRECATED_SYMBOL}])`; const IMPORT_SPECIFIERS_SELECTOR = `ImportClause ImportSpecifier`; const TARGET_IMPORT_SPECIFIER_SELECTOR = `ImportClause ImportSpecifier > Identifier[name=${DEPRECATED_SYMBOL}]`; const ES_MODULE_PATH_SELECTOR = `StringLiteral[value=${DEPRECATED_PACKAGE}]`; // CJS require: const { NxReactWebpackPlugin[, ...] } = require('@nx/react') const CJS_REQUIRE_STMT_SELECTOR = `VariableStatement:has(ObjectBindingPattern > BindingElement > Identifier[name=${DEPRECATED_SYMBOL}]):has(CallExpression:has(Identifier[name=require]) > StringLiteral[value=${DEPRECATED_PACKAGE}])`; const CJS_BINDING_ELEMENTS_SELECTOR = `ObjectBindingPattern > BindingElement`; const CJS_TARGET_BINDING_SELECTOR = `ObjectBindingPattern > BindingElement > Identifier[name=${DEPRECATED_SYMBOL}]`; const CJS_REQUIRE_PATH_SELECTOR = `CallExpression:has(Identifier[name=require]) > StringLiteral[value=${DEPRECATED_PACKAGE}]`; // Walk past whitespace from `fromIndex` to find a trailing comma. Returns the // position AFTER the comma if found, otherwise the original index. Handles // the `Foo , withReact` shape where the user wrote whitespace before the // comma — `node.getEnd()` stops at the identifier and the comma sits one // character past the whitespace. function endAfterTrailingComma(text, fromIndex) { let i = fromIndex; while (i < text.length && /\s/.test(text.charAt(i))) i++; return text.charAt(i) === ',' ? i + 1 : fromIndex; } async function removeNxReactWebpackPluginImport(tree) { (0, devkit_1.visitNotIgnoredFiles)(tree, '', (filePath) => { if (!filePath.endsWith('.ts') && !filePath.endsWith('.tsx') && !filePath.endsWith('.js') && !filePath.endsWith('.jsx') && !filePath.endsWith('.cjs') && !filePath.endsWith('.mjs')) { return; } let contents = tree.read(filePath, 'utf-8'); if (!contents) return; // Quick check: must contain the deprecated symbol and the deprecated package path if (!contents.includes(DEPRECATED_SYMBOL) || (!contents.includes(`'${DEPRECATED_PACKAGE}'`) && !contents.includes(`"${DEPRECATED_PACKAGE}"`))) { return; } let changed = false; // Re-parse on every iteration so multiple matches in the same file work // correctly. The multi-specifier branch prepends a new declaration which // shifts every offset; collected AST positions go stale after one rewrite, // so process one match at a time. let didRewrite = true; while (didRewrite) { didRewrite = false; const sourceFile = (0, tsquery_1.ast)(contents); const importNode = (0, tsquery_1.query)(sourceFile, ES_IMPORT_SELECTOR)[0]; if (importNode) { const specifiers = (0, tsquery_1.query)(importNode, IMPORT_SPECIFIERS_SELECTOR); const targetIdentifier = (0, tsquery_1.query)(importNode, TARGET_IMPORT_SPECIFIER_SELECTOR)[0]; if (targetIdentifier) { // Walk to the ImportSpecifier to cover `Foo` and `Foo as Bar`. const targetSpec = targetIdentifier.parent; if (specifiers.length === 1) { const modulePathNode = (0, tsquery_1.query)(importNode, ES_MODULE_PATH_SELECTOR)[0]; if (modulePathNode) { contents = contents.slice(0, modulePathNode.getStart()) + `'${NEW_PACKAGE}'` + contents.slice(modulePathNode.getEnd()); changed = true; didRewrite = true; continue; } } else { // Extract target spec verbatim (preserves alias) const end = endAfterTrailingComma(contents, targetSpec.getEnd()); contents = `import { ${targetSpec.getText()} } from '${NEW_PACKAGE}';\n` + contents.slice(0, targetSpec.getStart()) + contents.slice(end); changed = true; didRewrite = true; continue; } } } const stmtNode = (0, tsquery_1.query)(sourceFile, CJS_REQUIRE_STMT_SELECTOR)[0]; if (stmtNode) { const bindingElements = (0, tsquery_1.query)(stmtNode, CJS_BINDING_ELEMENTS_SELECTOR); const targetIdentifier = (0, tsquery_1.query)(stmtNode, CJS_TARGET_BINDING_SELECTOR)[0]; if (targetIdentifier) { // Walk to the BindingElement to cover `Foo` and `Foo: Bar`. const targetBinding = targetIdentifier.parent; if (bindingElements.length === 1) { const requirePathNode = (0, tsquery_1.query)(stmtNode, CJS_REQUIRE_PATH_SELECTOR)[0]; if (requirePathNode) { contents = contents.slice(0, requirePathNode.getStart()) + `'${NEW_PACKAGE}'` + contents.slice(requirePathNode.getEnd()); changed = true; didRewrite = true; continue; } } else { // Extract target binding verbatim (preserves alias) const end = endAfterTrailingComma(contents, targetBinding.getEnd()); contents = `const { ${targetBinding.getText()} } = require('${NEW_PACKAGE}');\n` + contents.slice(0, targetBinding.getStart()) + contents.slice(end); changed = true; didRewrite = true; continue; } } } } if (changed) { tree.write(filePath, contents); } }); await (0, devkit_1.formatFiles)(tree); }