streamich / nano-css

Distilled CSS-in-JS for gourmet developers
The Unlicense
431 stars 24 forks source link

Source maps #74

Open elektronik2k5 opened 6 years ago

elektronik2k5 commented 6 years ago

Hi @streamich,

Awesome and thorough work on this! :) I believe css-in-js is the future and am always glad to see new and ambitious projects. I also like how everything is optional, pluggable and that nothing is opinionated and that it is framework/runtime agnostic.

I realize it's a new project and probably still lacks some features, and I bet you've got a list of these too :). One thing which I personally, can't live without in css-in-js (and JS transpilers) is source maps. In the world of css-in-js, I've only used styled-components and emotion so far. The latter supports source maps, while the former doesn't. To me, it is such a detrimental feature that I may switch from styled to emotion in a large project I lead.

Hope you add source maps to nano-css at some point. I'll be keeping an eye on your project, since it seems promising.

streamich commented 6 years ago

@elektronik2k5 what do you think about having devtools for CSS, like a panel in Kuker?

https://github.com/krasimir/kuker/issues/6

elektronik2k5 commented 6 years ago

Thanks for introducing me to Kuker.

In my view, it is an interesting tool, but by no means a replacement for source maps. Source maps "just work" in all modern browsers, while Kuker requires both app runtime and an extension. That is a very high barrier to entry - just to inspect styles.

They can be complementary tools though.

streamich commented 6 years ago

Example source map implementation:

/* eslint-env browser */

import StackTrace from "stacktrace-js";
import {encode} from "sourcemap-codec";

const cache = {};

const schedule = window.requestIdleCallback
  ? task => window.requestIdleCallback(task, {timeout: 180})
  : window.requestAnimationFrame;

let counter = 0;
let queue = [];
let debugEnabled = false;

export function enableDebug() {
  debugEnabled = true;
}

export function addDebugClass(baseStyletron, stackIndex) {
  if (!debugEnabled) {
    return;
  }
  const {className, selector} = getUniqueId();
  baseStyletron.debugClass = className;

  const trace = getTrace();

  trace
    .then(stackframes => {
      const {fileName, lineNumber} = stackframes[stackIndex];
      addToQueue({selector, lineNumber, fileName});
    })
    .catch(err => console.log(err)); // eslint-disable-line no-console
}

function flush() {
  const {rules, segments, sources} = queue.reduce(
    (acc, {selector, lineNumber, fileName}) => {
      let sourceIndex = acc.sources.indexOf(fileName);
      if (sourceIndex === -1) {
        sourceIndex = acc.sources.push(fileName) - 1;
      }
      acc.rules.push(`${selector} {}`);
      acc.segments.push([[0, sourceIndex, lineNumber - 1, 0]]);
      return acc;
    },
    {rules: [], segments: [], sources: []},
  );
  queue = [];

  const mappings = encode(segments);
  const map = {
    version: 3,
    sources,
    mappings,
    sourcesContent: sources.map(source => cache[source]),
  };

  const json = JSON.stringify(map);
  const base64 = window.btoa(json);

  const css =
    rules.join("\n") +
    `\n\/*# sourceMappingURL=data:application/json;charset=utf-8;base64,${base64} */`;
  const style = document.createElement("style");
  style.appendChild(document.createTextNode(css));
  document.head.appendChild(style);
}

function addToQueue(item) {
  const prevCount = queue.length;
  queue.push(item);
  if (prevCount === 0) {
    schedule(flush);
  }
}

function getTrace() {
  return StackTrace.get({sourceCache: cache});
}

function getUniqueId() {
  const id = counter++;
  const className = `__debug_${id}`;
  return {
    selector: `.${className}`,
    className,
  };
}
streamich commented 6 years ago

Add sourcemaps addon: https://github.com/streamich/nano-css/blob/master/docs/sourcemaps.md

Tell me if it works for you!