├── .editorconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── index.js ├── package.json └── test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | tmp 4 | .idea 5 | .vscode 6 | typings 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 'node' 5 | - '6' 6 | - '4' 7 | - '0.12' 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Denis Malinochkin 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-attrs-sorter 2 | 3 | > A [PostHTML](https://github.com/posthtml/posthtml) plugin for sort attribute of the tag based on the given order. 4 | 5 | [![Travis](https://img.shields.io/travis/mrmlnc/posthtml-attrs-sorter.svg?style=flat-square)](https://travis-ci.org/mrmlnc/posthtml-attrs-sorter) 6 | [![NPM version](https://img.shields.io/npm/v/posthtml-attrs-sorter.svg?style=flat-square)](https://www.npmjs.com/package/posthtml-attrs-sorter) 7 | [![devDependency Status](https://img.shields.io/david/mrmlnc/posthtml-attrs-sorter.svg?style=flat-square)](https://david-dm.org/mrmlnc/posthtml-attrs-sorter#info=dependencies) 8 | [![devDependency Status](https://img.shields.io/david/dev/mrmlnc/posthtml-attrs-sorter.svg?style=flat-square)](https://david-dm.org/mrmlnc/posthtml-attrs-sorter#info=devDependencies) 9 | 10 | ## Install 11 | 12 | ```shell 13 | $ npm i -D posthtml-attrs-sorter 14 | ``` 15 | 16 | ## Why? 17 | 18 | To follow the guidelines for writing code such as [Code Guide by @mdo](http://codeguide.co/). 19 | 20 | ## Usage 21 | 22 | ```js 23 | const posthtml = require('posthtml'); 24 | const attrsSorter = require('posthtml-attrs-sorter'); 25 | const htmlRaw = fs.readFileSync('path/to/file.html'); 26 | 27 | posthtml() 28 | .use(attrsSorter({ 29 | // Options 30 | })) 31 | .process(htmlRaw) 32 | .then(function(result) { 33 | fs.writeFileSync('path/to/file.html'); 34 | }) 35 | ``` 36 | 37 | You can also use this plugin in the PostHTML plugin to [Gulp](https://www.npmjs.com/package/gulp-posthtml) or [Grunt](https://www.npmjs.com/package/grunt-posthtml). 38 | 39 | ## Options 40 | 41 | #### order 42 | 43 | * Type: `string[]` 44 | * Info: 45 | * Strings are turned into [Regular Expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp) 46 | * Use `$unknown$` to specify where the unsorted attributes are placed 47 | * Default: http://codeguide.co/#html-attribute-order 48 | 49 | ```json 50 | { 51 | "order": [ 52 | "class", "id", "name", 53 | "data-.+", "ng-.+", "src", 54 | "for", "type", "href", 55 | "values", "title", "alt", 56 | "role", "aria-.+", 57 | "$unknown$" 58 | ] 59 | } 60 | ``` 61 | 62 | An array of attributes in the correct order. 63 | 64 | ## Plugins for editors and IDE 65 | 66 | * [Visual Studio Code](https://github.com/mrmlnc/vscode-attrs-sorter) 67 | 68 | ## Changelog 69 | 70 | See the [Releases section of our GitHub project](https://github.com/mrmlnc/posthtml-attrs-sorter/releases) for changelogs for each release version. 71 | 72 | ## License 73 | 74 | MIT. 75 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = function(opts) { 2 | // Added Angular after data. See https://github.com/mdo/code-guide/issues/106 3 | var orderList = (opts && opts.order) || [ 4 | 'class', 'id', 'name', 5 | 'data-.+', 'ng-.+', 'src', 6 | 'for', 'type', 'href', 7 | 'values', 'title', 'alt', 8 | 'role', 'aria-.+', 9 | '$unknown$' 10 | ]; 11 | 12 | // A RegExp's for filtering and sorting 13 | var orderListFilterRegExp = new RegExp('^(' + orderList.join('|') + ')$'); 14 | var orderListRegExp = orderList.map(function(item) { 15 | return new RegExp('^' + item + '$'); 16 | }); 17 | 18 | return function(tree) { 19 | tree.walk(function(node) { 20 | if (!node.attrs) { 21 | return node; 22 | } 23 | 24 | var attrs = Object.keys(node.attrs); 25 | 26 | if (attrs.length === 1 || orderList.length === 0) { 27 | return node; 28 | } 29 | 30 | var sortableAttrs = []; 31 | var sortedAttrs = []; 32 | var notSortedAttrs = []; 33 | var notSortedAttrsIndex = orderList.indexOf('$unknown$'); 34 | var finalAttrs = {}; 35 | 36 | if (notSortedAttrsIndex === -1) { 37 | notSortedAttrsIndex = orderList.length; 38 | } 39 | 40 | sortableAttrs = attrs 41 | // The separation of the attributes on a sortable and not sortable basis 42 | .filter(function(attr) { 43 | if (orderListFilterRegExp.test(attr)) { 44 | return true; 45 | } 46 | 47 | notSortedAttrs.push(attr); 48 | 49 | return false; 50 | }); 51 | 52 | sortedAttrs = orderListRegExp 53 | // match to each position 54 | .map(function(regex) { 55 | return sortableAttrs 56 | // attrs that belong in this regex group 57 | .filter(function(attr) { 58 | return regex.test(attr); 59 | }) 60 | // alpha desc sort each group 61 | .sort(function(a, b) { 62 | return typeof a.localeCompare === 'function' ? a.localeCompare(b) : a - b; 63 | }); 64 | }) 65 | // remove empty groups 66 | .filter(function(group) { 67 | return group.length > 0; 68 | }); 69 | 70 | sortedAttrs 71 | // put the non-sorted attributes in desired slot 72 | .splice(notSortedAttrsIndex, 0, notSortedAttrs); 73 | 74 | sortedAttrs.forEach(function(group) { 75 | group.forEach(function(attr) { 76 | finalAttrs[attr] = (node.attrs[attr]) ? node.attrs[attr] : true; 77 | }); 78 | }); 79 | 80 | node.attrs = finalAttrs; 81 | 82 | return node; 83 | }); 84 | 85 | return tree; 86 | }; 87 | }; 88 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "posthtml-attrs-sorter", 3 | "description": "The sorting attribute of the tag based on the given order", 4 | "version": "1.1.0", 5 | "author": { 6 | "name": "Denis Malinochkin", 7 | "url": "canonium.com" 8 | }, 9 | "repository": "mrmlnc/posthtml-attrs-sorter", 10 | "license": "MIT", 11 | "keywords": [ 12 | "posthtml", 13 | "html", 14 | "posthtml-plugin", 15 | "attrs", 16 | "attributes", 17 | "sorter", 18 | "sort" 19 | ], 20 | "devDependencies": { 21 | "mocha": "^3.0.1", 22 | "posthtml": "^0.9.0", 23 | "xo": "^0.16.0" 24 | }, 25 | "engines": { 26 | "node": ">= 0.12.0" 27 | }, 28 | "scripts": { 29 | "test": "xo && mocha" 30 | }, 31 | "files": [ 32 | "index.js" 33 | ], 34 | "xo": { 35 | "rules": { 36 | "indent": [2, 2, { "SwitchCase": 1 }], 37 | "arrow-parens": [2, "always"], 38 | "object-curly-spacing": [2, "always"], 39 | "babel/object-curly-spacing": 0, 40 | "space-before-function-paren": [2, "never"], 41 | "generator-star-spacing": [2, "after"] 42 | }, 43 | "envs": [ 44 | "node", 45 | "mocha" 46 | ] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var posthtml = require('posthtml'); 3 | var m = require('./'); 4 | 5 | var test = function(input, output, opts, done) { 6 | posthtml() 7 | .use(m(opts)) 8 | .process(input) 9 | .then(function(result) { 10 | assert.equal(result.html, output); 11 | done(); 12 | }) 13 | .catch(function(err) { 14 | done(err); 15 | }); 16 | }; 17 | 18 | it('Standard test', function(done) { 19 | test( 20 | '', 21 | '', 22 | {}, 23 | done 24 | ); 25 | }); 26 | 27 | it('Angular test', function(done) { 28 | test( 29 | '{{phone.name}}', 30 | '{{phone.name}}', 31 | {}, 32 | done 33 | ); 34 | }); 35 | 36 | it('Aria test', function(done) { 37 | test( 38 | 'A checkbox label', 39 | 'A checkbox label', 40 | {}, 41 | done 42 | ); 43 | }); 44 | 45 | it('Custom config test', function(done) { 46 | test( 47 | '', 48 | '', 49 | { 50 | order: ['href', 'id', 'class'] 51 | }, 52 | done 53 | ); 54 | }); 55 | 56 | it('Empty config test', function(done) { 57 | test( 58 | '', 59 | '', 60 | { 61 | order: [] 62 | }, 63 | done 64 | ); 65 | }); 66 | 67 | it('Custom config bug (issue #12)', function(done) { 68 | test( 69 | 'image', 70 | 'image', 71 | { 72 | order: [ 73 | 'id', 74 | 'class', 75 | 'src', 76 | 'width', 77 | 'height', 78 | 'for', 79 | 'type', 80 | 'href', 81 | 'title', 82 | 'alt', 83 | 'value' 84 | ] 85 | }, 86 | done 87 | ); 88 | }); 89 | 90 | it('Put unsorted in specific location', function(done) { 91 | test( 92 | 'image', 93 | 'image', 94 | { 95 | order: [ 96 | 'src', 97 | 'id', 98 | '$unknown$', 99 | 'class' 100 | ] 101 | }, 102 | done 103 | ); 104 | }); 105 | --------------------------------------------------------------------------------