Giter Site home page Giter Site logo

jpeer264 / node-rcs-core Goto Github PK

View Code? Open in Web Editor NEW
37.0 5.0 7.0 2.39 MB

Rename css selectors across all files

License: MIT License

JavaScript 0.61% HTML 1.12% CSS 3.59% TypeScript 94.08% Pug 0.60%
css css-selector matches-selector minified-selectors rename rcs

node-rcs-core's Introduction

rcs-core

Build Status Coverage Status

rcs is short for rename css selectors

Why?

Having long CSS selectors, such as main-menu__item--disabled, can increase the filesizes. With this rcs-core it is easy to rename the selectors and therefore reduce the filesize. You can save around 20% of the filesize by just shorten the CSS selectors in the CSS files.

What does it do?

It basically just rename/minify all CSS selectors in all files. First the library has to be trained with selectors. Based on this data, the selectors can be renamed in all files. Here are some examples made with Bootstrap files.

Some live projects:

Caveats

Correctly using rcs-core or any of its plugins on large project means few rules should be followed.

This document explains most of them.

Installation

$ npm install --save rcs-core

or

$ yarn add rcs-core

Usage

Note couple of selectors are excluded by default. You can activate them by using .setInclude before you fill the library

  1. Fill your library with all selectors (we assume there is just one CSS file)
// excluding specific selectors
rcs.selectorsLibrary.setExclude('selector-to-ignore');
// include specific selectors which has been ignored by default
rcs.selectorsLibrary.setInclude('center');

rcs.fillLibraries(fs.readFileSync('./src/styles.css', 'utf8'));
  1. Optimize the selectors compression (optional)
rcs.optimize();
  1. Rewrite all files

Note: Do not forget to replace your CSS file

const css = rcs.replace.css(fs.readFileSync('./src/styles.css', 'utf8'));
const js = rcs.replace.js(fs.readFileSync('./src/App.js', 'utf8'));
const html = rcs.replace.html(fs.readFileSync('./src/index.html', 'utf8'));

// output some warnings which has been stacked through the process
rcs.warnings.warn();

fs.writeFileSync('./dist/styles.css', css);
fs.writeFileSync('./dist/App.js', js);
fs.writeFileSync('./dist/index.html', html);

API documentation

Plugins

node-rcs-core's People

Contributors

bertyhell avatar dependabot[bot] avatar dutiyesh avatar jpeer264 avatar x-ryl669 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

node-rcs-core's Issues

Move Documentation

Move the documentation to a own docs dir, in case there would a be website to get a cleaner structure.

Short class conflict with renamed class

Related to #85, let's say you have <div class='test'> and in javascript var sel = document.querySelector('a')
In the current code, when test is renamed, it's renamed to a.
Then, when a is looked in the javascript, it's being used as-is, instead of being renamed to something else, thus, in the former code, sel is empty, while, once renamed, it won't be.

Adding this test in test/selectorLibrary.js show the issue:

test('get | insure no mix if using existing selector', (t) => {
  t.context.setSelectors();

  const testSelector = rcs.selectorLibrary.get('test');
  const aSelector = rcs.selectorLibrary.get('a');

  t.is(testSelector, 'a');
  t.not(aSelector, 'a');
});

Here, a is not found in the replaced list and returned as-is (while it should not).
This case seems rare or impossible to fix, but sometime you'll get elements from ajax, and the JS code only expect to receive those element when querying them and not the initial document's renamed version.

Replacing attribute-selectors in a smart way

As mentioned in #33 (comment) it should be possible to prefix/suffix out the attribute selectors to achieve smaller selectors which are selectable by attribute selectors.

Example before:

.select-a-long-name, .select-another-name {
  margin-left: 10px;
}
.board > [class^=select-] {
  padding: 0;
}

After

.selecta, .selectb {
  ...
}
.a > [class^=select] {
  ...
}

html parsing problem

html:

<p class="cl1">text with 'single quote</p>
...
<p class="cl1">another s'ingle quote</p>

js:

rcs.replace.any(html);

result:

"cl1|single quote</p>...<p class="cl1">another s"

espree can't handle inline JSONs

Using rcs.replace.html fails for markup that contains inline JSONs like this one for example:

<amp-animation id="switchFormat" layout="nodisplay">
  <script type="application/json">
    {
       "duration": "0.4s",
       "delay": "0.4s"
    }
  </script>
</amp-animation>

I am not quite sure what's the best way to handle this - maybe exclude specific <script>-Types from parsing? There might also be an espree configuration that allows object literals ...

