Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/postcss-app-root-plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@modern-js/builder': patch
---

fix: resolve PostCSS plugins from the app root when needed
73 changes: 53 additions & 20 deletions packages/cli/builder/src/plugins/postcss.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,39 @@
import { createRequire } from 'node:module';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import { applyOptionsChain, isProd } from '@modern-js/utils';
import { type RsbuildPlugin, logger } from '@rsbuild/core';
import type { Options } from 'cssnano';
import { getCssSupport } from '../shared/getCssSupport';
import type { ToolsAutoprefixerConfig } from '../types';

const require = createRequire(import.meta.url);
const builderRequire = createRequire(import.meta.url);

const importPostcssPlugin = (name: string) =>
Promise.resolve(require(name)) as Promise<any>;
const createRootRequire = (rootPath: string) =>
createRequire(pathToFileURL(path.join(rootPath, 'package.json')).href);

export const loadPostcssPlugin = (name: string, appRootPath: string) => {
const resolvers = [
builderRequire,
createRootRequire(appRootPath),
createRootRequire(process.cwd()),
];

let firstError: unknown = null;

for (const resolveWith of resolvers) {
try {
return resolveWith(name);
} catch (error) {
firstError ??= error;
}
Comment thread
zllkjc marked this conversation as resolved.
}

throw firstError;
};

const importPostcssPlugin = (name: string, appRootPath: string) =>
Promise.resolve(loadPostcssPlugin(name, appRootPath)) as Promise<any>;

export interface PluginPostcssOptions {
autoprefixer?: ToolsAutoprefixerConfig;
Expand Down Expand Up @@ -54,29 +79,37 @@ export const pluginPostcss = (
};

const plugins = await Promise.all([
importPostcssPlugin('postcss-flexbugs-fixes'),
importPostcssPlugin('postcss-flexbugs-fixes', api.context.rootPath),
!cssSupport.customProperties &&
importPostcssPlugin('postcss-custom-properties'),
!cssSupport.initial && importPostcssPlugin('postcss-initial'),
!cssSupport.pageBreak && importPostcssPlugin('postcss-page-break'),
!cssSupport.fontVariant && importPostcssPlugin('postcss-font-variant'),
!cssSupport.mediaMinmax && importPostcssPlugin('postcss-media-minmax'),
importPostcssPlugin('postcss-nesting'),
importPostcssPlugin(
'postcss-custom-properties',
api.context.rootPath,
),
!cssSupport.initial &&
importPostcssPlugin('postcss-initial', api.context.rootPath),
!cssSupport.pageBreak &&
importPostcssPlugin('postcss-page-break', api.context.rootPath),
!cssSupport.fontVariant &&
importPostcssPlugin('postcss-font-variant', api.context.rootPath),
!cssSupport.mediaMinmax &&
importPostcssPlugin('postcss-media-minmax', api.context.rootPath),
importPostcssPlugin('postcss-nesting', api.context.rootPath),
enableCssMinify &&
importPostcssPlugin('cssnano').then(cssnano =>
importPostcssPlugin('cssnano', api.context.rootPath).then(cssnano =>
cssnano(cssnanoOptions),
),
// The last insert autoprefixer
importPostcssPlugin('autoprefixer').then(autoprefixerPlugin =>
autoprefixerPlugin(
applyOptionsChain(
{
flexbox: 'no-2009',
overrideBrowserslist: config.output.overrideBrowserslist!,
},
autoprefixer,
importPostcssPlugin('autoprefixer', api.context.rootPath).then(
autoprefixerPlugin =>
autoprefixerPlugin(
applyOptionsChain(
{
flexbox: 'no-2009',
overrideBrowserslist: config.output.overrideBrowserslist!,
},
autoprefixer,
),
),
),
),
]).then(results => results.filter(Boolean));

Expand Down
37 changes: 36 additions & 1 deletion packages/cli/builder/tests/postcssLegacy.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,45 @@
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { afterEach, describe, expect, it, rs } from '@rstest/core';
import { createBuilder } from '../src';
import { loadPostcssPlugin } from '../src/plugins/postcss';
import { matchRules, unwrapConfig } from './helper';

const tempDirs: string[] = [];

describe('plugin-postcssLegacy', () => {
afterEach(() => {
afterEach(async () => {
rs.unstubAllEnvs();
await Promise.all(
tempDirs.map(dir => rm(dir, { recursive: true, force: true })),
);
tempDirs.length = 0;
});

it('should resolve postcss plugin from app root', async () => {
const pluginName = 'postcss-app-root-plugin';
const appRoot = await mkdtemp(path.join(tmpdir(), 'builder-postcss-'));
tempDirs.push(appRoot);

const pluginDir = path.join(appRoot, 'node_modules', pluginName);
await mkdir(pluginDir, { recursive: true });
await writeFile(
path.join(appRoot, 'package.json'),
JSON.stringify({ name: 'app-root' }),
);
await writeFile(
path.join(pluginDir, 'package.json'),
JSON.stringify({ name: pluginName, main: 'index.js' }),
);
await writeFile(
path.join(pluginDir, 'index.js'),
"module.exports = { postcssPlugin: 'postcss-app-root-plugin' };",
);

expect(loadPostcssPlugin(pluginName, appRoot)).toEqual({
postcssPlugin: pluginName,
});
});

it('should register postcss plugin by browserslist', async () => {
Expand Down
Loading