Skip to content

Commit

Permalink
Merge pull request #247 from marp-team/root-increasing-specificity
Browse files Browse the repository at this point in the history
Add PostCSS root increasing specificity plugin
  • Loading branch information
yhatt authored May 17, 2020
2 parents 22edec5 + 7bef339 commit 09034b3
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 23 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Fixed

- Slide size defined in `:root` selector does not reflect to the theme instance ([#244](https://github.com/marp-team/marpit/issues/244), [#246](https://github.com/marp-team/marpit/pull/246))
- `:root` selector in Marpit is not following the specification of specificity ([#245](https://github.com/marp-team/marpit/issues/245), [#247](https://github.com/marp-team/marpit/pull/247))

## v1.6.0 - 2020-05-09

Expand Down
26 changes: 26 additions & 0 deletions src/postcss/root/increasing_specificity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/** @module */
import postcss from 'postcss'

export const pseudoClass = ':marpit-root'

const matcher = new RegExp(`\\b${pseudoClass}\\b`, 'g')

/**
* Marpit PostCSS root increasing specificity plugin.
*
* Replace `:marpit-root` pseudo-class selector into `:not(\9)`, to increase
* specificity.
*
* @alias module:postcss/root/increasing_specificity
*/
const plugin = postcss.plugin(
'marpit-postcss-root-increasing-specificity',
() => (css) =>
css.walkRules((rule) => {
rule.selectors = rule.selectors.map((selector) =>
selector.replace(matcher, ':not(\\9)')
)
})
)

export default plugin
20 changes: 11 additions & 9 deletions src/postcss/root/replace.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,18 @@ import postcss from 'postcss'
*
* @alias module:postcss/root/replace
*/
const plugin = postcss.plugin('marpit-postcss-root-replace', () => (css) =>
css.walkRules((rule) => {
// Replace `:root` pseudo-class selectors into `section`
rule.selectors = rule.selectors.map((selector) =>
selector.replace(
/(^|[\s>+~(])(?:section)?:root\b/g,
(_, s) => `${s}section`
const plugin = postcss.plugin(
'marpit-postcss-root-replace',
({ pseudoClass } = {}) => (css) =>
css.walkRules((rule) => {
// Replace `:root` pseudo-class selectors into `section`
rule.selectors = rule.selectors.map((selector) =>
selector.replace(
/(^|[\s>+~(])(?:section)?:root\b/g,
(_, s) => `${s}section${pseudoClass || ''}`
)
)
)
})
})
)

export default plugin
28 changes: 17 additions & 11 deletions src/postcss/section_size.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,25 @@ import postcss from 'postcss'
*/
const plugin = postcss.plugin(
'marpit-postcss-section-size',
() => (css, ret) => {
ret.marpitSectionSize = ret.marpitSectionSize || {}
({ pseudoClass } = {}) => {
const rootSectionMatcher = new RegExp(
`^(?:section|\\*?:root)${pseudoClass ? `(?:${pseudoClass})?` : ''}$`
)

css.walkRules((rule) => {
if (rule.selectors.includes('section')) {
rule.walkDecls(/^(width|height)$/, (decl) => {
const { prop } = decl
const value = decl.value.trim()
return (css, ret) => {
ret.marpitSectionSize = ret.marpitSectionSize || {}

ret.marpitSectionSize[prop] = value
})
}
})
css.walkRules((rule) => {
if (rule.selectors.some((s) => rootSectionMatcher.test(s))) {
rule.walkDecls(/^(width|height)$/, (decl) => {
const { prop } = decl
const value = decl.value.trim()

ret.marpitSectionSize[prop] = value
})
}
})
}
}
)

Expand Down
2 changes: 0 additions & 2 deletions src/theme.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import postcss from 'postcss'
import postcssImportParse from './postcss/import/parse'
import postcssMeta from './postcss/meta'
import postcssRootReplace from './postcss/root/replace'
import postcssSectionSize from './postcss/section_size'
import skipThemeValidationSymbol from './theme/symbol'

Expand Down Expand Up @@ -100,7 +99,6 @@ class Theme {

const { css, result } = postcss([
postcssMeta({ metaType }),
postcssRootReplace,
postcssSectionSize,
postcssImportParse,
]).process(cssString)
Expand Down
6 changes: 5 additions & 1 deletion src/theme_set.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import postcssPrintable, {
import postcssPseudoPrepend from './postcss/pseudo_selector/prepend'
import postcssPseudoReplace from './postcss/pseudo_selector/replace'
import postcssRootFontSize from './postcss/root/font_size'
import postcssRootIncreasingSpecificity, {
pseudoClass,
} from './postcss/root/increasing_specificity'
import postcssRem from './postcss/root/rem'
import postcssRootReplace from './postcss/root/replace'
import Theme from './theme'
Expand Down Expand Up @@ -270,8 +273,9 @@ class ThemeSet {
theme !== scaffold && ((css) => css.first.before(scaffold.css)),
opts.inlineSVG && postcssAdvancedBackground,
postcssPagination,
postcssRootReplace,
postcssRootReplace({ pseudoClass }),
postcssRootFontSize,
postcssRootIncreasingSpecificity,
postcssPseudoPrepend,
postcssPseudoReplace(opts.containers, slideElements),
opts.printable && postcssPrintablePostProcess,
Expand Down
21 changes: 21 additions & 0 deletions test/postcss/root/increasing_specificity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import postcss from 'postcss'
import increasingSpecificity, {
pseudoClass,
} from '../../../src/postcss/root/increasing_specificity'
import replace from '../../../src/postcss/root/replace'

describe('Marpit PostCSS root increasing specificity plugin', () => {
const run = (input) =>
postcss([replace({ pseudoClass }), increasingSpecificity]).process(input, {
from: undefined,
})

it('replaces specific pseudo-class into ":not(\\9)" to increase specificity', () => {
expect(run(`section${pseudoClass} {}`).css).toBe('section:not(\\9) {}')

// With replaced :root selector via root replace plugin
expect(run(`:root {}`).css).toBe('section:not(\\9) {}')
expect(run(`section :root {}`).css).toBe('section section:not(\\9) {}')
expect(run(`:root.klass div {}`).css).toBe('section:not(\\9).klass div {}')
})
})

0 comments on commit 09034b3

Please sign in to comment.