Skip to content

Commit f979da2

Browse files
committed
refactor(@schematics/angular): add PostCSS configuration detection to tailwind schematic
The Tailwind schematic now intelligently handles existing PostCSS configurations. Instead of unconditionally creating a new `.postcssrc.json` file, the schematic now searches for `postcss.config.json` or `.postcssrc.json` in both the workspace and project roots. If an existing configuration file is found, it is updated with the required `@tailwindcss/postcss` plugin. A new configuration file is only created if one does not already exist. This prevents conflicts and makes the schematic safer to use in projects that already have a customized PostCSS setup.
1 parent 921a981 commit f979da2

2 files changed

Lines changed: 56 additions & 46 deletions

File tree

packages/schematics/angular/tailwind/index.ts

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,18 +85,47 @@ function addTailwindStyles(options: { project: string }, project: ProjectDefinit
8585
};
8686
}
8787

88-
export default createProjectSchematic((options, { project }) => {
89-
const templateSource = apply(url('./files'), [
90-
applyTemplates({
91-
...strings,
92-
...options,
93-
}),
94-
move(project.root),
95-
]);
88+
function managePostCssConfiguration(project: ProjectDefinition): Rule {
89+
return async (tree) => {
90+
const configFiles = ['.postcssrc.json', 'postcss.config.json'];
91+
const searchPaths = ['/', project.root]; // Workspace root and project root
92+
93+
for (const path of searchPaths) {
94+
for (const configFile of configFiles) {
95+
const fullPath = join(path, configFile);
96+
if (tree.exists(fullPath)) {
97+
// Found one. Read, modify, write.
98+
const postcssConfig = JSON.parse(tree.readText(fullPath));
99+
if (!postcssConfig.plugins?.['@tailwindcss/postcss']) {
100+
postcssConfig.plugins = {
101+
...postcssConfig.plugins,
102+
'@tailwindcss/postcss': {},
103+
};
104+
tree.overwrite(fullPath, JSON.stringify(postcssConfig, null, 2));
105+
}
96106

107+
// Config found and handled
108+
return;
109+
}
110+
}
111+
}
112+
113+
// No existing config found, so create one from the template
114+
const templateSource = apply(url('./files'), [
115+
applyTemplates({
116+
...strings,
117+
}),
118+
move(project.root),
119+
]);
120+
121+
return mergeWith(templateSource);
122+
};
123+
}
124+
125+
export default createProjectSchematic((options, { project }) => {
97126
return chain([
98127
addTailwindStyles(options, project),
99-
mergeWith(templateSource),
128+
managePostCssConfiguration(project),
100129
...TAILWIND_DEPENDENCIES.map((name) =>
101130
addDependency(name, latestVersions[name], {
102131
type: DependencyType.Dev,

packages/schematics/angular/tailwind/index_spec.ts

Lines changed: 18 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -105,49 +105,30 @@ describe('Tailwind Schematic', () => {
105105
});
106106
});
107107

108-
describe('with complex build configurations', () => {
109-
beforeEach(async () => {
110-
appTree = await schematicRunner.runSchematic('workspace', workspaceOptions);
111-
appTree = await schematicRunner.runSchematic(
112-
'application',
113-
{ ...appOptions, style: Style.Scss },
114-
appTree,
115-
);
116-
117-
const angularJson = JSON.parse(appTree.readContent('/angular.json'));
118-
angularJson.projects.bar.architect.build.configurations = {
119-
...angularJson.projects.bar.architect.build.configurations,
120-
staging: {
121-
styles: [],
122-
},
123-
production: {
124-
styles: ['projects/bar/src/styles.prod.scss'],
125-
},
126-
development: {
127-
// No styles property
128-
},
129-
};
130-
appTree.overwrite('/angular.json', JSON.stringify(angularJson, null, 2));
108+
describe('with postcss configuration', () => {
109+
it('should create a .postcssrc.json if one does not exist', async () => {
110+
const tree = await schematicRunner.runSchematic('tailwind', { project: 'bar' }, appTree);
111+
expect(tree.exists('/projects/bar/.postcssrc.json')).toBe(true);
131112
});
132113

133-
it('should add tailwind.css to all configurations with styles', async () => {
114+
it('should update an existing .postcssrc.json in the project root', async () => {
115+
appTree.create(
116+
'/projects/bar/.postcssrc.json',
117+
JSON.stringify({ plugins: { autoprefixer: {} } }),
118+
);
134119
const tree = await schematicRunner.runSchematic('tailwind', { project: 'bar' }, appTree);
135-
const angularJson = JSON.parse(tree.readContent('/angular.json'));
136-
const { configurations } = angularJson.projects.bar.architect.build;
137-
138-
expect(configurations.production.styles).toEqual([
139-
'projects/bar/src/tailwind.css',
140-
'projects/bar/src/styles.prod.scss',
141-
]);
142-
expect(configurations.staging.styles).toEqual(['projects/bar/src/tailwind.css']);
120+
const postCssConfig = JSON.parse(tree.readContent('/projects/bar/.postcssrc.json'));
121+
expect(postCssConfig.plugins['@tailwindcss/postcss']).toBeDefined();
122+
expect(postCssConfig.plugins['autoprefixer']).toBeDefined();
143123
});
144124

145-
it('should not modify configurations without a styles property', async () => {
125+
it('should update an existing postcss.config.json in the workspace root', async () => {
126+
appTree.create('/postcss.config.json', JSON.stringify({ plugins: { autoprefixer: {} } }));
146127
const tree = await schematicRunner.runSchematic('tailwind', { project: 'bar' }, appTree);
147-
const angularJson = JSON.parse(tree.readContent('/angular.json'));
148-
const { configurations } = angularJson.projects.bar.architect.build;
149-
150-
expect(configurations.development.styles).toBeUndefined();
128+
const postCssConfig = JSON.parse(tree.readContent('/postcss.config.json'));
129+
expect(postCssConfig.plugins['@tailwindcss/postcss']).toBeDefined();
130+
expect(postCssConfig.plugins['autoprefixer']).toBeDefined();
131+
expect(tree.exists('/projects/bar/.postcssrc.json')).toBe(false);
151132
});
152133
});
153134
});

0 commit comments

Comments
 (0)