Persistent Dev Tools CSS Modifications

I would like to either find or build my own mechanism for changes users make to the dom css in dev tools to be saved. Upon revisiting the page the saved styles in dev tools would be re-applied to the page. Has anyone come across a tool or project that does this.

For example, on this simple page if someone were to change the back-ground color of the highlighted paragraph to red. JavaScript would pick up that change and persist the change to back-end. When the page is revisited the change would automatically be applied making the back-ground color of the paragraph indefinitely red for all visits going forward.

Failing to find an existing solution I have been considering the architecture for something like this. My thoughts are to build some type of generic library that can be added to a page and listen for changes to the dom css. When those changes occur go through some type of diff process to determine the styles that have been added or removed from the default css. Save those changes as flat files in the cloud. When the user revisits pull down the changes for the page and apply them using javascript which I’m already doing in some places already.

Although I have only thought about this having not directly experimented believe that mutationobserver can be used for this purpose. To effectively listen for changes to dom elements and act accordingly to effectively generate a structure that represents mutating the default dom css based on diffing.

1 Like

This might give some pointers

1 Like

There is some good info there. I think I’m in pursuit of a solution more closely aligned with my existing platform. Doesn’t require knowing a whole lot except just opening the dev tools editor and having changes just magically save. I think that is going to require using mutation observer to act on changes to act on changes to the dom and build diffs that can be reapplied.

I begin experimenting with this.

Using mutation observer seems to work. When the style attribute is changed in dev tools that change is picked up and recorded.

repo: https://github.com/ng-druid/sheath

1 Like

Nice one.

I was wondering what you would output and where you would store it — I see localstorage.

It did cross my mind whether you would create a new stylesheet with just amended styles and whether that was workable.

This only serves as a proof of concept which is working out quite well it seems (knock on wood). The end goal will be to save the overrides in a flat file on s3. When users visit a page that matches overrides apply the overrides using JavaScript to work around some of problems regarding default library and theme styles. I’m not sold on leaving this as a stand alone library or just adding it as a new Angular library under my main platform project. While I would like to maximize its flexibility building it will be far easier if it where integrated into my existing platform project once all the main concepts are worked out. I think it makes more sense to make it part of my main platform as an Angular library now that my platform supports module federation. Had I not implemented module federation I would highly consider taking the more difficult route of making it a stand alone library.

1 Like

Surprisingly with the addition of some very helpful libraries it has been fairly easy to achieve this. Without those libraries though it would be much more difficult.

import domElementPath from 'dom-element-path';
import { toJSON } from 'cssjson';
import { camelize, dasherize, underscore } from 'inflected';
import merge from 'deepmerge-json';

export default function stylize({ save }) {
  const overlay = new Map();
  const observer = new MutationObserver((records) => {
    records.forEach(r => {
      if (r.type === 'attributes' && r.attributeName === 'style' && r.target) {

        const path = domElementPath(r.target);

        const oldCssAsJson = toJSON(`${path} { ${r.oldValue} }`);
        console.log('oldCssAsJson', oldCssAsJson);

        const oldCssAsObject = Object.keys(oldCssAsJson.children[path].attributes).reduce((p, c) => ({ ...p, [camelize(c.replace('-', '_'), false)]: oldCssAsJson.children[path].attributes[c] }), {});
        console.log('oldCssAsObject', oldCssAsObject);

        const newCssAsObject = Object.keys(r.target.style).reduce((p, c) => parseInt(c) !== NaN ? { ...p, [camelize(r.target.style[c].replace('-', '_'), false)]: r.target.style[camelize(r.target.style[c].replace('-','_'), false)] } : p, {});
        console.log('newCssAsObject', newCssAsObject);

        const merged = merge(oldCssAsObject, newCssAsObject);
        console.log('merged', merged);

        overlay.set(path, merged);
        console.log('overlay changed', overlay);

        const rules = [];
        overlay.forEach((v, k) => {
          rules.push(k + ' { ' + Object.keys(v).reduce((p, c) => `${dasherize(underscore(p))}${c}:${v[c]};`, ``) + ' }');
        });

        console.log('rules', rules);

        const mergedCssAsJson = toJSON(rules.join(''));
        console.log('mergedCssAsJson', mergedCssAsJson);

        save({ mergedCssAsJson });

      }
    });
  });
  const targetNode = document.getElementsByTagName('body')[0];
  const observerOptions = { childList: true, attributes: true, subtree: true, attributeFilter: [ 'style' ], attributeOldValue: true }
  observer.observe(targetNode, observerOptions);
}
1 Like

I did spot that. :slight_smile:

In local storage the css is stored as a json object. This can only be done easily thanks to cssjson library. The cssjson library makes easy convert a string of css to json and back.


When the page is revisited the css override object will be pulled from local storage and reapplied to the dom using css-select library. That part has yet to be implemented but is the future vision.

Normally there would be a flicker when rendering css like this in the browser. However, when integrated into the main Angular project pre-rendering pages server-side will result in the physical pages actually containing the css overrides. Preventing the flicker from occurring since all pages will be pre-rendered.

I have been able to successfully do this.

Dev tools style changes are being saved so that they can be reapplied on future page loads.

I created a new sheath Angular module that manages this functionality using the concepts explored above but reactive and service based instead.

Example json:

{
   "id":"a04c3022-cc94-4db0-826b-258efde4abab",
   "styles":{
      "children":{
         ".panel-page   div.ng-untouched.ng-pristine.ng-valid  classifieds-ui-flex-layout-renderer  classifieds-ui-flex-layout  div  div  div  div  div.grid-item-inner":{
            "children":{
               
            },
            "attributes":{
               "height":"auto"
            }
         },
         ".panel-page .panel-0 .pane-0   div.ng-untouched.ng-pristine.ng-valid  classifieds-ui-snippet-pane-renderer  classifieds-ui-dynamic-content-viewer  p":{
            "children":{
               
            },
            "attributes":{
               "background-color":"red"
            }
         }
      },
      "attributes":{
         
      }
   }
}
2 Likes

I have been having a look at regex concatenation, in particular with regex literals.

This is a console.dir of an example regex.

v /\/\*[\s\S]+?\*\//
lastIndex: 0
dotAll: false
flags: ""
global: false
...
source: "\\/\\*[\\s\\S]+?\\*\\/"
...

The source property is the one of interest to me. Taking that I was able to build a simple helper function.

const concatRegexes = (flags = '', ...regexs) =>
  new RegExp(regexs.map((regex) => regex.source).join('|'), flags)

And testing it out

const commentsRx = /\/\*[\s\S]+?\*\//
const selectorRx = /((?:[.# ]?[-\w:]+)+)/
const declarationsRx = /\{\s*([\s\S]+?)\s*\}/

const cssRegexes = concatRegexes('gm', commentsRx, selectorRx, declarationsRx)

console.log(cssRegexes)

Output

/\/\*[\s\S]+?\*\/|((?:[.# ]?[-\w:]+)+)|\{\s*([\s\S]+?)\s*\}/gm

I can see from your concatenation that there are a couple of look-aheads introduced into the mix, so this doesn’t necessarily apply, but for what it’s worth just thought I would share.

That isn’t my code. The npm package for that lib has an error in it. Considering the size of the package is so small I decided the easiest work around is to copy the code and correct it directly.

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.