Skip to content

Commit

Permalink
version 0.0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
darsan-in committed Jul 21, 2024
1 parent fa4181f commit d3a5ab7
Show file tree
Hide file tree
Showing 8 changed files with 353 additions and 49 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
node_modules
node_modules
exp*
dist
34 changes: 34 additions & 0 deletions bin/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env node

import { Command } from "commander";
import { divJs } from "../div";

const program = new Command("div");

program
.version("0.0.1")
.description("Div.js")
.requiredOption(
"-p, --patterns <patterns>",
"HTML search patterns, comma-separated"
)
.requiredOption("-d, --destination <path>", "Destination base path")
.option("-b, --base <path>", "Base search path", process.cwd())
.option("-i, --ignore <patterns>", "Ignore patterns, comma-separated", "")
.action((options) => {
const htmlSearchPatterns: string[] = options.patterns.split(",");
const destinationBasePath: string = options.destination;
const baseSearchPath: string = options.base;
const ignorePatterns: string[] = options.ignore
? options.ignore.split(",")
: [];

divJs(
htmlSearchPatterns,
destinationBasePath,
baseSearchPath,
ignorePatterns
).catch(console.error);
});

program.parse(process.argv);
35 changes: 35 additions & 0 deletions div.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { globSync } from "glob";
import { divider } from "./lib/divider";
import { batchProcess } from "./lib/utils";

export async function divJs(
htmlSearchPatterns: string[],
destinationBasePath: string,
baseSearchPath: string = process.cwd(),
ignorePatterns: string[] = []
): Promise<void> {
ignorePatterns = [
...ignorePatterns,
"./node_modules/**",
`./${destinationBasePath}/**`,
];

const htmlFilePaths: string[] = globSync(htmlSearchPatterns, {
cwd: baseSearchPath,
absolute: true,
ignore: ignorePatterns,
});

const promises = new Array();

for (const htmlPath of htmlFilePaths) {
const proc = () => {
return divider(htmlPath, destinationBasePath);
};

promises.push(proc);
}

const MPP: number = 100; //memory per process
await batchProcess(promises, MPP);
}
216 changes: 216 additions & 0 deletions lib/divider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
import { Element, load } from "cheerio";
import { parse, stringify } from "css";
import MQS from "css-mediaquery";
import { readFileSync } from "fs";
import { readFile, writeFile } from "fs/promises";
import { cloneDeep } from "lodash";
import { basename, dirname, join, relative, resolve } from "path";
import configurations from "../configLoader";
import { screenCats } from "./options";
import { makeDirf } from "./utils";
const { screenSizes } = configurations;

function _getCSSLinks(htmlFilePath: string): Promise<string[]> {
htmlFilePath = resolve(htmlFilePath); //making abs path

const cssLinks: string[] = [];

return new Promise((resolve, reject) => {
readFile(htmlFilePath, { encoding: "utf8" })
.then((htmlContent: string) => {
const $ = load(htmlContent);

$('link[rel="stylesheet"]').each((_index: number, element: Element) => {
cssLinks.push(
join(dirname(htmlFilePath), $(element).attr("href") ?? "")
);
});

resolve(cssLinks);
})
.catch(reject);
});
}

function _writeCSS(
newCssFilePath: string,
stylesheet: Record<string, any>
): Promise<void> {
return new Promise((resolve, reject) => {
makeDirf(dirname(newCssFilePath));

writeFile(newCssFilePath, stringify(stylesheet), { encoding: "utf8" })
.then(resolve)
.catch(reject);
});
}

function _replaceCSSLinks(
htmlPath: string,
destinationPath: string,
newcssLinks: string[]
): Promise<void> {
return new Promise((resolve, reject) => {
readFile(htmlPath, { encoding: "utf8" })
.then((htmlString: string) => {
const $ = load(htmlString);

//remove old links
$('link[rel="stylesheet"]').remove();

for (const cssLink of newcssLinks) {
const mediaquery: string = `screen and (max-width: ${
/* @ts-ignore */
screenSizes[basename(cssLink, ".css").slice(-2)]
}px)`;

const csstag = $(
`<link rel="stylesheet" href="${cssLink}" media="${mediaquery}">`
);

$("head").append(csstag);
}

makeDirf(dirname(destinationPath));

//write to files
writeFile(destinationPath, $.html() ?? "")
.then(resolve)
.catch(reject);
})
.catch(reject);
});
}

