What does the script do?
1. The script recursively scans all html files in a directory 2. For each html file, it a. scans for ‘style’ element recursively b. replaces each style element with a css class ‘cx — <tagName>_<count>’ c. deletes the style element 3. All the created css classes are written to a file ‘generatedCSS.scss’
Steps to run the script
1. Copy package.json and RemoveInlineCSS.js file to a directory 2. In the directory, execute commands a. npm install → This will install all the required dependencies b. npm start → This will run the script 3. The script will prompt for a directory name <dirName> having HTML files to be processed 4. After successful script execution, existing html files will be updated and a new css file will be generated at location <dirName>/generatedCSS.scss
SCRIPTS
1. package.json
{
"name":"remove_inline_css",
"version":"1.0.0",
"description":"",
"scripts":{
"start":"node RemoveInlineCSS.js"
},
"author":"madhuri4011@gmail.com",
"license":"ISC",
"dependencies":{
"absurd":"⁰.3.9",
"fs":"0.0.1-security",
"glob":"⁷.1.4",
"inline-style-2-json":"¹.1.0",
"node-html-parser":"¹.1.16",
"readline":"¹.3.0"
}
}
2. RemoveInlineCSS.js
var HTMLParser = require('node-html-parser'); // HTML parser
var absurd = require("absurd"); // CSS preprocessor
var inline_style_2_json = require("inline-style-2-json");
//Converts CSS inline stlyes to JSON
var fs = require('fs'); // To access file system
var glob = require('glob');
//Used to find all HTML files in a drectory recursivelyconst readline = require('readline');
var count = 1;
var dirname = "";
var cssFile = "";
var api = absurd(cssFile);
var callBackCounter = 0;
// accept directory path from user
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('Enter directory path? ', (path) => {
dirname = path;
cssFile = `${dirname}/generatedCSS.scss`;
console.log("read data: " + dirname);
readFiles(dirname, compileCSS);
rl.close();
});
var compileCSS = function (err, success) {
api.compile(function (err, css) {
console.log("Compiled CSS: " + css);
fs.writeFile(cssFile, css, function () {
console.log("CSS file written successfully");
});
});
};
function processFile(filename, content, cb) {
var global = HTMLParser.parse(content, {
style: true
});
if (global.childNodes.length > 0) {
extractInlineCSS(global, true, filename, function () {
cb();
});
}
}
function readFiles(dirname, cb) {
glob(dirname + "/**/*.html", null, function (er, files) {
files.forEach(function (file) {
console.log(file);
fs.readFile(file, 'utf-8', function (err, content) {
if (err) {
return;
}
processFile(file, content, function () {
callBackCounter++;
if (callBackCounter == files.length) {
cb();
}
});
});
});
});
}
function extractInlineCSS(parentNode, isRoot, filename, cb) {
for (let i = 0; i < parentNode.childNodes.length; i++) {
var childNode = parentNode.childNodes[i];
if (childNode.attributes != undefined && childNode.attributes.style != undefined) {
console.log("***" + childNode.attributes.style);
let styleJsonObj = inline_style_2_json(childNode.attributes.style);
let htmlClassName = "cx - " + childNode.tagName + "_" + count;
let cssClassName = ".cx - " + childNode.tagName + "_" + count++;
let style = {
[cssClassName]: styleJsonObj
};
api.add(style);
// if a given node already has a class attribute
if (childNode.attributes.class != undefined) {
let newClasses = childNode.attributes.class + " " + htmlClassName;
childNode.attributes.class = newClasses;
childNode.rawAttributes.class = newClasses;
}
// A given node doesn't have a class attribute
else {
childNode.attributes.class = htmlClassName;
childNode.rawAttributes.class = htmlClassName;
}
delete childNode.attributes.style;
var str = '';
let updatedAttributes = childNode.attributes;
for (let i = 0; i < Object.keys(updatedAttributes).length; i++) {
str += `${Object.keys(updatedAttributes)[i]}="${updatedAttributes[Object.keys(updatedAttributes)[i]]}" `;
}
childNode.rawAttrs = str;
// console.log(`class ` + childNode.attributes.class);
// console.log(`updated html: ` + parentNode.toString());
}
if (childNode.childNodes.length > 0) {
extractInlineCSS(childNode, false, filename, cb);
}
};
if (isRoot) {
fs.writeFile(filename, parentNode.toString(), function (err) {
if (!err) {
console.log(`${filename} has been updated successfully.`);
cb();
}
});
}
}
3. Sample Test HTML — TestFile.html
<!DOCTYPE html>
<html>
<body>
<p style="color:red;">I am red</p>
<div style="color:red;">
I am red again
<div style="color:blue;">
I am blue
<p style="color:yellow;">
I am yellow
</p>
</div>
<p style="font-size:50px;">I am big</p>
</div>
</body>
</html>
4. Output
TestFile.html
<html>
<body>
<p class="cx - p_1" >I am red</p>
<div class="cx - div_2" >
I am red again
<div class="cx - div_3" >
I am blue
<p class="cx - p_4" >
I am yellow
</p>
</div>
<p class="cx - p_5" >I am big</p>
</div>
</body>
</html>
GeneratedCSS.scss
.cx - p_1, .cx - div_2 {
color: red;
}
.cx - div_3 {
color: blue;
}
.cx - p_4 {
color: yellow;
}
.cx - p_5 {
font-size: 50px;
}
Limitations
The script strips-off any comments and <!DOCTYPE html> in html file
PS: The script was written around 2 years back hence we may need to upgrade the library versions and the script can be enhanced using ES6 async/await.
Source: Medium - Madhuri Pednekar
The Tech Platform
Comments