postcss/import/replace.js

/** @module */
import postcssPlugin from '../../helpers/postcss_plugin'
import postcssImportParse from './parse'

/**
 * Marpit PostCSS import replace plugin.
 *
 * Replace parsed `@import` / `@import-theme` rules.
 *
 * Please see {@link module:postcss/import/parse} about the specification of
 * each syntax.
 *
 * @alias module:postcss/import/replace
 * @param {ThemeSet} themeSet ThemeSet instance.
 */
const plugin = (themeSet, importedThemes = []) =>
  postcssPlugin('marpit-postcss-import-replace', () => ({
    plugins: [
      postcssImportParse(),
      postcssPlugin(
        'marpit-postcss-import-replace-processor',
        () => (css, { postcss }) => {
          const prepends = []

          css.walk((node) => {
            const name = node.marpitImportParse

            if (name) {
              const theme = themeSet.get(name)

              if (theme) {
                if (importedThemes.includes(name))
                  throw new Error(
                    `Circular "${name}" theme import is detected.`
                  )

                const processed = postcss([
                  plugin(themeSet, [...importedThemes, name]),
                ]).process(theme.css)

                if (node.name === 'import') {
                  node.replaceWith(processed.root)
                } else {
                  node.remove()
                  prepends.unshift(processed.root)
                }
              }
            }
          })

          for (const root of prepends) css.first.before(root)
        }
      )(),
    ],
  }))

export default plugin