Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion: Adopt the j2c v1 backend API (and get automatic prefixes for free) #7

Open
pygy opened this issue Sep 18, 2017 · 5 comments

Comments

@pygy
Copy link

pygy commented Sep 18, 2017

Hi, @porsager I just notice this thanks to @JAForbes' tweet.

Would you mind adopting the j2c backend API? The general idea is that, rather than having your functions return strings, you pass a backend that accumulates them. So for example

export function selectorBlock(selector, style) {
  return selector + '{'
    + stylesToCss((typeof style === 'string' ? stringToObject(style) : style))
    + '}'
}

would become

export function selectorBlock(selector, style, backend) {
  backend.rule(selector)
  stylesToCss((typeof style === 'string' ? stringToObject(style) : style))
  backend._rule()
}

and

function propToString(style, k) {
  return (vendorRegex.test(k) ? '-' : '')
  + camelCaseToHyphen(k) + ':' + style[k] + ';'
}

would become

function emitDecl(style, k, backend) {
  backend.decl((vendorRegex.test(k) ? '-' : '') + camelCaseToHyphen(k), style[k])
}

The main advantage here is that it allows one to write plugins that decorate the backend, like this prefixing plugin that no one uses currently because I've yet to publish j2c v1 that uses the same architecture.

The prefix plugin supports more than one prefix per browser (FF and Edge have some properties that are exclusively available with a webkit prefix), is IIRC 3KB mingzipped and uses feature detection rather than a list of properties/selectors, etc... like autoprefixer and the inline style prefixer.

The architecture makes it trivial to turn one rule into many (e.g. flex-direction => -webkit-box-direction + -webkit-box-orient) without causing the allocation of extra objects.

Edit I can't English tonight...

Edit2: here's the full feature list: https://github.com/j2css/j2c/tree/3039875258bd69f04a16c51b69ca71361b5d5560/plugins/prefix-browser#features

Edit3: here's the full backend (sink) API.

@porsager
Copy link
Owner

Hi @pygy

That looks interesting.. I'm a bit behind with various stuff, so I hope its ok if I first take some time to look closer and grok this later on the week?

@pygy
Copy link
Author

pygy commented Sep 19, 2017

@porsager sure take your time :-)

@pygy
Copy link
Author

pygy commented Sep 19, 2017

Adding some clarifications, because there are j2c specific bits in the docs.

When you are ready, start here:

The backend/filter API provides a PostCSS-like system without a need to materialize the AST. Your engine calls it like this:

backend.init()
  backend.atrule('@-webkit-media', 'media', 'screen', 'rule' /*takes a block of rules*/)
    backend.rule('.foo')
      backend.decl('color', 'red')
    backend._rule()
  backend._atrule()
result = backend.done()

Which becomes with the default j2c backend the following string.

@-webkit-media screen {
  .foo {
    color: red;
  }
}

Plugins take the form of a decorator function that takes the backend as argument and return an object with a subset of the backend method:

function autoPxPlugin(next) { // next is the backend so far
  return {
    decl(prop, value) {
      next.decl(
        prop,
        needsPx(prop, value) ? value + 'px' : value
      )
  }
}

function needsPx(prop, value){
  // left as an exercise for the reader
}

The plugins are baked in when the lib is initialized:

const plugins = [autoPxPlugin]

const backend = plugins.reduce(
  (backend, plugin) => Object.assign({}, backend, plugin(backend),
  sink // the bare backend
)

The err() method allows one to display errors in context (try to add or remove a letter from an at-rule in the prefix-plugin playground), and the raw() method inserts raw text into the buffer. Not sure how useful they would be for bss.

Here's the default j2c sink:

let buf, err

const sink = {
  init: function () {buf=[], err=[]},
  done: function () {
    if (err.length != 0) throw new Error('j2c error(s): ' + JSON.stringify(err,null,2) + ', in context:\n' + buf.join(''))
    return buf.join('')
  },
  err: function (msg) {
    err.push(msg)
    buf.push('/* +++ ERROR +++ ' + msg + ' */\n')
  },
  raw: function (str) {buf.push(str, '\n')},
  atrule: function (rule, kind, param, takesBlock) {
    buf.push(rule, param && ' ', param, takesBlock ? ' {\n' : ';\n')
  },
  // close atrule
  _atrule: function () {buf.push('}\n')},
  rule: function (selector) {buf.push(selector, ' {\n')},
  // close rule
  _rule: function () {buf.push('}\n')},
  decl: function (prop, value) {buf.push(prop, ':', value, ';\n')}
}

Provided you are inserting rules using insertRule() may have to monitor the nesting level and insert when it reaches 0 when either _rule() or _atrule() are called (assuming it is possible for a single call to generate more than one rule).

@porsager
Copy link
Owner

porsager commented Oct 5, 2017

Hi @pygy .. Sorry for not getting back sooner.

bss already does automatic vendor prefixes and eg. px suffixing, but it is also browser only, relying on dom vendor prefix detection to do it.

I've been trying to wrap my head around this, and if I get it right implementing this would mainly be a benefit when generating actual stylesheets outside the browser for eg. SSR right?

I have some ideas for how to make SSR possible with bss, and they might fit well with the j2c backend api, but I also need to do a bit more, such as enabling the function calling style of writing css which bss provides.

I might be misunderstanding some things, so if it's easier to discuss on gitter we can also continue there ;)

@pygy
Copy link
Author

pygy commented Oct 5, 2017

@porsager The prefix plugin is client-side only at the moment, for SSR, I have a PostCSS adapter that creates a PostCSS AST out of what you feed it, applies PostCSS plugins and walks the resulting tree to call the next part of the backend...

I had missed the vendor prefix detection in bss... AFAICS, it only prefixes properties. The prefix plugin linked above also prefixes values (keywords, functions, properties as values (transition: , will-change:, ...), selectors (pseudo elements, ...), and at-rules (the rules themselves and their parameters where needed e.g. @media (min-resolution: 2dppx) to whatever the browser supports: -webkit-min-resolution: 2 or min-resolution: 192dpi, etc. for old Firefox and Opera). It also translates the final flexbox spec to the older variants, sometimes expanding one shortcut property to multiple ones.

I may have missed some things, the full feature list is in the readme.

For gitter, sure, the j2c channel would seem appropriate unless you have one for bss (porsager/bss returns a 404)

https://gitter.im/j2css/j2c

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants