Automatically Including Sass Files


#1

One of the great things about Angular2/Ionic2 compared with traditional web apps is the way it lets us organise our applications into well-structured, self-contained, re-usable components which can easily be moved around and reused: e.g.

ls app/pages/book-list/
  book-list.ts
  book-list.scss
  book-list.html
  book-list.icon.png

Being able to move thing around, re-name it, etc as the cobebase evolves is extremely powerful. Currently, though, the Sass files hinder this because they require that somewhere else we must maintain an enormous list of @import statements, with a line for each Sass file in our project:

@import "app/pages/authentication/authentication.scss";
@import "app/pages/authors/author-detail.scss";
@import "app/pages/authors/author-list.scss";
@import "app/pages/authors/author-thumbnail.scss";
@import "app/pages/books/book-detail.scss";
@import "app/pages/books/book-list.scss";
...

etc

This file just grows and grows as the project grows, adding no value to the project, simply adding a maintenance burden.

The responsibility of pulling together the project’s source files should, I believe, lie with the build system, rather than having to be a manual task performed by the developers, maintained in a text file.

I have modified my project’s build file to achieve this in a rather hacky way, but I would love to see this feature part of Ionic2 itself (I would be happy to contribute to this.) In the meantime, if anyone else would benefit from my hack, here it is:

The principle is to generate Sass files (one for each platform) in ./.tmp which @import every .scss or .css file in the project, starting with the ones in the theme directory.

In gulpfile.js, replace the ‘sass’ and ‘html’ tasks with:

var scss_generator = require('./sass_collector');
gulp.task('sass.collect', function () {
  var stream = require('merge-stream')();
  var flavours = ['ios', 'md'];
  for (var flavour of flavours) {
    stream.add(
      gulp.src(['app/theme/**/*.' + flavour + '.scss', 'app/**/*.*(scss|css)'])
        .pipe(scss_generator("app.generated." + flavour + ".scss"))
        .pipe(gulp.dest('.tmp')));
  }
  return stream;
});

gulp.task('sass', ['sass.collect'], function() {
  buildSass({
    src: '.tmp/app.generated.*.scss'
  });
});

gulp.task('html', function() {
  copyHTML({
    src: 'app/**/*.*(html|png|gif|jpg|jpeg|webp|svg)'
  });
});

And modify the gulpWatch expressions to:

  gulpWatch('app/**/*.(scss|css)', function(){ gulp.start('sass'); });
  gulpWatch('app/**/*.*(html|png|gif|jpg|jpeg|webp|svg)', function(){ gulp.start('html'); });

Then, in the same directory, add a sass_collector.js with the content:

var through = require('through2');
var gutil = require('gulp-util');

module.exports = function (destinationFilename) {
  var imports = '';

  var write = function (file, enc, callback) {
    if (file.path != "undefined") {
      imports = imports + '\n' + '@import "' + file.path + '";';
    }
    callback();
  };

  var flush = function (callback) {
    this.push(
      new gutil.File({
        base: __dirname,
        cwd: __dirname,
        path: __dirname + '/' + destinationFilename,
        contents: new Buffer(imports)
      }));
    callback();
  };

  return through.obj(write, flush);
};

Finally, modify the www/index.html so that the stylesheet links reference the generated css:

  <link ios-href="build/css/app.generated.ios.css" rel="stylesheet">
  <link md-href="build/css/app.generated.md.css" rel="stylesheet">

I would love to hear people’s thoughts on ways of improving this. Getting clean modularity of the application’s components is a really important goal.