UNPKG

@nx/vite

Version:

The Nx Plugin for building and testing applications using Vite

360 lines (359 loc) • 16.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ensureViteConfigIsCorrect = ensureViteConfigIsCorrect; const devkit_1 = require("@nx/devkit"); const js_1 = require("@nx/js"); function ensureViteConfigIsCorrect(tree, path, buildConfigString, buildConfigObject, imports, plugins, testConfigString, testConfigObject, cacheDir, projectAlreadyHasViteTargets) { const fileContent = tree.read(path, 'utf-8'); let updatedContent = undefined; if (!projectAlreadyHasViteTargets?.test && testConfigString?.length) { updatedContent = handleBuildOrTestNode(fileContent, testConfigString, testConfigObject, 'test'); } if (!projectAlreadyHasViteTargets?.build && buildConfigString?.length) { updatedContent = handleBuildOrTestNode(updatedContent ?? fileContent, buildConfigString, buildConfigObject, 'build'); } updatedContent = handlePluginNode(updatedContent ?? fileContent, imports, plugins) ?? updatedContent; if (cacheDir?.length) { updatedContent = handleCacheDirNode(updatedContent ?? fileContent, cacheDir); } if (updatedContent) { tree.write(path, updatedContent); return true; } else { return false; } } function handleBuildOrTestNode(updatedFileContent, configContentString, configContentObject, name) { const { tsquery } = require('@phenomnomnominal/tsquery'); const buildOrTestNode = tsquery.query(updatedFileContent, `PropertyAssignment:has(Identifier[name="${name}"])`); if (buildOrTestNode.length) { return tsquery.replace(updatedFileContent, `PropertyAssignment:has(Identifier[name="${name}"])`, (node) => { const existingProperties = tsquery.query(node.initializer, 'PropertyAssignment'); let updatedPropsString = ''; for (const prop of existingProperties) { const propName = prop.name.getText(); if (!configContentObject[propName] && propName !== 'dir' && propName !== 'reportsDirectory' && propName !== 'provider') { // NOTE: Watch for formatting. updatedPropsString += ` '${propName}': ${prop.initializer.getText()},\n`; } } for (const [propName, propValue] of Object.entries(configContentObject)) { // NOTE: Watch for formatting. if (propName === 'coverage') { let propString = ` '${propName}': {\n`; for (const [pName, pValue] of Object.entries(propValue)) { if (pName === 'provider') { propString += ` '${pName}': ${pValue} as const,\n`; } else { propString += ` '${pName}': '${pValue}',\n`; } } propString += `}`; updatedPropsString += `${propString}\n`; } else if (propName === 'lib') { let propString = ` '${propName}': {\n`; for (const [pName, pValue] of Object.entries(propValue)) { if (pName === 'formats') { propString += ` '${pName}': [${pValue .map((format) => `'${format}' as const`) .join(', ')}],\n`; } else { propString += ` '${pName}': ${JSON.stringify(pValue)},\n`; } } propString += ` },`; updatedPropsString += `${propString}\n`; } else { updatedPropsString += ` '${propName}': ${JSON.stringify(propValue)},\n`; } } return `${name}: { ${updatedPropsString} }`; }); } else { const foundDefineConfig = tsquery.query(updatedFileContent, 'CallExpression:has(Identifier[name="defineConfig"])'); if (foundDefineConfig.length) { const conditionalConfig = tsquery.query(foundDefineConfig[0], 'ArrowFunction'); if (conditionalConfig.length) { if (name === 'build') { return transformConditionalConfig(conditionalConfig, updatedFileContent, configContentString); } else { // no test config in conditional config return updatedFileContent; } } else { const propertyAssignments = tsquery.query(foundDefineConfig[0], 'PropertyAssignment'); if (propertyAssignments.length) { return (0, devkit_1.applyChangesToString)(updatedFileContent, [ { type: devkit_1.ChangeType.Insert, index: propertyAssignments[0].getStart(), text: configContentString, }, ]); } else { return (0, devkit_1.applyChangesToString)(updatedFileContent, [ { type: devkit_1.ChangeType.Insert, index: foundDefineConfig[0].getStart() + 14, text: configContentString, }, ]); } } } else { // build config does not exist and defineConfig is not used // could also potentially be invalid syntax, so try-catch try { const defaultExport = tsquery.query(updatedFileContent, 'ExportAssignment'); const found = tsquery.query(defaultExport?.[0], 'ObjectLiteralExpression'); const startOfObject = found?.[0].getStart(); return (0, devkit_1.applyChangesToString)(updatedFileContent, [ { type: devkit_1.ChangeType.Insert, index: startOfObject + 1, text: configContentString, }, ]); } catch { return updatedFileContent; } } } } function transformCurrentBuildObject(index, returnStatements, appFileContent, buildConfigObject) { if (!returnStatements?.[index]) { return undefined; } const { tsquery } = require('@phenomnomnominal/tsquery'); const currentBuildObject = tsquery .query(returnStatements[index], 'ObjectLiteralExpression')?.[0] .getText(); const currentBuildObjectStart = returnStatements[index].getStart(); const currentBuildObjectEnd = returnStatements[index].getEnd(); const newReturnObject = tsquery.replace(returnStatements[index].getText(), 'ObjectLiteralExpression', (_node) => { return `{ ...${currentBuildObject}, ...${JSON.stringify(buildConfigObject)} }`; }); const newContents = (0, devkit_1.applyChangesToString)(appFileContent, [ { type: devkit_1.ChangeType.Delete, start: currentBuildObjectStart, length: currentBuildObjectEnd - currentBuildObjectStart, }, { type: devkit_1.ChangeType.Insert, index: currentBuildObjectStart, text: newReturnObject, }, ]); return newContents; } function transformConditionalConfig(conditionalConfig, appFileContent, buildConfigObject) { const { tsquery } = require('@phenomnomnominal/tsquery'); const { SyntaxKind } = require('typescript'); const functionBlock = tsquery.query(conditionalConfig[0], 'Block'); const ifStatement = tsquery.query(functionBlock?.[0], 'IfStatement'); const binaryExpressions = tsquery.query(ifStatement?.[0], 'BinaryExpression'); const buildExists = binaryExpressions?.find((binaryExpression) => binaryExpression.getText() === `command === 'build'`); const buildExistsExpressionIndex = binaryExpressions?.findIndex((binaryExpression) => binaryExpression.getText() === `command === 'build'`); const serveExists = binaryExpressions?.find((binaryExpression) => binaryExpression.getText() === `command === 'serve'`); const elseKeywordExists = (0, js_1.findNodes)(ifStatement?.[0], SyntaxKind.ElseKeyword); const returnStatements = tsquery.query(ifStatement[0], 'ReturnStatement'); if (!buildExists) { if (serveExists && elseKeywordExists) { // build options live inside the else block return (transformCurrentBuildObject(returnStatements?.length - 1, returnStatements, appFileContent, buildConfigObject) ?? appFileContent); } else { // no build options exist yet const functionBlockStart = functionBlock?.[0].getStart(); const newContents = (0, devkit_1.applyChangesToString)(appFileContent, [ { type: devkit_1.ChangeType.Insert, index: functionBlockStart + 1, text: ` if (command === 'build') { return ${JSON.stringify(buildConfigObject)} } `, }, ]); return newContents; } } else { // build already exists // it will be the return statement which lives // at the buildExistsExpressionIndex return (transformCurrentBuildObject(buildExistsExpressionIndex, returnStatements, appFileContent, buildConfigObject) ?? appFileContent); } } function handlePluginNode(appFileContent, imports, plugins) { const { tsquery } = require('@phenomnomnominal/tsquery'); const file = tsquery.ast(appFileContent); const pluginsNode = tsquery.query(file, 'PropertyAssignment:has(Identifier[name="plugins"])'); let writeFile = false; if (pluginsNode.length) { appFileContent = tsquery.replace(file.getText(), 'PropertyAssignment:has(Identifier[name="plugins"])', (node) => { const found = tsquery.query(node, 'ArrayLiteralExpression'); let updatedPluginsString = ''; const existingPluginNodes = found?.[0].elements ?? []; for (const plugin of existingPluginNodes) { updatedPluginsString += `${plugin.getText()}, `; } for (const plugin of plugins) { if (!existingPluginNodes?.some((node) => node.getText().includes(plugin))) { updatedPluginsString += `${plugin}, `; } } return `plugins: [${updatedPluginsString}]`; }); writeFile = true; } else { // Plugins node does not exist yet // So make one from scratch const foundDefineConfig = tsquery.query(file, 'CallExpression:has(Identifier[name="defineConfig"])'); if (foundDefineConfig.length) { const conditionalConfig = tsquery.query(foundDefineConfig[0], 'ArrowFunction'); if (conditionalConfig.length) { // We are NOT transforming the conditional config // with plugins writeFile = false; } else { const propertyAssignments = tsquery.query(foundDefineConfig[0], 'PropertyAssignment'); if (propertyAssignments.length) { appFileContent = (0, devkit_1.applyChangesToString)(appFileContent, [ { type: devkit_1.ChangeType.Insert, index: propertyAssignments[0].getStart(), text: `plugins: [${plugins.join(', ')}],`, }, ]); writeFile = true; } else { appFileContent = (0, devkit_1.applyChangesToString)(appFileContent, [ { type: devkit_1.ChangeType.Insert, index: foundDefineConfig[0].getStart() + 14, text: `plugins: [${plugins.join(', ')}],`, }, ]); writeFile = true; } } } else { // Plugins option does not exist and defineConfig is not used // could also potentially be invalid syntax, so try-catch try { const defaultExport = tsquery.query(file, 'ExportAssignment'); const found = tsquery?.query(defaultExport?.[0], 'ObjectLiteralExpression'); const startOfObject = found?.[0].getStart(); appFileContent = (0, devkit_1.applyChangesToString)(appFileContent, [ { type: devkit_1.ChangeType.Insert, index: startOfObject + 1, text: `plugins: [${plugins.join(', ')}],`, }, ]); writeFile = true; } catch { writeFile = false; } } } if (writeFile) { const filteredImports = filterImport(appFileContent, imports); return filteredImports.join(';\n') + '\n' + appFileContent; } } function filterImport(appFileContent, imports) { const { tsquery } = require('@phenomnomnominal/tsquery'); const file = tsquery.ast(appFileContent); const importNodes = tsquery.query(file, ':matches(ImportDeclaration, VariableStatement)'); const importsArrayExisting = importNodes?.map((node) => { return node.getText().slice(0, -1); }); return imports.filter((importString) => { return !importsArrayExisting?.includes(importString); }); } function handleCacheDirNode(appFileContent, cacheDir) { const { tsquery } = require('@phenomnomnominal/tsquery'); const file = tsquery.ast(appFileContent); const cacheDirNode = tsquery.query(file, 'PropertyAssignment:has(Identifier[name="cacheDir"])'); if (!cacheDirNode?.length || cacheDirNode?.length === 0) { // cacheDir node does not exist yet // So make one from scratch const foundDefineConfig = tsquery.query(file, 'CallExpression:has(Identifier[name="defineConfig"])'); if (foundDefineConfig.length) { const conditionalConfig = tsquery.query(foundDefineConfig[0], 'ArrowFunction'); if (conditionalConfig.length) { // We are NOT transforming the conditional config // with cacheDir } else { const propertyAssignments = tsquery.query(foundDefineConfig[0], 'PropertyAssignment'); if (propertyAssignments.length) { appFileContent = (0, devkit_1.applyChangesToString)(appFileContent, [ { type: devkit_1.ChangeType.Insert, index: propertyAssignments[0].getStart(), text: cacheDir, }, ]); } else { appFileContent = (0, devkit_1.applyChangesToString)(appFileContent, [ { type: devkit_1.ChangeType.Insert, index: foundDefineConfig[0].getStart() + 14, text: cacheDir, }, ]); } } } else { // cacheDir option does not exist and defineConfig is not used // could also potentially be invalid syntax, so try-catch try { const defaultExport = tsquery.query(file, 'ExportAssignment'); const found = tsquery?.query(defaultExport?.[0], 'ObjectLiteralExpression'); const startOfObject = found?.[0].getStart(); appFileContent = (0, devkit_1.applyChangesToString)(appFileContent, [ { type: devkit_1.ChangeType.Insert, index: startOfObject + 1, text: cacheDir, }, ]); } catch { } } } return appFileContent; }