Skip to content

css preprocessor plugins #388

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 5, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ The generated project has dependencies that require **Node 4 or greater**.
* [Deploying the App via GitHub Pages](#deploying-the-app-via-github-pages)
* [Support for offline applications](#support-for-offline-applications)
* [Commands autocompletion](#commands-autocompletion)
* [CSS preprocessor integration](#css-preprocessor-integration)
* [Known Issues](#known-issues)

## Installation
Expand Down Expand Up @@ -249,6 +250,18 @@ ng completion >> ~/.bash_profile
source ~/.bash_profile
```


### CSS Preprocessor integration

We support all major CSS preprocessors:
- sass (node-sass)
- less (less)
- compass (compass-importer + node-sass)
- stylus (stylus)

To use one just install for example `npm install node-sass` and rename `.css` files in your project to `.scss` or `.sass`. They will be compiled automatically.


## Known issues

This project is currently a prototype so there are many known issues. Just to mention a few:
Expand Down
59 changes: 59 additions & 0 deletions lib/broccoli/angular-broccoli-compass.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/* jshint node: true, esversion: 6 */
'use strict';

const requireOrNull = require('./require-or-null');
const Plugin = require('broccoli-caching-writer');
const fse = require('fs-extra');
const path = require('path');
const Funnel = require('broccoli-funnel');

let sass = requireOrNull('node-sass');
let compass = requireOrNull('compass');
if (!sass || !compass) {
sass = requireOrNull(`${process.env.PROJECT_ROOT}/node_modules/node-sass`);
compass = requireOrNull(`${process.env.PROJECT_ROOT}/node_modules/compass-importer`);
}

class CompassPlugin extends Plugin {
constructor(inputNodes, options) {
super(inputNodes, {});

options = options || {};
Plugin.call(this, inputNodes, {
cacheInclude: [/\.scss$/, /\.sass$/]
});
this.options = options;
}

build() {
this.listEntries().forEach(e => {
let fileName = path.resolve(e.basePath, e.relativePath);
this.compile(fileName, this.inputPaths[0], this.outputPath);
});
}

compile(fileName, inputPath, outputPath) {
let sassOptions = {
file: path.normalize(fileName),
includePaths: this.inputPaths,
data: '@import "compass"; .transition { @include transition(all); }',
importer: compass
};

let result = sass.renderSync(sassOptions);
let filePath = fileName.replace(inputPath, outputPath).replace(/\.s[ac]ss$/, '.css');

fse.outputFileSync(filePath, result.css, 'utf8');
}
}

exports.makeBroccoliTree = (sourceDir) => {
if (sass && compass) {
let compassSrcTree = new Funnel(sourceDir, {
include: ['**/*.scss', '**/*.sass'],
allowEmpty: true
});

return new CompassPlugin([compassSrcTree]);
}
};
54 changes: 54 additions & 0 deletions lib/broccoli/angular-broccoli-less.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/* jshint node: true, esversion: 6 */
'use strict';

const requireOrNull = require('./require-or-null');
const Plugin = require('broccoli-caching-writer');
const fs = require('fs');
const fse = require('fs-extra');
const path = require('path');
const Funnel = require('broccoli-funnel');

let less = requireOrNull('less');
if (!less) {
less = requireOrNull(`${process.env.PROJECT_ROOT}/node_modules/less`);
}

class LESSPlugin extends Plugin {
constructor(inputNodes, options) {
super(inputNodes, {});

options = options || {};
Plugin.call(this, inputNodes, {
cacheInclude: [/\.less$/]
});
this.options = options;
}

build() {
return Promise.all(this.listEntries().map(e => {
let fileName = path.resolve(e.basePath, e.relativePath);
return this.compile(fileName, this.inputPaths[0], this.outputPath);
}));
}

compile(fileName, inputPath, outputPath) {
let content = fs.readFileSync(fileName, 'utf8');

return less.render(content)
.then(output => {
let filePath = fileName.replace(inputPath, outputPath).replace(/\.less$/, '.css');
fse.outputFileSync(filePath, output.css, 'utf8');
});
}
}

exports.makeBroccoliTree = (sourceDir) => {
if (less) {
let lessSrcTree = new Funnel(sourceDir, {
include: ['**/*.less'],
allowEmpty: true
});

return new LESSPlugin([lessSrcTree]);
}
};
61 changes: 61 additions & 0 deletions lib/broccoli/angular-broccoli-sass.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/* jshint node: true, esversion: 6 */
'use strict';

const requireOrNull = require('./require-or-null');
const Plugin = require('broccoli-caching-writer');
const fse = require('fs-extra');
const path = require('path');
const Funnel = require('broccoli-funnel');

let sass = requireOrNull('node-sass');
if (!sass) {
sass = requireOrNull(`${process.env.PROJECT_ROOT}/node_modules/node-sass`);
}

class SASSPlugin extends Plugin {
constructor(inputNodes, options) {
super(inputNodes, {});

options = options || {};
Plugin.call(this, inputNodes, {
cacheInclude: [/\.scss$/, /\.sass$/]
});
this.options = options;
}

build() {
this.listEntries().forEach(e => {
let fileName = path.resolve(e.basePath, e.relativePath);
this.compile(fileName, this.inputPaths[0], this.outputPath);
});
}

compile(fileName, inputPath, outputPath) {
let sassOptions = {
file: path.normalize(fileName),
includePaths: this.inputPaths
};

let result = sass.renderSync(sassOptions);
let filePath = fileName.replace(inputPath, outputPath).replace(/\.s[ac]ss$/, '.css');

fse.outputFileSync(filePath, result.css, 'utf8');
}
}

exports.makeBroccoliTree = (sourceDir) => {
// include sass support only if compass-importer is not installed
let compass = requireOrNull('compass-importer');
if (!compass) {
compass = requireOrNull(`${process.env.PROJECT_ROOT}/node_modules/compass-importer`);
}

if (sass && !compass) {
let sassSrcTree = new Funnel(sourceDir, {
include: ['**/*.sass', '**/*.scss'],
allowEmpty: true
});

return new SASSPlugin([sassSrcTree]);
}
};
53 changes: 53 additions & 0 deletions lib/broccoli/angular-broccoli-stylus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/* jshint node: true, esversion: 6 */
'use strict';

const requireOrNull = require('./require-or-null');
const Plugin = require('broccoli-caching-writer');
const fs = require('fs');
const fse = require('fs-extra');
const path = require('path');
const Funnel = require('broccoli-funnel');

let stylus = requireOrNull('stylus');
if (!stylus) {
stylus = requireOrNull(`${process.env.PROJECT_ROOT}/node_modules/stylus`);
}

class StylusPlugin extends Plugin {
constructor(inputNodes, options) {
super(inputNodes, {});

options = options || {};
Plugin.call(this, inputNodes, {
cacheInclude: [/\.styl$/]
});
this.options = options;
}

build() {
return Promise.all(this.listEntries().map(e => {
let fileName = path.resolve(e.basePath, e.relativePath);
return this.compile(fileName, this.inputPaths[0], this.outputPath);
}));
}

compile(fileName, inputPath, outputPath) {
let content = fs.readFileSync(fileName, 'utf8');

return stylus.render(content, { filename: path.basename(fileName) }, function(err, css) {
let filePath = fileName.replace(inputPath, outputPath).replace(/\.styl$/, '.css');
fse.outputFileSync(filePath, css, 'utf8');
});
}
}

exports.makeBroccoliTree = (sourceDir) => {
if (stylus) {
let stylusSrcTree = new Funnel(sourceDir, {
include: ['**/*.styl'],
allowEmpty: true
});

return new StylusPlugin([stylusSrcTree]);
}
};
19 changes: 18 additions & 1 deletion lib/broccoli/angular2-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ Angular2App.prototype.toTree = function () {
buildTrees.push(excludeDotfilesTree);
}

buildTrees = buildTrees.concat(
require('./angular-broccoli-sass').makeBroccoliTree(sourceDir),
require('./angular-broccoli-less').makeBroccoliTree(sourceDir),
require('./angular-broccoli-stylus').makeBroccoliTree(sourceDir),
require('./angular-broccoli-compass').makeBroccoliTree(sourceDir)
).filter(x => !!x);

var merged = mergeTrees(buildTrees, { overwrite: true });

return mergeTrees([merged, new SwManifest([merged])]);
Expand All @@ -63,6 +70,9 @@ Angular2App.prototype.toTree = function () {
*/
Angular2App.prototype._initProject = function () {
this.project = Project.closestSync(process.cwd());

// project root dir env used on angular-cli side for including packages from project
process.env.PROJECT_ROOT = process.env.PROJECT_ROOT || this.project.root;
};

/**
Expand Down Expand Up @@ -304,7 +314,14 @@ Angular2App.prototype._getVendorNpmTree = function () {
Angular2App.prototype._getAssetsTree = function () {
return new Funnel(sourceDir, {
include: ['**/*.*'],
exclude: ['**/*.ts', '**/*.js'],
exclude: [
'**/*.ts',
'**/*.js',
'**/*.scss',
'**/*.sass',
'**/*.less',
'**/*.styl'
],
allowEmpty: true
});
};
Expand Down
10 changes: 10 additions & 0 deletions lib/broccoli/require-or-null.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* jshint node: true, esversion: 6 */
'use strict';

module.exports = function(name) {
try {
return require(name);
} catch (e) {
return null;
}
};
Loading