export async function divider(
htmlFilePath: string,
destinationBasePath: string
) {
const destinationHtmlPath: string = join(
destinationBasePath,
relative(process.cwd(), htmlFilePath)
);

//getting css link from html
const oldCssLinks: string[] = await _getCSSLinks(htmlFilePath);

//combined CSS metas
let combinedCSSContent: string = "";
let combinedFileName: string = "";

oldCssLinks.forEach((cssLink: string) => {
const cssContent: string = readFileSync(cssLink, { encoding: "utf8" });

combinedFileName = `${combinedFileName}-${basename(cssLink, ".css")}`;
combinedCSSContent = combinedCSSContent + "\n" + cssContent;
});

//setting reciept watermark in css filename
const watermark: string = "~div-js";
combinedFileName = combinedFileName + watermark;

//combined css getting splitted by diff screen dependency
const renderedCSS = parse(combinedCSSContent);

//get media blocks with min or max width conditions
const cssWithMediaQueries =
cloneDeep(renderedCSS).stylesheet?.rules.filter(
(rule) =>
rule.type === "media" &&
/* @ts-ignore */
(rule.media.includes("max-width") || rule.media.includes("min-width"))
) ?? ([] as any);

//Non-Device dependant stylesheet
const commonCSS = cloneDeep(renderedCSS);
/* @ts-ignore */
commonCSS.stylesheet.rules = commonCSS.stylesheet?.rules.filter((rule) => {
if (
//retain all possible rules except media
rule.type === "rule" ||
rule.type === "keyframes" ||
rule.type === "font-face" ||
rule.type === "supports" ||
rule.type === "page" ||
rule.type === "charset" ||
rule.type === "import" ||
rule.type === "document" ||
rule.type === "namespace"
) {
return true;
} else if (
//retain media css if that is not about max and min width
rule.type === "media" &&
/* @ts-ignore */
!rule.media.includes("max-width") &&
/* @ts-ignore */
!rule.media.includes("min-width")
) {
return true;
}

return false;
}) as any;

//Device dependant stylesheets
const deviceDependantStylesheets: Partial<Record<screenCats, any>> = {};

//list for newly generated links
const newCssLinks: string[] = [];

//Iterations on Different ScreenSizes
for (const screenKey of Object.keys(screenSizes)) {
//copy of original css
deviceDependantStylesheets[screenKey as screenCats] =
cloneDeep(renderedCSS);

//filter to retain device dependant stylesheet
deviceDependantStylesheets[screenKey as screenCats].stylesheet.rules =
cloneDeep(cssWithMediaQueries).filter((rule: any) => {
//emulating media query function
const mediaQueryMatched: boolean = MQS.match(rule.media, {
/* @ts-ignore */
type: "screen",
width: `${screenSizes[screenKey as screenCats]}px`,
});

return mediaQueryMatched;
});

//combine common and deviceDependant
deviceDependantStylesheets[screenKey as screenCats].stylesheet.rules =
deviceDependantStylesheets[
screenKey as screenCats
].stylesheet.rules.concat(commonCSS.stylesheet?.rules ?? []);

const dirNameofCSS: string = relative(
process.cwd(),
dirname(oldCssLinks[0])
);

const newCssFilePath: string = join(
destinationBasePath,
dirNameofCSS,
`${combinedFileName}@${screenKey}.css`
);

const relativeCssPath: string = relative(
dirname(destinationHtmlPath),
newCssFilePath
);

newCssLinks.push(relativeCssPath);

await _writeCSS(
newCssFilePath,
deviceDependantStylesheets[screenKey as screenCats]
);
}

return new Promise((resolve, reject) => {
//replace links
_replaceCSSLinks(htmlFilePath, destinationHtmlPath, newCssLinks)
.then(resolve)
.catch(reject);
});
}
2 changes: 1 addition & 1 deletion lib/options.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
type screenCats = "1X" | "2X" | "3X" | "4X" | "5X" | "6X" | "7X";
export type screenCats = "1X" | "2X" | "3X" | "4X" | "5X" | "6X" | "7X";

export interface ConfigurationOptions {
screenSizes: Partial<Record<screenCats, number>>;
Expand Down
12 changes: 11 additions & 1 deletion lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import { mkdirSync } from "fs";
import { freemem } from "os";

export function makeDirf(dirPath: string): void {
mkdirSync(dirPath, { recursive: true });
}

export async function batchProcess(
promises: (() => Promise<any>)[],
batchSize: number
memoryPerProcess: number
): Promise<any[]> {
const freememInMB: number = Math.floor(freemem() / 1024 / 1024);
const batchSize: number = Math.floor(freememInMB / memoryPerProcess);

const promiseBatches: (() => Promise<any>)[][] = [];

for (let i: number = 0; i < promises.length; i += batchSize) {
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "div-js",
"name": "@iamspdarsan/div-js",
"displayName": "Div.js",
"version": "0.0.1",
"description": "Div.js is a tool designed to enhance web performance by splitting CSS into multiple files tailored for different devices. By delivering device-specific CSS files, Div.js minimizes network overhead, reduces network costs, and achieves faster load times.",
Expand Down Expand Up @@ -31,11 +31,11 @@
"license": "Apache-2.0",
"private": false,
"scripts": {
"dev": "cls && rimraf dist && tsc -p tscdev.json && ncp ./div.config.json ./dist/div.config.json",
"dev": "cls && rimraf dist && tsc -p tscdev.json && ncp ./div.c.json ./dist/div.c.json",
"dr": "yarn dev && yarn rp",
"rp": "node ./dist/div.js",
"test": "jest",
"build": "cls && rimraf dist && tsc -p tsconfig.json && ncp ./div.config.json ./dist/div.config.json",
"build": "cls && rimraf dist && tsc -p tsconfig.json && ncp ./div.c.json ./dist/div.c.json",
"clean": "cls && rimraf dist",
"deploy": "yarn test && yarn build && yarn publish --access public && git push"
},
Expand Down Expand Up @@ -79,4 +79,4 @@
"ts-node": "latest",
"typescript": "latest"
}
}
}
Loading

0 comments on commit d3a5ab7

Please sign in to comment.