@nx/next
Version:
212 lines (211 loc) • 9.84 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = addSvgrToNextConfig;
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"));
async function addSvgrToNextConfig(tree) {
const projects = new Map();
// Find all Next.js projects using withNx that have svgr explicitly set to true or an options object
(0, executor_options_utils_1.forEachExecutorOptions)(tree, '@nx/next:build', (options, project, target) => {
const projectConfig = (0, devkit_1.readProjectConfiguration)(tree, project);
const nextConfigPath = `${projectConfig.root}/next.config.js`;
if (!tree.exists(nextConfigPath))
return;
const content = tree.read(nextConfigPath, 'utf-8');
if (!content.includes('withNx'))
return;
const sourceFile = (0, tsquery_1.ast)(content);
let svgrValue;
const nextConfigDeclarations = (0, tsquery_1.query)(sourceFile, 'VariableDeclaration:has(Identifier[name=nextConfig]) > ObjectLiteralExpression');
if (nextConfigDeclarations.length > 0) {
const objLiteral = nextConfigDeclarations[0];
const nxProp = objLiteral.properties.find((prop) => ts.isPropertyAssignment(prop) &&
ts.isIdentifier(prop.name) &&
prop.name.text === 'nx');
if (nxProp && ts.isObjectLiteralExpression(nxProp.initializer)) {
const svgrProp = nxProp.initializer.properties.find((prop) => ts.isPropertyAssignment(prop) &&
ts.isIdentifier(prop.name) &&
prop.name.text === 'svgr');
if (svgrProp) {
// It's an object with options
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;
}
}
}
// It's a boolean
else {
svgrValue =
svgrProp.initializer.kind === ts.SyntaxKind.TrueKeyword;
}
}
}
}
// If svgr is not defined, skip this project
if (svgrValue === undefined)
return;
projects.set(nextConfigPath, {
svgrOptions: svgrValue,
});
});
if (projects.size === 0)
return;
// Update next.config.js files to add SVGR webpack configuration
for (const [nextConfigPath, config] of projects.entries()) {
let content = tree.read(nextConfigPath, 'utf-8');
const sourceFile = (0, tsquery_1.ast)(content);
const changes = [];
const nextConfigDeclarations = (0, tsquery_1.query)(sourceFile, 'VariableDeclaration:has(Identifier[name=nextConfig]) > ObjectLiteralExpression');
if (nextConfigDeclarations.length > 0) {
const objLiteral = nextConfigDeclarations[0];
const nxProp = objLiteral.properties.find((prop) => ts.isPropertyAssignment(prop) &&
ts.isIdentifier(prop.name) &&
prop.name.text === 'nx');
if (nxProp && ts.isObjectLiteralExpression(nxProp.initializer)) {
const svgrProp = nxProp.initializer.properties.find((prop) => ts.isPropertyAssignment(prop) &&
ts.isIdentifier(prop.name) &&
prop.name.text === 'svgr');
if (svgrProp) {
const nxObj = nxProp.initializer;
const hasOnlySvgrProperty = nxObj.properties.length === 1;
// Remove svgr property and leave remaining ones
if (hasOnlySvgrProperty) {
changes.push({
type: devkit_1.ChangeType.Delete,
start: nxObj.getStart(),
length: nxObj.getEnd() - nxObj.getStart(),
});
changes.push({
type: devkit_1.ChangeType.Insert,
index: nxObj.getStart(),
text: '{}',
});
}
// If svgr property is the only property, remove the entire nx object
else {
const propIndex = nxObj.properties.indexOf(svgrProp);
const isLastProp = propIndex === nxObj.properties.length - 1;
const isFirstProp = propIndex === 0;
let removeStart = svgrProp.getFullStart();
let removeEnd = svgrProp.getEnd();
// Handle comma removal
if (!isLastProp) {
// Remove trailing comma
const nextProp = nxObj.properties[propIndex + 1];
removeEnd = nextProp.getFullStart();
}
else if (!isFirstProp) {
// Remove preceding comma from previous property
const prevProp = nxObj.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,
});
}
}
}
}
// Only add SVGR webpack config if svgrOptions is true or an object
if (config.svgrOptions === true || typeof config.svgrOptions === 'object') {
let svgrOptions = '';
if (config.svgrOptions === true) {
svgrOptions = `{
svgo: false,
titleProp: true,
ref: true,
}`;
}
else if (typeof config.svgrOptions === 'object') {
const options = Object.entries(config.svgrOptions)
.map(([key, value]) => ` ${key}: ${value}`)
.join(',\n');
svgrOptions = `{\n${options}\n }`;
}
const svgrWebpackFunction = `(config) => {
const originalWebpack = config.webpack;
// @ts-ignore
config.webpack = (webpackConfig, ctx) => {
// Add SVGR support with webpack 5 asset modules
webpackConfig.module.rules.push({
test: /\.svg$/,
oneOf: [
{
resourceQuery: /url/,
type: 'asset/resource',
generator: {
filename: 'static/media/[name].[hash][ext]',
},
},
{
issuer: { not: /\.(css|scss|sass)$/ },
resourceQuery: {
not: [
/__next_metadata__/,
/__next_metadata_route__/,
/__next_metadata_image_meta__/,
],
},
use: [
{
loader: require.resolve('@svgr/webpack'),
options: ${svgrOptions},
},
],
},
],
});
return originalWebpack
? originalWebpack(webpackConfig, ctx)
: webpackConfig;
};
return config;
};`;
const pluginsArrayDeclarations = (0, tsquery_1.query)(sourceFile, 'VariableDeclaration:has(Identifier[name=plugins]) ArrayLiteralExpression');
if (pluginsArrayDeclarations.length > 0) {
const pluginsArray = pluginsArrayDeclarations[0];
const pluginsStatement = pluginsArray.parent.parent;
changes.push({
type: devkit_1.ChangeType.Insert,
index: pluginsStatement.getEnd(),
text: `\n\n// Add SVGR webpack config function\n// @ts-ignore\nconst withSvgr = ${svgrWebpackFunction};`,
});
}
const composePluginsCalls = (0, tsquery_1.query)(sourceFile, 'CallExpression[expression.name=composePlugins]');
if (composePluginsCalls.length > 0) {
const composeCall = composePluginsCalls[0];
const spreadArg = composeCall.arguments.find((arg) => ts.isSpreadElement(arg));
if (spreadArg) {
changes.push({
type: devkit_1.ChangeType.Insert,
index: spreadArg.getEnd(),
text: ', withSvgr',
});
}
}
}
content = (0, devkit_1.applyChangesToString)(content, changes);
tree.write(nextConfigPath, content);
}
await (0, devkit_1.formatFiles)(tree);
}