├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── index.js ├── package.json └── test ├── basic.expected.css ├── basic.source.csjs.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4.3" 4 | - "5.6" 5 | script: npm run travis-test 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Ryan Tsao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # csjs-extractify 2 | 3 | [![build status][build-badge]][build-href] 4 | [![coverage status][coverage-badge]][coverage-href] 5 | [![dependencies status][deps-badge]][deps-href] 6 | 7 | Browserify plugin to extract [csjs](https://github.com/rtsao/csjs) into an external css bundle at build time 8 | 9 | ### Usage 10 | 11 | To use this plugin, create a seperate files for each CSJS module. For example: 12 | 13 | **main.csjs.js** 14 | ```javascript 15 | const csjs = require('csjs'); 16 | 17 | module.exports = csjs` 18 | 19 | .foo { 20 | color: red; 21 | } 22 | 23 | `; 24 | ``` 25 | 26 | When bundling your app, all exported CSJS will be extracted into a single, static CSS bundle. 27 | 28 | ### Options 29 | ``` 30 | --extension [default: '.csjs.js'] 31 | The file extension of your CSJS modules 32 | 33 | --output 34 | The path to write the output css bundle 35 | 36 | ``` 37 | 38 | ### CLI usage 39 | 40 | ``` 41 | browserify -p [ csjs-extractify -o dist/main.css ] index.js 42 | ``` 43 | 44 | ### API usage 45 | 46 | Write to file: 47 | ```javascript 48 | const browserify = require('browserify'); 49 | const extractify = require('csjs-extractify'); 50 | 51 | const b = browserify('./main.js'); 52 | b.plugin(extractify, {output: './dist/scoped.css'}); 53 | b.bundle(); 54 | ``` 55 | 56 | Or grab output stream: 57 | ```javascript 58 | const fs = require('fs'); 59 | const browserify = require('browserify'); 60 | const extractify = require('csjs-extractify'); 61 | 62 | const b = browserify('./main.js'); 63 | b.plugin(extractify); 64 | 65 | const bundle = b.bundle(); 66 | bundle.on('extracted_csjs_stream', css => { 67 | css.pipe(fs.createWriteStream('scoped.css')); 68 | }); 69 | ``` 70 | 71 | ### Limitations 72 | 73 | * Your CSJS files must export the result of a CSJS tagged template string 74 | * Your CSJS files (and any dependencies) must be executable natively on your version of Node without any transpilation or browserify transforms 75 | 76 | More sophisticated extraction is being worked on, but this should cover many use cases for now. 77 | 78 | [build-badge]: https://travis-ci.org/rtsao/csjs-extractify.svg?branch=master 79 | [build-href]: https://travis-ci.org/rtsao/csjs-extractify 80 | [coverage-badge]: https://coveralls.io/repos/rtsao/csjs-extractify/badge.svg?branch=master&service=github 81 | [coverage-href]: https://coveralls.io/github/rtsao/csjs-extractify?branch=master 82 | [deps-badge]: https://david-dm.org/rtsao/csjs-extractify.svg 83 | [deps-href]: https://david-dm.org/rtsao/csjs-extractify 84 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var stream = require('stream'); 6 | var through2 = require('through2'); 7 | var requireFromString = require('require-from-string'); 8 | var getCss = require('csjs/get-css'); 9 | 10 | module.exports = function(browserify, options) { 11 | var extension = options.extension || '.csjs.js'; 12 | var output = options.output || options.o; 13 | var files = []; 14 | var contents = {}; 15 | var cssStream; 16 | 17 | var extensionSubstr = extension.length * -1; 18 | 19 | function extractionTransform(filename) { 20 | if (filename.substr(extensionSubstr) !== extension) { 21 | return through2(); 22 | } 23 | 24 | var transform = through2(function(buf, enc, next) { 25 | var source = buf.toString('utf8'); 26 | 27 | var css; 28 | try { 29 | css = getCss(requireFromString(source, filename)); 30 | } catch (err) { 31 | return error(err); 32 | } 33 | 34 | if (css) { 35 | contents[filename] = css; 36 | files.push(filename); 37 | } 38 | 39 | this.push(source); 40 | next(); 41 | }); 42 | 43 | function error(msg) { 44 | var err = typeof msg === 'string' ? new Error(msg) : msg; 45 | transform.emit('error', err); 46 | } 47 | 48 | return transform; 49 | } 50 | 51 | browserify.transform(extractionTransform, { 52 | global: true 53 | }); 54 | 55 | browserify.on('bundle', function (bundle) { 56 | // on each bundle, create a new stream b/c the old one might have ended 57 | cssStream = new stream.Readable(); 58 | cssStream._read = function() {}; 59 | 60 | bundle.emit('extracted_csjs_stream', cssStream); 61 | bundle.on('end', function () { 62 | var contentString = ''; 63 | 64 | files.forEach(function(file) { 65 | contentString += contents[file]; 66 | }); 67 | 68 | cssStream.push(contentString); 69 | cssStream.push(null); 70 | 71 | if (output) { 72 | fs.writeFile(output, contentString, 'utf8', function (error) { 73 | // bundle was destroyed, emit new events on `browserify` 74 | if (error) browserify.emit('error', error); 75 | browserify.emit('extracted_csjs_stream_end', output); 76 | }); 77 | } 78 | }); 79 | }); 80 | }; 81 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "csjs-extractify", 3 | "version": "1.0.0-alpha.0", 4 | "description": "Browserify plugin to extract csjs into an external css bundle", 5 | "author": "Ryan Tsao ", 6 | "main": "index.js", 7 | "homepage": "https://github.com/rtsao/csjs-extractify", 8 | "repository": "git@github.com:rtsao/csjs-extractify.git", 9 | "bugs": "https://github.com/rtsao/csjs-extractify/issues", 10 | "scripts": { 11 | "cover": "istanbul cover test/index.js", 12 | "test": "node test/index.js", 13 | "travis-test": "npm run cover && ((cat coverage/lcov.info | coveralls) || exit 0)" 14 | }, 15 | "dependencies": { 16 | "csjs": "^1.0.0", 17 | "require-from-string": "^1.1.0", 18 | "through2": "^2.0.0" 19 | }, 20 | "devDependencies": { 21 | "browserify": "^13.0.0", 22 | "coveralls": "^2.11.4", 23 | "istanbul": "^0.4.1", 24 | "tape": "^4.2.2" 25 | }, 26 | "engines": { 27 | "node": ">=4.0.0" 28 | }, 29 | "license": "MIT" 30 | } 31 | -------------------------------------------------------------------------------- /test/basic.expected.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | .foo_rH1Pe { 4 | color: red; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /test/basic.source.csjs.js: -------------------------------------------------------------------------------- 1 | var csjs = require('csjs'); 2 | 3 | module.exports = csjs` 4 | 5 | .foo { 6 | color: red; 7 | } 8 | 9 | `; 10 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var test = require('tape'); 6 | var browserify = require('browserify'); 7 | var plugin = require('../'); 8 | 9 | var tests = [ 10 | { 11 | name: 'basic' 12 | } 13 | ]; 14 | 15 | tests.forEach(testFromConfig); 16 | 17 | function testFromConfig(config) { 18 | var fixtures = getFixtures(config.name); 19 | runTest(config.name, fixtures.sourcePath, fixtures.expectedCss, config.opts); 20 | } 21 | 22 | function runTest(name, sourcePath, expectedCss, opts) { 23 | test('test ' + name, function t(assert) { 24 | var b = browserify('./test/basic.source.csjs.js'); 25 | b.plugin(plugin); 26 | 27 | var bundle = b.bundle(function(){}); 28 | bundle.on('extracted_csjs_stream', function (css) { 29 | var output = ''; 30 | css.on('data', function(buffer) { 31 | output += buffer; 32 | }); 33 | css.on('end', function() { 34 | assert.equal(output, expectedCss, 'css matches expected'); 35 | assert.end(); 36 | }); 37 | }); 38 | }); 39 | } 40 | 41 | function getFixtures(name) { 42 | var sourcePath = './test/' + name + '.source.js'; 43 | var cssPath = path.join(__dirname, name + '.expected.css'); 44 | 45 | return { 46 | sourcePath: sourcePath, 47 | expectedCss: fs.readFileSync(cssPath, 'utf8') 48 | } 49 | } 50 | --------------------------------------------------------------------------------