-
Notifications
You must be signed in to change notification settings - Fork 400
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add customProperties config to postcss-lwc-plugin (#349)
## Details This PR introduces `customProperties` config to the `postcss-lwc-plugin`. This new API would allow the compiler to do the inline transformation of the CSS custom properties on compat browsers (eg. IE11). ## Does this PR introduce a breaking change? * [ ] Yes * [X] No
- Loading branch information
Showing
14 changed files
with
346 additions
and
24 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
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
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
69 changes: 69 additions & 0 deletions
69
packages/postcss-plugin-lwc/src/__tests__/custom-property-transform.spec.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,69 @@ | ||
import { process } from './shared'; | ||
|
||
describe('var transform', () => { | ||
it('should handle single variable in declaration value', async () => { | ||
const { css } = await process('div { color: var(--lwc-color); }'); | ||
|
||
expect(css).toBe( | ||
'div[x-foo_tmpl] { color: $VAR(--lwc-color)$; }', | ||
); | ||
}); | ||
|
||
it('should handle default value', async () => { | ||
const { css } = await process('div { color: var(--lwc-color, black); }'); | ||
|
||
expect(css).toBe( | ||
'div[x-foo_tmpl] { color: $VAR(--lwc-color, black)$; }', | ||
); | ||
}); | ||
|
||
it('should handle variables with tails', async () => { | ||
const { css } = await process( | ||
'div { color: var(--lwc-color) important; }', | ||
); | ||
|
||
expect(css).toBe( | ||
'div[x-foo_tmpl] { color: $VAR(--lwc-color)$ important; }', | ||
); | ||
}); | ||
|
||
it('should handle multiple variables in a single declaration value', async () => { | ||
const { css } = await process( | ||
'div { color: var(--lwc-color), var(--lwc-other); }', | ||
); | ||
|
||
expect(css).toBe( | ||
'div[x-foo_tmpl] { color: $VAR(--lwc-color)$, $VAR(--lwc-other)$; }', | ||
); | ||
}); | ||
|
||
it('should handle function in default value', async () => { | ||
const { css } = await process( | ||
'div { border: var(--border, 1px solid rgba(0, 0, 0, 0.1)); }', | ||
); | ||
|
||
expect(css).toBe( | ||
'div[x-foo_tmpl] { border: $VAR(--border, 1px solid rgba(0, 0, 0, 0.1))$; }', | ||
); | ||
}); | ||
|
||
it('should handle multiple variable in a function', async () => { | ||
const { css } = await process( | ||
'div { background: linear-gradient(to top, var(--lwc-color), var(--lwc-other)); }', | ||
); | ||
|
||
expect(css).toBe( | ||
'div[x-foo_tmpl] { background: linear-gradient(to top, $VAR(--lwc-color)$, $VAR(--lwc-other)$); }', | ||
); | ||
}); | ||
|
||
it('should handle nested var', async () => { | ||
const { css } = await process( | ||
'div { background: var(--lwc-color, var(--lwc-other, black)); }', | ||
); | ||
|
||
expect(css).toBe( | ||
'div[x-foo_tmpl] { background: $VAR(--lwc-color, $VAR(--lwc-other, black)$)$; }', | ||
); | ||
}); | ||
}); |
35 changes: 35 additions & 0 deletions
35
packages/postcss-plugin-lwc/src/__tests__/custom-property-validate.spec.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,35 @@ | ||
import { process, FILE_NAME, DEFAULT_TAGNAME, DEFAULT_TOKEN } from './shared'; | ||
|
||
const NO_CUSTOM_PROPERTY_CONFIG = { | ||
tagName: DEFAULT_TAGNAME, | ||
token: DEFAULT_TOKEN, | ||
customProperties: { | ||
allowDefinition: false, | ||
}, | ||
}; | ||
|
||
it('should prevent definition of standard custom properties', () => { | ||
return expect( | ||
process('div { --bg-color: blue; }', NO_CUSTOM_PROPERTY_CONFIG), | ||
).rejects.toMatchObject({ | ||
message: expect.stringContaining( | ||
`Invalid definition of custom property "--bg-color"`, | ||
), | ||
file: FILE_NAME, | ||
line: 1, | ||
column: 7, | ||
}); | ||
}); | ||
|
||
it('should prevent definition of lwc-prefixed custom properties', () => { | ||
return expect( | ||
process('div { --lwc-bg-color: blue; }', NO_CUSTOM_PROPERTY_CONFIG), | ||
).rejects.toMatchObject({ | ||
message: expect.stringContaining( | ||
`Invalid definition of custom property "--lwc-bg-color"`, | ||
), | ||
file: FILE_NAME, | ||
line: 1, | ||
column: 7, | ||
}); | ||
}); |
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
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
95 changes: 95 additions & 0 deletions
95
packages/postcss-plugin-lwc/src/custom-properties/transform.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,95 @@ | ||
import { Declaration } from 'postcss'; | ||
|
||
import { VarTransformer } from '../config'; | ||
|
||
// Match on " var(" | ||
const VAR_FUNC_REGEX = /(^|[^\w-])var\(/; | ||
|
||
// Match on "<property-name>" and "<property-name>, <fallback-value>" | ||
const VAR_ARGUMENTS_REGEX = /[\f\n\r\t ]*([\w-]+)(?:[\f\n\r\t ]*,[\f\n\r\t ]*([\W\w]+))?/; | ||
|
||
/** | ||
* Returns the index of the matching closing parenthesis. If no matching parenthesis is found | ||
* the method returns -1. | ||
*/ | ||
function indexOfMatchingParenthesis(value: string, start: number): number { | ||
let i = start; | ||
|
||
// Counter keeping track of the function call nesting count. | ||
let nesting = 0; | ||
|
||
while (i < value.length) { | ||
const ch = value.charAt(i); | ||
|
||
// When the function arguments contains an open parenthesis, it means that the function | ||
// arguments contains nested function calls. | ||
// For example: `var(--min-width, calc(100% - 80px));` | ||
if (ch === '(') { | ||
nesting += 1; | ||
} | ||
|
||
if (ch === ')') { | ||
if (nesting === 0) { | ||
return i; | ||
} else { | ||
nesting -= 1; | ||
} | ||
} | ||
|
||
i += 1; | ||
} | ||
|
||
// Handle case where no matching closing parenthesis has been found. | ||
return -1; | ||
} | ||
|
||
function transform(decl: Declaration, transformer: VarTransformer, value: string): string { | ||
const varMatch = VAR_FUNC_REGEX.exec(value); | ||
|
||
// Early exit of the value doesn't contain any `var` function call | ||
if (varMatch === null) { | ||
return value; | ||
} | ||
|
||
const [, prefix] = varMatch; | ||
|
||
// Extract start and end location of the function call | ||
const varStart = varMatch.index; | ||
const varEnd = indexOfMatchingParenthesis(value, varStart + varMatch[0].length); | ||
|
||
if (varEnd === -1) { | ||
throw decl.error( | ||
`Missing closing ")" for "${value.slice(varStart)}"` | ||
); | ||
} | ||
|
||
// Extract function call arguments | ||
const varFunction = value.slice(varStart, varEnd + 1); | ||
const varArguments = value.slice(varStart + varMatch[0].length, varEnd); | ||
const varArgumentsMatch = VAR_ARGUMENTS_REGEX.exec(varArguments); | ||
|
||
if (varArgumentsMatch === null) { | ||
throw decl.error( | ||
`Invalid var function signature for "${varFunction}"` | ||
); | ||
} | ||
|
||
const [, name, fallback] = varArgumentsMatch; | ||
const transformationResult = transformer(name, fallback); | ||
|
||
if (typeof transformationResult !== 'string') { | ||
throw new TypeError(`Expected a string, but received instead "${typeof transformationResult}"`); | ||
} | ||
|
||
// Recursively calling transform to processed the remaining `var` function calls. | ||
const processed = value.slice(0, varStart); | ||
const toProcess = transformationResult + value.slice(varEnd + 1); | ||
const tail = transform(decl, transformer, toProcess); | ||
|
||
return processed + prefix + tail; | ||
} | ||
|
||
export default function(decl: Declaration, transformer: VarTransformer) { | ||
const { value } = decl; | ||
decl.value = transform(decl, transformer, value); | ||
} |
13 changes: 13 additions & 0 deletions
13
packages/postcss-plugin-lwc/src/custom-properties/validate.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,13 @@ | ||
import { Declaration } from 'postcss'; | ||
|
||
const CUSTOM_PROPERTY_IDENTIFIER = '--'; | ||
|
||
export default function validate(decl: Declaration): void { | ||
const { prop } = decl; | ||
|
||
if (prop.startsWith(CUSTOM_PROPERTY_IDENTIFIER)) { | ||
throw decl.error( | ||
`Invalid definition of custom property "${prop}".`, | ||
); | ||
} | ||
} |
Oops, something went wrong.