diff --git a/.babelrc b/.babelrc deleted file mode 100644 index f7fd45b..0000000 --- a/.babelrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "presets": [ - "es2015" - ], - "plugins": [ - "syntax-object-rest-spread", - "transform-object-rest-spread" - ] -} diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 52494a4..0000000 --- a/.editorconfig +++ /dev/null @@ -1,9 +0,0 @@ -# editorconfig.org - -[*] -indent_style = space -indent_size = 2 -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 2c370f0..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,16 +0,0 @@ -module.exports = { - "extends": "airbnb", - "env": { - "browser": true, - "node": true, - "mocha": true - }, - "rules": { - "prefer-arrow-callback": 0, // mocha tests (recommendation) - "func-names": 0, // mocha tests (recommendation) - "comma-dangle": ["error", "never"], // personal preference - "no-param-reassign": 0, // the plugin needs this (webpack design :( ) - "no-use-before-define": 0, // personal preference - "no-console": 0 // allow logging - } -}; diff --git a/.gitignore b/.gitignore index 9d256f2..bf0d423 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,33 @@ -node_modules/ -build/ -dist/ -coverage/ +#floobits +.floobits +.flooignore + +# Logs +logs *.log -.eslintcache +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git +node_modules + +example \ No newline at end of file diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 552f221..0000000 --- a/.npmignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules/ -*.log diff --git a/.travis.yml b/.travis.yml index 33fce7f..d2f3209 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1 @@ -language: node_js -node_js: - - "4" - - "5" - - "6" -script: - - (cd examples && npm i) - - npm run build - - npm run test:all -after_success: - - bash <(curl -s https://codecov.io/bash) +language: node_js \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 05933b9..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,90 +0,0 @@ -## purifycss-webpack - -0.7.0 / 2017-05-16 -================== - - * Breaking - Push **purify-css** as a peer dependency. #108 - -0.6.2 / 2017-05-08 -================== - - * Docs - Add **glob-all** example. #105 - -0.6.1 / 2017-04-14 -================== - - * Docs - Fix CSS Modules example (prefix has to be lowercase to work). - -0.6.0 / 2017-04-07 -================== - - * Feature - Allow asset names to contain `?`. Example: `style.css?218aa9358a709a5a0a12`. #94 - -0.5.0 / 2017-03-02 -================== - - * Feature - Add strict validation against `paths`. #88 - -0.4.3 / 2017-03-01 -================== - - * Bug fix - Fix chunk file match dependency on [name]. #86 - -0.4.2 / 2017-01-28 -================== - - * Feature - Show nicer errors if there are extra fields. - -0.4.1 / 2017-01-28 -================== - - * Bug fix - If an entry name does not match while processing, skip it. #72 - -0.4.0 / 2017-01-27 -================== - - * Bug fix - Support `: ` style entries. #71 - * Bug fix - Fail at validation if entry keys don't match with path keys. #67 - * Feature - Expose `minimize` flag for CSS minification. - -0.3.1 / 2017-01-26 -================== - - * Bug fix - Make `search.files` more robust against potentially missing data. #67 - -0.3.0 / 2017-01-26 -================== - - * Breaking - Rename `fileExtensions` as `styleExtensions`. This communicates better how it works underneath. - -0.2.2 / 2017-01-24 -================== - - * Chore - Push option defaults to schema. - -0.2.1 / 2017-01-24 -================== - - * Bug fix - Include webpack 2 properly to the peer dependency. - -0.2.0 / 2017-01-24 -================== - - * Feature - Add stricter plugin option validation. - -0.1.1 / 2017-01-23 -================== - - * Bug fix - Include `lib` to distribution to avoid installation failure. - -0.1.0 / 2017-01-23 -================== - - * Complete rewrite with more functionality and a new API. Different name due to npm issues. - -## purifycss-webpack-plugin - -2.0.3 / 2016-02-12 -================== - - * Last release without changelogs. diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md deleted file mode 100644 index 0434854..0000000 --- a/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,5 +0,0 @@ -1. Check the version of package you are using. If it's not the newest version, update and try again (see changelog while updating!). -2. If the issue is still there, write a minimal project showing the problem and expected output. -3. Link to the project and mention Node version and OS in your report. - -**IMPORTANT! You should use [Stack Overflow](https://stackoverflow.com/) for support related questions.** diff --git a/LICENSE b/LICENSE index 8c11fc7..b464b78 100644 --- a/LICENSE +++ b/LICENSE @@ -1,20 +1,22 @@ -Copyright JS Foundation and other contributors +The MIT License (MIT) -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: +Copyright (c) 2015 fog-glory -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. +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. -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. diff --git a/PurifyCssPlugin.js b/PurifyCssPlugin.js new file mode 100644 index 0000000..1cd0071 --- /dev/null +++ b/PurifyCssPlugin.js @@ -0,0 +1,81 @@ +var fs = require('fs'); + +var PurifyCssPlugin = function(absolutePath) { + var htmlPaths = Array.prototype.slice.call(arguments, 1); + this.htmlContent = concatFiles(absolutePath, htmlPaths); +}; + +module.exports = PurifyCssPlugin; + +var concatFiles = function(absolutePath, filePaths){ + return filePaths.reduce(function(content, filePath){ + return content + fs.readFileSync(absolutePath + filePath, 'utf8'); + }, ''); +}; + +var isCss = function(module){ + return module._source && module._source._value && + module._source._name.indexOf('.css') > -1; +}; + +var isContent = function(module){ + return module._source && module._source._value && + module._source._name.indexOf('.css') === -1; +}; + +var getContentHash = function(compilation){ + return compilation.chunks.reduce(function(contentHash, chunk){ + var modules = chunk.modules; + var content = modules.reduce(function(total, m){ + if(isContent(m)){ + total += m._source._value; + } + + return total; + }, ''); + + contentHash[chunk.name] = content; + return contentHash; + }, {}); +}; + +var getCssModules = function(modules){ + return modules.filter(function(module){ + return isCss(module); + }); +}; + +PurifyCssPlugin.prototype.apply = function(compiler) { + var chunkContent; + var cssModules; + var htmlContent = this.htmlContent; + + compiler.plugin('this-compilation', function(compilation) { + compilation._purifycss_callback = function(callback){ + if(cssModules.length === 0){ + return callback(); + } + + var nextModule = cssModules.pop(); + compilation._purifycss_content = htmlContent; + + nextModule.chunks.forEach(function(chunk){ + compilation._purifycss_content += chunkContent[chunk.name] + ' '; + }); + + compilation.rebuildModule(nextModule, function(err){ + if(err){ + console.log(err); + } + + compilation._purifycss_callback(callback); + }); + }; + + compilation.plugin('optimize-tree', function(chunks, modules, callback) { + chunkContent = getContentHash(compilation); + cssModules = getCssModules(modules); + compilation._purifycss_callback(callback); + }); + }); +}; diff --git a/README.md b/README.md index 078fd07..d061dec 100644 --- a/README.md +++ b/README.md @@ -1,190 +1,28 @@ -# UNMAINTAINED +* [PurifyCSS](https://github.com/purifycss/purifycss) plugin/loader for webpack. -Please use: https://github.com/FullHuman/purgecss-webpack-plugin - ---- -[![npm][npm]][npm-url] -[![deps][deps]][deps-url] -[![test][test]][test-url] -[![coverage][cover]][cover-url] -[![quality][quality]][quality-url] -[![chat][chat]][chat-url] - -
- - - - -

