How to use Sass inside a Polymer component - css

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

Related

Vue2 + Vite: css preprocessor additionalData SFC styles

So I'm working with vue 2.7.14. Moving from webpack to vite.
All my scss styles in every component (SFC) are not loading. Actually, the style is loaded in the head tag, but it is adding some url params to the url and not loading the styles to the component (see 'html output')
If I put my scss files to load with vite.config.js inside the preprocesorOption, it will load and apply styles, but I want my components styles to be load in every component.
Reproduction of the case: https://stackblitz.com/edit/vitejs-vite-n72kb6
vite.config.js
css: {
preprocessorOptions: {
scss: {
// THIS WAY ALL THE STYLES IN SFC ARE NOT LOADING
// UNLESS I ADD HERE STYLES ONE BY ONE (see last one commented)
additionalData: () => {
let prepends = '';
prepends += `$app: ${app};`;
prepends += `#import "#/style.scss";`;
prepends += `#import "#/assets/vite/scss/colors.scss";`;
// prepends += `#import "#/components/searchbar/scss/searchbar.scss";`;
return prepends;
}
},
},
},
saerchbar.vue
<template src="./searchbar.html"></template>
<!--THIS IS NOT LOADING UNLESS I ADD IT TO THE vite.config.js-->
<style lang="scss" src="./searchbar.scss"></style>
<script>...</script>
The problem here is the way Im using the additionalData function.
It seems that when using "additionalData" as a function, will completely take control and as the content is passed as the first argument, we have to manually return it:
additionalData: (content) => {
let prepends = '';
prepends += `$app: ${app};`;
prepends += `#import "#/style.scss";`;
prepends += `#import "#/assets/vite/scss/colors.scss";`;
prepends += content;
return prepends;
}
or use the "additionalData" as a sass string:
additionalData: `
$app: ${app};
#import "#/style.scss";
#import "#/assets/vite/scss/colors.scss";
`,
I used this last way as string using template literals and everything works fine now.
IMPORTANT: Avoid adding any css in additionalData as it is going to get duplicated for every component that has scss. Use it for global, variables, mixins...

Can't get GulpFile to act the way I want, missing something?

