gulp sass: merge folders with file inheritance - css

I have a folder structure like this (simplified):
|-project1
|--_file1.scss
|--_file2.scss
|--_file3.scss
|
|-project2
|--_file2.scss
|
|-css
|--project1.css
|--project2.css
I am looking for a way to compile the sass files with inheritance. The idea behind this is, that I have a base project (project1) and project 2 only contains those files that need to be changed.
So upon compilation gulp should render 2 css files:
project1.css
This contains only the files from project1/scss/ folder
project2.css
This one should contain file1 and file3 from project 1 and file2 from project 2.
Is this possible? What modules would be needed?
Thank you

Here is something that should work for you. I note that you have all and only partials in your project folders, i.e. _file1.scss, _file2.scss, etc. You will have to have at least one file that is not a partial that imports those partials for sass to work.
const gulp = require('gulp');
const fs = require('fs');
const path = require('path');
const filter = require('gulp-filter');
const sass = require('gulp-sass');
const concat = require('gulp-concat');
const addsrc = require('gulp-add-src');
// const glob = require("glob");
const sources = ['project1', 'project2', 'project3'];
// could glob your sourceFolders with something like
// const sources = glob.sync("project*");
const filterSources = Object.keys(sources);
function isUnique(file, index) {
console.log(path.basename(file.path)); // file1.scss, file2.scss, etc. all from project1
baseName = path.basename(file.path);
folder = sources[index]; // project
// does that basename exist in thecurrent project (sources[index] )
return !fs.existsSync(folder + path.sep + baseName);
}
gulp.task('default', function () {
// loop through all the project folders
filterSources.forEach(function (project, index) {
const f = filter(function (file) {
return isUnique(file, index);
});
// always using project1 files as basis
const stream = gulp.src('./project1/*.scss')
// filter out from the source stream (project1 files) any files that appear in the current project directory
.pipe(f)
// add all files from the current project directory, i.e., project2, project3, etc.
.pipe(addsrc.append('./' + sources[index] + '/*.scss'))
.pipe(sass().on('error', sass.logError))
// give the concat the filename of the current project
.pipe(concat(sources[index] + '.css'))
.pipe(gulp.dest('css'));
return stream;
});
This works for any number of project folders, just make sure that your non-partial scss file that imports the others is not one that is filtered out.
But it looks like you don't really want any partials anyway since you want to concat all files in each project so remove those leading underscores from each file name.

Related

Gulp Task: CleanCSS and Paths

I've created a task with Gulp that is supposed to:
Join multiple CSS files;
Minify + remove unnecessary CSS;
Fix paths for url() directives and others;
Generate source maps;
My current code for this is:
var gulp = require("gulp"),
concat = require("gulp-concat"),
cleanCSS = require("gulp-clean-css"),
sourcemaps = require("gulp-sourcemaps");
var styleList = [
"Resources/Include/ionicons/css/ionicons.css",
"Resources/base.css",
"Resources/extra.css",
];
gulp.task("deploy-css", function() {
gulp.src(styleList)
.pipe(sourcemaps.init())
.pipe(concat("style.min.css"))
.pipe(cleanCSS({
debug: true,
compatibility: "ie8",
keepSpecialComments : 0,
target: "Resources/",
relativeTo: "Resources/"
})
)
.pipe(sourcemaps.write())
.pipe(gulp.dest("Resources/"))
});
url() path example, taken from file Resources/Include/ionicons/css/ionicons.css:
#font-face { font-family: "Ionicons"; src: url("../fonts/ionicons.eot?v=2.0.0");
This is my current file structure:
./Resources/style.min.css // -> Final processed file
./Resources/base.css
./Resources/extras.css
./Resources/Include/ // -> Original CSS files with URL (installed via Bower)
Test folder: https://dl.dropboxusercontent.com/u/2333896/gulp-path-test.zip (install and then run with gulp deploy-css).
Almost everything works as expected, except for when CSS files include references to images or fonts using the url() option. After running the task (and style.min.css created) those references are broken - no change was made to the paths found on the original CSS files.
Isn't cleanCSS supposed to check where the referenced files are and fix the paths automatically? Aren't the options target and relativeTo used to control that?
How can I fix this? Thank you.
I managed to fix the issue, my main problems were a misplaced concat operation breaking gulp-clean-css rebase feature and wrong target and relativeTo options. Apparently I didn't think much about the previous workflow.
var gulp = require("gulp"),
concat = require("gulp-concat"),
cleanCSS = require("gulp-clean-css"),
sourcemaps = require("gulp-sourcemaps");
var styleList = [
"Resources/Include/ionicons/css/ionicons.css",
"Resources/base.css",
"Resources/extra.css",
"Resources/Include/teste/base.css"
];
gulp.task("deploy-css", function() {
gulp.src(styleList)
.pipe(sourcemaps.init())
.pipe(cleanCSS({
compatibility: "ie8",
keepSpecialComments : 0,
target: "Resources",
relativeTo: ""
})
)
.pipe(concat("style.min.css", {newLine: ""}))
.pipe(sourcemaps.write())
.pipe(gulp.dest("Resources"))
});
This new workflow works as:
Optimize all individual CSS files - including rebasing urls;
Contact individual optimized files into the final file - (note newLine: "" avoids line breaks in the file);
Write the file.

Ignore empty css files

I'm using gulp to generate css and min.css files from less files. However some less files has no css output (like variables.less that just define variables for import).
Is there a way to skip the empty css stream output for this files?
That's the current code that compile all less files:
var config = {
lessSrc: './Content/**/*.less'
};
gulp.task('css:lessmin', function () {
gulp.src(config.lessSrc, { base: '.' })
.pipe(less())
.pipe(gulp.dest('.'))
.pipe(minifyCss())
.pipe(rename({ suffix: '.min' }))
.pipe(gulp.dest('.'));
});
I could change the lessSrc to specific only the files I want to compile or to ignore the files I do not want to generate a css and min.css files but I have lots of situations like that, not just in the less files.
A good choice would be to use a filter, filtering the empty results of your globbing set:
var gulp = require('gulp');
var filter = require('gulp-filter');
var less = require('gulp-less');
gulp.task('less', function() {
return gulp.src('*.less')
.pipe(filter(function(file) {
return file.stat && file.contents.length;
}))
.pipe(less())
.pipe(gulp.dest('.'));
});
You pass virtual file objects to your stream, and each one of those contains contents as well as information on the file. The stat object tells you not only that is has been loaded, but also if it's a file. The contents are a buffer. With the length you can check if there's actually any content in there. Note: this will not help you with whitespaces. The file has to be really empty.

Customize semantic-ui using Bower and Grunt

My project uses Bower to install deps and Grunt to build. My project tree looks like this
|
|-bower_components
| |
| |-jquery
| |-semantic
| |-...
|-Bower.json
|-Gruntfile.js
|-public
| |
| |-css // Compiled, concatenated and minified semantic-ui
| |-js // and other libs should be here
|-...
|-etc..
Is it possible to build custom semantic-ui (ie customize fonts, colors, remove unused components) using Grunt (or maybe using Gulp called from Grunt)?
Where to place semantic theme config and overrides files?
It's not difficulty to use grunt to build semantic-ui. I don't know about bower, but this is how I did it.
Install grunt-contrib-less.
Create a new directory somewhere in your project, e.g. '/less/semantic'. Copy 'site' directory from your semantic packagea, i.e. 'bower_components/semantic/src/site' to the new directory. All your overrides will be done here.
Create a config.json file in '/less/semantic' to configure what components you want to be included in your build. The file content will be something like this:
{
"elements": ["button", "divider"],
"collections": ["form"],
"modules": ["checkbox"]
}
Add following to your gruntFile.js file:
var fs = require('fs');
// Defines files property for less task
var getSemanticFiles = function() {
var files = {};
var config = JSON.parse(fs.readFileSync('less/semantic/config.json'));
var srcDir = 'bower_components/semantic/definitions/';
var outputDir = 'less/semantic/output/';
for (var type in config) {
config[type].forEach(function(ele) {
files[outputDir + type + '.' + ele + '.output'] = [srcDir + type + '/' + ele + '.less'];
});
}
return files;
};
Configure less task as following:
less: {
semantic: {
options: { compile: true }
files: getSemanticFiels()
},
dist: {
options: { compile: true }
files: { 'public/css/semantic.css': ['less/semantic/output/*'] }
}
}
Edit theme.config in 'bower_components/semantic/src', change #siteFoler to '../../../less/site/', and make any additional changes as needed per semantic document.
You run grunt less:semantic to compile all needed components, and then run less:dist to put them into a single css file.
Of course you can configure a watch task to automate the process. Then every time you make a change, the css will be automaticly re-built.
I am sure someone will build a grunt build to semantic one day, but for now, I just use this to call all the gulp commands using grunt. https://github.com/sindresorhus/grunt-shell. Just make sure you are calling the gulp build task and not the default gulp task. It has a watch task that will cause grunt to not finish the shell task.

How do I process multiple files as templates with yeoman generator?

I'm working on a custom generator that I originally wrote with grunt-init. One difference I'm noticing is grunt-init automatically processes all the files in root as templates but with yeoman generator you have to do this using .template(). I'm familiar with how to process any individual file with .template() but is it possible to process an entire directory?
This issue is an annoying one. I came across this when I used yeoman for the first time. I think the below code snippet can help you.
this.directory('scripts', 'scripts');// script is folder name
Looking at the Yeoman generator code, there doesn't seem to be a built in way to do this. The way I solved this was to copy some of the code from the built in generator code and modify it for my needs. I determine if a file is a template based on the _ prefix convention since I want to rename the files to exclude that prefix, but you could just treat every file as a template and it would work fine. This will copy all of the files in the directory, so what I also did is to exclude the .DS_STORE files that you find by default on OSX, but since that is a specific case I didn't include that here.
require('path');
MyGenerator.prototype._processDirectory = function(source, destination) {
var root = this.isPathAbsolute(source) ? source : path.join(this.sourceRoot(), source);
var files = this.expandFiles('**', { dot: true, cwd: root });
for (var i = 0; i < files.length; i++) {
var f = files[i];
var src = path.join(root, f);
if(path.basename(f).indexOf('_') == 0){
var dest = path.join(destination, path.dirname(f), path.basename(f).replace(/^_/, ''));
this.template(src, dest);
}
else{
var dest = path.join(destination, f);
this.copy(src, dest);
}
}
};
Yeoman uses mem-fs-editor, which has support for glob patterns. However the documentation is not very clear, and you may miss that point. Here is the documentation of copyTpl, that says it accepts the same options as copy. So since copy has suppport for glob patterns, copyTpl too.
At any point on your yeoman generator you can do:
const from = 'myFolder/**.js'
const to = 'project/'
this
.fs
.copyTpl(
this.templatePath(from),
this.destinationPath(to),
this.props, {interpolate: /<%=([\s\S]+?)%>/g}
);
},
Not that, if you are using a glob pattern the destination path should be a folder.

Dynamic mapping for destinations in grunt.js

I have a project with several sub folders that contain JavaScript files I want to concatenate. what would be the right way to configure them?
eg.
source: /modules/$modulename/js/*.js (several files)
dest: /modules/$modulename/js/compiled.js
So what I want to do is to compile js-files of an unknown/unconfigured count of subfolders ($modulename) into one file per subfolder.
Is this possible?
The following function (built after hereandnow78's instructions) does the job:
grunt.registerTask('preparemodulejs', 'iterates over all module directories and compiles modules js files', function() {
// read all subdirectories from your modules folder
grunt.file.expand('./modules/*').forEach(function(dir){
// get the current concat config
var concat = grunt.config.get('concat') || {};
// set the config for this modulename-directory
concat[dir] = {
src: [dir + '/js/*.js', '!' + dir + '/js/compiled.js'],
dest: dir + '/js/compiled.js'
};
// save the new concat config
grunt.config.set('concat', concat);
});
});
after that i put preparemodulejs before the concat job in my default configuration.
you will probably need to code your own task, where you iterate over your subfolders, and dynamically append to your concat configuration.
grunt.registerTask("your-task-name", "your description", function() {
// read all subdirectories from your modules folder
grunt.file.expand("./modules/*").forEach(function (dir) {
// get the current concat config
var concat = grunt.config.get('concat') || {};
// set the config for this modulename-directory
concat[dir] = {
src: ['/modules/' + dir + '/js/*.js', '!/modules/' + dir + '/js/compiled.js'],
dest: '/modules/' + dir + '/js/compiled.js'
};
// save the new concat configuration
grunt.config.set('concat', concat);
});
// when finished run the concatinations
grunt.task.run('concat');
});
run this with:
$ grunt your-task-name
this code is untested, but i think it should do your job.
HINT: you can put this code into an external file and include in your gruntfile if you want to keep your gruntfile small, e.g. put this into a file inside a tasks-directory:
module.exports = function(grunt) {
grunt.registerTask("your-task-name", "your description", function() {
...
});
};
and load in in your gruntfile:
grunt.loadTasks("./tasks");

Resources