├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── index.js ├── lib └── cssModules.es6 ├── package.json └── test ├── classes.json └── cssModules.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "rules": { 4 | "indent": [2, 4, {"SwitchCase": 1}], 5 | "quotes": [2, "single"], 6 | "linebreak-style": [2, "unix"], 7 | "semi": [2, "always"], 8 | "camelcase": [2, {"properties": "always"}], 9 | "brace-style": [2, "1tbs", {"allowSingleLine": true}] 10 | }, 11 | "env": { 12 | "es6": true, 13 | "node": true, 14 | "browser": false, 15 | "mocha": true 16 | }, 17 | "extends": "eslint:recommended", 18 | "ecmaFeatures": { 19 | "experimentalObjectRestSpread": false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Source: https://github.com/github/gitignore/blob/master/Node.gitignore 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 20 | .grunt 21 | 22 | # node-waf configuration 23 | .lock-wscript 24 | 25 | # Compiled binary addons (http://nodejs.org/api/addons.html) 26 | build/Release 27 | 28 | # Dependency directory 29 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 30 | node_modules 31 | 32 | # Optional npm cache directory 33 | .npm 34 | 35 | # Optional REPL history 36 | .node_repl_history 37 | 38 | 39 | 40 | # Custom 41 | /lib/*.js 42 | /package-lock.json 43 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | .eslintrc 3 | .travis.yml 4 | node_modules/ 5 | npm-debug.log 6 | /lib/*.es6 7 | /test/ 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - stable 5 | - "0.12" 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | 6 | ## [0.1.3] - 2017-09-03 7 | ### Added 8 | - [Pass CSS modules as an object](https://github.com/posthtml/posthtml-css-modules/pull/10) 9 | 10 | 11 | ## [0.1.2] - 2017-03-24 12 | ### Added 13 | - [Multiple CSS modules](https://github.com/posthtml/posthtml-css-modules/pull/8) 14 | 15 | 16 | ## [0.1.1] - 2016-08-28 17 | ### Fixed 18 | - Windows build 19 | - [gulp watch issue](https://github.com/posthtml/posthtml-css-modules/pull/5) 20 | 21 | 22 | 23 | [0.1.3]: https://github.com/posthtml/posthtml-css-modules/compare/0.1.2...0.1.3 24 | [0.1.2]: https://github.com/posthtml/posthtml-css-modules/compare/0.1.1...0.1.2 25 | [0.1.1]: https://github.com/posthtml/posthtml-css-modules/compare/0.1.0...0.1.1 26 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Kirill Maltsev 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # posthtml-css-modules 2 | [![npm version](https://badge.fury.io/js/posthtml-css-modules.svg)](http://badge.fury.io/js/posthtml-css-modules) 3 | [![Build Status](https://travis-ci.org/posthtml/posthtml-css-modules.svg?branch=master)](https://travis-ci.org/posthtml/posthtml-css-modules) 4 | 5 | [PostHTML](https://github.com/posthtml/posthtml) plugin that inlines [CSS modules](https://github.com/css-modules/css-modules) in HTML. 6 | 7 | 8 | ## Usage 9 | I suggest using [postcss-modules](https://github.com/outpunk/postcss-modules) to generate CSS modules. 10 | Check [the PostHTML documentation](https://github.com/posthtml/posthtml#usage) for integration examples with grunt, gulp, and other build systems. 11 | 12 | If you're more into webpack then you don't need all these modules at all. 13 | With `css`, `style`, and `html` loaders you can achieve the same result: 14 | [css-modules-webpack-example](https://github.com/maltsev/css-modules-webpack-example) 15 | 16 | ### Global file 17 | Let's say we have `cssClasses.json` with all CSS modules inside: 18 | ```json 19 | { 20 | "title": "_title_116zl_1 _heading_9dkf", 21 | "profile": { 22 | "user": "_profile_user_f93j" 23 | } 24 | } 25 | ``` 26 | 27 | Now we can inline these CSS modules in our HTML: 28 | ```js 29 | var posthtml = require('posthtml'); 30 | 31 | posthtml([require('posthtml-css-modules')('./cssClasses.json')]) 32 | .process( 33 | '

My profile

' + 34 | // You can also use nested modules 35 | '
John
' 36 | ) 37 | .then(function (result) { 38 | console.log(result.html); 39 | }); 40 | 41 | //

My profile

42 | //
John
43 | ``` 44 | 45 | ### Directory with several files 46 | CSS modules could be also separated into several files. 47 | For example, `profile.js` and `article.js` inside directory `cssModules/`: 48 | ```js 49 | // profile.js 50 | module.exports = { 51 | user: '_profile_user_f93j' 52 | } 53 | ``` 54 | 55 | ```js 56 | // article.js 57 | module.exports = { 58 | title: '_article__tile _heading' 59 | } 60 | ``` 61 | You can use both JS and JSON for a declaration, as long as the file could be required via `require()`. 62 | 63 | ```js 64 | var posthtml = require('posthtml'); 65 | 66 | posthtml([require('posthtml-css-modules')('./cssModules/')]) 67 | .process( 68 | '
John
' + 69 | '

' 70 | ) 71 | .then(function (result) { 72 | console.log(result.html); 73 | }); 74 | 75 | //
John
76 | //

77 | ``` 78 | 79 | 80 | ### Object 81 | You can also pass CSS modules as an object to the plugin: 82 | ```js 83 | var posthtml = require('posthtml'), 84 | cssModules = { 85 | title: "_title_116zl_1 _heading_9dkf", 86 | profile: { 87 | user: "_profile_user_f93j" 88 | } 89 | }; 90 | 91 | posthtml([require('posthtml-css-modules')(cssModules)]) 92 | .process( 93 | '

My profile

' + 94 | // You can also use nested modules 95 | '
John
' 96 | ) 97 | .then(function (result) { 98 | console.log(result.html); 99 | }); 100 | 101 | //

My profile

102 | //
John
103 | ``` 104 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/cssModules').default; 2 | -------------------------------------------------------------------------------- /lib/cssModules.es6: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import _get from 'lodash.get'; 4 | import parseAttrs from 'posthtml-attrs-parser'; 5 | 6 | let cssModulesCache = {}; 7 | 8 | export default (cssModulesPath) => { 9 | return function cssModules(tree) { 10 | // If the plugin is used in gulp watch or another similar tool, files with CSS modules 11 | // might change between runs. Therefore we purge the cache before each run. 12 | cssModulesCache = {}; 13 | tree.match({attrs: {'css-module': /\w+/}}, node => { 14 | const attrs = parseAttrs(node.attrs); 15 | const cssModuleName = attrs['css-module']; 16 | delete attrs['css-module']; 17 | 18 | attrs.class = attrs.class || []; 19 | attrs.class.push(getCssClassName(cssModulesPath, cssModuleName)); 20 | node.attrs = attrs.compose(); 21 | 22 | return node; 23 | }); 24 | }; 25 | }; 26 | 27 | 28 | function getCssClassName(cssModulesPath, cssModuleName) { 29 | if (typeof cssModulesPath === 'string') { 30 | return getCssClassNameFromPath(cssModulesPath, cssModuleName); 31 | } else { 32 | return getCssClassNameFromObject(cssModulesPath, cssModuleName); 33 | } 34 | } 35 | 36 | 37 | function getCssClassNameFromPath(cssModulesPath, cssModuleName) { 38 | if (fs.lstatSync(cssModulesPath).isDirectory()) { 39 | let cssModulesDir = cssModulesPath; 40 | let cssModuleNameParts = cssModuleName.split('.'); 41 | let cssModulesFile = cssModuleNameParts.shift(); 42 | cssModuleName = cssModuleNameParts.join('.'); 43 | cssModulesPath = path.join(cssModulesDir, cssModulesFile); 44 | } 45 | 46 | const cssModules = getCssModules(path.resolve(cssModulesPath)); 47 | 48 | return getCssClassNameFromObject(cssModules, cssModuleName); 49 | } 50 | 51 | 52 | function getCssClassNameFromObject(cssModules, cssModuleName) { 53 | return cssModuleName.trim().split(' ') 54 | .map(cssModuleName => { 55 | const cssClassName = _get(cssModules, cssModuleName); 56 | if (! cssClassName) { 57 | throw getError('CSS module "' + cssModuleName + '" is not found'); 58 | } else if (typeof cssClassName !== 'string') { 59 | throw getError('CSS module "' + cssModuleName + '" is not a string'); 60 | } 61 | return cssClassName; 62 | }) 63 | .join(' '); 64 | } 65 | 66 | 67 | function getCssModules(cssModulesPath) { 68 | let fullPath = require.resolve(cssModulesPath); 69 | if (! cssModulesCache[fullPath]) { 70 | delete require.cache[fullPath]; 71 | cssModulesCache[fullPath] = require(fullPath); 72 | } 73 | 74 | return cssModulesCache[fullPath]; 75 | } 76 | 77 | 78 | function getError(message) { 79 | const fullMessage = '[posthtml-css-modules] ' + message; 80 | return new Error(fullMessage); 81 | } 82 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "posthtml-css-modules", 3 | "version": "0.1.3", 4 | "description": "Use CSS modules in HTML", 5 | "main": "index.js", 6 | "author": "Kirill Maltsev ", 7 | "license": "MIT", 8 | "scripts": { 9 | "compile": "rimraf lib/*.js && babel -d lib/ lib/", 10 | "lint": "eslint *.js lib/*.es6 test/", 11 | "pretest": "npm run lint && npm run compile", 12 | "test": "mocha --compilers js:babel-core/register --recursive --check-leaks", 13 | "prepublish": "npm run compile" 14 | }, 15 | "keywords": [ 16 | "posthtml", 17 | "posthtml-plugin", 18 | "html", 19 | "postproccessor", 20 | "css", 21 | "css-modules" 22 | ], 23 | "babel": { 24 | "presets": [ 25 | "es2015" 26 | ] 27 | }, 28 | "dependencies": { 29 | "lodash.get": "^4.0.2", 30 | "posthtml-attrs-parser": "^0.1.1" 31 | }, 32 | "devDependencies": { 33 | "babel-cli": "^6.26.0", 34 | "babel-core": "^6.26.0", 35 | "babel-eslint": "^6.0.0", 36 | "babel-preset-es2015": "^6.24.1", 37 | "eslint": "^2.0.0", 38 | "expect": "^1.14.0", 39 | "mocha": "^3.2.0", 40 | "posthtml": "^0.9.2", 41 | "rimraf": "^2.6.1" 42 | }, 43 | "repository": { 44 | "type": "git", 45 | "url": "git://github.com/posthtml/posthtml-css-modules.git" 46 | }, 47 | "bugs": { 48 | "url": "https://github.com/posthtml/posthtml-css-modules/issues" 49 | }, 50 | "homepage": "https://github.com/posthtml/posthtml-css-modules" 51 | } 52 | -------------------------------------------------------------------------------- /test/classes.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "__title __heading", 3 | "color": "__color", 4 | "user": { 5 | "profile": { 6 | "photo": "__user__profile__photo" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/cssModules.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import expect from 'expect'; 3 | import posthtml from 'posthtml'; 4 | import cssModules from '..'; 5 | 6 | const classesPath = path.join(__dirname, 'classes.json'); 7 | const classesDir = path.dirname(classesPath); 8 | const classesObj = require(classesPath); 9 | 10 | 11 | describe('posthtml-css-modules', () => { 12 | it('should inline CSS module from the file (flat config)', () => { 13 | return init( 14 | '
', 15 | '
', 16 | classesPath 17 | ); 18 | }); 19 | 20 | 21 | it('should inline CSS module from the file (deep config)', () => { 22 | return init( 23 | '
', 24 | '
', 25 | classesPath 26 | ); 27 | }); 28 | 29 | it('should inline CSS module from the file (multiple classes)', () => { 30 | return init( 31 | '
', 32 | '
', 33 | classesPath 34 | ); 35 | }); 36 | 37 | it('should do not broken if classes not trimmed', () => { 38 | return init( 39 | '
', 40 | '
', 41 | classesPath 42 | ); 43 | }); 44 | 45 | 46 | it('should inline CSS module from the directory', () => { 47 | return init( 48 | '
', 49 | '
', 50 | classesDir 51 | ); 52 | }); 53 | 54 | 55 | it('should inline CSS module from the object', () => { 56 | return init( 57 | '
', 58 | '
', 59 | classesObj 60 | ); 61 | }); 62 | 63 | 64 | it('should throw an error if the file with the CSS modules is not found', () => { 65 | return init( 66 | '
', 67 | '
', 68 | classesPath 69 | ).catch(error => { 70 | expect(error.message) 71 | .toInclude('Cannot find module') 72 | .toInclude('config/notExists.json'); 73 | }); 74 | }); 75 | 76 | 77 | it('should throw an error if the CSS module is not found in the file', () => { 78 | return init( 79 | '
', 80 | '
', 81 | classesPath 82 | ).catch(error => { 83 | expect(error.message) 84 | .toInclude('[posthtml-css-modules] CSS module "notExists" is not found'); 85 | }); 86 | }); 87 | 88 | 89 | it('should throw an error if the file with the CSS module is not found in the directory', () => { 90 | return init( 91 | '
', 92 | '
', 93 | classesDir 94 | ).catch(error => { 95 | expect(error.message) 96 | .toInclude('Cannot find module') 97 | .toInclude('test/notExists'); 98 | }); 99 | }); 100 | }); 101 | 102 | 103 | function init(html, expectedHtml, options) { 104 | return posthtml([cssModules(options)]).process(html).then(result => { 105 | expect(result.html).toBe(expectedHtml); 106 | }); 107 | } 108 | --------------------------------------------------------------------------------