I'm currently using Polymer as my front end development framework. I love SASS.
Now I understand I can create a Sass file and import it like I normally would.
However, I've really gotten into the habit of using style tags within my web components.
Basically the workflow I am looking for is to be able to simply define a script tag within my Web Component maybe add type='sass; to it. Then have grunt go through and compile all of my SASS within those tags before outputting the files to my .tmp directory.
Is something like this achievable with something like Grunt or Gulp? If so what are the best modules to help me achieve this?
My implementation is based on a replacement of a tag inside the Polymer html file. I'm using gulp
but could be changed to use simply fs
.
The files structure should be as this example:
app-view
|- app-view.html
|- app-view.scss
app-view.html:
<dom-module id="app-view">
<template>
<style>
<!-- inject{scss} -->
</style>
</template>
</dom-module>
app-view.scss:
:host{
margin-top: 50px;
justify-content: center;
display: flex;
}
#container{
font-size: 12px;
h1{
font-size: 20px;
}
}
gulpfile.js:
var gulp = require('gulp');
var nodeSass = require('node-sass');
var path = require('path');
var fs = require('fs');
var map = require('map-stream');
var srcPath = 'src/';
var buildPath = 'build/';
var buildSrcPath = path.join(buildPath, 'target');
gulp.task('processComponents', function () {
return gulp.src([srcPath + '/components/**/*.html'])
.pipe(map(function (file, cb) {
var injectString = '<!-- inject{scss} -->';
// convert file buffer into a string
var contents = file.contents.toString();
if (contents.indexOf(injectString) >= 0) {
//Getting scss
var scssFile = file.path.replace(/\.html$/i, '.scss');
fs.readFile(scssFile, function (err, data) {
if (!err && data) {
nodeSass.render({
data: data.toString(),
includePaths: [path.join(srcPath, 'style/')],
outputStyle: 'compressed'
}, function (err, compiledScss) {
if (!err && compiledScss) {
file.contents = new Buffer(contents.replace(injectString, compiledScss.css.toString()), 'binary');
}
return cb(null, file);
});
}
return cb(null, file);
});
} else {
// continue
return cb(null, file);
}
}))
.pipe(gulp.dest(path.join(buildSrcPath, 'components')));
});
RESULT:
<dom-module id="app-view">
<template>
<style>
:host{margin-top:50px;justify-content:center;display:flex}#container{font-size:12px}#container h1{font-size:20px}
</style>
</template>
</dom-module>
First of all, a million Thanks and gratitude goes to David Vega for showing how it is done! I made some adaptations and optimized the code a little bit.
Here's the github for the file!
https://github.com/superjose/polymer-sass/tree/master
Well, this took me a while. Here it goes!
Polymer unleashed version 1.1. From its website:
Note: Style modules were introduced in Polymer 1.1; they replace the
experimental support for external stylesheets.
Instead, they now support "shared styles".
So this means that we can import .html files with css content. The problem is that we can't do .sass the normal way.
Fortunately here's a simpler solution.
What the following script does is that it gets your .scss files, parse them, and inject them into the shared style .html.
Here is the code. Below it, it's step by step on how to use and setup:
var gulp = require('gulp');
var nodeSass = require('node-sass');
var path = require('path');
var fs = require('fs');
var map = require('map-stream');
var basePath = "app/";
var excludeDir = basePath+"bower_components/";
var ext = "**/*.html";
/**
* We need to specify to nodeSass the include paths for Sass' @import
* command. These are all the paths that it will look for it.
*
* Failing to specify this, will NOT Compile your scss and inject it to
* your .html file.
*
*/
var includePaths = ['app/elements/**/'];
gulp.task('watchSass', function(){
gulp.watch(['app/**/*.scss', '!app/bower_components/**/*.scss'], ["injectSass"]);
});
//This is currently not used. But you can enable by uncommenting
// " //return gulp.src([basePath+ext,...excludeDirs])" above the return.
var excludeDirs = [`!${basePath}/bower_components/${ext}`,`!${basePath}/images/${ext}`]
/**
*
* Enable for advanced use:
*
*
*/
gulp.task('injectSass', function () {
/* Original creator: David Vega. I just modified
* it to take advantage of the Polymer 1.1's shared styles.
*
* This will look all the files that are inside:
* app/elements folder. You can change this to match
* your structure. Note, this gulp script uses convention
* over configuration. This means that if you have a file called
* my-element-styles.html you should have a file called
* my-element-styles.scss
*
* Note #2:
* We use "!" (Exclamation Mark) to exclude gulp from searching these paths.
* What I'm doing here, is that Polymer Starter Kit has inside its app folder
* all the bower dependencies (bower_components). If we don't specify it to
* exclude this path, this will look inside bower_components and will take a long time
* (around 7.4 seconds in my machine) to replace all the files.
*/
//Uncomment if you want to specify multiple exclude directories. Uses ES6 spread operator.
//return gulp.src([basePath+ext,...excludeDirs])
return gulp.src([basePath+ext, '!'+excludeDir+ext])
.pipe(map(function (file, cb) {
//This will match anything between the Start Style and End Style HTML comments.
var startStyle = "<!-- Start Style -->";
var endStyle = "<!-- End Style -->";
//Creates the regEx this ways so I can pass the variables.
var regEx = new RegExp(startStyle+"[\\s\\S]*"+endStyle, "g");
// Converts file buffer into a string
var contents = file.contents.toString();
//Checks if the RegEx exists in the file. If not,
//don't do anything and return.
//Rewrote the if for reduced nesting.
if (!regEx.test(contents)) {
//Return empty. if we return cb(null, file). It will add
//the file that we do not want to the pipeline!!
return cb();
}
/**
* Getting scss
* This will get the .html file that matches the current name
* This means that if you have my-app.component.html
* this will match my-app.component.scss. Replace with .sass if you
* have .sass files instead.
*/
var scssFile = file.path.replace(/\.html$/i, '.scss');
fs.readFile(scssFile, function (err, data) {
//Rewrote the if for reduced nesting.
//If error or there is no Sass, return null.
if (err || !data) {
return cb();
}
nodeSass.render({
data: data.toString(),
includePaths: [path.join('app', 'style/'), ...includePaths],
outputStyle: 'compressed'
}, function (err, compiledScss) {
//Rewrote the if for reduced nesting.
//If error or there is no Sass, return null.
if (err || !compiledScss)
return cb();
/**
* What we are doing here is simple:
* We are re-creating the start and end placeholders
* that we had and inject them back to the .html file
*
* This will allow us to re-inject any changes that we
* do to our .scss or files.
*
*/
var injectSassContent = startStyle +
"<style>" +
compiledScss.css.toString() +
"</style>" +
endStyle;
//This is going to replace everything that was between the <!-- Start Style --> and
// "<!-- End Style -->"
file.contents = new Buffer(contents.replace(regEx, injectSassContent), 'binary');
//This return is necessary, or the modified map will not be modified!
return cb(null,file);
});
});
}))
.pipe(gulp.dest(basePath));
}); //Ends
1) Setup your element:
Suppose you have an element called "hero-tournament":
<dom-module id="hero-tournament">
<template>
<style>
</style>
</template>
<script>
(function() {
'use strict';
Polymer({
is: 'hero-tournament',
});
})();
</script>
</dom-module>
And you want to inject your .scss file into it.
Create besides it two new files:
hero-tournament-style.html
hero-tournament-style.scss
Inside the first file, hero-tournament-style.html write the following:
<!-- hero-tournament-style.html -->
<dom-module id="hero-tournament-style">
<template>
<!-- Start Style -->
<style>
</style>
<!-- End Style -->
</template>
</dom-module>
Note the:
<!-- Start Style --> <!-- End Style -->
comments?
These are SUPER important, as all the css will go inside these ones. These are case sensitive, but not position sensitive. Be sure to include them inside your template tags and outside of your style tags.
Then on your hero-tournament-style.scss file, include your sass' css:
(Example)
.blank{
display: none;
}
2) Run Gulp:
gulp watchSass
Bam! You'll see that your "hero-tournament-style.scss" file will be overwritten with your css!!!
<!-- -hero-tournament-style.html -->
<dom-module id="-hero-tournament-style">
<template>
<!-- Start Style -->
<style>.blank{display:none}
</style><!-- End Style -->
</template>
</dom-module>
Now, you can refer that file anywhere!!! Remember your first element, the original one ("hero-tournament.html")? Do the following to it:
<!-- import the module -->
<link rel="import" href="../path-to-my-element/.html">
<dom-module id="hero-tournament">
<template>
<!-- include the style module by name -->
<style include="hero-tournament-styles"></style>
</template>
<script>
(function() {
'use strict';
Polymer({
is: 'hero-tournament',
});
})();
</script>
</dom-module>
Some last notes:
Using SASS Imports
Using Sass imports is easy, just need to watch out for the following:
In the gulpfile there is a variable called: "includePaths". It's an array in which nodeSass will look for all the imports. Failing to specify your import in any of the mentioned places, will prevent your file from injecting and compiling. By default, in the script there is a 'app/style' directory which will look for it.
Folder structure
Folder structure is important, and it can be adapted as your liking.
This assumes that your elements are inside an "app" folder brother to your gulpfile (In the same hierarchy):
-gulpfile.js
/app
/element
/hero-tournament
-hero-tournament.html
-hero-tournament-styles.html
-hero-tournament-styles.scss
/maybe-other-folder
If you want to change your folder structure, change the "basePath" variable. Be sure to check for leading "/" so you don't mess up your structure!
How do I run my gulpfile?
It's easy:
Call the "watchSass" method for watching, or "injectSass" for using it once.
gulp watchSass
gulp injectSass
More information in the github page!!!
In Polymer 2.0 it's also possible to just import a stylesheet inside the element's template like that:
<dom-module id="your-module-name">
<template>
<style><!-- you can also add additional styling in here --></style>
<link rel="stylesheet" href="link_to_stylesheet.css">
<!-- your template in here -->
</template>
<script>
//your element class + registration here
</script>
</dom-module>
Inside of the stylesheet you can style your content just like in the style-tag. The styles only affect the element and its content.
If you want to use SASS, Stylus, LESS or anything like that, you just have to use a middleware (HOWTO: Stack Overflow) in Express that renders the SASS-Code into CSS on every request. I prefer this solution over GULP/GRUNT task, because I think it's way easier, because you don't always have to run the Task, because of the Middleware it's compiling automatically whenever it's needed.
I hope that helps you