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: [email protected]

*/

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\$
2
  • \$\begingroup\$ Should I paste the entire source code? \$\endgroup\$ Commented Oct 20, 2017 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\$ Commented Oct 20, 2017 at 18:49

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.