-
-
Notifications
You must be signed in to change notification settings - Fork 9.8k
NextJS-Vite: Automatically fix bad PostCSS configuration #32691
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
1cafff7
0dc191c
d86e68a
4ad2494
c874228
857bfc4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| // @ts-check | ||
| import { readFile, writeFile } from 'node:fs/promises'; | ||
| import { createRequire } from 'node:module'; | ||
|
|
||
| import { getProjectRoot } from 'storybook/internal/common'; | ||
| import { IncompatiblePostCssConfigError } from 'storybook/internal/server-errors'; | ||
|
|
||
| import config from 'lilconfig'; | ||
| import postCssLoadConfig from 'postcss-load-config'; | ||
| import yaml from 'yaml'; | ||
|
|
||
| type Options = import('lilconfig').Options; | ||
|
|
||
| const require = createRequire(import.meta.url); | ||
|
|
||
| async function loader(filepath: string) { | ||
| return require(filepath); | ||
| } | ||
|
|
||
| async function yamlLoader(_: string, content: string) { | ||
| return yaml.parse(content); | ||
| } | ||
ndelangen marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| const withLoaders = (options: Options = {}) => { | ||
| const moduleName = 'postcss'; | ||
|
|
||
| return { | ||
| ...options, | ||
| loaders: { | ||
| ...options.loaders, | ||
| '.cjs': loader, | ||
| '.cts': loader, | ||
| '.js': loader, | ||
| '.mjs': loader, | ||
| '.mts': loader, | ||
| '.ts': loader, | ||
| '.yaml': yamlLoader, | ||
| '.yml': yamlLoader, | ||
| }, | ||
| searchPlaces: [ | ||
| ...(options.searchPlaces ?? []), | ||
| 'package.json', | ||
| `.${moduleName}rc`, | ||
| `.${moduleName}rc.json`, | ||
| `.${moduleName}rc.yaml`, | ||
| `.${moduleName}rc.yml`, | ||
| `.${moduleName}rc.ts`, | ||
| `.${moduleName}rc.cts`, | ||
| `.${moduleName}rc.mts`, | ||
| `.${moduleName}rc.js`, | ||
| `.${moduleName}rc.cjs`, | ||
| `.${moduleName}rc.mjs`, | ||
| `${moduleName}.config.ts`, | ||
| `${moduleName}.config.cts`, | ||
| `${moduleName}.config.mts`, | ||
| `${moduleName}.config.js`, | ||
| `${moduleName}.config.cjs`, | ||
| `${moduleName}.config.mjs`, | ||
| ], | ||
| } satisfies Options; | ||
| }; | ||
|
|
||
| /** | ||
| * Find PostCSS config file path (without loading the config) | ||
| * | ||
| * @param {String} path Config Path | ||
| * @param {Object} options Config Options | ||
| * @returns {Promise<string | null>} Config file path or null if not found | ||
| */ | ||
| export async function postCssFindConfig(path: string, options: Options = {}) { | ||
ndelangen marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| const result = await config.lilconfig('postcss', withLoaders(options)).search(path); | ||
|
|
||
| return result ? result.filepath : null; | ||
| } | ||
|
|
||
| export { postCssLoadConfig }; | ||
|
|
||
| /** Handle PostCSS config loading with fallback mechanism */ | ||
ndelangen marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| export const loadPostCssConfigWithFallback = async (searchPath: string): Promise<boolean> => { | ||
| const configPath = await postCssFindConfig(searchPath); | ||
| if (!configPath) { | ||
| return true; | ||
| } | ||
|
|
||
| let error: any; | ||
|
|
||
| // First attempt: try loading config as-is | ||
| try { | ||
| await postCssLoadConfig({}, searchPath, { stopDir: getProjectRoot() }); | ||
| return true; // Success! | ||
| } catch (e: any) { | ||
| error = e; | ||
| } | ||
|
|
||
| // No config found is not an error we need to handle | ||
| if (error.message.includes('No PostCSS Config found')) { | ||
| return true; | ||
| } | ||
|
|
||
| // NextJS uses an incompatible format for PostCSS plugins, we make an attempt to fix it | ||
| if (error.message.includes('Invalid PostCSS Plugin found')) { | ||
| // Second attempt: try with modified config | ||
| try { | ||
| const originalContent = await readFile(configPath, 'utf8'); | ||
| const modifiedContent = originalContent.replace( | ||
| 'plugins: ["@tailwindcss/postcss"]', | ||
| 'plugins: { "@tailwindcss/postcss": {} }' | ||
| ); | ||
|
Comment on lines
+119
to
+122
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This approach will not work for
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not think we need it. I had to copy code from here: And i wanted to keep it close to the original code.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we don't support modifying YAML-based PostCSS files, we should clean up the code above (searching for YAML files, registering a YAML config loader,...). Then we can also actually remove the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You would rather not keep the code similar to where we got it from? Okay. Their code seems to prefer yaml config files, so our code skipping/not looking for them, might cause their code to find different files, if the user has many different ones, possibly on multiple levels. The replacement code, also does not "work" for JSON files, should I remove handling for those as well?
ndelangen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Write the modified content | ||
| await writeFile(configPath, modifiedContent, 'utf8'); | ||
|
|
||
| // Retry loading the config | ||
| await postCssLoadConfig({}, searchPath, { stopDir: getProjectRoot() }); | ||
| return true; // Success with modified config! | ||
| } catch (e: any) { | ||
| // We were unable to fix the config, so we throw an error | ||
| throw new IncompatiblePostCssConfigError({ error }); | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| }; | ||
ndelangen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Uh oh!
There was an error while loading. Please reload this page.