-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: generate overrides.css on generate (#11735)
* feat: generate overrides.css on generate * ci: fix * ci: fix
- Loading branch information
Showing
17 changed files
with
281 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
3 changes: 3 additions & 0 deletions
3
packages/preset-umi/fixtures/overrides/less/node_modules/bar/bar.less
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
4 changes: 4 additions & 0 deletions
4
packages/preset-umi/fixtures/overrides/less/node_modules/bar/package.json
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
5 changes: 5 additions & 0 deletions
5
packages/preset-umi/fixtures/overrides/less/node_modules/foo/foo.less
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
4 changes: 4 additions & 0 deletions
4
packages/preset-umi/fixtures/overrides/less/node_modules/foo/package.json
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Empty file.
Empty file.
Empty file.
3 changes: 3 additions & 0 deletions
3
packages/preset-umi/fixtures/overrides/normal/foo/bar/hoo.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.hoo { | ||
color: red; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
43 changes: 43 additions & 0 deletions
43
packages/preset-umi/src/features/overrides/compileLess.manullytest.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
// }); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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( | ||
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
79
packages/preset-umi/src/features/overrides/transform.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
`); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.