Trying to set up gulp and one of the steps is frustrating. I am following a tutorial and can't get it to work right.
https://css-tricks.com/gulp-for-beginners/
Basically, I want to create a build task that compiles sass, concats the css files, minimizes it, and output it to the public folder. Here is my code for my index.html. (Simplified).
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--build:css css/styles.min.css -->
<link rel="stylesheet" href="scss/styles.scss">
<!-- endbuild -->
</head>
<body>
</body>
</html>
Now here is my Gulpfile.js
var gulp = require('gulp'),
sass = require('gulp-sass'),
useref = require('gulp-useref'), // Use to concatenate files
gulpIf = require('gulp-if'),
cssnano = require('gulp-cssnano'),
uglify = require('gulp-uglify'),
imagemin = require('gulp-imagemin'),
imagecache = require('gulp-cache'),
del = require('del'),
runsequence = require('run-sequence');
/* ********************************* */
// PRODUCTION TASKS ONLY \\
/*Used to start with a clean slate on the public folder */
gulp.task('clean:public', function () {
return del.sync('public');
})
gulp.task('watch:prod', function () {
gulp.watch('src/scss/**/*.scss', ['sass']);
});
gulp.task('sass:prod', function () {
return gulp.src('src/scss/**/*.scss')
.pipe(sass())
.pipe(gulp.dest('public/css'))
});
gulp.task('useref:prod', function () {
return gulp.src('src/*.html')
.pipe(useref())
.pipe(gulpIf('*.js', uglify()))
.pipe(gulpIf('*.css', cssnano()))
.pipe(gulp.dest('public'));
});
gulp.task('images:prod', function () {
return gulp.src('src/images/**/*.+(png|jpg|gif|svg)')
.pipe(imagecache(imagemin([
imagemin.gifsicle({interlaced: true}),
imagemin.jpegtran({progressive: true}),
imagemin.optipng({optimizationLevel: 5}),
imagemin.svgo({plugins: [{removeViewBox: true}]})
])))
.pipe(gulp.dest('public/images'));
});
gulp.task('cssimages:prod', function () {
return gulp.src('src/css/cssimages/**/*.+(png|jpg|gif|svg)')
.pipe(imagecache(imagemin([
imagemin.gifsicle({interlaced: true}),
imagemin.jpegtran({progressive: true}),
imagemin.optipng({optimizationLevel: 5}),
imagemin.svgo({plugins: [{removeViewBox: true}]})
])))
.pipe(gulp.dest('public/css/cssimages'));
});
/* BRING EVERYTHING TOGETHER */
gulp.task('build:prod', function (callback){
runsequence
(
'clean:public',
['sass:prod','useref:prod','images:prod', 'cssimages:prod'],
callback
)
})
As per the tutorial, this should create a file in the public folder under css names styles.min.css
This file should also already be compiled down from sass. I did an example styles.scss and inside it I have.
$bgcolor : yellow;
body {
background: $bgcolor;
}
div {
width: 100px;
height: 20px;
}
When I run gulp build:prod , this is what it outputs in styles.min.css
$bgcolor:#ff0;body{background:$bgcolor}div{width:100px;height:20px}
The files minimizing fine but i can't get the sass part run right and compile when use the build task.
^^^ As you see, instead of sassing the file and then concatenating the file, it create 2 files. I'm trying to have gulp sass the file first, and then have useref move the file to the public folder and rename it to styles.min.css
It seems I'm missing something somewhere or not sourcing/destinating to the right folders?
If I run gulp sass:prod, it works fine. But can't seem to get my build task to run right I'm stumped.
From the article that you have mentioned,
Gulp-useref concatenates any number of CSS and JavaScript files into a
single file by looking for a comment that starts with "". Its syntax is:
<!-- build:<type> <path> --> ... HTML Markup, list of script / link tags. <!-- endbuild -->
path here refers to the target path of the generated file.
According to the document you have specified the following.
<!--build:css css/styles.min.css -->
<link rel="stylesheet" href="scss/styles.scss">
<!-- endbuild -->
So the useref will copy the styles from styles.scss and creates styles.min.css and pastes the scss styles. That is the reason you are getting scss styles in the minified styles.min.css
To achieve what you wanted you have to modify your sass:prod dest path like below.
gulp.task('sass:prod', function () {
return gulp.src('src/scss/**/*.scss')
.pipe(sass())
.pipe(gulp.dest('src/css'))
});
and in the html, you have to reference the css file.
<!--build:css css/styles.min.css -->
<link rel="stylesheet" href="css/styles.css">
<!-- endbuild -->
And also as specified by #Mark, it is better to modify the run-sequence to make sure that the sass:prod task completes before the useref:prod task.
gulp.task('build:prod', function (callback){
runsequence
(
'clean:public','sass:prod',
['useref:prod','images:prod', 'cssimages:prod'],
callback
)
})
From the run-sequence documentatin :
You can still run some of the tasks in parallel, by providing an array of task names for one or more of the arguments.
So, in your tasks array :
['sass:prod','useref:prod','images:prod', 'cssimages:prod'],
these tasks run in parallel. There is no guarantee that the 'sass:prod' task will complete before the 'useref:prod' task. If you want that to happen change to:
gulp.task('build:prod', function (callback){
runsequence
(
'clean:public',
'sass:prod',
['useref:prod','images:prod', 'cssimages:prod'],
callback
)
})

Stylus pre-processor won't work inside useref with gulpif

I have a very basic gulp task, and I want to process a Stylus CSS file (.styl). This is the task inside the gulpfile:
const gulp = require("gulp");
const gulpIf = require("gulp-if");
const useref = require("gulp-useref");
const stylus = require("gulp-stylus");
const cssnano = require("gulp-cssnano");
gulp.task("default", function(){
return gulp.src("*.htm")
.pipe(useref())
.pipe(gulpIf("*.css", stylus()))
.pipe(gulp.dest("dist"))
});
And this is the particular section of the html
<!--build:css css/style.min.css -->
<link rel="stylesheet" href="style.styl">
<!-- endbuild -->
For whatever reason, the Stylus file just doesn't get processed, and gets copied to css/style.min.css without any processing.
Even stranger, is that if I format the CSS as a normal CSS file and replace "stylus()" with "cssnano()", cssnano works fine and minifies the file. Stylus works fine outside of the useref and gulpIf when ran as its own task, but I preferably want to use it like this.
Can you try specifying the root path for useref where it can find assets:
options.searchPath
Type: String or Array Default: none
Specify the location to search for asset files, relative to the
current working directory. Can be a string or array of strings.
Your task
gulp.task("default", function(){
return gulp.src("*.htm")
.pipe(useref({
searchPath: 'assets_path_here'
}))
.pipe(gulpIf("*.css", stylus()))
.pipe(gulp.dest("dist"))
});
I personally just set the path to the root that contains my entire app.

How to switch css file when using Webpack to load css?

