@nx/react
Version:
438 lines (427 loc) • 20.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = addSvgrToWebpackConfig;
const tslib_1 = require("tslib");
const devkit_1 = require("@nx/devkit");
const executor_options_utils_1 = require("@nx/devkit/src/generators/executor-options-utils");
const tsquery_1 = require("@phenomnomnominal/tsquery");
const ts = tslib_1.__importStar(require("typescript"));
const withSvgrFunctionForWithReact = `
// SVGR support function (migrated from svgr option in withReact/NxReactWebpackPlugin)
function withSvgr(svgrOptions = {}) {
const defaultOptions = {
svgo: false,
titleProp: true,
ref: true,
};
const options = { ...defaultOptions, ...svgrOptions };
return function configure(config) {
// Remove existing SVG loader if present
const svgLoaderIdx = config.module.rules.findIndex(
(rule) =>
typeof rule === 'object' &&
typeof rule.test !== 'undefined' &&
rule.test.toString().includes('svg')
);
if (svgLoaderIdx !== -1) {
config.module.rules.splice(svgLoaderIdx, 1);
}
// Add SVGR loader with webpack 5 asset modules
config.module.rules.push({
test: /\\.svg$/,
oneOf: [
{
resourceQuery: /url/,
type: 'asset/resource',
generator: {
filename: '[name].[hash][ext]',
},
},
{
issuer: /\\.(js|ts|md)x?$/,
use: [
{
loader: require.resolve('@svgr/webpack'),
options,
},
],
},
],
});
return config;
};
}
`;
const withSvgrFunctionForNxReactWebpackPlugin = `
// SVGR support function (migrated from svgr option in withReact/NxReactWebpackPlugin)
function withSvgr(svgrOptions = {}) {
const defaultOptions = {
svgo: false,
titleProp: true,
ref: true,
};
const options = { ...defaultOptions, ...svgrOptions };
return (config) => {
config.plugins.push({
apply: (compiler) => {
// Remove ALL existing SVG loaders
compiler.options.module.rules = compiler.options.module.rules.filter(
(rule) =>
!(
rule &&
typeof rule === 'object' &&
rule.test &&
rule.test.toString().includes('svg')
)
);
// Add SVGR loader with webpack 5 asset modules
compiler.options.module.rules.push({
test: /\.svg$/,
oneOf: [
{
resourceQuery: /url/,
type: 'asset/resource',
generator: {
filename: '[name].[hash][ext]',
},
},
{
issuer: /\.[jt]sx?$/,
use: [
{
loader: require.resolve('@svgr/webpack'),
options,
},
],
},
],
});
},
});
return config;
};
}
`;
async function addSvgrToWebpackConfig(tree) {
const projects = new Map();
// Find all React webpack projects using either withReact OR NxReactWebpackPlugin
(0, executor_options_utils_1.forEachExecutorOptions)(tree, '@nx/webpack:webpack', (options, project, target) => {
if (!options.webpackConfig)
return;
const webpackConfigPath = options.webpackConfig;
if (!tree.exists(webpackConfigPath))
return;
const content = tree.read(webpackConfigPath, 'utf-8');
const sourceFile = (0, tsquery_1.ast)(content);
// Check if this is a withReact setup
if (content.includes('withReact')) {
const withReactCalls = (0, tsquery_1.query)(sourceFile, 'CallExpression[expression.name=withReact]');
if (withReactCalls.length > 0) {
const callExpr = withReactCalls[0];
if (callExpr.arguments.length === 0)
return;
const arg = callExpr.arguments[0];
if (!ts.isObjectLiteralExpression(arg))
return;
const svgrProp = arg.properties.find((prop) => ts.isPropertyAssignment(prop) &&
ts.isIdentifier(prop.name) &&
prop.name.text === 'svgr');
if (svgrProp) {
let svgrValue;
if (ts.isObjectLiteralExpression(svgrProp.initializer)) {
svgrValue = {};
for (const prop of svgrProp.initializer.properties) {
if (!ts.isPropertyAssignment(prop))
continue;
if (!ts.isIdentifier(prop.name))
continue;
const key = prop.name.text;
if (prop.initializer.kind === ts.SyntaxKind.TrueKeyword) {
svgrValue[key] = true;
}
else if (prop.initializer.kind === ts.SyntaxKind.FalseKeyword) {
svgrValue[key] = false;
}
}
}
else {
svgrValue =
svgrProp.initializer.kind === ts.SyntaxKind.TrueKeyword;
}
projects.set(webpackConfigPath, {
svgrOptions: svgrValue,
isWithReact: true,
});
}
}
}
// Otherwise check if this is NxReactWebpackPlugin setup
else if (content.includes('NxReactWebpackPlugin')) {
const pluginCalls = (0, tsquery_1.query)(sourceFile, 'NewExpression[expression.name=NxReactWebpackPlugin]');
if (pluginCalls.length > 0) {
const newExpr = pluginCalls[0];
if (!newExpr.arguments || newExpr.arguments.length === 0)
return;
const arg = newExpr.arguments[0];
if (!ts.isObjectLiteralExpression(arg))
return;
const svgrProp = arg.properties.find((prop) => ts.isPropertyAssignment(prop) &&
ts.isIdentifier(prop.name) &&
prop.name.text === 'svgr');
if (svgrProp) {
let svgrValue;
if (ts.isObjectLiteralExpression(svgrProp.initializer)) {
svgrValue = {};
for (const prop of svgrProp.initializer.properties) {
if (!ts.isPropertyAssignment(prop))
continue;
if (!ts.isIdentifier(prop.name))
continue;
const key = prop.name.text;
if (prop.initializer.kind === ts.SyntaxKind.TrueKeyword) {
svgrValue[key] = true;
}
else if (prop.initializer.kind === ts.SyntaxKind.FalseKeyword) {
svgrValue[key] = false;
}
}
}
else if (svgrProp.initializer.kind === ts.SyntaxKind.TrueKeyword) {
svgrValue = true;
}
else if (svgrProp.initializer.kind === ts.SyntaxKind.FalseKeyword) {
svgrValue = false;
}
// Add to projects if svgr is explicitly set
if (svgrValue !== undefined) {
projects.set(webpackConfigPath, {
svgrOptions: svgrValue,
isWithReact: false,
});
}
}
}
}
});
if (projects.size === 0)
return;
// Update webpack configs to add withSvgr function inline
for (const [webpackConfigPath, config] of projects.entries()) {
let content = tree.read(webpackConfigPath, 'utf-8');
const sourceFile = (0, tsquery_1.ast)(content);
const changes = [];
// Build the svgr options for this specific config
let svgrOptionsStr = '';
if (config.svgrOptions) {
const importStatements = (0, tsquery_1.query)(sourceFile, 'ImportDeclaration');
const requireStatements = (0, tsquery_1.query)(sourceFile, 'VariableStatement:has(CallExpression[expression.name=require])');
const allImportRequires = [
...importStatements,
...requireStatements,
].sort((a, b) => a.getEnd() - b.getEnd());
const lastImportOrRequire = allImportRequires[allImportRequires.length - 1];
if (config.svgrOptions === true || config.svgrOptions === undefined) {
svgrOptionsStr = '';
}
else if (typeof config.svgrOptions === 'object') {
const options = Object.entries(config.svgrOptions)
.map(([key, value]) => ` ${key}: ${value}`)
.join(',\n');
svgrOptionsStr = `{\n${options}\n}`;
}
if (lastImportOrRequire) {
changes.push({
type: devkit_1.ChangeType.Insert,
index: lastImportOrRequire.getEnd(),
text: config.isWithReact
? withSvgrFunctionForWithReact
: withSvgrFunctionForNxReactWebpackPlugin,
});
}
else {
changes.push({
type: devkit_1.ChangeType.Insert,
index: 0,
text: (config.isWithReact
? withSvgrFunctionForWithReact
: withSvgrFunctionForNxReactWebpackPlugin) + '\n',
});
}
}
// Remove svgr option based on the style (withReact OR NxReactWebpackPlugin)
if (config.isWithReact) {
// Remove svgr option from first withReact call (only one expected)
const withReactCalls = (0, tsquery_1.query)(sourceFile, 'CallExpression[expression.name=withReact]');
if (withReactCalls.length > 0) {
const callExpr = withReactCalls[0];
if (callExpr.arguments.length > 0) {
const arg = callExpr.arguments[0];
if (ts.isObjectLiteralExpression(arg)) {
const svgrProp = arg.properties.find((prop) => ts.isPropertyAssignment(prop) &&
ts.isIdentifier(prop.name) &&
prop.name.text === 'svgr');
if (svgrProp) {
const hasOnlySvgrProperty = arg.properties.length === 1;
if (hasOnlySvgrProperty) {
changes.push({
type: devkit_1.ChangeType.Delete,
start: arg.getStart(),
length: arg.getEnd() - arg.getStart(),
});
}
else {
const propIndex = arg.properties.indexOf(svgrProp);
const isLastProp = propIndex === arg.properties.length - 1;
const isFirstProp = propIndex === 0;
let removeStart = svgrProp.getFullStart();
let removeEnd = svgrProp.getEnd();
if (!isLastProp) {
const nextProp = arg.properties[propIndex + 1];
removeEnd = nextProp.getFullStart();
}
else if (!isFirstProp) {
const prevProp = arg.properties[propIndex - 1];
const textBetween = content.substring(prevProp.getEnd(), svgrProp.getFullStart());
const commaIndex = textBetween.indexOf(',');
if (commaIndex !== -1) {
removeStart = prevProp.getEnd() + commaIndex;
}
}
changes.push({
type: devkit_1.ChangeType.Delete,
start: removeStart,
length: removeEnd - removeStart,
});
}
if (config.svgrOptions) {
const composePluginsCalls = (0, tsquery_1.query)(sourceFile, 'CallExpression[expression.name=composePlugins]');
if (composePluginsCalls.length > 0) {
const composeCall = composePluginsCalls[0];
let svgrCallStr = '';
if (config.svgrOptions === true ||
config.svgrOptions === undefined) {
svgrCallStr = 'withSvgr()';
}
else if (typeof config.svgrOptions === 'object') {
svgrCallStr = `withSvgr(${svgrOptionsStr})`;
}
const withReactIdx = composeCall.arguments.findIndex((arg) => arg.getText().includes('withReact'));
// Insert withSvgr as the last argument before the closing paren
const argToInsertAfter = composeCall.arguments[withReactIdx];
if (argToInsertAfter) {
changes.push({
type: devkit_1.ChangeType.Insert,
index: argToInsertAfter.getEnd(),
text: `, ${svgrCallStr}`,
});
}
}
}
}
}
}
}
}
else {
// Remove svgr option from first NxReactWebpackPlugin call
const pluginCalls = (0, tsquery_1.query)(sourceFile, 'NewExpression[expression.name=NxReactWebpackPlugin]');
if (pluginCalls.length > 0) {
const newExpr = pluginCalls[0];
if (newExpr.arguments && newExpr.arguments.length > 0) {
const arg = newExpr.arguments[0];
if (ts.isObjectLiteralExpression(arg)) {
const svgrProp = arg.properties.find((prop) => ts.isPropertyAssignment(prop) &&
ts.isIdentifier(prop.name) &&
prop.name.text === 'svgr');
if (svgrProp) {
const hasOnlySvgrProperty = arg.properties.length === 1;
// Replace entire object argument with empty parentheses when only svgr property exists
if (hasOnlySvgrProperty) {
changes.push({
type: devkit_1.ChangeType.Delete,
start: arg.getStart(),
length: arg.getEnd() - arg.getStart(),
});
}
// If more properties exist, just remove the svgr property
else {
const propIndex = arg.properties.indexOf(svgrProp);
const isLastProp = propIndex === arg.properties.length - 1;
const isFirstProp = propIndex === 0;
let removeStart = svgrProp.getFullStart();
let removeEnd = svgrProp.getEnd();
if (!isLastProp) {
const nextProp = arg.properties[propIndex + 1];
removeEnd = nextProp.getFullStart();
}
else if (!isFirstProp) {
const prevProp = arg.properties[propIndex - 1];
const textBetween = content.substring(prevProp.getEnd(), svgrProp.getFullStart());
const commaIndex = textBetween.indexOf(',');
if (commaIndex !== -1) {
removeStart = prevProp.getEnd() + commaIndex;
}
}
changes.push({
type: devkit_1.ChangeType.Delete,
start: removeStart,
length: removeEnd - removeStart,
});
}
}
}
}
}
// For NxReactWebpackPlugin style, wrap the entire module.exports or export default with withSvgr
if (config.svgrOptions) {
const allAssignments = (0, tsquery_1.query)(sourceFile, 'BinaryExpression');
// Find the one that has module.exports on the left side
const moduleExportsAssignment = allAssignments.find((node) => {
const binaryExpr = node;
const left = binaryExpr.left;
return (ts.isPropertyAccessExpression(left) &&
ts.isIdentifier(left.expression) &&
left.expression.text === 'module' &&
ts.isIdentifier(left.name) &&
left.name.text === 'exports');
});
// Also check for export default
const exportDefaultStatements = (0, tsquery_1.query)(sourceFile, 'ExportAssignment');
const exportDefaultStatement = exportDefaultStatements[0];
// Use whichever export style is found
let exportValue;
if (moduleExportsAssignment) {
exportValue = moduleExportsAssignment.right;
}
else if (exportDefaultStatement) {
exportValue = exportDefaultStatement.expression;
}
if (exportValue) {
let svgrCallStr = '';
if (config.svgrOptions === true || config.svgrOptions === undefined) {
svgrCallStr = 'withSvgr()';
}
else if (typeof config.svgrOptions === 'object') {
const options = Object.entries(config.svgrOptions)
.map(([key, value]) => ` ${key}: ${value}`)
.join(',\n');
svgrCallStr = `withSvgr({\n${options}\n})`;
}
changes.push({
type: devkit_1.ChangeType.Insert,
index: exportValue.getStart(),
text: `${svgrCallStr}(`,
});
changes.push({
type: devkit_1.ChangeType.Insert,
index: exportValue.getEnd(),
text: ')',
});
}
}
}
content = (0, devkit_1.applyChangesToString)(content, changes);
tree.write(webpackConfigPath, content);
}
await (0, devkit_1.formatFiles)(tree);
}