Full Code of tscanlin/css-razor for AI

master 4bdf4286e09d cached
15 files
25.7 KB
7.5k tokens
6 symbols
1 requests
Download .txt
Repository: tscanlin/css-razor
Branch: master
Commit: 4bdf4286e09d
Files: 15
Total size: 25.7 KB

Directory structure:
gitextract_1dfxmu6b/

├── .github/
│   └── FUNDING.yml
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── cli.js
├── defaultOptions.js
├── index.js
├── index.test.js
├── package.json
└── test/
    ├── input/
    │   ├── index.css
    │   ├── index.html
    │   └── tachyons.html
    └── results.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: tscanlin


================================================
FILE: .gitignore
================================================
node_modules
npm-debug*
.DS_Store
test/output
dist/*


================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
  - "6.9"
sudo: false
branches:
  only:
    - master
install:
- npm install
script:
- npm run test


================================================
FILE: CHANGELOG.md
================================================
### Unreleased
...


### 2.3.0

#### Added
- reportDetails option for all selectors to be listed
- get travis-ci working
- add webpages option

#### Updated
- rename config.js to defaultOptions.js
- refactor tests
- update readme
- ignore option appends by default


### 2.2.0

#### Added
- new overwriteCss option


### 2.1.1

#### Fixed
- fix cli when no output file specified and report improvements


### 2.1.0

#### Added
- More docs & examples
- CHANGELOG.md
- Support for passing glob patterns


### 2.0.0

#### Added
- Added `report` option to display stats about used vs unused selectors

#### Changed
- Rewrite tests with lab and code
- Parse args with yargs


### 1.1.0

#### Added
- Got CLI working
- Promise API
- Tests
- Core functionality


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2017 Tim Scanlin

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# css-razor

![Build Status](https://travis-ci.org/tscanlin/css-razor.svg?branch=master)

css-razor is a fast way to remove unused selectors from css. Essentially, it accomplishes the same goal as [uncss](https://github.com/giakki/uncss). However, it accomplishes this goal differently. Rather than loading a webpage in phantomjs and using `document.querySelector` to determine if a selector is being used, css-razor uses [cheeriojs](https://github.com/cheeriojs/cheerio) to parse static html and css files to removed unused selectors.

- Helps trim down CSS so you only keep the necessary parts
- Built for speed using the amazing  [cheeriojs](https://github.com/cheeriojs/cheerio)
- has an ignore list that can be added to
- ignores common pseudo elements & pseudo selectors by default
- Supports multiple files / globs
- Supports raw html & css input
- Supports html paths from URLs
- Reporting stats detailing how many selectors are removed.


## Getting Started

Install with npm

```bash
npm install --save-dev css-razor
```

You can then use the cli

```bash
css-razor build/css/index.css build/index.html --stdout > build/css/index.min.css
```

And you can even pass globs

```bash
css-razor build/css/*.css build/*.html --stdout > build/css/index.min.css
```

Or you can use the js api

```js
const cssRazor = require('css-razor').default

cssRazor({
  html: ['build/index.html'],
  css: ['build/css/index.css'],
}, function(err, data) {
  console.log(data.css)
})
```


## Options

```js
module.exports = {
  // Array of HTML file globs.
  html: [],
  // Array of CSS file globs.
  css: [],
  // Raw HTML string.
  htmlRaw: '',
  // Raw CSS string.
  cssRaw: '',
  // Array of webpages to add to HTML.
  webpages: [],
  // Strings in CSS classes to ignore. Pass `false`
  // (or `--no-ignore` via cli) to not ignore these.
  ignore: [
    'html', // global element
    'body', // global element
    'button', // global element

    'active', // state class
    'inactive', // state class
    'collapsed', // state class
    'expanded', // state class
    'show', // state class
    'hide', // state class
    'hidden', // state class
    'is-', // state class
  ],
  // Where to output
  outputFile: 'dist/index.css',
  // Disable output via stdout w/ `--no-stdout`.
  stdout: false,
  // Report Stats about used vs unused selectors.
  report: false,
  // Detailed Report Stats including every selector used vs unused.
  // Note: this also depends on the `report` option being true.
  reportDetails: false,
  // Overwrite the input css file if there is only one.
  overwriteCss: false,
}
```


## Usage with Postcss

```js
const postcssRazor = require('css-razor').postcss

postcss([
    postcssRazor({
      html: "<html>your html string</html>",
    })
  ])
  .process(css, {
    from: 'index.css',
    to: 'output.css'
  })
```


## React to HTML Example

Below is an example of building an html file from a react app created with `create-react-app`. The resulting HTML file can then be used for server rendering and detecting selectors with css-razor.

index.js:
```js
import App from './components/App'
import './index.css'

if (typeof window !== 'undefined') { // Web
  ReactDOM.render(
    <App />,
    window.document.getElementById('root')
  )
} else { // Node / server render
  global.appToRender = App
}

```

buildStatic.js:
```js
const app = global.appToRender
const markup = ReactDOM.renderToString(ReactDOM.createElement(app));

const html = fs.readFileSync(HTML_FILE)
const newHtml = html.toString().split('<div id="root"></div>').join(
  '<div id="root">' + markup + '</div>'
)

fs.writeFileSync(HTML_FILE, newHtml, 'utf8')
```


## Todo

- html input via stdin?
- more tests for raw and globs
- test for postcss plugin usage


================================================
FILE: cli.js
================================================
#!/usr/bin/env node

const cssRazor = require('./index.js').default
const defaultOptions = require('./defaultOptions.js')
const argv = require('yargs')
  .usage('Usage: $0 <command> [options]')
  .argv

if (process.argv && process.argv.length > 2) {
  defaultOptions.outputFile = '' // Default to no output file over cli because of stdout.
  const options = Object.assign({}, defaultOptions, argv)

  options._.forEach((arg, i) => {
    if (arg.indexOf('.html') === arg.length - 5) {
      options.html.push(arg)
    } else if (arg.indexOf('.css') === arg.length - 4) {
      options.css.push(arg)
    }
    // TODO: Set more CLI options here.
  })

  cssRazor(options, (err, data) => {
    if (err) {
      process.stderr.write(err)
      process.exit(1)
    }
    if (options.stdout) {
      process.stdout.write(data.css)
      process.exit(0)
    }
  })
} else {
  throw new Error('You need to pass arguments to css-razor')
}


================================================
FILE: defaultOptions.js
================================================
module.exports = {
  // Array of HTML file globs.
  html: [],
  // Array of CSS file globs.
  css: [],
  // Raw HTML string.
  htmlRaw: '',
  // Raw CSS string.
  cssRaw: '',
  // Array of webpages to add to HTML.
  webpages: [],
  // Strings in CSS classes to ignore. Pass `false`
  // (or `--no-ignore` via cli) to not ignore these.
  ignore: [
    'html', // global element
    'body', // global element
    'button', // global element

    'active', // state class
    'inactive', // state class
    'collapsed', // state class
    'expanded', // state class
    'show', // state class
    'hide', // state class
    'hidden', // state class
    'is-' // state class
  ],
  // Where to output
  outputFile: 'dist/index.css',
  // Disable output via stdout w/ `--no-stdout`.
  stdout: false,
  // Report Stats about used vs unused selectors.
  report: false,
  // Detailed Report Stats including every selector used vs unused.
  // Note: this also depends on the `report` option being true.
  reportDetails: false,
  // Overwrite the input css file if there is only one.
  overwriteCss: false
}


================================================
FILE: index.js
================================================
'use strict'

const cheerio = require('cheerio')
const postcss = require('postcss')
const fs = require('fs')
const path = require('path')
const globby = require('globby')
const mkdirp = require('mkdirp')

require('es6-promise').polyfill()
require('isomorphic-fetch')

const defaultOptions = require('./defaultOptions')
const DELIMITER = ' || '

function cssRazor (options, callback) {
  let ignoreList = []
  if (typeof options.ignore === 'undefined') {
    ignoreList = defaultOptions.ignore.concat(options.ignore)
  }
  options = Object.assign({}, defaultOptions, options)
  options.ignore = ignoreList

  if (!((options.htmlRaw || options.html.length || options.webpages.length) && (options.cssRaw || options.css.length))) {
    throw new Error('You must include HTML and CSS for input.')
  }

  const p = new Promise(function (resolve, reject) {
    let htmlRaw = options.htmlRaw
    let cssRaw = options.cssRaw

    Promise.all([
      globby(options.html),
      globby(options.css)
    ]).then((pathsArray) => {
      const htmlFiles = pathsArray[0]
      const cssFiles = pathsArray[1]
      getTextFromUrls(options.webpages, (webHtml) =>
        getTextFromFiles(htmlFiles, (html) =>
          getTextFromFiles(cssFiles, (css) => {
            // TODO: Is there a better way to do this. I'd rather not nest it
            // but I don't want to pass more args either.
            function processInput (html, css) {
              const outputFile = options.overwriteCss
                ? cssFiles[0]
                : options.outputFile
              postcss([
                postcssRazor({
                  html: html,
                  ignore: options.ignore,
                  report: options.report
                })
              ])
                .process(css, {
                  from: options.inputCss,
                  to: outputFile
                })
                .then((result) => {
                  if (outputFile) {
                    // Make sure the directory exists first.
                    mkdirp(path.dirname(outputFile), (err, d1) => {
                      if (err) {
                        return reject(err)
                      }
                      fs.writeFile(outputFile, result.css, (err, d2) => {
                        if (err) {
                          return reject(err)
                        }
                        resolve(result)
                      })
                    })
                  } else {
                    resolve(result)
                  }
                })
                .catch((e) => {
                  reject(e)
                })
            }

            return processInput(html + htmlRaw + webHtml, css + cssRaw)
          })
        )
      )
    })
  })

  // Enable callback support too.
  if (callback) {
    p.then((result) => {
      callback(null, result)
    }).catch(err => callback(err))
  }

  return p
}

const postcssRazor = postcss.plugin('postcss-razor', (opt) => {
  const html = opt.html
  let keepCount = 0
  let keepSelectors = ''
  let removeCount = 0
  let removeSelectors = ''
  return (root) => {
    const $ = cheerio.load(html)
    root.walk((node) => {
      if (node.type === 'rule') {
        const exists = checkExists(node, $)
        const ignore = opt.ignore.some((ignore) => {
          return node.selector.indexOf(ignore) !== -1
        })
        if (!exists && !ignore) {
          node.remove()
          removeSelectors += node.selector + DELIMITER
          removeCount++
        } else {
          keepSelectors += node.selector + DELIMITER
          keepCount++
        }
      }
    })

    // Remove empty media queries.
    root.walkAtRules((rule) => {
      if (typeof rule.nodes === 'undefined' || rule.nodes.length === 0) {
        rule.remove()
      }
    })

    if (opt.report) {
      const percent = ((removeCount / (keepCount + removeCount)) * 100).toFixed()
      console.log('   Selectors kept: ' + keepCount)
      console.log('Selectors removed: ' + removeCount)
      console.log('  Percent removed: ' + percent + '%')
      console.log(' ')
      if (opt.reportDetails) {
        console.log('Removed selectors: ' + removeSelectors)
        console.log(' ')
        console.log('   Kept selectors: ' + keepSelectors)
      }
    }
  }
})

function getTextFromFiles (files, cb) {
  let text = ''
  if (files.length) {
    files.forEach((file, i) => {
      fs.readFile(file, (err, data) => {
        if (err) {
          console.error(err)
        }
        text += data.toString()

        if (i === files.length - 1) {
          cb(text)
        }
      })
    })
  } else {
    cb(text)
  }
}

function getTextFromUrls (urls, cb) {
  let text = ''
  if (urls.length) {
    urls.forEach((file, i) => {
      fetch(file).then(function (response) {
        if (response.status >= 400) {
          throw new Error('Bad response from server')
        }
        return response.text()
      }).then(function (responseText) {
        text += responseText

        if (i === urls.length - 1) {
          cb(text)
        }
      })
    })
  } else {
    cb(text)
  }
}

function checkExists (node, $) {
  // Right now this try is needed because cheerio doesn't handle `pseudo-element` well.
  // See: https://github.com/cheeriojs/cheerio/issues/979
  try {
    return $(removePseudoClasses(node.selector)).length > 0
  } catch (e) {
    return true
  }
}

function removePseudoClasses (selector) {
  return [
    ':active',
    ':focus',
    ':hover',
    ':visited',
    '::before',
    ':before',
    '::after',
    ':after'
  ].reduce((p, c) => {
    return p.split(c).join('')
  }, selector)
}

module.exports = {
  default: cssRazor,
  postcss: postcssRazor
}


================================================
FILE: index.test.js
================================================
const Code = require('code') // assertion library
const Lab = require('lab')
const lab = exports.lab = Lab.script()

const cssRazor = require('./index').default
const spawn = require('child_process').spawn

// Test output
const testResults = require('./test/results.js')

lab.experiment('css-razor', () => {
  lab.test('returns promise with used CSS based on input HTML & CSS', (done) => {
    cssRazor({
      html: ['test/input/index.html'],
      css: ['test/input/index.css'],
      outputFile: 'test/output/index.css'
    }).then((data) => {
      Code.expect(data.css.split('\r').join('')).to.equal(testResults.simpleCss)
      done()
    })
  })

  lab.test('calls callback with used CSS based on input HTML & CSS', (done) => {
    cssRazor({
      html: ['test/input/index.html'],
      css: ['test/input/index.css'],
      outputFile: 'test/output/index.css'
    }, function (err, data) {
      if (err) {
        console.error(err)
      }
      Code.expect(data.css.split('\r').join('')).to.equal(testResults.simpleCss)
      done()
    })
  })

  lab.test('returns promise with used CSS based on more complex input HTML & CSS', (done) => {
    cssRazor({
      html: ['test/input/tachyons.html'],
      css: ['test/input/tachyons.min.css'],
      outputFile: 'test/output/tachyons.css'
    }).then((data) => {
      Code.expect(data.css.split('\r').join('')).to.equal(testResults.complexCss)
      done()
    })
  })

  lab.test('calls callback with used CSS based on more complex input webpage & CSS', (done) => {
    cssRazor({
      webpages: ['http://blog.timscanlin.net/'],
      css: ['test/input/tachyons.min.css'],
      outputFile: 'test/output/tachyons.css'
    }, function (err, data) {
      if (err) {
        console.error(err)
      }
      Code.expect(data.css.split('\r').join('')).to.equal(testResults.complexHttpCss)
      done()
    })
  })

  lab.test('CLI returns used CSS based on input HTML & CSS', (done) => {
    const cli = spawn('node', [
      './cli.js',
      'test/input/index.html',
      'test/input/index.css'
    ])

    cli.stdout.on('data', (data) => {
      Code.expect(data.toString().split('\r').join('')).to.equal(testResults.simpleCss)
    })

    cli.on('close', (code) => {
      Code.expect(code).to.equal(0)
      done()
    })
  })

  // empty input
  // no files

  // multiple files
  // raw
  // postcss
  // set output file
})


================================================
FILE: package.json
================================================
{
  "name": "css-razor",
  "version": "2.4.4",
  "description": "Remove unused selectors from CSS efficiently",
  "main": "index.js",
  "bin": {
    "css-razor": "./cli.js"
  },
  "scripts": {
    "lint": "standard --globals=fetch",
    "test:unit": "lab ./index.test.js --ignore fetch,Response,Headers,Request,Base64",
    "test": "npm run lint && npm run test:unit",
    "v-patch": "npm version patch && git push --tags && npm publish && git push",
    "v-minor": "npm version minor && git push --tags && npm publish && git push",
    "v-major": "npm version major && git push --tags && npm publish && git push"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/tscanlin/css-razor.git"
  },
  "keywords": [
    "css",
    "razor",
    "strip",
    "cut",
    "slim",
    "cheerio",
    "postcss",
    "postcss-runner"
  ],
  "author": "Tim Scanlin",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/tscanlin/css-razor/issues"
  },
  "homepage": "https://github.com/tscanlin/css-razor#readme",
  "dependencies": {
    "cheerio": "^0.22.0",
    "es6-promise": "^4.1.0",
    "globby": "^6.1.0",
    "isomorphic-fetch": "^2.2.1",
    "mkdirp": "^0.5.1",
    "postcss": "^5.2.13",
    "yargs": "^6.6.0"
  },
  "devDependencies": {
    "code": "^4.0.0",
    "lab": "^12.1.0",
    "standard": "^10.0.2"
  }
}


================================================
FILE: test/input/index.css
================================================
body {
  font-size: 20px;
}

.some-element {
  margin: 20px;
}

.some-element .inner-element {
  text-align: center;
}

.some-element > .inner-element {
  color: blue;
}

.non-existent {
  padding: 10px;
}

@media screen and (min-width > 200px) {
  .nothing {
    font-weight: bold;
  }
}


================================================
FILE: test/input/index.html
================================================
<div class="some-element">
  <span class="inner-element">Foo Bar</span>
</div>


================================================
FILE: test/input/tachyons.html
================================================
<article class="br2 ba dark-gray b--black-10 mv4 w-100 w-50-m w-25-l mw5 center">
  <img src="http://placekitten.com/g/600/300" class="db w-100 br2 br--top" alt="Photo of a kitten looking menacing.">
  <div class="pa2 ph3-ns pb3-ns">
    <div class="dt w-100 mt1">
      <div class="dtc">
        <h1 class="f5 f4-ns mv0">Cat</h1>
      </div>
      <div class="dtc tr">
        <h2 class="f5 mv0">$1,000</h2>
      </div>
    </div>
    <p class="f6 lh-copy measure mt2 mid-gray">
      If it fits, i sits burrow under covers. Destroy couch leave hair everywhere,
      and touch water with paw then recoil in horror.
    </p>
  </div>
</article>


================================================
FILE: test/results.js
================================================
exports.simpleCss = 'body {\n  font-size: 20px;\n}\n\n.some-element {\n  margin: 20px;\n}\n\n.some-element .inner-element {\n  text-align: center;\n}\n\n.some-element > .inner-element {\n  color: blue;\n}\n'

exports.complexCss = '/*! TACHYONS v4.6.1 | http://tachyons.io */\n/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}a:active,a:hover{outline-width:0}img{border-style:none}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}/* 1 */ [type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}/* 1 */[hidden],template{display:none}.border-box,a,article,body,code,dd,div,dl,dt,fieldset,footer,form,h1,h2,h3,h4,h5,h6,header,html,input[type=email],input[type=number],input[type=password],input[type=tel],input[type=text],input[type=url],legend,li,main,ol,p,pre,section,table,td,textarea,th,tr,ul{box-sizing:border-box}img{max-width:100%}.ba{border-style:solid;border-width:1px}.b--black-10{border-color:rgba(0,0,0,.1)}.br2{border-radius:.25rem}.br--top{border-bottom-right-radius:0}.br--right,.br--top{border-bottom-left-radius:0}.db{display:block}.dt{display:table}.dtc{display:table-cell}.button-reset::-moz-focus-inner,.input-reset::-moz-focus-inner{border:0;padding:0}.lh-copy{line-height:1.5}.link,.link:active,.link:focus,.link:hover,.link:link,.link:visited{-webkit-transition:color .15s ease-in;transition:color .15s ease-in}.mw5{max-width:16rem}.w-100{width:100%}.overflow-hidden{overflow:hidden}.overflow-x-hidden{overflow-x:hidden}.overflow-y-hidden{overflow-y:hidden}.dark-gray{color:#333}.mid-gray{color:#555}.pa2{padding:.5rem}.mt1{margin-top:.25rem}.mt2{margin-top:.5rem}.mv0{margin-top:0;margin-bottom:0}.mv4{margin-top:2rem;margin-bottom:2rem}.tr{text-align:right}.f5{font-size:1rem}.f6{font-size:.875rem}.measure{max-width:30em}.center{margin-right:auto;margin-left:auto}.dim:active{opacity:.8;-webkit-transition:opacity .15s ease-out;transition:opacity .15s ease-out}.hide-child .child{opacity:0;-webkit-transition:opacity .15s ease-in;transition:opacity .15s ease-in}.hide-child:active .child,.hide-child:focus .child,.hide-child:hover .child{opacity:1;-webkit-transition:opacity .15s ease-in;transition:opacity .15s ease-in}.grow:active{-webkit-transform:scale(.9);transform:scale(.9)}.grow-large:active{-webkit-transform:scale(.95);transform:scale(.95)}@media screen and (min-width:30em){.overflow-hidden-ns{overflow:hidden}.overflow-x-hidden-ns{overflow-x:hidden}.overflow-y-hidden-ns{overflow-y:hidden}.pb3-ns{padding-bottom:1rem}.ph3-ns{padding-left:1rem;padding-right:1rem}.f4-ns{font-size:1.25rem}}@media screen and (min-width:30em) and (max-width:60em){.w-50-m{width:50%}.overflow-hidden-m{overflow:hidden}.overflow-x-hidden-m{overflow-x:hidden}.overflow-y-hidden-m{overflow-y:hidden}}@media screen and (min-width:60em){.w-25-l{width:25%}.overflow-hidden-l{overflow:hidden}.overflow-x-hidden-l{overflow-x:hidden}.overflow-y-hidden-l{overflow-y:hidden}}\n'

exports.complexHttpCss = '/*! TACHYONS v4.6.1 | http://tachyons.io */\n/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}small{font-size:80%}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}/* 1 */ [type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}/* 1 */[hidden],template{display:none}.border-box,a,article,body,code,dd,div,dl,dt,fieldset,footer,form,h1,h2,h3,h4,h5,h6,header,html,input[type=email],input[type=number],input[type=password],input[type=tel],input[type=text],input[type=url],legend,li,main,ol,p,pre,section,table,td,textarea,th,tr,ul{box-sizing:border-box}.bb{border-bottom-style:solid;border-bottom-width:1px}.b--light-gray{border-color:#eee}.top-0{top:0}.right-0{right:0}.db{display:block}.dib{display:inline-block}.normal{font-weight:400}.b{font-weight:700}.button-reset::-moz-focus-inner,.input-reset::-moz-focus-inner{border:0;padding:0}.lh-solid{line-height:1}.lh-title{line-height:1.25}.lh-copy{line-height:1.5}.link,.link:active,.link:focus,.link:hover,.link:link,.link:visited{-webkit-transition:color .15s ease-in;transition:color .15s ease-in}.mw6{max-width:32rem}.mw7{max-width:48rem}.w5{width:16rem}.overflow-hidden{overflow:hidden}.overflow-x-hidden{overflow-x:hidden}.overflow-y-hidden{overflow-y:hidden}.relative{position:relative}.absolute{position:absolute}.o-80{opacity:.8}.o-70{opacity:.7}.o-60{opacity:.6}.o-40{opacity:.4}.black{color:#000}.white{color:#fff}.bg-mid-gray{background-color:#555}.pa0{padding:0}.pa1{padding:.25rem}.pa3{padding:1rem}.pb2{padding-bottom:.5rem}.pb3{padding-bottom:1rem}.pt2{padding-top:.5rem}.pv4{padding-top:2rem;padding-bottom:2rem}.ph1{padding-left:.25rem;padding-right:.25rem}.ma0{margin:0}.ma1{margin:.25rem}.mb4{margin-bottom:2rem}.mt4{margin-top:2rem}.mv1{margin-top:.25rem;margin-bottom:.25rem}.no-underline{text-decoration:none}.tc{text-align:center}.ttu{text-transform:uppercase}.f1{font-size:3rem}.f3{font-size:1.5rem}.f6{font-size:.875rem}.center{margin-right:auto;margin-left:auto}.dim:active{opacity:.8;-webkit-transition:opacity .15s ease-out;transition:opacity .15s ease-out}.glow,.glow:focus,.glow:hover{-webkit-transition:opacity .15s ease-in;transition:opacity .15s ease-in}.glow:focus,.glow:hover{opacity:1}.hide-child .child{opacity:0;-webkit-transition:opacity .15s ease-in;transition:opacity .15s ease-in}.hide-child:active .child,.hide-child:focus .child,.hide-child:hover .child{opacity:1;-webkit-transition:opacity .15s ease-in;transition:opacity .15s ease-in}.grow:active{-webkit-transform:scale(.9);transform:scale(.9)}.grow-large:active{-webkit-transform:scale(.95);transform:scale(.95)}@media screen and (min-width:30em){.overflow-hidden-ns{overflow:hidden}.overflow-x-hidden-ns{overflow-x:hidden}.overflow-y-hidden-ns{overflow-y:hidden}.pa4-ns{padding:2rem}}@media screen and (min-width:30em) and (max-width:60em){.overflow-hidden-m{overflow:hidden}.overflow-x-hidden-m{overflow-x:hidden}.overflow-y-hidden-m{overflow-y:hidden}}@media screen and (min-width:60em){.overflow-hidden-l{overflow:hidden}.overflow-x-hidden-l{overflow-x:hidden}.overflow-y-hidden-l{overflow-y:hidden}}\n'
Download .txt
gitextract_1dfxmu6b/

├── .github/
│   └── FUNDING.yml
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── cli.js
├── defaultOptions.js
├── index.js
├── index.test.js
├── package.json
└── test/
    ├── input/
    │   ├── index.css
    │   ├── index.html
    │   └── tachyons.html
    └── results.js
Download .txt
SYMBOL INDEX (6 symbols across 1 files)

FILE: index.js
  constant DELIMITER (line 14) | const DELIMITER = ' || '
  function cssRazor (line 16) | function cssRazor (options, callback) {
  function getTextFromFiles (line 145) | function getTextFromFiles (files, cb) {
  function getTextFromUrls (line 165) | function getTextFromUrls (urls, cb) {
  function checkExists (line 187) | function checkExists (node, $) {
  function removePseudoClasses (line 197) | function removePseudoClasses (selector) {
Condensed preview — 15 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (28K chars).
[
  {
    "path": ".github/FUNDING.yml",
    "chars": 64,
    "preview": "# These are supported funding model platforms\n\ngithub: tscanlin\n"
  },
  {
    "path": ".gitignore",
    "chars": 53,
    "preview": "node_modules\nnpm-debug*\n.DS_Store\ntest/output\ndist/*\n"
  },
  {
    "path": ".travis.yml",
    "chars": 126,
    "preview": "language: node_js\nnode_js:\n  - \"6.9\"\nsudo: false\nbranches:\n  only:\n    - master\ninstall:\n- npm install\nscript:\n- npm run"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 754,
    "preview": "### Unreleased\n...\n\n\n### 2.3.0\n\n#### Added\n- reportDetails option for all selectors to be listed\n- get travis-ci working"
  },
  {
    "path": "LICENSE",
    "chars": 1068,
    "preview": "MIT License\n\nCopyright (c) 2017 Tim Scanlin\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
  },
  {
    "path": "README.md",
    "chars": 3753,
    "preview": "# css-razor\n\n![Build Status](https://travis-ci.org/tscanlin/css-razor.svg?branch=master)\n\ncss-razor is a fast way to rem"
  },
  {
    "path": "cli.js",
    "chars": 930,
    "preview": "#!/usr/bin/env node\n\nconst cssRazor = require('./index.js').default\nconst defaultOptions = require('./defaultOptions.js'"
  },
  {
    "path": "defaultOptions.js",
    "chars": 1098,
    "preview": "module.exports = {\n  // Array of HTML file globs.\n  html: [],\n  // Array of CSS file globs.\n  css: [],\n  // Raw HTML str"
  },
  {
    "path": "index.js",
    "chars": 5720,
    "preview": "'use strict'\n\nconst cheerio = require('cheerio')\nconst postcss = require('postcss')\nconst fs = require('fs')\nconst path "
  },
  {
    "path": "index.test.js",
    "chars": 2391,
    "preview": "const Code = require('code') // assertion library\nconst Lab = require('lab')\nconst lab = exports.lab = Lab.script()\n\ncon"
  },
  {
    "path": "package.json",
    "chars": 1345,
    "preview": "{\n  \"name\": \"css-razor\",\n  \"version\": \"2.4.4\",\n  \"description\": \"Remove unused selectors from CSS efficiently\",\n  \"main\""
  },
  {
    "path": "test/input/index.css",
    "chars": 289,
    "preview": "body {\n  font-size: 20px;\n}\n\n.some-element {\n  margin: 20px;\n}\n\n.some-element .inner-element {\n  text-align: center;\n}\n\n"
  },
  {
    "path": "test/input/index.html",
    "chars": 79,
    "preview": "<div class=\"some-element\">\n  <span class=\"inner-element\">Foo Bar</span>\n</div>\n"
  },
  {
    "path": "test/input/tachyons.html",
    "chars": 648,
    "preview": "<article class=\"br2 ba dark-gray b--black-10 mv4 w-100 w-50-m w-25-l mw5 center\">\n  <img src=\"http://placekitten.com/g/6"
  },
  {
    "path": "test/results.js",
    "chars": 7950,
    "preview": "exports.simpleCss = 'body {\\n  font-size: 20px;\\n}\\n\\n.some-element {\\n  margin: 20px;\\n}\\n\\n.some-element .inner-elemen"
  }
]

About this extraction

This page contains the full source code of the tscanlin/css-razor GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 15 files (25.7 KB), approximately 7.5k tokens, and a symbol index with 6 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!