I use gulp to compile my sass file to css files, and reference the css file in my html. The project support theme switch. For example, I have 3 css theme files:
red.css
yellow.css
blue.css
I can currently switch the theme css like this:
var styleDom = $('#theme-style');
var newHref = 'styles/themes/' + themeName + '.css';
if (styleDom.attr('href') !== newHref) {
styleDom.attr('href', newHref);
}
Now I want to use webpack to load the css file.
require('styles/themes/red.css');
It seems work well, but I cannot find a way to switch the theme css file now, does anyone have a solution?
Your approach doesn’t need to change. Just use Extract Text plugin to save out the CSS files. You’ll need to make multiple entry points to create multiple CSS files.
OR
More ideally, (the approach I would take) make your CSS switch based on a different html or body class and just change the class. It won’t add much overhead, and it will be a more ideal UX when changing themes.
You'll need to use a combination of webpacks style-loader and file-loader (second example ) and use require.ensure (second example "dynamic imports") to accomplish this:
function switchTheme(name) {
// Remove the current theme stylesheet
// Note: it is important that your theme css always is the last
// <link/> tag within the <head/>
$('head link[rel="stylesheet"]').last().remove();
// Since webpack needs all filePaths at build-time
// you can't dynamically build the filePath
switch(name) {
case 'red':
// With require.ensure, it is possible to tell webpack
// to only load the module (css) when require is actually called
return require.ensure([], function () {
require('style-loader/url!file-loader!styles/themes/red.css');
});
case 'yellow':
return require.ensure([], function () {
require('style-loader/url!file-loader!styles/themes/yellow.css');
});
case 'blue':
return require.ensure([], function () {
require('style-loader/url!file-loader!styles/themes/blue.css');
});
default:
throw new Error('Unknown theme "' + name + '"');
}
}
Then a call like switchTheme('blue') should do the trick.
And you might have to check your current webpack.config.js, in case you already have configured a loader for .css files.

Dynamic scss variable meteor

I have a scss variable $tint-color that is used in about 100 places.
Once the user logs in, I would like to load a color based on their profile and replace all the usages of $tint-color.
So far I have found two non-ideal solutions:
1) Iterate through all elements and replace the relevant properties.
I am constantly generating new elements -- so this would need to happen repeatedly.
2) Create an override stylesheet, that targets each element.
This will require a lot of duplicate code.
Is there a better / simpler way? I have thought about adding a class to an element in scss, but I am not sure this is possible. Thank you for your help in advance!
What I am doing now, is loading a theme css file after the profile is loaded.
On the server I expose an iron-router route that dynamically replaces any occurrence of the color and returns the theme css.
The issue is that I am not replacing the scss variables, instead I am replacing any occurrence of the color. This is because when the code is executed the .scss files have already been bundled into a .css file on the server.
// return a theme based on the tintColor parameter
this.route('theme', {
where: 'server',
action: function () {
var files = fs.readdirSync('../client');
// find the css file (not the .map file)
var cssFile = _(files).find(function (fileName) {
return fileName.indexOf('.css') > 0 && fileName.indexOf('.map') < 0;
});
var style = fs.readFileSync('../client/' + cssFile, 'utf8');
// remove comments (cannot have them for minification)
style = style.replace(/(?:\/\*(?:[\s\S]*?)\*\/)|(?:([\s;])+\/\/(?:.*)$)/gm, '');
// replace the default tint-color with the dynamic color
style = style.replace(/8cb850/g, this.params.tintColor);
// minify css
if (Settings.isProduction()) {
// from the minifiers package
style = CssTools.minifyCss(style);
}
this.response.writeHead(200, {'Content-Type': 'text/css'});
this.response.end(style);
}
});
Update: I got it to generate with scss variables.
Theme.compile = function (tintColor) {
var dirName = path.dirname(styleFile);
var styles = fs.readFileSync(styleFile, 'utf8');
//replace default theme with dynamic theme
var theme = '$tint-color: #' + tintColor + ';' + '\n';
styles = styles.replace('#import "app/theme.scssimport";', theme);
var options = {
data: styles,
sourceComments: 'map',
includePaths: [dirName] // for #import
};
var css = sass.renderSync(options);
// minify css
if (Settings.isProduction()) {
// remove comments -- cannot have them for minification
css = css.replace(/(?:\/\*(?:[\s\S]*?)\*\/)|(?:([\s;])+\/\/(?:.*)$)/gm, '');
// Use CssTools from the minifiers package
css = CssTools.minifyCss(css);
}
return css;
};
If you do this make sure you add the scss files as assets in the package, example here.
Set a basic $tint-color in your original css.
Then use meteor to send inline CSS with the selected user-tint.
Example:
.tint {
background-color: USER-TINT;
color: USER-TINT;
}
That way you can cache the original css file and save loads of transfer!

Resources