2
\$\begingroup\$

First of all, thanks for reviewing my code. I am half-way done through a GitHub Project I call ScriptSass, that parses SCSS Code using Javascript into regular CSS. Now, I have reluctantly added an extra bit to the mix. That is, it can compile scss code in inline style elements too. This SO answer says that this is not a feature in regular SCSS. I wanted review on this.

Here is the entire source code :

/*
ScriptSass - A Javascript SCSS compiler

Copyright (C) 2017  Sagnik Modak

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
	

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Contact Me:

Phone: +919774817655
E-mail: sagnikmodak1118@gmail.com

*/

function splitWithTail(str,delim,count){
  var parts = str.split(delim);
  var tail = parts.slice(count).join(delim);
  var result = parts.slice(0,count);
  result.push(tail);
  return result;
}

function findFirstPositive(b, a, i, c) {
  c=(d,e)=>e>=d?(a=d+(e-d)/2,0<b(a)&&(a==d||0>=b(a-1))?a:0>=b(a)?c(a+1,e):c(d,a-1)):-1
  for (i = 1; 0 >= b(i);) i *= 2
  return c(i / 2, i)|0
}

var ScriptSass = new Object();
ScriptSass.inString = function(i,arr){
					var j;
					var inString = 2;
					for(j=0;j<i;j++){
						if(arr[j].indexOf("\"") > -1)
							inString++;
					}
					return (inString%2 == 0)?true:false;
	};
ScriptSass.findClosure = function(arr,i){
					var j;
					var go = 0;
					for(j=i+1;j<arr.length;j++){
						if(arr[j] == "{")
							go++;
						if(arr[j] == "}" && go > 0)
							go--;
						if(arr[j] == "}" && go == 0)
							break;
					}
					return j;
	};
	
ScriptSass.isProperty = function(v){
							var pattern = new RegExp("^[^\$:].+$");
							if(pattern.test(v) && v.indexOf(":") > -1){
								return true;
							}
							return false;
	};

ScriptSass.replaceVars = function(str,vars){
						var i;
						var parts = str.split(/([:\s])+/g);
						for(i=0;i<parts.length;i++){
							if(parts[i].startsWith("$") && vars.hasOwnProperty(parts[i]))
								parts[i] = vars[parts[i]].data;
						}
						return parts.join("");
};
	
ScriptSass.replacePlaceholder = function(name,parentstr){
							if(name.indexOf("&") > -1){
								parentstr = parentstr.trim();
								var pstr = parentstr.split(" ");
								var p = pstr.pop();
								pstr = pstr.join(" ")+" ";
								return (pstr+name.replace(/[&]/g, p)).trim();
							}else
								return (parentstr+name).trim();
};