Feat: more trigger options in replace.html

(ref: #52)

By now just id and class attributes will be triggered. data-* or similar attributes will not get triggered.

Suggested options:

  • triggerClassAttributes - for triggering JUST class attributes
  • triggerIdAttributes - for triggering JUST id attributes

Types: <Array|RegExp>

Definition:
If, e.g. triggerClassAttributes has the value [/data-*/, 'my-custom-attribute'], then all data attributes and the my-custom-attribute will get triggered and the content inside will get replaced by just the set class selectors.

Example:

const rcs = require('rcs-core');

// first set the id to replace
rcs.selectorLibrary.set('.my-class');

const replacedHtml = rcs.replace.html(`
  <div>
  	<element
      class="my-class"
      data-anything="my-class"
      my-custom-attribute="my-class"
      any-attribute="my-class"
    />
  </div>
`);

// outputs following:
//  <div>
//   <element
//     class="a"
//     data-anything="a"
//     my-custom-attribute="a"
//     any-attribute="my-class" <-- stays the same
//   />
// </div>

Triggers selectors in CSS block

Related: node-rename-css-selectors#2
Trigger classes which are actually no classes.

.my-class {
    filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
}

will trigger the classes:

.my-class, .Microsoft and .gradient(.

Fix: Do not trigger selectors within blocks

Replacing @keyframes name doesn't replace animation name

With new replaceKeyframes: true option, this code

@keyframes Spinner_rotate_21ZiX{
  0%{
    transform:rotate(0deg);
  }
  100%{
    transform:rotate(360deg);
  }
}
.Spinner_spinner_LszC2{
  animation:Spinner_rotate_21ZiX 1.1s infinite linear;
}

is transformed into

@keyframes t{
  0%{
    transform:rotate(0deg);
  }
  100%{
    transform:rotate(360deg);
  }
}
.n{
  animation:Spinner_rotate_21ZiX 1.1s infinite linear;
}

template in javascript aren't replaced

Let's say you have this:

var el = `<div class="item" id="it_${i}" draggable="true"><i class="i-bolt"></i><b>${days}</b><span>${time}</span><i class="br"></i><a class="tr i-bin"></a></div>`;

This is not replaced because the engine only check for String literal and this appears as a Template node (not to confuse with html's template tag)
I think it can be fixed easy by changing replace/js.js that reads:

traverse(ast, {
    pre: (node) => {
      if (node.type === 'Literal' && typeof node.value === 'string') {
// eslint-disable-next-line no-param-reassign

should read

traverse(ast, {
    pre: (node) => {
      if ((node.type === 'Literal' || node.type === 'Template') && typeof node.value === 'string') {
// eslint-disable-next-line no-param-reassign

Selector matching a HTML's node shouldn't be replaced in querySelector

Example of failing code:

<p class='b'>zurg</p>
<div id="zorg">It is <span></span>, for <b><b> days</div>
  var el = document.getElementById('zorg'); 
  el.querySelector('span').textContent = time;
  el.querySelector('b').textContent = days;

Here, the 'b' selector is understood as some class by rcs and not as a HTML's element name.
I wonder if it wouldn't be better to improve the JS parser in order to assign a flag to each string if it's inside an unknown string, or inside a function expecting a selector (like querySelector and querySelectorsAll)

In the later case, only valid CSS selectors will be changed (that is, with a . or # prefix).

This won't break code like:

  var t = "someclass";
  var el = document.querySelector('.' + t);

since the former StringLiteral will be replaced as usual.

This will still break code like (like currently):

   var t = "b";
   var el = document.querySelector(t);

Another solution is to reserve all HTML's possible element name (like a, b, i, q, s, u, ul, etc...) and exclude any mapping using them.
That's probably safer but will limit the possible compression ratio.

What do you think ?

selectorLibrary.set() does not check if the given selector is valid.

Using selectorLibrary.set() does not throw any errors if the given selector is incorrectly formatted.

This can easily lead to some funky bugs, for example if you try to do selectorLibrary.setMultiple(selectorLibrary.getAll()) without specifying addSelectorType: true for getAll(). I was essentially doing this when exporting/importing the library to a disk cache, which eventually led to the cache blowing up with tons of invalid selectors that did nothing except slow down the program. It was also a slightly hard to detect error since everything appeared to function normally until you by chance happened to look at the contents of the cache.

I would suggest doing a quick check that the first character is at least "." or "#".

Shrink replace.css

replace.css should just replace the css files, and should not fill up the library.

selectorLibrary.set() does not check if the given selector is valid.

Using selectorLibrary.set() does not throw any errors if the given selector is incorrectly formatted.

This can easily lead to some funky bugs, for example if you try to do selectorLibrary.setMultiple(selectorLibrary.getAll()) without specifying addSelectorType: true for getAll(). I was essentially doing this when exporting/importing the library to a disk cache, which eventually led to the cache blowing up with tons of invalid selectors that did nothing except slow down the program. It was also a slightly hard to detect error since everything appeared to function normally until you by chance happened to look at the contents of the cache.

<label for='id'> should be replaced

Currently, if you have a <label> element, the for attribute expect an id. Since ids are renamed, so should be the for attribute.

I'm trying to correct this and submit a PR.

Minified javascript with classnames

I've narrowed down why classnames in my 1Mb minified javascript bundle are not replaced with renamed ones.
AttributeList_table_vdlBA and AttributeList_key_3npMX are classnames.

function(e,t){e.exports="\t\n\v\f\r   ᠎              \u2028\u2029\ufeff"},function(e,t){e.exports={table:"AttributeList_table_vdlBA",key:"AttributeList_key_3npMX"}}

==>

function(e,t){e.exports="\t\n\v\f\r   ᠎              \u2028\u2029\ufeff"},function(e,t){e.exports={table:"AttributeList_table_vdlBA",key:"AttributeList_key_3npMX"}}

If I put line brake between two functions, replacement gets back to normal

function(e,t){e.exports="\t\n\v\f\r   ᠎              \u2028\u2029\ufeff"},
function(e,t){e.exports={table:"AttributeList_table_vdlBA",key:"AttributeList_key_3npMX"}}

==>

function(e,t){e.exports="\t\n\v\f\r   ᠎              \u2028\u2029\ufeff"},
function(e,t){e.exports={table:"nM",key:"iH"}}

feat: warn on unused class names

It will be good to have warnings or some statistics on unused classes, this will help developers to clean unused code.
As I can see it possible to store in Set each class name during parsing and finally compare with collected names.

minification problem

I noticed different results in minified files, both css and js, in these cases it does not seen to work properly.

Code to reproduce:

const rcs = require('rcs-core');

const css = `
.test{
  background-color: red
}
.test-two{
  color: rgb(0,0,0)
}
.test-three{
  color: rgb(1,1,1)
}
`;

const js = `
<Component className={'test'}/>
<Component className="test-two test test-three"/>
<Component className={'test-three'}/>
`

const minRegex = /\r?\n|\r|\s/g;

const minCss = css.replace(minRegex, '');

const replacedBuffer = rcs.replace.bufferCss(new Buffer(minCss));
console.log('replacedBufferCSS', replacedBuffer.toString('utf-8'));

const replacedBufferJS = rcs.replace.buffer(new Buffer(js));
console.log('replacedBufferJS', replacedBufferJS.toString('utf-8'));

Console output:

replacedBufferCSS: .t{background-color:red}.t-two{color:rgb(0,0,0)}.t-three{color:rgb(1,1,1)}
replacedBufferJS:
<Component className={'t'}/>
<Component className="test-two t test-three"/>
<Component className={'test-three'}/>

Last color without semicolon breaks next classname renaming

I use rename-css-selectors after webpack with postss (and csswring) have finished their work.

So css goes to rcs-core in minify state, without last semicolon inside class body. And seems like it breaks renaming.

Here is minimal example:

.link{background:#616060}.tag{display:flex}

is transformed into

.t{background:#616060}.tag{display:flex}

testing keys in a object shouldn't be replaced

Let's say you have this:

<style>
.content { color: red }
</style>
<template id='test'></template>
<script>
  // Test if HTML5
  var temp = document.getElementById("test");
  if ("content" in temp)
  {
     // It's HTML5
  }
</script>

Here, content in "content" in object will be replaced but it shouldn't. In fact, as far as I understand it, there is no case where a "key" in obj should be replaced, since you should never have CSS class or id or type as the key of some JS objects.
In the recast AST, you'll find that the String literal is a child of a BinaryExpression with operator in. In that case, it easy to fix, replace in replace/js.js, code:

traverse(ast, {
    pre: (node) => {
      if (node.type === 'Literal' && typeof node.value === 'string') {
// eslint-disable-next-line no-param-reassign

with

traverse(ast, {
    pre: (node) => {
      // Avoid recursing into a "in" node since it can't be a CSS class or variable. 
      if (node.type === 'BinaryExpression' && node.operator === 'in') 
          return false;
      if (node.type === 'Literal' && typeof node.value === 'string') {
// eslint-disable-next-line no-param-reassign

Make global pre- and suffix

Pre- and suffixes (PnS), should be set in fillLibraries. selectorLibrary constructor should additionally get this.prefix and this.suffix

PnS should not get set in selectorLibrary.set. PnS will be just appended in selectorLibrary.get or selectorLibrary.getAll

todo:

  • add global PnS in selectorLibrary
  • remove PnS in selectorLibrary.setValue
  • remove compressedSelectorPlain
  • add setPrefix, setSuffix in selectorLibrary
  • remove PnS in replace.css#getAttributeSelector, use global PnS instead
  • remove plainCompressed option in selectorLibrary.getAll

Motivation:

get rid of options in replace.css

Plugin for postcss and webpack

Hi, Thanks for great project!

I'm just thinking, since modern css pipeline in most cases is built around webpack/postcss, would be great to have corresponding plugins for rcs.

For instance, I'm using css-modules, that means in development I use postcss-, css- and styles- loaders for webpack rules stack.
And for production build I additionally use extract-text-webpack-plugin to extract it into separate css bundle and then apply postcss css-mqpacker and csswring plugins on that result bundle. I wish there also was rcs plugin for postcss to include it into that last stage of production assembly process, so having source maps would be possible.
In case of css-modules, rcs should have two plugins - for postcss to gather and replace class names in css bundle, and webpack plugin to replace mapped names in result js chunks.

The order of parsing CSS file breaks the renaming

Ok, this is a more complex issue.
To simplify, let's say you have 2 CSS files containing:
B.css

.i-icon:before { color: red; }

A.css

[class^="i-"]:before {
  font: myfont;
}

.i-icon:before {
   content: '\f101';
}
.i-file:before {
   content: '\f102';
}

If B.css is parsed first, then the selector .i-icon will be mapped to .a.
If A.css is parsed first, then the selector .i-icon will be (correctly) mapped to .i-a (because of the attribute selector above).

In the former case, all the rules in the attribute selector are ignored, and this is a bug.

Change buffer to code

Available methods just take code:

  • replace.any
  • replace.css
  • replace.js

or buffer:

  • replace.any
  • replace.css
  • replace.js

Remove:

  • replace.buffer
  • replace.bufferCss
  • replace.bufferJs

webpack plugin and way to change the regex only in js target files

Nice work. I was looking for a webpack plugin and I ended up falling here. It would be interesting to create one besides the gulp and grunt.

In my case, working on a react project, how could I change the regex to match the js target file, since the classname attribute in react is not prefixed by dots?

<Component className="class-name-example" />

Update espree

Hi @JPeer264!
Seems like it's time to update espree : )
rename-css-selectors fails on optional catch binding in a code (catch without the arguent)

Exclude regex

In exclude it would be nice to have a second param for regex (either string or array):

rcs.selectorLibrary.setExclude('no-js', /[a-z]/);

rcs.selectorLibrary.setExclude('no-js', [/[a-z]/, /[A-Z]/]);

Attribute selectors not replaced

I'm trying to replace some attribute selectors, for example [class*=" cls-"] and [class^=cls-]. Tweaking ignoreAttributeSelectors doesn't make any difference.

The node-rcs-core Bootstrap example looks broken as well. Rule [class*="col-"] is unaffected while all other col-* selectors were replaced.

Before:

.no-gutters > [class*="col-"] {
padding-right: 0;
padding-left: 0;
}
.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl {

After:

.P > [class*="col-"] {
padding-right: 0;
padding-left: 0;
}
.B, .j, .F, .I, .q, .R, .U, .z, .W, .X, .V, .J, .H, .K, .Q, .G, .Y, .Z, .te, .tt, .tn, .tr, .ti, .ts, .to, .tu, .ta, .tf, .tl, .tc, .th, .tp, .td, .tv, .tm, .tg, .ty, .tb, .tw, .tE, .tS, .tx, .tT, .tN, .tC, .tk, .tL, .tA, .tO, .tM, .t_, .tD, .tP, .tH, .tB, .tj, .tF, .tI, .tq, .tR, .tU, .tz, .tW, .tX, .tV {

escodegen is not in dependencies

Error: Cannot find module 'escodegen'
    at Function.Module._resolveFilename (module.js:470:15)
    at Function.Module._load (module.js:418:25)
    at Module.require (module.js:498:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (/~/rcs-core/lib/options/replace.js:8:19)
    at Module._compile (module.js:571:32)
    at Module._extensions..js (module.js:580:10)
    at Object.require.extensions.(anonymous function) [as .js] (/~/babel-register/lib/node.js:152:7)
    at Module.load (module.js:488:32)
    at tryModuleLoad (module.js:447:12)

Support querySelector and querySelectorAll

I just tried out gulp-rcs for a static site that I'm ceating and everything seems to work well except that the js parser doesn't seem to support querySelector and querySelectorAll. My guess is that its because it doesn't match 'xxx' or "xxx". In the below cases it can have a leading . or # and that there can be more complex patterns (i.e. .xxx .ttt).

Here is my sample thats currently failing:

    var simple_dots = document.querySelector('#js-carousel');
    var dot_count = simple_dots.querySelectorAll('.js_slide').length;
    var dot_container = simple_dots.querySelector('#js-slider-indicators');

Short class should not be replaced

Let say you have <i class="br"></i>

Unless you have a toy code, this might be mapped to a 2 letter class (like ag) or worse, to a 3 letter class (like tra). This is problematic because:

  1. You are not saving anything (you are increasing the code size instead of reducing it)
  2. The mapping is not easy to follow (br now is about .whatever)
  3. It would be very useful to be able to have some "keys" that are never modified so that they can be dealed with in the code untouched (like this: if (corner == "bottom") class='b'; else class='t'; if (side == "right") class += 'r' else class += 'l';). I know about the exclude rules, but if you have to list all possibilities in your code, it'll be a mess and hard to maintain.

Typically, let's say the code does this, first it extract all CSS class/id. If a class or id is already found in the "already" mapped class/id then relocate the former mapping to a new one so this class or id can be used directly (either it's removed from the mapping table or it maps to itself). That way, you know that small class or id in your code are left untouched and can be generated dynamically (they are kind of reserved for you).

Unfortunately, I don't know enough of this code to give a fix for this issue.

Publish 0.9.1

Seems like npm doesn't have latest 0.9.1 version, only 0.8.4.

Last color without semicolon breaks next classname renaming 2

Related to #13 : )
But now if last property value without semicolon ends with rem

.ToolBar{padding:0 .357143rem}.ToolBar_selected{color:#0f705d}

==>

.t{padding:0 .357143rem}.t_selected{color:#0f705d}

Or such media query

@media(max-width:480px){.AttributeList{display:block}.Header{display:table}}

==>

@media(max-width:480px){.AttributeList{display:block}.t{display:table}}

rcs-core version 0.9.1

parse html doesn't parse template tag

<div class="will-be-parsed">
  <template type="some_type">
    <div class="will-not-be-parse"></div>
  </template>
</div>

Parse5 handles template tag differently, children of template tag are placed in to content property.

Javascript with classnames 2

After last fix of #17, replacement in my regular (not minified) javascript bundle has become broken : )
I narrowed it down to that example. Classes AttributeList_table_vdlBA, AttributeList_key_3npMX are not replaced anymore.

/**
 * Finds the index of the first character
 * that's not common between the two given strings.
 *
 * @return {number} the index of the character where the strings diverge
 */
function firstDifferenceIndex(string1, string2) {
  var minLen = Math.min(string1.length, string2.length);
  for (var i = 0; i < minLen; i++) {
    if (string1.charAt(i) !== string2.charAt(i)) {
      return i;
    }
  }
  return string1.length === string2.length ? -1 : minLen;
}

/***/ (function(module, exports) {

// removed by extract-text-webpack-plugin
module.exports = {"table":"AttributeList_table_vdlBA","key":"AttributeList_key_3npMX"};

/***/ }),
/* 523 */
/***/ (function(module, exports, __webpack_require__) {

var defineProperty = __webpack_require__(531);

/**
 * The base implementation of `assignValue` and `assignMergeValue` without
 * value checks.
 *
 * @private
 * @param {Object} object The object to modify.
 * @param {string} key The key of the property to assign.
 * @param {*} value The value to assign.
 */
function baseAssignValue(object, key, value) {
  if (key == '__proto__' && defineProperty) {
    defineProperty(object, key, {
      'configurable': true,
      'enumerable': true,
      'value': value,
      'writable': true
    });
  } else {
    object[key] = value;
  }
}

module.exports = baseAssignValue;


/***/ }),

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.