PurifyCSS Plugin

-

PurifyCSS for Webpack.

-

- -This plugin uses [PurifyCSS](https://github.com/purifycss/purifycss) to remove unused selectors from your CSS. You **should** use it with the [extract-text-webpack-plugin](https://www.npmjs.com/package/extract-text-webpack-plugin). - -Without any CSS file being emitted as an asset, this plugin will do nothing. You can also use the `file` plugin to drop a CSS file into your output folder, but it is highly recommended to use the PurifyCSS plugin with the Extract Text plugin. - -> This plugin replaces earlier [purifycss-webpack-plugin](https://www.npmjs.com/package/purifycss-webpack-plugin) and it has a different API! - -

Install

- -```bash -npm i -D purifycss-webpack purify-css +# Installation ``` - -

Usage

- -Configure as follows: - -```javascript -const path = require('path'); -const glob = require('glob'); -const ExtractTextPlugin = require('extract-text-webpack-plugin'); -const PurifyCSSPlugin = require('purifycss-webpack'); - -module.exports = { - entry: {...}, - output: {...}, - module: { - rules: [ - { - test: /\.css$/, - loader: ExtractTextPlugin.extract({ - fallbackLoader: 'style-loader', - loader: 'css-loader' - }) - } - ] - }, - plugins: [ - new ExtractTextPlugin('[name].[contenthash].css'), - // Make sure this is after ExtractTextPlugin! - new PurifyCSSPlugin({ - // Give paths to parse for rules. These should be absolute! - paths: glob.sync(path.join(__dirname, 'app/*.html')), - }) - ] -}; +npm install purifycss-loader ``` -And, that's it! Your scripts and view files will be scanned for classes, and those that are unused will be stripped off your CSS - aka. "purified". - -In order to use this plugin to look into multiple paths you will need to: +# Usage +``` javascript +var PurifyCssPlugin = require('purifycss-loader/PurifyCssPlugin'); -1. npm install --save glob-all -2. Add `const glob = require('glob-all');` at the top of your webpack config -3. Then you can pass your paths to an array, like so: - -```javascript -paths: glob.sync([ - path.join(__dirname, '.php'), - path.join(__dirname, 'partials/.php') -]), -``` +var webpackConfig = { + // ... -> You can pass an object (` -> []`) to `paths` if you want to control the behavior per entry. - -

Options

- -This plugin, unlike the original PurifyCSS plugin, provides special features, such as scanning the dependency files. You can configure using the following fields: - -| Property | Description -|---------------------|------------ -| `styleExtensions` | An array of file extensions for determining used classes within style files. Defaults to `['.css']`. -| `moduleExtensions` | An array of file extensions for determining used classes within `node_modules`. Defaults to `[]`, but `['.html']` can be useful here. -| `minimize` | Enable CSS minification. Alias to `purifyOptions.minify`. Disabled by default. -| `paths` | An array of absolute paths or a path to traverse. This also accepts an object (` -> `). It can be a good idea [glob](http://npmjs.org/glob) these. -| `purifyOptions` | Pass [custom options to PurifyCSS](https://github.com/purifycss/purifycss#the-optional-options-argument). -| `verbose` | Set this flag to get verbose output from the plugin. This sets `purifyOptions.info`, but you can override `info` separately if you want less logging. - -> The plugin does **not** emit sourcemaps even if you enable `sourceMap` option on loaders! - -

Usage with CSS Modules

- -PurifyCSS doesn't support classes that have been namespaced with CSS Modules. However, by adding a static string to `css-loader`'s `localIdentName`, you can effectively whitelist these namespaced classes. - -In this example, `purify` will be our whitelisted string. **Note:** Make sure this string doesn't occur in any of your other CSS class names. Keep in mind that whatever you choose will end up in your application at runtime - try to keep it short! - -```javascript -module.exports = { module: { - rules: [ - { - test: /\.css$/, - loader: ExtractTextPlugin.extract({ - fallback: 'style-loader', - use: [ - { - loader: 'css-loader', - options: { - localIdentName: 'purify_[hash:base64:5]', - modules: true - } - } - ] - }) - } - ] + loaders: [{ + test: /\.css$/, loader: "style-loader!css-loader!purifycss-loader" + }] }, + plugins: [ - ..., - new PurifyCSSPlugin({ - purifyOptions: { - whitelist: ['*purify*'] - } - }) + new PurifyCssPlugin(__dirname, '/index.html') ] -}; +} ``` -

Maintainers

- - - - - - - - - - -
- -
- Juho Vepsäläinen -
- -
- Joshua Wiens -
- -
- Kees Kluskens -
- -
- Sean Larkin -
- - -[npm]: https://img.shields.io/npm/v/purifycss-webpack.svg -[npm-url]: https://npmjs.com/package/purifycss-webpack - -[deps]: https://david-dm.org/webpack-contrib/purifycss-webpack.svg -[deps-url]: https://david-dm.org/webpack-contrib/purifycss-webpack - -[chat]: https://img.shields.io/badge/gitter-webpack%2Fwebpack-brightgreen.svg -[chat-url]: https://gitter.im/webpack/webpack - -[test]: https://secure.travis-ci.org/webpack-contrib/purifycss-webpack.svg -[test-url]: http://travis-ci.org/webpack-contrib/purifycss-webpack - -[cover]: https://codecov.io/gh/webpack-contrib/purifycss-webpack/branch/master/graph/badge.svg -[cover-url]: https://codecov.io/gh/webpack-contrib/purifycss-webpack - -[quality]: https://www.bithound.io/github/webpack-contrib/purifycss-webpack/badges/score.svg -[quality-url]: https://www.bithound.io/github/webpack-contrib/purifycss-webpack +# API +Pass in the filepath to the root html for us to detect classes there. PurifyCSS will look at all your bundles on its own. diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index daa6189..0000000 --- a/examples/README.md +++ /dev/null @@ -1,6 +0,0 @@ -1. `npm install` at project root -2. `npm run build` -3. `cd examples` -4. `npm install` -5. `npm run build` -6. Examine `./build` diff --git a/examples/another/component.js b/examples/another/component.js deleted file mode 100644 index 859f06b..0000000 --- a/examples/another/component.js +++ /dev/null @@ -1,8 +0,0 @@ -export default function () { - const element = document.createElement('h1'); - - element.className = 'pure-table'; - element.innerHTML = 'Hello world Again'; - - return element; -} diff --git a/examples/another/index.js b/examples/another/index.js deleted file mode 100644 index 960ad6b..0000000 --- a/examples/another/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import 'purecss'; -import './main.css'; -import component from './component'; - -document.body.appendChild(component()); diff --git a/examples/another/main.css b/examples/another/main.css deleted file mode 100644 index b1645d6..0000000 --- a/examples/another/main.css +++ /dev/null @@ -1,7 +0,0 @@ -body { - background: cornsilk; -} - -body div { - background-color: deepskyblue; -} diff --git a/examples/app/component.js b/examples/app/component.js deleted file mode 100644 index 54d7e41..0000000 --- a/examples/app/component.js +++ /dev/null @@ -1,8 +0,0 @@ -export default function () { - const element = document.createElement('h1'); - - element.className = 'pure-button'; - element.innerHTML = 'Hello world'; - - return element; -} diff --git a/examples/app/index.js b/examples/app/index.js deleted file mode 100644 index 960ad6b..0000000 --- a/examples/app/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import 'purecss'; -import './main.css'; -import component from './component'; - -document.body.appendChild(component()); diff --git a/examples/app/main.css b/examples/app/main.css deleted file mode 100644 index b1645d6..0000000 --- a/examples/app/main.css +++ /dev/null @@ -1,7 +0,0 @@ -body { - background: cornsilk; -} - -body div { - background-color: deepskyblue; -} diff --git a/examples/package.json b/examples/package.json deleted file mode 100644 index 5469eec..0000000 --- a/examples/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "examples", - "scripts": { - "build": "webpack" - }, - "dependencies": { - "css-loader": "^0.26.1", - "extract-text-webpack-plugin": "^2.0.0-beta.5", - "glob": "^7.1.1", - "purecss": "^0.6.2", - "style-loader": "^0.13.1", - "webpack": "^2.2.0-rc.3", - "webpack-merge": "^2.4.0" - } -} diff --git a/examples/webpack.config.js b/examples/webpack.config.js deleted file mode 100644 index aacb91d..0000000 --- a/examples/webpack.config.js +++ /dev/null @@ -1,52 +0,0 @@ -const path = require('path'); -const merge = require('webpack-merge'); -const glob = require('glob'); - -const parts = require('./webpack.parts'); - -const PATHS = { - app: path.join(__dirname, 'app'), - another: path.join(__dirname, 'another'), - build: path.join(__dirname, 'build') -}; - -module.exports = [ - merge( - { - entry: { - app: PATHS.app - }, - output: { - path: path.join(PATHS.build, 'first'), - filename: '[name].js' - } - }, - parts.extractCSS(), - parts.purifyCSS({ - verbose: true, - minimize: true, - paths: glob.sync(`${PATHS.app}/*`), - styleExtensions: ['.css'] - }) - ), - merge( - { - entry: { - first: PATHS.app, - second: PATHS.another - }, - output: { - path: path.join(PATHS.build, 'second'), - filename: '[name].js' - } - }, - parts.extractCSS(), - parts.purifyCSS({ - paths: { - first: glob.sync(`${PATHS.app}/*`), - second: glob.sync(`${PATHS.another}/*`) - }, - styleExtensions: ['.css'] - }) - ) -]; diff --git a/examples/webpack.parts.js b/examples/webpack.parts.js deleted file mode 100644 index 4933b1f..0000000 --- a/examples/webpack.parts.js +++ /dev/null @@ -1,30 +0,0 @@ -const ExtractTextPlugin = require('extract-text-webpack-plugin'); -const PurifyCSSPlugin = require('../'); - -exports.extractCSS = function extractCSS(paths) { - return { - module: { - rules: [ - { - test: /\.css$/, - include: paths, - loader: ExtractTextPlugin.extract({ - fallbackLoader: 'style-loader', - loader: 'css-loader?sourceMap' - }) - } - ] - }, - plugins: [ - new ExtractTextPlugin('[name].css?[hash]') - ] - }; -}; - -exports.purifyCSS = function purifyCSS(options) { - return { - plugins: [ - new PurifyCSSPlugin(options) - ] - }; -}; diff --git a/lib/post_install.js b/lib/post_install.js deleted file mode 100644 index 8d6218c..0000000 --- a/lib/post_install.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// adapted based on rackt/history (MIT) -// Node 4+ -var execSync = require('child_process').execSync; -var stat = require('fs').stat; - -function exec(command) { - execSync(command, { - stdio: [0, 1, 2] - }); -} - -stat('dist', function(error, stat) { - // Skip building on Travis - if (process.env.TRAVIS) { - return; - } - - if (error || !stat.isDirectory()) { - exec('npm i babel-cli babel-preset-es2015 babel-plugin-syntax-object-rest-spread babel-plugin-transform-object-rest-spread'); - exec('npm run build'); - } -}); diff --git a/loader.js b/loader.js new file mode 100644 index 0000000..2ff4afe --- /dev/null +++ b/loader.js @@ -0,0 +1,11 @@ +var purify = require('purify-css'); + +module.exports = function(input){ + if(typeof this._compilation._purifycss_content === 'undefined'){ + return input; + } + + var content = this._compilation._purifycss_content; + + return purify(content, input); +}; diff --git a/package.json b/package.json index cee2a3e..32d1b16 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,14 @@ { - "name": "purifycss-webpack", - "version": "0.7.0", - "description": "PurifyCSS for webpack", - "main": "./dist", + "name": "purifycss-loader", + "version": "1.0.0", + "description": "PurifyCSS plugin for webpack", + "main": "loader.js", "scripts": { - "build": "rimraf dist && babel src -d dist", - "build:watch": "npm-watch", - "test:all": "npm run test:coverage && npm run test:lint", - "test": "jest", - "test:coverage": "jest --coverage", - "test:watch": "jest --watch", - "test:lint": "eslint . --ext .js --ignore-path .gitignore --cache", - "preversion": "npm run test:all && npm run build && git commit --allow-empty -am \"Update dist\"", - "postinstall": "node lib/post_install.js" + "test": "./node_modules/.bin/nodeunit test/plugin.js" }, "repository": { "type": "git", - "url": "https://github.com/webpack-contrib/purifycss-webpack.git" + "url": "https://github.com/purifycss/purifycss-webpack-plugin.git" }, "keywords": [ "webpack", @@ -24,63 +16,21 @@ "plugin", "purify" ], - "files": [ - "dist", - "lib" - ], - "jest": { - "collectCoverage": true, - "collectCoverageFrom": "src/**/*.js", - "moduleFileExtensions": [ - "js" - ], - "moduleDirectories": [ - "node_modules" - ] - }, - "author": "Kenny Tran, Matthew Rourke, Phoebe Li, Kevin \"Ingwie Phoenix\" Ingwersen", - "license": "MIT", + "author": "Kenny Tran, Matthew Rourke, Phoebe Li", + "license": "ISC", "bugs": { - "url": "https://github.com/webpack-contrib/purifycss-webpack/issues" - }, - "homepage": "https://github.com/webpack-contrib/purifycss-webpack", - "peerDependencies": { - "purify-css": ">= 1.0.0 < 2.0.0" + "url": "https://github.com/purifycss/purifycss-webpack-plugin/issues" }, + "homepage": "https://github.com/purifycss/purifycss-webpack-plugin", "dependencies": { - "ajv": "^4.11.2", - "webpack-sources": "^0.1.4" + "css-loader": "^0.14.2", + "purify-css": "^1.0.0", + "style-loader": "^0.12.3" }, "devDependencies": { - "babel-cli": "^6.18.0", - "babel-core": "^6.21.0", - "babel-eslint": "^7.1.1", - "babel-jest": "^18.0.0", - "babel-plugin-syntax-object-rest-spread": "^6.13.0", - "babel-plugin-transform-object-rest-spread": "^6.20.2", - "babel-preset-es2015": "^6.18.0", - "eslint": "^3.13.1", - "eslint-config-airbnb": "^14.0.0", - "eslint-plugin-import": "^2.2.0", - "eslint-plugin-jsx-a11y": "^3.0.2", - "eslint-plugin-react": "^6.9.0", - "git-prepush-hook": "^1.0.1", - "jest": "^18.1.0", - "npm-watch": "^0.1.8", - "purify-css": "^1.2.2", - "rimraf": "^2.6.1" - }, - "pre-push": [ - "build", - "test:all" - ], - "watch": { - "build": { - "patterns": [ - "src" - ], - "extensions": "js", - "quiet": false - } + "purify-css": "^1.0.0", + "string": "^3.1.1", + "nodeunit": "^0.9", + "webpack": "^1.0" } } diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 3acdaaf..0000000 --- a/src/index.js +++ /dev/null @@ -1,85 +0,0 @@ -import fs from 'fs'; -import purify from 'purify-css'; -import { ConcatSource } from 'webpack-sources'; -import * as parse from './parse'; -import * as search from './search'; -import validateOptions from './validate-options'; -import schema from './schema'; - -module.exports = function PurifyPlugin(options) { - return { - apply(compiler) { - const validation = validateOptions( - schema({ - entry: compiler.options.entry - }), - options - ); - - if (!validation.isValid) { - throw new Error(validation.error); - } - - compiler.plugin('this-compilation', (compilation) => { - const entryPaths = parse.entryPaths(options.paths); - - parse.flatten(entryPaths).forEach((p) => { - if (!fs.existsSync(p)) throw new Error(`Path ${p} does not exist.`); - }); - - // Output debug information through a callback pattern - // to avoid unnecessary processing - const output = options.verbose ? - messageCb => console.info(...messageCb()) : - () => {}; - - compilation.plugin('additional-assets', (cb) => { - // Go through chunks and purify as configured - compilation.chunks.forEach( - ({ name: chunkName, files, modules }) => { - const assetsToPurify = search.assets( - compilation.assets, options.styleExtensions - ).filter( - asset => files.indexOf(asset.name) >= 0 - ); - - output(() => [ - 'Assets to purify:', - assetsToPurify.map(({ name }) => name).join(', ') - ]); - - assetsToPurify.forEach(({ name, asset }) => { - const filesToSearch = parse.entries(entryPaths, chunkName).concat( - search.files( - modules, options.moduleExtensions || [], file => file.resource - ) - ); - - output(() => [ - 'Files to search for used rules:', - filesToSearch.join(', ') - ]); - - // Compile through Purify and attach to output. - // This loses sourcemaps should there be any! - compilation.assets[name] = new ConcatSource( - purify( - filesToSearch, - asset.source(), - { - info: options.verbose, - minify: options.minimize, - ...options.purifyOptions - } - ) - ); - }); - } - ); - - cb(); - }); - }); - } - }; -}; diff --git a/src/parse.js b/src/parse.js deleted file mode 100644 index 0797085..0000000 --- a/src/parse.js +++ /dev/null @@ -1,36 +0,0 @@ -function parseEntryPaths(paths) { - const ret = paths || []; - - // Convert possible string to an array - if (typeof ret === 'string') { - return [ret]; - } - - return ret; -} - -function flattenEntryPaths(paths) { - return Array.isArray(paths) ? - paths : - Object.keys(paths).reduce((acc, val) => [...acc, ...paths[val]], []); -} - -function parseEntries(paths, chunkName) { - if (Array.isArray(paths)) { - return paths; - } - - if (!(chunkName in paths)) { - return []; - } - - const ret = paths[chunkName]; - - return Array.isArray(ret) ? ret : [ret]; -} - -module.exports = { - entryPaths: parseEntryPaths, - flatten: flattenEntryPaths, - entries: parseEntries -}; diff --git a/src/parse.test.js b/src/parse.test.js deleted file mode 100644 index 1b75d7e..0000000 --- a/src/parse.test.js +++ /dev/null @@ -1,83 +0,0 @@ -const assert = require('assert'); -const parse = require('./parse'); - -describe('Parse entry paths', function () { - it('returns an empty array by default', function () { - assert.deepEqual(parse.entryPaths(), []); - }); - - it('returns an object as itself', function () { - const o = { a: ['a', 'b', 'c'] }; - - assert.deepEqual(parse.entryPaths(o), o); - }); - - it('puts a string inside an array', function () { - const str = 'foobar'; - - assert.deepEqual(parse.entryPaths(str), [str]); - }); -}); - -describe('Flatten entry paths', function () { - it('returns an array as itself', function () { - const a = ['a', 'b', 'c']; - - assert.deepEqual(parse.flatten(a), a); - }); - - it('returns an object of arrays as one flat array', function () { - const o = { a: ['a', 'b'], b: ['c', 'd'] }; - - assert.deepEqual(parse.flatten(o), ['a', 'b', 'c', 'd']); - }); -}); - -describe('Parse entries', function () { - it('returns paths if there is no chunk name', function () { - const paths = ['a', 'b', 'c']; - - assert.deepEqual(parse.entries(paths), paths); - }); - - it('returns paths if paths are an array already', function () { - const paths = ['a', 'b', 'c']; - - assert.deepEqual(parse.entries(paths, 'foobar'), paths); - }); - - it('returns chunk paths', function () { - const entryPaths = ['a', 'b', 'c']; - const paths = { - foobar: entryPaths - }; - - assert.deepEqual(parse.entries(paths, 'foobar'), entryPaths); - }); - - it('returns chunk path wrapped in an array', function () { - const entryPaths = 'a'; - const paths = { - foobar: entryPaths - }; - - assert.deepEqual(parse.entries(paths, 'foobar'), [entryPaths]); - }); - - it('returns an empty array if failed to find entry', function () { - const paths = { - foobar: 'a' - }; - - assert.deepEqual(parse.entries(paths, 'barbar'), []); - }); - - it('returns an empty array if failed to find entry with multiple paths', function () { - const paths = { - foobar: 'a', - barbar: 'b' - }; - - assert.deepEqual(parse.entries(paths, 'foofoo'), []); - }); -}); diff --git a/src/schema.js b/src/schema.js deleted file mode 100644 index cce104d..0000000 --- a/src/schema.js +++ /dev/null @@ -1,68 +0,0 @@ -const schema = ({ entry } = {}) => ({ - $schema: 'http://json-schema.org/draft-04/schema#', - additionalProperties: false, - type: 'object', - properties: { - styleExtensions: { - type: 'array', - items: { - type: 'string' - }, - default: ['.css'] - }, - minimize: { - type: 'boolean' - }, - moduleExtensions: { - type: 'array', - items: { - type: 'string' - } - }, - paths: parsePaths(entry), - purifyOptions: { - type: 'object', - properties: {} - }, - verbose: { - type: 'boolean' - } - }, - required: [ - 'paths' - ] -}); - -function parsePaths(entry) { - const ret = { - type: ['array', 'object'] - }; - - if (entry instanceof Object) { - ret.additionalProperties = false; - ret.properties = generateProperties(entry); - } else { - ret.items = { - type: 'string' - }; - } - - return ret; -} - -function generateProperties(entry) { - const ret = {}; - - Object.keys(entry).forEach((e) => { - ret[e] = { - type: ['array', 'string'], - items: { - type: 'string' - } - }; - }); - - return ret; -} - -export default schema; diff --git a/src/schema.test.js b/src/schema.test.js deleted file mode 100644 index 8d01808..0000000 --- a/src/schema.test.js +++ /dev/null @@ -1,21 +0,0 @@ -const assert = require('assert'); -const schema = require('./schema').default; - -describe('Schema', function () { - it('converts an object entry to validation', function () { - const entry = { - a: 'foo' - }; - const result = schema({ entry }); - const expected = { - a: { - type: ['array', 'string'], - items: { - type: 'string' - } - } - }; - - assert.deepEqual(result.properties.paths.properties, expected); - }); -}); diff --git a/src/search.js b/src/search.js deleted file mode 100644 index 80d33e8..0000000 --- a/src/search.js +++ /dev/null @@ -1,37 +0,0 @@ -const path = require('path'); - -function searchAssets( - assets = [], - extensions = [] -) { - return Object.keys(assets).map( - name => ( - extensions.indexOf( - path.extname( - name.indexOf('?') >= 0 ? name.split('?').slice(0, -1).join('') : name - ) - ) >= 0 && { name, asset: assets[name] } - ) - ).filter(a => a); -} - -function searchFiles( - modules = {}, - extensions = [], - getter = a => a -) { - return Object.keys(modules).map((name) => { - const file = getter(modules[name]); - - if (!file) { - return null; - } - - return extensions.indexOf(path.extname(file)) >= 0 && file; - }).filter(a => a); -} - -module.exports = { - assets: searchAssets, - files: searchFiles -}; diff --git a/src/search.test.js b/src/search.test.js deleted file mode 100644 index a542091..0000000 --- a/src/search.test.js +++ /dev/null @@ -1,89 +0,0 @@ -const assert = require('assert'); -const search = require('./search'); - -describe('Search assets', function () { - it('returns nothing if nothing is passed', function () { - assert.deepEqual(search.assets(), []); - }); - - it('returns matches based on a pattern', function () { - const modules = { - 'foobar.txt': {}, - 'barbar.css': {} - }; - const extensions = ['.txt']; - const matches = [{ name: 'foobar.txt', asset: {} }]; - - assert.deepEqual(search.assets(modules, extensions), matches); - }); - - it('returns matches if they have query', function () { - const modules = { - 'foobar.txt?123': {}, - 'barbar.css': {} - }; - const extensions = ['.txt']; - const matches = [{ name: 'foobar.txt?123', asset: {} }]; - - assert.deepEqual(search.assets(modules, extensions), matches); - }); -}); - -describe('Search files', function () { - it('returns nothing if nothing is passed', function () { - assert.deepEqual(search.files(), []); - }); - - it('returns matches based on extension', function () { - const modules = ['foobar.txt', 'barbar.css']; - const extensions = ['.txt']; - const matches = ['foobar.txt']; - - assert.deepEqual(search.files(modules, extensions), matches); - }); - - it('does not fail with missing modules', function () { - const modules = ['foobar.txt', '', 'barbar.css']; - const extensions = ['.txt']; - const matches = ['foobar.txt']; - - assert.deepEqual(search.files(modules, extensions), matches); - }); - - it('returns matches based on extension with a customized getter', function () { - const modules = { - foobar: { - resource: 'foobar.txt' - }, - barbar: { - resource: 'barbar.css' - } - }; - const extensions = ['.txt']; - const matches = ['foobar.txt']; - - assert.deepEqual( - search.files(modules, extensions, file => file.resource), - matches - ); - }); - - it('does not fail with missing modules when a getter fails', function () { - const modules = { - foobar: { - resource: 'foobar.txt' - }, - demo: {}, - barbar: { - resource: 'barbar.css' - } - }; - const extensions = ['.txt']; - const matches = ['foobar.txt']; - - assert.deepEqual( - search.files(modules, extensions, file => file.resource), - matches - ); - }); -}); diff --git a/src/validate-options.js b/src/validate-options.js deleted file mode 100644 index c06ec45..0000000 --- a/src/validate-options.js +++ /dev/null @@ -1,16 +0,0 @@ -import Ajv from 'ajv'; - -function validateOptions(schema, data) { - const ajv = new Ajv({ - useDefaults: true, // This mutates the original data with defaults! - errorDataPath: 'property' - }); - const isValid = ajv.validate(schema, data); - - return { - isValid, - error: ajv.errors && ajv.errorsText() - }; -} - -export default validateOptions; diff --git a/src/validate-options.test.js b/src/validate-options.test.js deleted file mode 100644 index fbae580..0000000 --- a/src/validate-options.test.js +++ /dev/null @@ -1,63 +0,0 @@ -const assert = require('assert'); -const validateOptions = require('./validate-options').default; -const schema = require('./schema').default; - -describe('Validate options', function () { - it('fails without a schema and data', function () { - assert.throws( - () => { - validateOptions(); - }, - Error - ); - }); - - it('fails with empty data', function () { - const result = validateOptions(schema()); - - assert.ok(!result.isValid); - assert.ok(result.error); - }); - - it('does not fail if paths are provided', function () { - const result = validateOptions(schema(), { paths: ['./foo'] }); - - assert.ok(result.isValid); - assert.ok(!result.error); - }); - - it('does not allow arbitrary properties', function () { - const result = validateOptions(schema(), { paths: ['./foo'], foobar: ['./foo'] }); - - assert.ok(!result.isValid); - assert.ok(result.error); - }); - - it('styleExtensions have defaults', function () { - const paths = ['./foo']; - const data = { paths }; - - // Currently this mutates data with defaults due to ajv design. It - // might be a good idea to change that behavior, though. - const result = validateOptions(schema(), data); - - assert.deepEqual(data, { paths, styleExtensions: ['.css'] }); - assert.ok(!result.error); - }); - - it('fails without matching path keys', function () { - const data = { - paths: { - a: './foo' - } - }; - - const result = validateOptions(schema({ - entry: { - b: './bar' - } - }), data); - - assert.ok(result.error); - }); -}); diff --git a/test/fixtures/content.js b/test/fixtures/content.js new file mode 100644 index 0000000..3c463bb --- /dev/null +++ b/test/fixtures/content.js @@ -0,0 +1,3 @@ +module.exports = "It works from content.js."; + +console.log('body'); diff --git a/test/fixtures/entry.js b/test/fixtures/entry.js new file mode 100644 index 0000000..a9eb919 --- /dev/null +++ b/test/fixtures/entry.js @@ -0,0 +1,3 @@ +require("!style!css!./style.css"); +require("!style!css!./style2.css"); +document.write(require("./content.js")); \ No newline at end of file diff --git a/test/fixtures/index.html b/test/fixtures/index.html new file mode 100644 index 0000000..49c6b5a --- /dev/null +++ b/test/fixtures/index.html @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/test/fixtures/style.css b/test/fixtures/style.css new file mode 100644 index 0000000..ace4a01 --- /dev/null +++ b/test/fixtures/style.css @@ -0,0 +1,12 @@ +body { + background: yellow; +} +p{ + background: green; +} +video { + background: white; +} +span{ + background: blue; +} \ No newline at end of file diff --git a/test/fixtures/style2.css b/test/fixtures/style2.css new file mode 100644 index 0000000..4138bce --- /dev/null +++ b/test/fixtures/style2.css @@ -0,0 +1,19 @@ +body { + background: red; +} + +div{ + color:purple; +} + +h1 { + margin: 5px; +} + +h2 { + padding: 20em; +} + +button { + background-color: black; +} \ No newline at end of file diff --git a/test/output/bundle.js b/test/output/bundle.js new file mode 100644 index 0000000..88f8191 --- /dev/null +++ b/test/output/bundle.js @@ -0,0 +1,408 @@ +/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.loaded = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ function(module, exports, __webpack_require__) { + + __webpack_require__(1); + __webpack_require__(5); + document.write(__webpack_require__(7)); + +/***/ }, +/* 1 */ +/***/ function(module, exports, __webpack_require__) { + + // style-loader: Adds some css to the DOM by adding a tag + + // load the styles + var content = __webpack_require__(2); + if(typeof content === 'string') content = [[module.id, content, '']]; + // add the styles to the DOM + var update = __webpack_require__(4)(content, {}); + if(content.locals) module.exports = content.locals; + // Hot Module Replacement + if(false) { + // When the styles change, update the tags + if(!content.locals) { + module.hot.accept("!!./../../node_modules/css-loader/index.js!./style.css", function() { + var newContent = require("!!./../../node_modules/css-loader/index.js!./style.css"); + if(typeof newContent === 'string') newContent = [[module.id, newContent, '']]; + update(newContent); + }); + } + // When the module is disposed, remove the tags + module.hot.dispose(function() { update(); }); + } + +/***/ }, +/* 2 */ +/***/ function(module, exports, __webpack_require__) { + + exports = module.exports = __webpack_require__(3)(); + exports.push([module.id, "body {\n background: yellow;\n}\np{\n background: green;\n}\n", ""]); + +/***/ }, +/* 3 */ +/***/ function(module, exports, __webpack_require__) { + + /* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra + */ + // css base code, injected by the css-loader + module.exports = function() { + var list = []; + + // return the list of modules as css string + list.toString = function toString() { + var result = []; + for(var i = 0; i < this.length; i++) { + var item = this[i]; + if(item[2]) { + result.push("@media " + item[2] + "{" + item[1] + "}"); + } else { + result.push(item[1]); + } + } + return result.join(""); + }; + + // import a list of modules into the list + list.i = function(modules, mediaQuery) { + if(typeof modules === "string") + modules = [[null, modules, ""]]; + var alreadyImportedModules = {}; + for(var i = 0; i < this.length; i++) { + var id = this[i][0]; + if(typeof id === "number") + alreadyImportedModules[id] = true; + } + for(i = 0; i < modules.length; i++) { + var item = modules[i]; + // skip already imported module + // this implementation is not 100% perfect for weird media query combinations + // when a module is imported multiple times with different media queries. + // I hope this will never occur (Hey this way we have smaller bundles) + if(typeof item[0] !== "number" || !alreadyImportedModules[item[0]]) { + if(mediaQuery && !item[2]) { + item[2] = mediaQuery; + } else if(mediaQuery) { + item[2] = "(" + item[2] + ") and (" + mediaQuery + ")"; + } + list.push(item); + } + } + }; + return list; + }; + + +/***/ }, +/* 4 */ +/***/ function(module, exports, __webpack_require__) { + + /* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra + */ + var stylesInDom = {}, + memoize = function(fn) { + var memo; + return function () { + if (typeof memo === "undefined") memo = fn.apply(this, arguments); + return memo; + }; + }, + isOldIE = memoize(function() { + return /msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase()); + }), + getHeadElement = memoize(function () { + return document.head || document.getElementsByTagName("head")[0]; + }), + singletonElement = null, + singletonCounter = 0; + + module.exports = function(list, options) { + if(false) { + if(typeof document !== "object") throw new Error("The style-loader cannot be used in a non-browser environment"); + } + + options = options || {}; + // Force single-tag solution on IE6-9, which has a hard limit on the # of + // tags it will allow on a page + if (typeof options.singleton === "undefined") options.singleton = isOldIE(); + + var styles = listToStyles(list); + addStylesToDom(styles, options); + + return function update(newList) { + var mayRemove = []; + for(var i = 0; i < styles.length; i++) { + var item = styles[i]; + var domStyle = stylesInDom[item.id]; + domStyle.refs--; + mayRemove.push(domStyle); + } + if(newList) { + var newStyles = listToStyles(newList); + addStylesToDom(newStyles, options); + } + for(var i = 0; i < mayRemove.length; i++) { + var domStyle = mayRemove[i]; + if(domStyle.refs === 0) { + for(var j = 0; j < domStyle.parts.length; j++) + domStyle.parts[j](); + delete stylesInDom[domStyle.id]; + } + } + }; + } + + function addStylesToDom(styles, options) { + for(var i = 0; i < styles.length; i++) { + var item = styles[i]; + var domStyle = stylesInDom[item.id]; + if(domStyle) { + domStyle.refs++; + for(var j = 0; j < domStyle.parts.length; j++) { + domStyle.parts[j](item.parts[j]); + } + for(; j < item.parts.length; j++) { + domStyle.parts.push(addStyle(item.parts[j], options)); + } + } else { + var parts = []; + for(var j = 0; j < item.parts.length; j++) { + parts.push(addStyle(item.parts[j], options)); + } + stylesInDom[item.id] = {id: item.id, refs: 1, parts: parts}; + } + } + } + + function listToStyles(list) { + var styles = []; + var newStyles = {}; + for(var i = 0; i < list.length; i++) { + var item = list[i]; + var id = item[0]; + var css = item[1]; + var media = item[2]; + var sourceMap = item[3]; + var part = {css: css, media: media, sourceMap: sourceMap}; + if(!newStyles[id]) + styles.push(newStyles[id] = {id: id, parts: [part]}); + else + newStyles[id].parts.push(part); + } + return styles; + } + + function createStyleElement() { + var styleElement = document.createElement("style"); + var head = getHeadElement(); + styleElement.type = "text/css"; + head.appendChild(styleElement); + return styleElement; + } + + function createLinkElement() { + var linkElement = document.createElement("link"); + var head = getHeadElement(); + linkElement.rel = "stylesheet"; + head.appendChild(linkElement); + return linkElement; + } + + function addStyle(obj, options) { + var styleElement, update, remove; + + if (options.singleton) { + var styleIndex = singletonCounter++; + styleElement = singletonElement || (singletonElement = createStyleElement()); + update = applyToSingletonTag.bind(null, styleElement, styleIndex, false); + remove = applyToSingletonTag.bind(null, styleElement, styleIndex, true); + } else if(obj.sourceMap && + typeof URL === "function" && + typeof URL.createObjectURL === "function" && + typeof URL.revokeObjectURL === "function" && + typeof Blob === "function" && + typeof btoa === "function") { + styleElement = createLinkElement(); + update = updateLink.bind(null, styleElement); + remove = function() { + styleElement.parentNode.removeChild(styleElement); + if(styleElement.href) + URL.revokeObjectURL(styleElement.href); + }; + } else { + styleElement = createStyleElement(); + update = applyToTag.bind(null, styleElement); + remove = function() { + styleElement.parentNode.removeChild(styleElement); + }; + } + + update(obj); + + return function updateStyle(newObj) { + if(newObj) { + if(newObj.css === obj.css && newObj.media === obj.media && newObj.sourceMap === obj.sourceMap) + return; + update(obj = newObj); + } else { + remove(); + } + }; + } + + var replaceText = (function () { + var textStore = []; + + return function (index, replacement) { + textStore[index] = replacement; + return textStore.filter(Boolean).join('\n'); + }; + })(); + + function applyToSingletonTag(styleElement, index, remove, obj) { + var css = remove ? "" : obj.css; + + if (styleElement.styleSheet) { + styleElement.styleSheet.cssText = replaceText(index, css); + } else { + var cssNode = document.createTextNode(css); + var childNodes = styleElement.childNodes; + if (childNodes[index]) styleElement.removeChild(childNodes[index]); + if (childNodes.length) { + styleElement.insertBefore(cssNode, childNodes[index]); + } else { + styleElement.appendChild(cssNode); + } + } + } + + function applyToTag(styleElement, obj) { + var css = obj.css; + var media = obj.media; + var sourceMap = obj.sourceMap; + + if(media) { + styleElement.setAttribute("media", media) + } + + if(styleElement.styleSheet) { + styleElement.styleSheet.cssText = css; + } else { + while(styleElement.firstChild) { + styleElement.removeChild(styleElement.firstChild); + } + styleElement.appendChild(document.createTextNode(css)); + } + } + + function updateLink(linkElement, obj) { + var css = obj.css; + var media = obj.media; + var sourceMap = obj.sourceMap; + + if(sourceMap) { + // http://stackoverflow.com/a/26603875 + css += "\n/*# sourceMappingURL=data:application/json;base64," + btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))) + " */"; + } + + var blob = new Blob([css], { type: "text/css" }); + + var oldSrc = linkElement.href; + + linkElement.href = URL.createObjectURL(blob); + + if(oldSrc) + URL.revokeObjectURL(oldSrc); + } + + +/***/ }, +/* 5 */ +/***/ function(module, exports, __webpack_require__) { + + // style-loader: Adds some css to the DOM by adding a tag + + // load the styles + var content = __webpack_require__(6); + if(typeof content === 'string') content = [[module.id, content, '']]; + // add the styles to the DOM + var update = __webpack_require__(4)(content, {}); + if(content.locals) module.exports = content.locals; + // Hot Module Replacement + if(false) { + // When the styles change, update the tags + if(!content.locals) { + module.hot.accept("!!./../../node_modules/css-loader/index.js!./style2.css", function() { + var newContent = require("!!./../../node_modules/css-loader/index.js!./style2.css"); + if(typeof newContent === 'string') newContent = [[module.id, newContent, '']]; + update(newContent); + }); + } + // When the module is disposed, remove the tags + module.hot.dispose(function() { update(); }); + } + +/***/ }, +/* 6 */ +/***/ function(module, exports, __webpack_require__) { + + exports = module.exports = __webpack_require__(3)(); + exports.push([module.id, "body {\n background: red;\n}\n", ""]); + +/***/ }, +/* 7 */ +/***/ function(module, exports, __webpack_require__) { + + module.exports = "It works from content.js."; + + console.log('body'); + + +/***/ } +/******/ ]); \ No newline at end of file diff --git a/test/plugin.js b/test/plugin.js new file mode 100644 index 0000000..e9e6241 --- /dev/null +++ b/test/plugin.js @@ -0,0 +1,67 @@ +var fs = require('fs'); +var path = require('path'); +var webpack = require('webpack'); +var DemoPlugin = require('../'); +var S = require('string'); + +var inputFolder = path.join(__dirname, 'fixtures'); +var inputFile = path.join(inputFolder, 'entry.js'); +var outputFolder = path.join(__dirname, 'output'); +var outputFile = path.join(outputFolder, 'bundle.js'); + +var options = { + chunkModules: true, + exclude: [/node_modules[\\\/]/] +}; + +var compiler = webpack({ + entry: inputFile, + + output: { + path: outputFolder, + filename: "bundle.js" + }, + + loaders: [{ + test: /\.css$/, + loader: "style!css" + }], + + plugins: [ + new DemoPlugin(path.join(__dirname), { + chunkModules: true, + }) + ] +}); + +module.exports.tests = { + + 'bundle includes all css classes': function(test) { + compiler.run(function(err, stats) { + var expected = ["body", "p"]; + var actual = fs.readFileSync(outputFile, 'utf-8'); + + var allPass = expected.every(function(className) { + return S(actual).contains(className) + }); + + test.ok(allPass, "bundle includes all css classes"); + test.done(); + }) + }, + + 'bundle does not include css classes not in source html/js': function(test) { + compiler.run(function(err, stats) { + var expected = ["video", "span", "div", "h1", "h2", "button"]; + var actual = fs.readFileSync(outputFile, 'utf-8'); + + var allPass = expected.every(function(className) { + return !S(actual).contains(className) + }); + + test.ok(allPass, "bundle does not include css classes not in source html/js"); + test.done(); + }); + } + +}; \ No newline at end of file