ScriptSass.evaluateExpression = function(exp,base_fs=16){
									var i,n;
									var nums = exp.match(/(\d+\.?\d*\s?)(px|em|vh|vw|%|cm|mm|in|pt|pc|rem|vmax|vmin)/g);
									var width = $(window).width();
									var height = $(window).height();
									var dpi = findFirstPositive(x => matchMedia(`(max-resolution: ${x}dpi)`).matches);
									if(nums != null){
										for(i=0;i<nums.length;i++){
											if(nums[i].endsWith("%")){
												n = nums[i].substring(0,nums[i].indexOf("%")).trim();
												exp = exp.replace(nums[i],(n*width/100));
											}else if(nums[i].endsWith("px")){
												exp = exp.replace(nums[i],nums[i].substring(0,nums[i].indexOf("px")).trim());
											}else if(nums[i].endsWith("vw")){
												n = nums[i].substring(0,nums[i].indexOf("vw")).trim();
												exp = exp.replace(nums[i],n*width/100);
											}else if(nums[i].endsWith("vh")){
												n = nums[i].substring(0,nums[i].indexOf("vh")).trim();
												exp = exp.replace(nums[i],n*height/100);
											}else if(nums[i].endsWith("em")){
												n = nums[i].substring(0,nums[i].indexOf("em")).trim();
												exp = exp.replace(nums[i],n*base_fs);
											}else if(nums[i].endsWith("vmax")){
												n = nums[i].substring(0,nums[i].indexOf("vmax")).trim();
												exp = exp.replace(nums[i],n*Math.max(width,height));
											}else if(nums[i].endsWith("vmin")){
												n = nums[i].substring(0,nums[i].indexOf("vmin")).trim();
												exp = exp.replace(nums[i],n*Math.min(width,height));
											}else if(nums[i].endsWith("pt")){
												n = nums[i].substring(0,nums[i].indexOf("pt")).trim();
												exp = exp.replace(nums[i],n*dpi/72);
											}else if(nums[i].endsWith("pc")){
												n = nums[i].substring(0,nums[i].indexOf("pc")).trim();
												exp = exp.replace(nums[i],n*dpi/6);
											}else if(nums[i].endsWith("rem")){
												n = nums[i].substring(0,nums[i].indexOf("rem")).trim();
												exp = exp.replace(nums[i],n*16);
											}else if(nums[i].endsWith("in")){
												n = nums[i].substring(0,nums[i].indexOf("in")).trim();
												exp = exp.replace(nums[i],n*dpi);
											}else if(nums[i].endsWith("cm")){
												n = nums[i].substring(0,nums[i].indexOf("cm")).trim();
												exp = exp.replace(nums[i],n*dpi*0.3937007874);
											}else if(nums[i].endsWith("mm")){
												n = nums[i].substring(0,nums[i].indexOf("mm")).trim();
												exp = exp.replace(nums[i],n*dpi*0.03937007874);
											}
										}
									}
									var expression = exp.match(/(^#{0})(\d*\.?\d*\s?\+?\-?\/?\*?\(?\)?)+/g);
									if(expression == null){
										return exp;
									}else{
										expression = expression.filter(function(v,i){return (v.trim()=="")?false:true});
										for(i=0;i<expression.length;i++){
											exp = exp.replace(expression[i],eval(expression[i])+"px ");
										}
									}
									return exp;
};

ScriptSass.tokenize = function(rawcode,parentstr=""){
		var par = parentstr;
		var imp = [];
		var code = rawcode.replace(/[^\x20-\x7E]/gmi, "").split('"').map(function(v,i){
				if(i%2){
					return v;
					//do nothing;
				}else{
					var parts = v.split(/([{};])/g);
					for(i = 0; i < parts.length; i++){
					parts[i] = parts[i].trim();
					if(!parts[i].startsWith("@")){
						var colindex = parts[i].indexOf(":");
						if(colindex > -1){
							parts[i] = parts[i].substring(0,colindex+1) + parts[i].substring(colindex+1,parts[i].length).trim();
						}else{
							parts[i] = parts[i].replace(/\s/g, "");
						}
					}
					}
					v = parts.join('');
				}
					return v;
			}).join('"');
		var tokens = code.split(/([{};])/g);
		tokens = tokens.map(function (v,i,arr){
			if(v.startsWith("@")){
				if(v.startsWith("@import")){
					var url = splitWithTail(v," ",1)[1].replace(/^"(.*)"$/, '$1');
					arr.splice(i,1);
					i--;
					imp = imp.concat(ScriptSass.importSCSS(url));
					return "";
				}
				if(v.startsWith("@extend")){
					var selector = splitWithTail(v," ",1)[1].replace(/^"(.*)"$/, '$1');
					return {type:"extend",data:selector};
				}
				if(v.startsWith("@mixin")){
					var func_def = splitWithTail(v," ",1)[1];
					var func_name = func_def.substring(0,func_def.indexOf("("));
					var func_var_list = func_def.substring(func_def.indexOf("(")+1,func_def.indexOf(")")).split(",");
					for(k=0;k<func_var_list.length;k++)
						func_var_list[k] = func_var_list[k].trim();
					var func_body = arr.slice(i+2,ScriptSass.findClosure(arr,i)).join('');
					var closure = ScriptSass.findClosure(arr,i+1) - i;
					arr.splice(i+1,closure);
					return {type:"mixin",data:{name:func_name,params:func_var_list,data:ScriptSass.tokenize(func_body,par)}};
				}
				if(v.startsWith("@include")){
					var inc_def = splitWithTail(v," ",1)[1];
					var inc_name = inc_def.substring(0,inc_def.indexOf("("));
					var inc_params = inc_def.substring(inc_def.indexOf("(")+1,inc_def.indexOf(")")).split(",");
					for(k=0;k<inc_params.length;k++)
						inc_params[k] = inc_params[k].trim();
					return {type:"include",name:inc_name,params:inc_params};
				}
			}
			if(ScriptSass.isProperty(v) && (arr[i+1] == ";" || i == arr.length-1)){
				var prop_def = splitWithTail(v,":",1);
				var prop_name = prop_def[0];
				var prop_data = prop_def[1];
				return {type:"property",data:{name:prop_name,data:prop_data}};
			}
			if(arr[i+1] == "{"){
				var sel_name = v;
				var sel_body = arr.slice(i+2,ScriptSass.findClosure(arr,i)).join('');
				var closure = ScriptSass.findClosure(arr,i+1) - i;
				arr.splice(i+1,closure);
				var pstr = par+sel_name+" ";
				return {type:"selector",data:{name:sel_name,data:ScriptSass.tokenize(sel_body,pstr)},parent_string:par};
			}
			if(v.startsWith("$")){
					var var_def = splitWithTail(v,":",1);
					var var_name = var_def[0];
					var var_data = var_def[1];
					var var_type;
					if(!isNaN(var_data)){
						var_type = "number";
					}else{
						if(var_data.startsWith("(") && var_data.endsWith(")"))
							var_type = "map";
						else
							var_type = "expression";
					}
					if(var_type == "map"){
						var_data.replace(/[\(\)]/g,"");
						var pairs = var_data.split(",");
						var map = [];
						var pair,key,val;
						for(k=0;k<pairs.length;k++){
							pair = pairs[k].split(":");
							key = pair[0];
							val = pair[1];
							map[key] = val;
						}
						var_data = map;
					}
					return {type:"variable",data:{name:var_name,type:var_type,data:var_data}};
			}
			return v;
		}).filter(function (v){
			return (typeof v == "object");
		});
		return imp.concat(tokens);
};
ScriptSass.lexer = function (tk,sel_list = [],flag = 0,varlist = {},mixlist = {}){
	var i;
	var vars = varlist;
	var mixins = mixlist;
	var imports = [];
	var data = [];
		for(i=0;i<tk.length;i++){
			if(typeof tk[i] == "object"){
				if(tk[i].type == "variable"){
					vars[tk[i].data.name] = {type:tk[i].data.type,data:tk[i].data.data};
					tk.splice(i,1);
					i--;
					continue;
				}
				if(tk[i].type == "mixin"){
					mixins[tk[i].data.name] = {params:tk[i].data.params,data:tk[i].data.data};
					tk.splice(i,1);
					i--;
					continue;
				}
				if(tk[i].type == "selector"){
					var sel = tk[i];
					tk.splice(i,1);
					i--;
					sel.data.data = ScriptSass.lexer(sel.data.data,sel_list,1);
					sel_list.push({"name":ScriptSass.replacePlaceholder(sel.data.name,sel.parent_string),data:sel.data.data});
					continue;
				}
				if(tk[i].type == "property"){
					data.push(tk[i]);
					tk.splice(i,1);
					i--;
					continue;
				}
				if(tk[i].type == "include"){
					data.push(tk[i]);
					tk.splice(i,1);
					i--;
					continue;
				}
			}
		}
	if(flag)	
		return {variables:vars,mixes:mixins,imports:imports,props:data};
	else
		return {variables:vars,mixes:mixins,imports:imports,selectors:sel_list};
};
ScriptSass.parse = function(ast){
				var i,j,k;
				//variable and include replacements
				if(ast.hasOwnProperty('selectors')){
					for(i=0;i<ast.selectors.length;i++){
						ast.selectors[i].name = ScriptSass.replaceVars(ast.selectors[i].name,ast.variables);
						for(j=0;j<ast.selectors[i].data.props.length;j++){
							if(ast.selectors[i].data.props[j].type == "property"){
								ast.selectors[i].data.props[j].data.name = ScriptSass.replaceVars(ast.selectors[i].data.props[j].data.name,ast.variables);
								for(var attr in ast.variables){
									if(!ast.selectors[i].data.variables.hasOwnProperty(attr))
										ast.selectors[i].data.variables[attr]=ast.variables[attr];
								}
								ast.selectors[i].data.props[j].data.data = ScriptSass.replaceVars(ast.selectors[i].data.props[j].data.data,ast.selectors[i].data.variables);
							}else if(ast.selectors[i].data.props[j].type == "include"){
								var params = ast.selectors[i].data.props[j].params;
								var fname = ast.selectors[i].data.props[j].name;
								ast.selectors[i].data.props.splice(j,1);
								j--;
								for(var attr in ast.mixes){
									if(!ast.selectors[i].data.mixes.hasOwnProperty(attr))
										ast.selectors[i].data.mixes[attr]=ast.mixes[attr];
								}
								var vlist={};
								for(k=0;k<ast.selectors[i].data.mixes[fname].params.length;k++){
									vlist[ast.selectors[i].data.mixes[fname].params[k]] = {type:"expression",data:params[k]};
								}
								var props = ScriptSass.parse(ScriptSass.lexer(ast.selectors[i].data.mixes[fname].data,[],1,vlist,ast.selectors[i].data.mixes)).props;
								ast.selectors[i].data.props = ast.selectors[i].data.props.concat(props);
							}
						}
					}
				}else if(ast.hasOwnProperty('props')){
					for(j=0;j<ast.props.length;j++){
							if(ast.props[j].type == "property"){
								ast.props[j].data.name = ScriptSass.replaceVars(ast.props[j].data.name,ast.variables);
								ast.props[j].data.data = ScriptSass.replaceVars(ast.props[j].data.data,ast.variables);
							}else if(ast.props[j].type == "include"){
								var params = ast.props[j].params;
								var fname = ast.props[j].name;
								ast.props.splice(j,1);
								j--;
								var vlist={};
								for(k=0;k<ast.mixes[fname].params.length;k++){
									vlist[ast.mixes[fname].params[k]] = {type:"expression",data:params[k]};
								}
								var props = ScriptSass.parse(ScriptSass.lexer(ast.selectors[i].data.mixes[fname].data,[],1,vlist,ast.mixes)).props;
								ast.props = ast.props.concat(props);
							}
						}
				}
				return ast;
};
ScriptSass.compileInternal = function(ast,base_fs = 16){
					var i;
					var css="";
					if(ast.hasOwnProperty('selectors')){
						for(i=0;i<ast.selectors.length;i++){
							css+=ast.selectors[i].name+"{"+ScriptSass.compileInternal(ast.selectors[i].data)+"}";
						}
					}else if(ast.hasOwnProperty('props')){
						for(i=0;i<ast.props.length;i++){
							if(ast.props[i].type == "property")
								css+=ast.props[i].data.name+":"+ScriptSass.evaluateExpression(ast.props[i].data.data, base_fs)+";";
						}
					}
					return css;
};
ScriptSass.compile = function(code){
					return ScriptSass.compileInternal(ScriptSass.parse(ScriptSass.lexer(ScriptSass.tokenize(code))));
};
ScriptSass.compileInline = function(inlineUnrestricted = false){
		var dfd = $.Deferred();
		var ast;
		var mixins = [];
		var vars = [];
		$('code[type="sass"]').each(function (){
			$(this).hide();
			code = $(this).html();
			ast = ScriptSass.lexer(ScriptSass.tokenize(code));
			for(v in ast.mixes){mixins[v] = ast.mixes[v]};
			for(v in ast.variables){vars[v] = ast.variables[v]};
			($('head').find('style').length > 0) ? $('head style').append(ScriptSass.compile(code)):$('head').append('<style>'+ScriptSass.compile(code)+'</style>');
		});
		if(inlineUnrestricted){
			$('[style]').each(function (){
				code = ScriptSass.compileInternal(ScriptSass.parse(ScriptSass.lexer(ScriptSass.tokenize($(this).attr('style')),[],1,vars,mixins)));
				$(this).attr('style',code);
			});
		}else{
			$('.scss[style]').each(function (){
				code = ScriptSass.compileInternal(ScriptSass.parse(ScriptSass.lexer(ScriptSass.tokenize($(this).attr('style')),[],1,vars,mixins)));
				$(this).attr('style',code);
			});
		}
		dfd.resolve();
		return dfd.promise();
};
ScriptSass.load = function(file){
				var dfd = $.Deferred();
				var code="";
				$.ajax({
					url: file,
					success: function (response){
						code = response;
					},
					async: false
				});
				($('head').find('style').length > 0) ? $('head style').append(ScriptSass.compile(code)):$('head').append('<style>'+ScriptSass.compile(code)+'</style>');
				dfd.resolve();
				return dfd.promise();
};

ScriptSass.importSCSS = function(name){
				var filename = name.substring(name.lastIndexOf('/') + 1);
				filename = name.substring(0,name.lastIndexOf('/'))+"_"+filename+".sass";
				var code="";
				$.ajax({
					url: filename,
					success: function (response){
						code = response;
					},
					async: false
				});
				return ScriptSass.tokenize(code);
};
/*
Nothing in here.
See code block in HTML
*/
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<code type="sass">
  $bg: #515151;
  $c: #adef6b;
  $ci: #ffffff;
  @mixin $bord($p,$c){
      border-bottom: $p solid $c;
      padding: $p + 4px;
      margin-bottom: $p + 4px;
      font-size: 14pt;
      font-family: Arial;
  }
  div{
    header{
      @include $bord(1px,white);
      color: $ci;
    }
  }
</code>
<div style="background-color: $bg;color: $c;padding: 10px;">
<header>Specific use case</header>
This is an example div with scss code inside style attribute.
</div>
<script>
$(document).ready(function (){
  ScriptSass.compileInline(true);
});
</script>

\$\endgroup\$
  • \$\begingroup\$ Should I paste the entire source code? \$\endgroup\$ – Roy Oct 20 '17 at 17:47
  • \$\begingroup\$ @BillalBEGUERADJ I have added the entire source code with an example snippet of the specific use case for the style property. Thanks again for reviewing. \$\endgroup\$ – Roy Oct 20 '17 at 18:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.