Skip to content

Commit

Permalink
transform: implement tagged templates
Browse files Browse the repository at this point in the history
Supersedes GH-29 GH-38

Implements GH-31

Precedes GH-21 GH-34 GH-35 GH-39

This is a wip patch, as it touches almost every line of code currently
present. It implements #31 mostly, but should provide the groundworks to
further expand upon. Also implementing tests as we go to verify features
work as advertised.

The most notable change is that this patch now uses `falafel` to modify
esprima nodes, as seen in
[hyperxify](https://github.com/substack/hyperxify). This allows require
calls to work both with template strings as with file requires.

progress
--------
- [x] detect and namespace template strings
- [x] write tests for template string modification
- [x] detect, read, and namespace files from disk
- [x] write tests for reading and transforming files
- [x] write tests for server - returns prefix if vanilla node
- [ ] write tests for correct plugin resolution
- [x] write tests for CSS injection in header in transform

clean up

test: apply std-fmt

docs: annotate source

{transform,index} -> {index,sheetify}

docs: add tagged template docs

[tmp] transform: add tests

[tmp] transform: find template strings

[tmp] transform: more stuff

[tmp] transform: fix template strings

[tmp] {sheetify, transform} -> {index,transform}

[tmp] test/fixtures/ -> test/basic/

[tmp] deps: remove unused

[tmp] test: test server

[tmp] test: add coverage report

[tmp] server: fix tagged templates

[tmp] transform: detect disk calls

[tmp] server: only parse if in transform

[tmp] server: refactor core logic

[tmp] index: fix assert calls

[tmp] transform: make async

[tmp] transform: fix and test

[tmp] transform: fix disk read

[tmp] deps: remove dead dev deps

[tmp] test: rm dead fixtures

[tmp] docs: add plugin docs

[tmp] plugins: add tests

[tmp] transform: fix from cli

[tmp] docs: fix examples

Signed-off-by: Yoshua Wuyts <i@yoshuawuyts.com>

Closes #41
  • Loading branch information
ahdinosaur authored and yoshuawuyts committed Feb 7, 2016
1 parent e2f21df commit da9c50d
Show file tree
Hide file tree
Showing 21 changed files with 394 additions and 314 deletions.
108 changes: 82 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,52 +12,108 @@
[![Downloads][downloads-image]][downloads-url]
[![js-standard-style][standard-image]][standard-url]

Modular CSS bundler. Works with [npm](http://npmjs.org/) modules like
[browserify](http://browserify.org/) does.
Modular CSS bundler for browserify. Works with [npm](http://npmjs.org/) modules
like [browserify](http://browserify.org/) does.

## Features
- rich plugin ecosystem
- namespaced CSS modules using browserify
- tiny API surface

## Installation
## Example
Given some inline CSS:
```js
const vdom = require('virtual-dom')
const hyperx = require('hyperx')
const sf = require('sheetify')
const hx = hyperx(vdom.h)

const prefix = sf`
h1 {
text-align: center;
}
`

const tree = hx`
<section className=${prefix}>
<h1>My beautiful, centered title</h1>
</section>
`

document.body.appendChild(vdom.create(tree))
```

Compile with browserify using `-t sheetify`:
```sh
$ npm install sheetify
$ browserify -t sheetify/transform index.js > bundle.js
```

## Usage
__js api__
## External files
To include an external CSS file you can pass a path to sheetify as
`sheetify('./my-file.css')`:
```js
const browserify = require('browserify')
const path = require('path')
const vdom = require('virtual-dom')
const hyperx = require('hyperx')
const sf = require('sheetify')
const hx = hyperx(vdom.h)

const prefix = sf('./my-styles.css')

browserify(path.join(__dirname, './index.js'))
.transform('sheetify/transform')
.bundle()
.pipe(process.stdout)
const tree = hx`
<section className=${prefix}>
<h1>My beautiful, centered title</h1>
</section>
`

document.body.appendChild(vdom.create(tree))
```

__package.json transform__
```json
{
"name": "my-app",
"browserify":{
"transform": [
"sheetify/transform"
]
Compile with browserify using `-t [ sheetify/transform -u sheetify-cssnext ]`:
```sh
$ browserify -t [ sheetify/transform -u sheetify-cssnext ] index.js > bundle.js
```

## Plugins
Sheetify supports [plugins](#plugins) that take CSS and apply a transform. To
include [sheetify-cssnext](https://github.com/sheetify/sheetify-cssnext) to
support autoprefixing, variables and more:
```js
const vdom = require('virtual-dom')
const hyperx = require('hyperx')
const sf = require('sheetify')
const hx = hyperx(vdom.h)

const prefix = sf`
h1 {
transform: translate(0, 0);
}
`

const tree = hx`
<section className=${prefix}>
<h1>My beautiful, centered title</h1>
</section>
`

document.body.appendChild(vdom.create(tree))
```
Transforms the CSS into:
```css
h1 {
-webkit-transform: translate(0, 0);
transform: translate(0, 0);
}
```

__cli__
The following plugins are available:
- [sheetify-cssnext](https://github.com/sheetify/sheetify-cssnext) - use
tomorrow's CSS syntax today

## Installation
```sh
$ browserity -t sheetify/transform index.js > bundle.js
$ npm install sheetify
```

## Plugins
- [sheetify-cssnext](https://github.com/sheetify/sheetify-cssnext) cssnext
plugin for sheetify

## License
[MIT](https://tldrlegal.com/license/mit-license)

Expand Down
120 changes: 59 additions & 61 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,86 +1,84 @@
const prefix = require('postcss-prefix')
const resolve = require('style-resolve')
const cssPrefix = require('postcss-prefix')
const nodeResolve = require('resolve')
const mapLimit = require('map-limit')
const postcss = require('postcss')
const assert = require('assert')
const crypto = require('crypto')
const fs = require('fs')
const callerPath = require('caller-path')
const path = require('path')

module.exports = sheetify

function sheetify (filename, options, done) {
if (typeof options === 'function') {
done = options
options = {}
}

done = done || throwop
options = options || {}

// default basedir option to
// path of module that called this module
options.basedir = options.basedir || path.dirname(callerPath())

filename = resolve.sync(filename, {
basedir: options.basedir
})
// transform css
// (str, str, obj?, fn) -> str
function sheetify (src, filename, options, push) {
// handle tagged template calls directly from Node
if (Array.isArray(src)) src = src.join('')
assert.equal(typeof src, 'string', 'src must be a string')

var src = fs.readFileSync(filename)
var id = '_' + crypto.createHash('md5')
const prefix = '_' + crypto.createHash('md5')
.update(src)
.digest('hex')
.slice(0, 8)

src = postcss()
.use(prefix('.' + id))
.process(src.toString())
.toString()
// only parse if in a browserify transform
if (filename) parseCss(src, filename, prefix, options, push)

transform(filename, src, options, function (err, src) {
return done(err, src, id)
})

return id
return prefix
}

function throwop (err) {
if (err) throw err
}
// parse css
// (str, str, str, obj, fn) -> null
function parseCss (src, filename, prefix, options, next) {
assert.equal(typeof filename, 'string', 'filename must be a string')
assert.equal(typeof prefix, 'string', 'prefix must be a string')
assert.equal(typeof options, 'object', 'options must be a object')
assert.equal(typeof next, 'function', 'done must be a function')

function transform (filename, src, options, done) {
var use = options.use || []
use = Array.isArray(use) ? use.slice() : [use]
const processedCss = postcss()
.use(cssPrefix('.' + prefix))
.process(src.toString())
.toString()

mapLimit(use, 1, iterate, function (err) {
if (err) return done(err)
done(null, src)
next(function (done) {
applyTransforms(filename, processedCss, options, function (err, css) {
return done(err, css, prefix)
})
})

function iterate (plugin, next) {
if (typeof plugin === 'string') {
plugin = [plugin, {}]
} else
if (!Array.isArray(plugin)) {
return done(new Error('Plugin must be a string or array'))
}

const name = plugin[0]
const opts = plugin[1] || {}
// apply transforms to a string of css,
// one at the time
// (str, str, obj, fn) -> null
function applyTransforms (filename, src, options, done) {
var use = options.use || []
use = Array.isArray(use) ? use.slice() : [ use ]

nodeResolve(name, {
basedir: opts.basedir || options.basedir
}, function (err, transformPath) {
mapLimit(use, 1, iterate, function (err) {
if (err) return done(err)
done(null, src)
})

const transform = require(transformPath)

transform(filename, src, opts, function (err, result) {
if (err) return next(err)
src = result
next()
// find and apply a transform to a string of css
// (fn, fn) -> null
function iterate (plugin, next) {
if (typeof plugin === 'string') {
plugin = [ plugin, {} ]
} else if (!Array.isArray(plugin)) {
return done(new Error('Plugin must be a string or array'))
}

const name = plugin[0]
const opts = plugin[1] || {}

const resolveOpts = { basedir: opts.basedir || options.basedir }
nodeResolve(name, resolveOpts, function (err, transformPath) {
if (err) return done(err)

const transform = require(transformPath)
transform(filename, src, opts, function (err, result) {
if (err) return next(err)
src = result
next()
})
})
})
}
}
}
7 changes: 0 additions & 7 deletions insert-css.js

This file was deleted.

41 changes: 14 additions & 27 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,41 @@
"name": "sheetify",
"version": "3.3.3",
"description": "Modular CSS bundler",
"repository": "sheetify/sheetify",
"license": "MIT",
"scripts": {
"test": "standard && tape test/*",
"test:cov": "standard && NODE_ENV=test istanbul cover test/*",
"deps": "dependency-check . --entry transform.js . && dependency-check . --entry transform.js --extra --no-dev -i insert-css",
"test": "standard && npm run deps && tape test/*",
"test:cov": "standard && npm run deps && NODE_ENV=test istanbul cover test/coverage.js",
"format": "standard --format"
},
"repository": "sheetify/sheetify",
"browser": {
"index.js": "./browser.js"
},
"keywords": [
"modular",
"css",
"bundle",
"browserify",
"css-modules"
],
"license": "MIT",
"dependencies": {
"astw-babylon": "^1.0.0",
"babylon": "^5.8.23",
"caller-path": "^0.1.0",
"cliclopts": "^1.1.1",
"css-prefix": "0.0.2",
"domify": "^1.4.0",
"escodegen": "^1.7.0",
"findup": "^0.1.5",
"falafel": "^1.2.0",
"insert-css": "^0.2.0",
"is-require": "0.0.1",
"map-limit": "0.0.1",
"minimist": "^1.2.0",
"noop2": "^2.0.0",
"postcss": "^5.0.10",
"postcss-prefix": "^1.0.3",
"pump": "^1.0.1",
"readable-stream": "^2.0.4",
"resolve": "^1.1.6",
"run-waterfall": "^1.1.3",
"sleuth": "^0.1.1",
"static-eval": "^0.2.4",
"style-resolve": "0.0.0",
"through2": "^2.0.0",
"uuid": "^2.0.1"
"through2": "^2.0.0"
},
"devDependencies": {
"browserify": "^13.0.0",
"dependency-check": "^2.5.1",
"istanbul": "^0.3.19",
"jsdom": "^8.0.2",
"npm-check-updates": "^2.2.0",
"sheetify-cssnext": "^1.0.0",
"standard": "^5.2.1",
"tape": "^4.2.0",
"through2": "^2.0.0",
"wrap-selectors": "^0.1.0",
"xtend": "^4.0.1"
"tape": "^4.2.0"
}
}
34 changes: 0 additions & 34 deletions test/basic.js
Original file line number Diff line number Diff line change
@@ -1,34 +0,0 @@
const test = require('tape')
const path = require('path')
const fs = require('fs')

const sheetify = require('../')

test('basic prefixing', function (t) {
t.plan(1)

const route = path.join(__dirname, 'fixtures', 'index-out.css')
const expected = fs.readFileSync(route, 'utf8')

const opts = { basedir: path.join(__dirname, 'fixtures') }
sheetify('./index.css', opts, function (err, actual) {
if (err) return t.error(err, 'no error')
t.equal(actual, expected, 'output is as expected')
})
})

test('basic prefixing', function (t) {
t.plan(1)

const route = path.join(__dirname, 'fixtures', 'index-out.css')
const expected = fs.readFileSync(route, 'utf8')

const opts = {
basedir: path.join(__dirname, 'fixtures'),
use: [[ 'sheetify-cssnext', { sourcemap: false } ]]
}
sheetify('./index.css', opts, function (err, actual) {
if (err) return t.error(err, 'no error')
t.equal(actual, expected, 'output is as expected')
})
})
File renamed without changes.
File renamed without changes.
5 changes: 5 additions & 0 deletions test/coverage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
require('./basic')
require('./disk')
require('./plugins')
require('./server')
require('./transform')
Loading

0 comments on commit da9c50d

Please sign in to comment.