根据使用的 sass mixin,将来自 scss 源的结果 css 代码拆分为多个 css 文件

split resulting css code from scss source into multiple css files based on used sass mixin

我需要用不同的 "flavors" 或颜色变体编写一个 html5 模板。

我想要处理一个 scss 文件,但要渲染几个 css 文件。

假设我的 scss 入口点是 app.scss

恕我直言,理想的方法应该是这样的:

$flavors: (
    flavor-a: (
        background: white
    ),
    flavor-b: (
        background: grey
    )
);

@mixin flavor($name) {
    /* parser-rule-start */
    @content;
    /* parser-rule-end */
}

html {
    /* regular rule - valid for all flavors => goes to app.css */
    font-family: sans-serif;
    @each $name, $options in $flavors {
        @include flavor($name) {
            /* flavor-rule => goes to flavor-a.css / flavor-b.css */
            background: map-get($options, 'background');
        }
    }
}

所以我最终得到

我以前有过这个需求,并用多个入口文件解决了它,调用 mixins 进行着色等等。

但我不喜欢这种方法,因为在我为新组件编写 scss 代码后,我需要将大块的行从结构文件移动到 flavor-mixin,这在flavor-*.scss 入口文件。

ATM 我的构建看起来像 (gulp):

/**
 * compile/concat scss
 */
gulp.task('css', function () {

    const sassOptions = {
        outputStyle: "compressed",
        errorLogToConsole: true
    };

    const autoprefixerOptions = {
        browsersList: [
            "last 2 versions",
            "ie >= 11"
        ]
    };

    return gulp
        .src("src/scss/*.scss")
        .pipe(sourcemaps.init())
        .pipe(sass(sassOptions).on('error', makeErrorLogger('css')))
        .pipe(autoprefixer(autoprefixerOptions))
//      .pipe(splitFlavors()) <- the point i would need some magic
        .pipe(sourcemaps.write('.'))
        .pipe(gulp.dest("public/static/css"))
        .pipe(browserSync.stream({match: '**/*.css'}));
});

有人知道用于该目的的 gulp 插件吗?还是我必须自己编写代码?

更新 3

另一个领养替代品

let newSelector = selector.replace(/\s?\:\:flavor\-([^\s]+)/g, "").trim();

更新 2

需要采用css规则选择器中的替换

let newSelector = selector.replace(/\s?\:\:flavor\-([a-zA-Z0-9\-\_\s]+)/g, "").trim();

更新

通过使用修改后的选择器而不是属性,您可以继续在 "for-flavor" 块中使用嵌套规则。

因此将 mixin 调整为如下所示:

@mixin for-flavor($name) {
  ::flavor-#{$name} & {
    @content;
  }
}

和 gulp 任务:

const path = require("path");
const gulp = require("gulp");
const sourcemaps = require("gulp-sourcemaps");
const sass = require("gulp-sass");
const autoprefixer = require("gulp-autoprefixer");
const through = require("through2");
const postcss = require("gulp-postcss");

/**
 * compile/concat scss
 */
gulp.task('css', function () {

    const sassOptions = {
        outputStyle: "compressed",
        errorLogToConsole: true
    };

    const autoprefixerOptions = {
        browsersList: [
            "last 2 versions",
            "ie >= 11"
        ]
    };

    function addFlavorFiles() {
        return through.obj(function(file, encoding, callback) {
            /* @var file File */
            let content = file.contents.toString();
            let names = [];
            let matches = content.match(/\:\:flavor\-([^\s\{]+)/g);
            if (matches) {
                names = matches.map(match => match.replace(/\:\:flavor\-/, '').trim());
                // unique
                names = names.filter((el, index, arr) => {
                    return index === arr.indexOf(el);
                });
            }
            names.forEach(name => {
                let newFile = file.clone();
                newFile.contents = Buffer.concat([Buffer.from(`/*!flavor:${name}*/\n`, encoding), file.contents]);
                let filePath = path.parse(file.path);
                newFile.path = path.join(filePath.dir, `flavor-${name + filePath.ext}`);
                this.push(newFile);
            });
            callback(null, file);
        })
    }

    function filterFlavors(css, opts) {
        let flavor = null;
        if (css.nodes[0].type === "comment" && css.nodes[0].text.indexOf('!flavor:') === 0) {
            flavor = css.nodes[0].text.replace(/^\!flavor\:/, "").trim();
        }
        css.walkRules(rule => {
            let selector = rule.selector;
            if (/^\:\:flavor\-/.test(selector)) {
                // flavor rule
                if (flavor === null) {
                    // general file, all flavor rules must go...
                    rule.remove();
                } else {
                    let matches = selector.match(/\:\:flavor\-([a-zA-Z0-9\-\_]+)/);
                    let currentFlavor = matches[1];
                    if (flavor !== currentFlavor) {
                        // wrong flavor
                        rule.remove();
                    } else {
                        // keep rule but adjust selector
                        let newSelector = selector.replace(/^\:\:flavor\-([a-zA-Z0-9\-\_]+)/, "").trim();
                        rule.selector = newSelector;
                    }
                }
            } else if(flavor !== null) {
                // general rule but flavor file, so remove the rule
                rule.remove();
            }
        });
        css.walkRules(rule => {
            if (!rule.nodes || rule.nodes.length === 0) {
                rule.remove();
            }
        });
        css.walkAtRules(atRule => {
            if (!atRule.nodes  || atRule.nodes.length === 0) {
                atRule.remove();
            }
        });
        // optional: delete all font-face definitions from flavor file
        if (flavor !== null) {
            css.walkAtRules(atRule => {
                if (atRule.name === "font-face") {
                    atRule.remove();
                }
            });
        }
    }

    return gulp
        .src("src/scss/*.scss")
        .pipe(sourcemaps.init())
        .pipe(sass(sassOptions).on('error', makeErrorLogger('css')))
        .pipe(addFlavorFiles())
        .pipe(autoprefixer(autoprefixerOptions))
        .pipe(postcss([filterFlavors]))
        .pipe(sourcemaps.write('.'))
        .pipe(gulp.dest("public/static/css"))
});

原版POST

我设法按照我的预期解决了它

使用 sass mixin,添加解析规则:

@mixin for-flavor($name) {
  -flavor-start: unquote($name);
  @content;
  -flavor-end: unquote($name);
}

将其用于您的 css 声明

// you get the idea...
$flavors: (
  "lightblue": (
    background: lightblue,
  ),
  "pink": (
    background: pink,
  ),
  "dark": (
    background: black
  ),
);

#page-header {
  background: black;
  @each $name, $options in $flavors {
    @if map_has_key($options, 'background') {
      @include for-flavor($name) {
        background: map_get($options, 'background');
      }
    }
  }
  @include for-flavor("lightblue") {
    /* special rule for specific flavor */
    color: black;
  }
}

gulp

const path = require("path");
const gulp = require("gulp");
const sourcemaps = require("gulp-sourcemaps");
const sass = require("gulp-sass");
const autoprefixer = require("gulp-autoprefixer");
const through = require("through2");
const postcss = require("gulp-postcss");

/**
 * compile/concat scss
 */
gulp.task('css', function () {

    const sassOptions = {
        outputStyle: "compressed",
        errorLogToConsole: true
    };

    const autoprefixerOptions = {
        browsersList: [
            "last 2 versions",
            "ie >= 11"
        ]
    };

    function addFlavorFiles() {
        return through.obj(function(file, encoding, callback) {
            /* @var file File */
            let content = file.contents.toString();
            let names = [];
            let matches = content.match(/\-flavor\-start\:([^\;]+)/g);
            if (matches) {
                names = matches.map(match => match.replace(/\-flavor\-start\:/, '').trim());
            }
            names.forEach(name => {
                let newFile = file.clone();
                newFile.contents = Buffer.concat([Buffer.from(`/*!flavor:${name}*/\n`, encoding), file.contents]);
                let filePath = path.parse(file.path);
                newFile.path = path.join(filePath.dir, `flavor-${name + filePath.ext}`);
                this.push(newFile);
            });
            callback(null, file);
        })
    }

    function filterFlavors(css, opts) {
        let flavor = null;
        if (css.nodes[0].type === "comment" && css.nodes[0].text.indexOf('!flavor:') === 0) {
            flavor = css.nodes[0].text.replace(/^\!flavor\:/, "").trim();
        }
        let inFlavorBlock = "";
        css.walkDecls(decl => {
            let prop = decl.prop;
            let isControlProp = false;
            let value = decl.value.trim();
            if (prop === "-flavor-end") {
                inFlavorBlock = "";
                isControlProp = true;
            } else if (prop === "-flavor-start") {
                inFlavorBlock = value;
                isControlProp = true;
            }
            let isValid = ((inFlavorBlock === "" && flavor === null) || inFlavorBlock === flavor);
            if (isValid === false || isControlProp) {
                decl.remove();
            }
        });
        css.walkRules(rule => {
            if (!rule.nodes || rule.nodes.length === 0) {
                rule.remove();
            }
        });
        css.walkAtRules(atRule => {
            if (!atRule.nodes  || atRule.nodes.length === 0) {
                atRule.remove();
            }
        });
    }

    return gulp
        .src("src/scss/*.scss")
        .pipe(sourcemaps.init())
        .pipe(sass(sassOptions))
        .pipe(addFlavorFiles())
        .pipe(autoprefixer(autoprefixerOptions))
        .pipe(postcss([filterFlavors]))
        .pipe(sourcemaps.write('.'))
        .pipe(gulp.dest("public/static/css"))
});

唯一剩下的问题是源映射没有被过滤...