Skip to content
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

feat: generate overrides.css on generate #11735

Merged
merged 3 commits into from
Oct 18, 2023
Merged
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
7 changes: 7 additions & 0 deletions packages/preset-umi/fixtures/overrides/less/index.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@import "foo/foo.less";

@red: green;
.a {
aaa: @red;
bbb: @primary-color;
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Empty file.
Empty file.
Empty file.
3 changes: 3 additions & 0 deletions packages/preset-umi/fixtures/overrides/normal/foo/bar/hoo.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.hoo {
color: red;
}
2 changes: 2 additions & 0 deletions packages/preset-umi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@
"enhanced-resolve": "5.9.3",
"fast-glob": "3.2.12",
"html-webpack-plugin": "5.5.0",
"less-plugin-resolve": "1.0.0",
"path-to-regexp": "1.7.0",
"postcss": "^8.4.21",
"postcss-prefix-selector": "1.16.0",
"react": "18.1.0",
"react-dom": "18.1.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import assert from 'assert';
import fs from 'fs';
import { join } from 'path';
import { compileLess } from './compileLess';

const fixturesDir = join(__dirname, '../../../fixtures');

// 在 jest 下跑会出错,所以只能手动跑来验证了
// test('normal', async () => {
(async () => {
const filePath = join(fixturesDir, 'overrides/less/index.less');
const modifyVars = {
'primary-color': '#1DA57A',
};
const alias = {
barbar: join(filePath, '../node_modules/bar'),
};
const result = await compileLess(
fs.readFileSync(filePath, 'utf-8'),
filePath,
modifyVars,
alias,
);
assert(
result.includes(
`
.bar {
color: red;
}
.foo {
color: red;
}
.a {
aaa: green;
bbb: #1DA57A;
}
`.trim(),
),
);
})().catch((e) => {
console.error(e);
});
// });
20 changes: 20 additions & 0 deletions packages/preset-umi/src/features/overrides/compileLess.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import less from '@umijs/bundler-utils/compiled/less';

export async function compileLess(
lessContent: string,
filePath: string,
modifyVars: Record<string, string> = {},
alias: Record<string, string> = {},
) {
const result = await less.render(lessContent, {
filename: filePath,
plugins: [
new (require('less-plugin-resolve') as any)({
aliases: alias,
}),
],
javascriptEnabled: true,
modifyVars,
});
return result.css;
}
70 changes: 34 additions & 36 deletions packages/preset-umi/src/features/overrides/overrides.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,48 @@
import { winPath } from '@umijs/utils';
import { existsSync } from 'fs';
import { existsSync, readFileSync } from 'fs';
import { join } from 'path';
import { expandCSSPaths } from '../../commands/dev/watch';
import type { IApi } from '../../types';
import { compileLess } from './compileLess';
import { transform } from './transform';

export function getOverridesCSS(absSrcPath: string) {
return expandCSSPaths(join(absSrcPath, 'overrides')).find(existsSync);
}

export default (api: IApi) => {
api.modifyConfig((memo) => {
if (getOverridesCSS(api.paths.absSrcPath)) {
memo.extraPostCSSPlugins ??= [];
memo.extraPostCSSPlugins.push(
// prefix #root for overrides.{ext} style file, to make sure selector priority is higher than async chunk style
require('postcss-prefix-selector')({
// why not #root?
// antd will insert dom into body, prefix #root will not works for that
prefix: 'body',
transform(
_p: string,
selector: string,
prefixedSelector: string,
filePath: string,
) {
const isOverridesFile =
winPath(api.appData.overridesCSS[0]) === winPath(filePath);

if (isOverridesFile) {
if (selector === 'html') {
// special :first-child to promote html selector priority
return `html:first-child`;
} else if (/^body([\s+~>[:]|$)/.test(selector)) {
// use html parent to promote body selector priority
return `html ${selector}`;
}

return prefixedSelector;
}

return selector;
let cachedContent: string | null = null;
api.onGenerateFiles(async () => {
if (api.appData.overridesCSS.length) {
const filePath = api.appData.overridesCSS[0];
let content = readFileSync(filePath, 'utf-8');
if (content === cachedContent) return;
const isLess = filePath.endsWith('.less');
if (isLess) {
content = await compileLess(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个地方放弃了 scss / sass 是一个 Breaking change 。

content,
filePath,
{
...api.config.theme,
...api.config.lessLoader?.modifyVars,
},
}),
);
api.config.alias,
);
}
content = await transform(content, filePath);
api.writeTmpFile({
path: 'core/overrides.css',
content,
noPluginDir: true,
});
cachedContent = content;
}
});

return memo;
api.addEntryImports(() => {
return [
api.appData.overridesCSS.length && {
source: '@@/core/overrides.css',
},
].filter(Boolean);
});
};
79 changes: 79 additions & 0 deletions packages/preset-umi/src/features/overrides/transform.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { winPath } from '@umijs/utils';
import { join } from 'path';
import { transform } from './transform';

const fixturesDir = join(__dirname, '../../../fixtures');

test('transform selector', async () => {
const result = await transform(
`
html {}
.a {}
#b {}
div {}
@media (max-width: 100px) {
.b {}
}
`,
'/foo/bar/hoo.css',
);
expect(result).toEqual(`
html:first-child {}
body .a {}
body #b {}
body div {}
@media (max-width: 100px) {
body .b {}
}
`);
});

test('transform import', async () => {
const filePath = join(fixturesDir, 'overrides/normal/foo/bar/hoo.css');
const result = await transform(
`
@import "a";
@import "~b";
@import "./a.css";
@import './a.css';
@import "../a.css";
@import "../not-exists.css";
@import "a.css";
@import "child/a.css";
@import "child/not-exists.css";
`,
filePath,
);
expect(result.replace(new RegExp(`${winPath(fixturesDir)}`, 'g'), ''))
.toEqual(`
@import "a";
@import "~b";
@import "/overrides/normal/foo/bar/a.css";
@import "/overrides/normal/foo/bar/a.css";
@import "/overrides/normal/foo/a.css";
@import "../not-exists.css";
@import "/overrides/normal/foo/bar/a.css";
@import "/overrides/normal/foo/bar/child/a.css";
@import "child/not-exists.css";
`);
});

test('transform import with url', async () => {
const filePath = join(fixturesDir, 'overrides/normal/foo/bar/hoo.css');
const result = await transform(
`
@import url("a.css");
@import url('a.css');
@import url(a.css);
@import url(not-exists.css);
`,
filePath,
);
expect(result.replace(new RegExp(`${winPath(fixturesDir)}`, 'g'), ''))
.toEqual(`
@import "/overrides/normal/foo/bar/a.css";
@import "/overrides/normal/foo/bar/a.css";
@import "/overrides/normal/foo/bar/a.css";
@import url(not-exists.css);
`);
});
59 changes: 59 additions & 0 deletions packages/preset-umi/src/features/overrides/transform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { winPath } from '@umijs/utils';
import fs from 'fs';
import path from 'path';
import type { AtRule } from 'postcss';

export async function transform(cssContent: string, filePath: string) {
const importPlugin = {
postcssPlugin: 'importPlugin',
AtRule: {
import(atRule: AtRule) {
let origin = atRule.params;
// remove url() wrapper
origin = origin.replace(/^url\((.+)\)$/g, '$1');
// remove start ' or " and end ' or "
origin = origin.replace(/^'(.+)'$/g, '$1').replace(/^"(.+)"$/g, '$1');

// ~ 开头的肯定是从 node_modules 下查找
if (origin.startsWith('~')) return;
if (origin.startsWith('/')) return;

function getResolvedPath(origin: string) {
return winPath(path.resolve(path.dirname(filePath), origin));
}

// 已经包含 ./ 和 ../ 的场景,不需要额外处理
// CSS 会优先找相对路径,如果找不到,会再找 node_modules 下的
const resolvedPath = getResolvedPath(origin);
if (fs.existsSync(resolvedPath)) {
atRule.params = `"${resolvedPath}"`;
} else {
// Warn user if file not exists, but it should be existed
if (origin.startsWith('./') || origin.startsWith('../')) {
console.warn(`File does not exist: ${resolvedPath}`);
}
}
},
},
};
const selectorPlugin = require('postcss-prefix-selector')({
// why not #root?
// antd will insert dom into body, prefix #root will not work for that
prefix: 'body',
transform(_p: string, selector: string, prefixedSelector: string) {
if (selector === 'html') {
// special :first-child to promote html selector priority
return `html:first-child`;
} else if (/^body([\s+~>[:]|$)/.test(selector)) {
// use html parent to promote body selector priority
return `html ${selector}`;
}
return prefixedSelector;
},
});
const result = await require('postcss')([
selectorPlugin,
importPlugin,
]).process(cssContent, {});
return result.css;
}
7 changes: 1 addition & 6 deletions packages/preset-umi/src/features/tmpFiles/tmpFiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,12 +305,7 @@ declare module '*.txt' {
imports: importsToStr(
await api.applyPlugins({
key: 'addEntryImports',
initialValue: [
// append overrides.{ext} style file
api.appData.overridesCSS.length && {
source: api.appData.overridesCSS[0],
},
].filter(Boolean),
initialValue: [],
}),
).join('\n'),
basename: api.config.base,
Expand Down
Loading
Loading