├── test ├── .gitignore ├── .bowerrc ├── mocha.opts ├── bower.json ├── lib │ └── mock-fs-helper.js ├── stylus-native-tests.js └── stylus-tech.test.js ├── .eslintignore ├── .gitignore ├── .eslintrc ├── .travis.yml ├── .editorconfig ├── appveyor.yml ├── .jscs.json ├── package.json ├── CHANGELOG.md ├── api.en.md ├── api.ru.md ├── README.md ├── techs └── stylus.js └── LICENSE.txt /test/.gitignore: -------------------------------------------------------------------------------- 1 | fixtures 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test/fixtures 3 | -------------------------------------------------------------------------------- /test/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "fixtures" 3 | } 4 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --ui bdd 2 | --reporter spec 3 | --require must 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | npm-debug.log 4 | node_modules 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | env: 2 | node: true 3 | mocha: true 4 | es6: true 5 | 6 | extends: pedant 7 | -------------------------------------------------------------------------------- /test/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "enb-stylus-fixtures", 3 | "private": true, 4 | "dependencies": { 5 | "stylus": "stylus/stylus#0.54.5" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | 5 | matrix: 6 | include: 7 | - node_js: "8" 8 | env: COVERALLS=1 9 | 10 | after_success: 11 | - if [ "x$COVERALLS" = "x1" ]; then npm run coveralls; fi 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | 10 | [*.{json,*rc,yml}] 11 | indent_size = 2 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{build}" 2 | 3 | environment: 4 | nodejs_version: "0.12" 5 | 6 | matrix: 7 | fast_finish: true 8 | 9 | install: 10 | - ps: Install-Product node $env:nodejs_version 11 | - node --version 12 | - npm --version 13 | - npm install 14 | 15 | test_script: 16 | - npm run unit 17 | 18 | build: off 19 | -------------------------------------------------------------------------------- /.jscs.json: -------------------------------------------------------------------------------- 1 | { 2 | "excludeFiles": [ 3 | "node_modules/**", 4 | "coverage/**", 5 | "test/fixtures" 6 | ], 7 | "requireSpaceAfterKeywords": [ 8 | "if", 9 | "else", 10 | "for", 11 | "while", 12 | "do", 13 | "switch", 14 | "return", 15 | "try", 16 | "catch" 17 | ], 18 | "requireSpaceBeforeBlockStatements": true, 19 | "requireSpacesInConditionalExpression": true, 20 | "requireSpacesInFunction": { 21 | "beforeOpeningCurlyBrace": true 22 | }, 23 | "requireSpacesInAnonymousFunctionExpression": { 24 | "beforeOpeningRoundBrace": true 25 | }, 26 | "disallowSpacesInNamedFunctionExpression": { 27 | "beforeOpeningRoundBrace": true 28 | }, 29 | "requireBlocksOnNewline": 1, 30 | "disallowPaddingNewlinesInBlocks": true, 31 | "disallowSpacesInsideArrayBrackets": "nested", 32 | "requireSpacesInsideObjectBrackets": "all", 33 | "disallowQuotedKeysInObjects": "allButReserved", 34 | "disallowSpaceAfterObjectKeys": true, 35 | "requireCommaBeforeLineBreak": true, 36 | "requireOperatorBeforeLineBreak": true, 37 | "disallowSpaceAfterPrefixUnaryOperators": true, 38 | "disallowSpaceBeforePostfixUnaryOperators": true, 39 | "requireSpaceBeforeBinaryOperators": true, 40 | "requireSpaceAfterBinaryOperators": true, 41 | "requireCamelCaseOrUpperCaseIdentifiers": true, 42 | "disallowKeywords": ["with"], 43 | "disallowMultipleLineStrings": true, 44 | "disallowMultipleLineBreaks": true, 45 | "validateLineBreaks": "LF", 46 | "validateQuoteMarks": { 47 | "mark": "'", 48 | "escape": true 49 | }, 50 | "disallowMixedSpacesAndTabs": true, 51 | "disallowTrailingWhitespace": true, 52 | "disallowKeywordsOnNewLine": [ 53 | "else", 54 | "catch" 55 | ], 56 | "requireLineFeedAtFileEnd": true, 57 | "maximumLineLength": 120, 58 | "requireCapitalizedConstructors": true, 59 | "safeContextKeyword": ["_this"], 60 | "disallowYodaConditions": true, 61 | "requireSpaceAfterLineComment": true, 62 | "disallowNewlineBeforeBlockStatements": true 63 | } 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "enb-stylus", 3 | "version": "3.2.0", 4 | "description": "enb stylus techs", 5 | "keywords": [ 6 | "enb", 7 | "stylus", 8 | "styl", 9 | "css", 10 | "autoprefixer", 11 | "nib" 12 | ], 13 | "author": "Marat Dulin ", 14 | "licenses": [ 15 | { 16 | "type": "MPL-2.0", 17 | "url": "https://github.com/enb/enb-stylus/blob/master/LICENSE.txt" 18 | } 19 | ], 20 | "repository": { 21 | "type": "git", 22 | "url": "git@github.com:enb/enb-stylus.git" 23 | }, 24 | "homepage": "https://github.com/enb/enb-stylus", 25 | "bugs": "https://github.com/enb/enb-stylus/issues", 26 | "maintainers": [ 27 | { 28 | "name": "Andrew Abramov", 29 | "email": "andrewblond@yandex.com", 30 | "github-username": "blond" 31 | }, 32 | { 33 | "name": "Marat Dulin", 34 | "email": "mdevils@yandex.ru", 35 | "github-username": "mdevils" 36 | } 37 | ], 38 | "contributors": [ 39 | "Andrew Abramov ", 40 | "Marat Dulin ", 41 | "Nickolay Ilchenko " 42 | ], 43 | "peerDependencies": { 44 | "enb": ">=0.16.0 <2.0.0" 45 | }, 46 | "dependencies": { 47 | "autoprefixer": "9.8.0", 48 | "csswring": "7.0.0", 49 | "nib": "1.1.2", 50 | "postcss": "7.0.32", 51 | "postcss-import": "7.1.3", 52 | "postcss-url": "8.0.0", 53 | "stylus": "0.54.7" 54 | }, 55 | "devDependencies": { 56 | "bower": "^1.8.4", 57 | "bower-npm-install": "^0.5.11", 58 | "deep-extend": "^0.6.0", 59 | "enb": ">=0.16.0 <2.0.0", 60 | "eslint": "^5.8.0", 61 | "eslint-config-pedant": "^1.0.1", 62 | "istanbul": "^0.4.5", 63 | "jscs": "^3.0.7", 64 | "mocha": "^5.2.0", 65 | "mock-enb": "^0.3.6", 66 | "mock-fs": "^4.7.0", 67 | "must": "^0.13.4" 68 | }, 69 | "scripts": { 70 | "test": "npm run lint && npm run unit", 71 | "unit": "npm run fixtures && mocha", 72 | "lint": "eslint . && jscs .", 73 | "fixtures": "cd test && bower-npm-install", 74 | "cover": "istanbul cover _mocha", 75 | "coveralls": "npm i coveralls && npm run cover -- --report lcovonly && cat ./coverage/lcov.info | coveralls" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /test/lib/mock-fs-helper.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | path = require('path'); 3 | 4 | module.exports = { 5 | /** 6 | * Duplicate of the real file system for passed dir, used for mock fs for tests 7 | * @param {String} dir – filename of directory (full path to directory) 8 | * @returns {Object} - object with duplicating fs 9 | */ 10 | duplicateFSInMemory: function (dir) { 11 | var obj = {}; 12 | 13 | fs.readdirSync(dir).forEach(function (basename) { 14 | var filename = path.join(dir, basename), 15 | stat = fs.statSync(filename); 16 | 17 | if (stat.isDirectory()) { 18 | process(obj, dir, basename); 19 | } else { 20 | obj[basename] = readFile(filename); 21 | } 22 | }); 23 | 24 | return obj; 25 | }, 26 | 27 | /** 28 | * 1. Remove all css comments, because they going to remove after @import stylus 29 | * 2. Remove all spaces and white lines 30 | * @param {String} contents - file contents 31 | * @returns {String} 32 | */ 33 | normalizeFile: function (contents) { 34 | return contents 35 | .replace(/(\r\n|\n|\r)/gm, '') // remove line breaks 36 | .replace(/(\/\*([\s\S]*?)\*\/)|(\/\/(.*)$)/gm, '') // spaces 37 | .trim(); 38 | }, 39 | 40 | readFile: readFile 41 | }; 42 | 43 | /** 44 | * Function to traverse the directory tree 45 | * @param {Object} obj - model of fs 46 | * @param {String} root - root dirname 47 | * @param {String} dir - dirname 48 | */ 49 | function process(obj, root, dir) { 50 | var dirname = dir ? path.join(root, dir) : root, 51 | name = dir || root, 52 | additionObj = obj[name] = {}; 53 | 54 | fs.readdirSync(dirname).forEach(function (basename) { 55 | var filename = path.join(dirname, basename), 56 | stat = fs.statSync(filename); 57 | 58 | if (stat.isDirectory()) { 59 | process(additionObj, dirname, basename); 60 | } else { 61 | additionObj[basename] = readFile(filename); 62 | } 63 | }); 64 | } 65 | 66 | /** 67 | * Helper for reading file. 68 | * For text files calls a function to delete /r symbols 69 | * @param {String} filename - filename 70 | * @returns {*} 71 | */ 72 | function readFile(filename) { 73 | var ext = path.extname(filename); 74 | 75 | if (['.gif', '.png', '.jpg', '.jpeg', '.svg'].indexOf(ext) !== -1) { 76 | return fs.readFileSync(filename); 77 | } 78 | 79 | return fs.readFileSync(filename, 'utf-8'); 80 | } 81 | -------------------------------------------------------------------------------- /test/stylus-native-tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Running of the original tests of stylus module 3 | */ 4 | 5 | var fs = require('fs'), 6 | path = require('path'), 7 | mockFs = require('mock-fs'), 8 | mockFsHelper = require(path.join(__dirname, 'lib', 'mock-fs-helper')), 9 | MockNode = require('mock-enb/lib/mock-node'), 10 | FileList = require('enb/lib/file-list'), 11 | StylusTech = require('../techs/stylus'), 12 | stylusDir = path.join(__dirname, 'fixtures', 'stylus', 'test'), 13 | casesMock = mockFsHelper.duplicateFSInMemory(path.join(stylusDir, 'cases')), 14 | imagesMock = mockFsHelper.duplicateFSInMemory(path.join(stylusDir, 'images')), 15 | nodeModules = mockFsHelper.duplicateFSInMemory(path.resolve('node_modules')), 16 | stylusCasesIgnores = [ 17 | // File is`t included in the test cases 18 | 'index', 19 | 20 | // it is expected in advance is`t valid css, which then cannot be parse by postcss 21 | 'bifs.remove', 22 | 'escape', 23 | 'for.complex', 24 | 'object', 25 | 'operators', 26 | 27 | // not use native stylus compress option, 28 | // by default, compression is a `postcss` plugin `csswring` 29 | 'atrules.compressed', 30 | 'compress.units', 31 | 'regression.248.compressed', 32 | 'compress.comments', 33 | 34 | // enb-technology `stylus` not use option `hoist atrules` 35 | 'hoist.at-rules', 36 | 37 | // skip this test, because on BEM project we don't need to check 38 | // for the file while you override links on stylus 39 | // for history: https://github.com/stylus/stylus/issues/1951 40 | 'import.include.resolver.nested', 41 | 42 | // The skipped tests cases for which written tests inside the package enb-stylus. 43 | // It need to take into account the work stylus + postcss 44 | 'functions.url', 45 | 'import.include.complex', 46 | 'import.include.function', 47 | 'import.include.in.function', 48 | 'import.include.resolver.absolute', 49 | 'import.include.resolver.images', 50 | 'import.include.megacomplex', 51 | 'require.include', 52 | 53 | // Does not work when disclosure @import 54 | 'bifs.selector.exitsts', 55 | 'introspection', 56 | 'media.complex', 57 | 'object.complex', 58 | 'supports', 59 | 60 | // Does not work in NodeJS 4 61 | 'bifs.use', 62 | 'import.lookup' 63 | ]; 64 | 65 | addSuite('cases', readDir(stylusDir + '/cases', '.styl'), function (test, done) { 66 | // Expected css for this test 67 | // stylusDir + '/cases/' + test + '.css' 68 | var css = mockFsHelper.readFile(path.join(stylusDir, 'cases', test + '.css'), true), 69 | // base scheme for mock, contain requiring images and cases 70 | fsScheme = { 71 | cases: casesMock, 72 | images: imagesMock, 73 | // jscs:disable 74 | node_modules: nodeModules 75 | // jscs:enable 76 | }; 77 | 78 | // mock file system 79 | mockFs(fsScheme); 80 | 81 | var node = new MockNode('cases'), 82 | fileList = new FileList(); 83 | 84 | fileList.addFiles([{ 85 | fullname: 'cases/' + test + '.styl', 86 | name: test + '.styl', 87 | suffix: 'styl' 88 | }]); 89 | node.provideTechData('?.files', fileList); 90 | 91 | node.runTechAndGetContent( 92 | StylusTech, { 93 | includes: ['./images', './cases/import.basic'], 94 | prefix: test.indexOf('prefix.') !== -1 && 'prefix-', 95 | 96 | // non stylus option 97 | comments: false, 98 | imports: (test.indexOf('include') !== -1 || test.indexOf('import.include.resolver.css-file') !== -1) && 99 | 'include', 100 | url: test.indexOf('resolver') !== -1 && 'rebase' || test.indexOf('functions.url') !== -1 && 'inline' 101 | } 102 | ) 103 | .spread(function (source) { 104 | var processedSource = mockFsHelper.normalizeFile(source), 105 | processedCss = mockFsHelper.normalizeFile(css); 106 | 107 | processedSource.must.eql(processedCss); 108 | mockFs.restore(); 109 | done(); 110 | }) 111 | .fail(function (err) { 112 | mockFs.restore(); 113 | done(err); 114 | }); 115 | }, stylusCasesIgnores); 116 | 117 | /** 118 | * Helper for generating test by passed arguments 119 | * @param {String} desc - description 120 | * @param {String[]} cases - case names 121 | * @param {Function} fn - callback 122 | * @param {String[]} ignores — case names to ignore 123 | */ 124 | function addSuite(desc, cases, fn, ignores) { 125 | describe(desc, function () { 126 | cases.forEach(function (test) { 127 | var name = normalizeTestName(test); 128 | 129 | // skip some test, that non important for working enb-stylus technology 130 | if (ignores && ignores.indexOf(test) === -1) { 131 | it(name, function (done) { 132 | fn(test, done); 133 | }); 134 | } 135 | }); 136 | }); 137 | } 138 | 139 | /** 140 | * Helper for reading and filter files in passed dir by extensions 141 | * @param {String} dir - dir filename 142 | * @param {String} ext - extention 143 | * @returns {*} 144 | */ 145 | function readDir(dir, ext) { 146 | ext = ext || '.styl'; 147 | 148 | return fs.readdirSync(dir) 149 | .filter(function (file) { 150 | return file.indexOf(ext) !== -1; 151 | }) 152 | .map(function (file) { 153 | return file.replace(ext, ''); 154 | }); 155 | } 156 | 157 | /** 158 | * Normalize name for add title to test, remove dash and dots 159 | * @param {String} name - source name of test file 160 | * @returns {String} 161 | */ 162 | function normalizeTestName(name) { 163 | return name.replace(/[-.]/g, ' '); 164 | } 165 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | История изменений 2 | ================= 3 | 4 | 3.1.0 5 | ----- 6 | 7 | ### Зависимости 8 | * Модуль `stylus@0.54.5` обновлен до версии `0.54.7`. 9 | * Модуль `postcss@7.0.5` обновлен до версии `7.0.32`. 10 | * Модуль `autoprefixer@9.2.0` обновлен до версии `9.8.0`. 11 | 12 | 3.0.0 13 | ----- 14 | 15 | ### Крупные изменения 16 | * Прекращена поддержка node < 8.x 17 | * Добавлена поддержка новых версий npm 18 | 19 | ### Зависимости 20 | * Удалены зависимости от модулей `es6-promise` и `vow`. 21 | * Модуль `autoprefixer@6.7.7` обновлен до версии `9.2.0`. 22 | * Модуль `csswring@4.2.3` обновлен до версии `7.0.0`. 23 | * Модуль `postcss@5.2.15` обновлен до версии `7.0.5`. 24 | * Модуль `postcss-url@5.1.2` обновлен до версии `8.0.0`. 25 | 26 | 2.4.0 27 | ----- 28 | 29 | ### Опции 30 | 31 | * Добавлена опция [inlineMaxSize](api.ru.md#inlinemaxsize) (@creedencewright [#128]). 32 | 33 | ### Зависимости 34 | 35 | * Модуль `autoprefixer@6.3.4` обновлен до версии `6.4.0`. 36 | * Модуль `csswring@4.2.2` обновлен до версии `4.2.3`. 37 | * Модуль `es6-promise@3.1.2` обновлен до версии `3.2.1`. 38 | * Модуль `nib@1.1.0` обновлен до версии `1.1.2`. 39 | * Модуль `postcss@5.0.19` обновлен до версии `5.1.2`. 40 | * Модуль `postcss-url@5.1.1` обновлен до версии `5.1.2`. 41 | 42 | 2.3.3 43 | ----- 44 | 45 | ### Зависимости 46 | 47 | * Модуль `stylus@0.54.2` обновлен до версии `0.54.5`. 48 | 49 | 2.3.2 50 | ----- 51 | 52 | ### Исправления ошибок 53 | 54 | * Исправлена опция [autoprefixer](./api.ru.md#autoprefixer) ([#125]). 55 | 56 | 2.3.1 57 | ----- 58 | 59 | К сожалению, в `postcss-import` версии 8 было отрезано всё, что не вписывается в идеальную модель авторов модуля. 60 | 61 | С учетом того, что при сборке `@import` попадают в центр файла, которые в версии 8 не раскрываются ([postcss/postcss-import#176](https://github.com/postcss/postcss-import/issues/176)) и использовать его 62 | как есть далее не представляется возможным. 63 | 64 | ### Зависимости 65 | 66 | * Обновление модуля `postcss-import@8.0.2` отменено, используется версия `7.1.3`. 67 | 68 | 2.3.0 69 | ----- 70 | 71 | ### Опции 72 | 73 | * Добавлена опция [use](./api.ru.md#use) ([#111]). 74 | * Добавлена опция [importPaths](./api.ru.md#importpaths) ([#111]). 75 | 76 | ### Зависимости 77 | 78 | * Модуль `autoprefixer@6.0.3` обновлен до версии `6.3.4`. 79 | * Модуль `csswring@4.0.0` обновлен до версии `4.2.2`. 80 | * Модуль `es6-promise@3.0.2` обновлен до версии `3.1.2`. 81 | * Модуль `postcss@5.0.10` обновлен до версии `5.0.19`. 82 | * Модуль `postcss-import@7.1.0` обновлен до версии `8.0.2`. 83 | * Модуль `postcss-url@5.0.2` обновлен до версии `5.1.1`. 84 | * Модуль `stylus@0.52.0` обновлен до версии `0.54.2`. 85 | * Модуль `vow@0.4.10` обновлен до версии `0.4.12`. 86 | 87 | ### Остальное 88 | 89 | * Уменьшено время подключения технологий ([#120]). 90 | 91 | 2.2.0 92 | ----- 93 | 94 | ### Опции 95 | 96 | * Добавлена опция [globals](api.ru.md#globals) ([#113]). 97 | 98 | ### Зависимости 99 | 100 | * Модуль `postcss@4.1.16` обновлен до версии `5.0.10` ([#116]). 101 | * Модуль `postcss-import@6.2.0` обновлен до версии `7.1.0` ([#116]). 102 | * Модуль `postcss-url@4.0.1` обновлен до версии `5.0.2` ([#116]). 103 | * Модуль `csswring@3.0.5` обновлен до версии `4.0.0` ([#116]). 104 | * Вместо модуля `autoprefixer-core@5.2.1` используется `autoprefixer@6.0.3` ([#116]). 105 | 106 | 2.1.0 107 | ----- 108 | 109 | * Добавлена поддержка `enb` версии `1.x` ([#109]). 110 | 111 | 2.0.0 112 | ----- 113 | 114 | ### Технологии 115 | 116 | * [ __*major*__ ] Технологии `css-stylus`, `css-stylus-with-nib` и `css-stylus-with-autoprefixer` объединены в одну — [stylus](api.ru.md) ([#67], [#68]). 117 | 118 | ### Крупные изменения 119 | 120 | * Добавлена поддержка карт кода (source maps) ([#60]). 121 | * [ __*major*__ ] Для пост-обработки вместо [css-preprocessor](https://github.com/enb/enb/blob/v0.17.0/lib/preprocess/css-preprocessor.js) используется [postcss](https://github.com/postcss/postcss) ([#33]). 122 | * [ __*major*__ ] Для минификации кода вместо модуля [stylus](https://github.com/stylus/stylus/blob/master/docs/executable.md) используется модуль [csswring](https://github.com/hail2u/node-csswring) ([#71]). 123 | * [ __*major*__ ] Для добавления вендорных префиксов вместо [autoprefixer](https://github.com/postcss/autoprefixer) используется [autoprefixer-core](https://github.com/postcss/autoprefixer-core) ([#24]). 124 | * [ __*major*__ ] Исправлена обработка CSS-файлов: если БЭМ-сущность на одном уровне переопределения реализована и в файле с расширением `.styl`, и в файле с расширением `.css`, то в сборку попадет только `.styl`-файл ([#73]). 125 | 126 | ### Опции 127 | 128 | * [ __*major*__ ] Из технологии `stylus` удалена опция `variables` ([#36]). 129 | 130 | В технологию `stylus` добавлены следующие опции: 131 | 132 | * [sourcemap](api.ru.md#sourcemap) ([#60]) 133 | * [autoprefixer](api.ru.md#autoprefixer) ([#64]) 134 | * [compress](api.ru.md#compress) ([#71]) 135 | * [url](api.ru.md#url) ([#58]) 136 | * [imports](api.ru.md#imports) ([#57]) 137 | * [comments](api.ru.md#comments) ([#55]) 138 | * [useNib](api.ru.md#usenib) ([#65]) 139 | * [includes](api.ru.md#includes) ([#54]) 140 | 141 | ### Зависимости 142 | 143 | * [ __*major*__ ] Изменились требования к версии модуля `enb`. Теперь для корректной работы требуется `enb` версии `0.16.0` или выше. 144 | * Модуль `stylus@0.50.0` обновлен до версии `0.52.0` ([#90]). 145 | * Модуль `vow@0.4.8` обновлен до версии `0.4.10`. 146 | 147 | ### Engines 148 | 149 | * Добавлена поддержка `io.js` ([#34]). 150 | * Добавлена поддержка `node.js` версии `0.12` ([#35]). 151 | 152 | ### Тестирование 153 | 154 | * Добавлены тесты для технологии `stylus` ([#36]). 155 | * Добавлено тестирование под Windows в Continues Integration при помощи [AppVeyor](http://www.appveyor.com) ([#37]). 156 | 157 | [#24]: https://github.com/enb/enb-stylus/issues/24 158 | [#26]: https://github.com/enb/enb-stylus/issues/26 159 | [#33]: https://github.com/enb/enb-stylus/issues/33 160 | [#34]: https://github.com/enb/enb-stylus/issues/34 161 | [#35]: https://github.com/enb/enb-stylus/issues/35 162 | [#36]: https://github.com/enb/enb-stylus/issues/36 163 | [#37]: https://github.com/enb/enb-stylus/issues/37 164 | [#48]: https://github.com/enb/enb-stylus/issues/48 165 | [#54]: https://github.com/enb/enb-stylus/issues/54 166 | [#55]: https://github.com/enb/enb-stylus/issues/55 167 | [#56]: https://github.com/enb/enb-stylus/issues/56 168 | [#57]: https://github.com/enb/enb-stylus/issues/57 169 | [#58]: https://github.com/enb/enb-stylus/issues/58 170 | [#60]: https://github.com/enb/enb-stylus/issues/60 171 | [#64]: https://github.com/enb/enb-stylus/issues/64 172 | [#65]: https://github.com/enb/enb-stylus/issues/65 173 | [#67]: https://github.com/enb/enb-stylus/issues/67 174 | [#68]: https://github.com/enb/enb-stylus/issues/68 175 | [#71]: https://github.com/enb/enb-stylus/issues/71 176 | [#73]: https://github.com/enb/enb-stylus/issues/73 177 | [#90]: https://github.com/enb/enb-stylus/issues/90 178 | [#109]: https://github.com/enb/enb-stylus/pull/109 179 | [#111]: https://github.com/enb/enb-stylus/pull/111 180 | [#113]: https://github.com/enb/enb-stylus/issues/113 181 | [#116]: https://github.com/enb/enb-stylus/pull/116 182 | [#120]: https://github.com/enb/enb-stylus/pull/120 183 | [#125]: https://github.com/enb/enb-stylus/pull/125 184 | [#128]: https://github.com/enb/enb-stylus/pull/128 185 | -------------------------------------------------------------------------------- /api.en.md: -------------------------------------------------------------------------------- 1 | # Stylus technology API 2 | 3 | Collects the source files of style blocks written in Stylus syntax (files with the `.styl` extension) or in pure CSS (files with the `.CSS` extension). 4 | 5 | Uses the [Stylus](https://github.com/stylus/stylus) CSS preprocessor to compile Stylus files into CSS code. 6 | 7 | The result of the build is a CSS file. The [postcss](https://github.com/postcss/postcss) post processor is used for processing the resulting CSS. 8 | 9 | ### Options 10 | 11 | * [target](#target) 12 | * [filesTarget](#filestarget) 13 | * [sourceSuffixes](#sourcesuffixes) 14 | * [URL](#url) 15 | * [imports](#imports) 16 | * [sourcemap](#sourcemap) 17 | * [autoprefixer](#autoprefixer) 18 | * [prefix](#prefix) 19 | * [compress](#compress) 20 | * [comments](#comments) 21 | * [globals](#globals) 22 | * [includes](#includes) 23 | * [use](#use) 24 | * [useNib](#usenib) 25 | * [importPaths](#importpaths) 26 | * [inlineMaxSize](#inlinemaxsize) 27 | 28 | #### target 29 | 30 | Type: `String`. Default: `?.css`. 31 | 32 | The name of the file for saving the build result with the necessary `.styl` and `.css` project files. 33 | 34 | #### filesTarget 35 | 36 | Type: `String`. Default: `?.files`. 37 | 38 | The name of the target for accessing the list of source files for the build. The file list is provided by the [files](https://github.com/enb/enb-bem-techs/blob/master/docs/api/api.en.md#files) technology in the [enb-bem-techs](https://github.com/enb/enb-bem-techs/blob/master/README.md) package. 39 | 40 | #### sourceSuffixes 41 | 42 | Type: `String | String[]`. Default: `['styl', 'css']`. 43 | 44 | The suffixes to use for filtering style files for the build. 45 | 46 | #### url 47 | 48 | Type: `String`. Default: `rebase`. 49 | 50 | Processing `url()` in `.styl` and `.css` files. 51 | 52 | *Acceptable values:* 53 | 54 | * **inline** — The file content will be `base64`-encoded. 55 | 56 | > **Important!** 57 | > 58 | > * The default maximum size is `14kb`. You can change it in the `inlineMaxSize` option. 59 | > * Encoding is not supported for `.svg` files with a hash. Example: `url(image.svg#hash)`. This type of `url()` won't be processed. 60 | 61 | * **rebase** — Changes the path to the content relative to the target. 62 | 63 | **Example** 64 | 65 | ```bash 66 | blocks/ 67 | └── block/ 68 | ├── block.styl 69 | └── block.png 70 | bundle/ 71 | └── bundle.css # target 72 | ``` 73 | 74 | Source `block.styl` file: 75 | 76 | ```css 77 | .block 78 | background-image: url(block.png) 79 | ``` 80 | 81 | File for connecting `bundle.css` to the page: 82 | 83 | ```css 84 | .block 85 | { 86 | background-image: url(../../blocks/block/block.png); 87 | } 88 | ``` 89 | 90 | #### imports 91 | 92 | Type: `String`. Default: `include`. 93 | 94 | Detects CSS `@import`s. 95 | 96 | *Acceptable values:* 97 | 98 | * **include** — Deletes `@import` and replaces it with its content in the compiled file. 99 | 100 | #### sourcemap 101 | 102 | Type: `String | Boolean`. Default: `false`. 103 | 104 | The source map with information about the source files. 105 | 106 | *Acceptable values:* 107 | 108 | * **true** — The source map is stored in a separate file with the `.map` extension. 109 | * **inline** — The map is embedded in a compiled file as a `base64` line. 110 | 111 | #### autoprefixer 112 | 113 | Type: `Object | Boolean`. Default: `false`. 114 | 115 | Adds vendor prefixes using [autoprefixer](https://github.com/postcss/autoprefixer). 116 | 117 | *Acceptable values:* 118 | 119 | * **false** — Disables `autoprefixer`. 120 | * **true** — Prefixes are added for the latest browser versions based on data from the [caniuse.com](http://caniuse.com) service. 121 | * **options** — Sets the configuration if an exact list of supported browsers must be passed. 122 | 123 | **Example** 124 | 125 | ```js 126 | { 127 | autoprefixer: { browsers: ['Explorer 10', 'Opera 12'] } 128 | } 129 | ``` 130 | 131 | > **Note.** For more information, see the [Autoprefixer](https://github.com/postcss/autoprefixer#options) documentation. 132 | 133 | #### prefix 134 | 135 | Type: `String`. Default: `''`. 136 | 137 | Adding a prefix for CSS classes. 138 | 139 | **Important!** The option only works for files with the `.styl` extension. 140 | 141 | #### compress 142 | 143 | Type: `Boolean`. Default: `false`. 144 | 145 | CSS minification. Support source maps. 146 | 147 | #### comments 148 | 149 | Type: `Boolean`. Default: `true`. 150 | 151 | Wrapping CSS code in comments in the file. Comments contain the relative path to the source file. This can be useful during project development. 152 | 153 | **Example** 154 | 155 | ```css 156 | /* ../../blocks/block/block.styl:begin */ 157 | .block 158 | { 159 | background-image: url(../../blocks/block/block.png); 160 | } 161 | /* ../../blocks/block/block.styl:end */ 162 | ``` 163 | 164 | #### globals 165 | 166 | Type: `String | String[]`. Default: `[]`. 167 | 168 | Links `.styl` files with global variables, methods, or mixins at the beginning of the file. 169 | 170 | #### includes 171 | 172 | Type: `String | String[]`. Default: `[]`. 173 | 174 | Specifies the paths to use when processing `@import` and `url()`. 175 | You can use it to connect third-party libraries such as `nib`. 176 | 177 | **Important!** The option only works for files with the `.styl` extension. 178 | 179 | ### use 180 | 181 | Type: `Function | Function[]`. Default: `[]`. 182 | 183 | Enables plugins or a single plugin for Stylus [ via use()](https://github.com/stylus/stylus/blob/dev/docs/js.md#usefn) 184 | 185 | **Important!** The option only works for files with the `.styl` extension. 186 | 187 | ### useNib 188 | 189 | Type: `Boolean`. Default: `false`. 190 | 191 | Connects the [nib](https://github.com/tj/nib) library of CSS3 mixins for Stylus. 192 | 193 | **Important!** The option only works for files with the `.styl` extension. 194 | 195 | ### importPaths 196 | 197 | Type: `String[]`. Default: `[]`. 198 | 199 | Connects `.styl` files or directories with `index.styl` [via import()](https://github.com/stylus/stylus/blob/dev/docs/js.md#importpath) 200 | 201 | **Important!** The option only works for files with the `.styl` extension. 202 | 203 | ### inlineMaxSize 204 | 205 | Type: `Number`. Default: ` 14`. 206 | 207 | The maximum file size in kilobytes that can be base64-encoded in `inline` mode. 208 | 209 | **Example** 210 | 211 | ```js 212 | var stylusTech = require('enb-stylus/techs/stylus'), 213 | FileProvideTech = require('enb/techs/file-provider'), 214 | nib = require('nib'), 215 | rupture = require('rupture'), 216 | bemTechs = require('enb-bem-techs'); 217 | 218 | module.exports = function(config) { 219 | config.node('bundle', function(node) { 220 | // Getting the file names (FileList) 221 | node.addTechs([ 222 | [FileProvideTech, { target: '?.bemdecl.js' }], 223 | [bemTechs.levels, { levels: ['blocks'] }], 224 | bemTechs.deps, 225 | bemTechs.files 226 | ]); 227 | 228 | // Creating CSS files 229 | node.addTech([stylusTech, { 230 | use: [nib(), rupture()], 231 | importPaths: [nib.path + '/nib'] 232 | }]); 233 | node.addTarget('?.css'); 234 | }); 235 | }; 236 | ``` 237 | 238 | **License** 239 | 240 | © 2014 YANDEX LLC. Code released under the [Mozilla Public License 2.0](LICENSE.txt). 241 | -------------------------------------------------------------------------------- /api.ru.md: -------------------------------------------------------------------------------- 1 | # API технологии stylus 2 | 3 | Собирает исходные файлы блоков со стилями, написанными в синтаксисе Stylus (файлы с расширением `.styl`), или на чистом CSS (файлы с расширением `.css`). 4 | 5 | Использует CSS-препроцессор [Stylus](https://github.com/stylus/stylus) для компиляции Stylus-файлов в CSS-код. 6 | 7 | Результатом сборки является CSS-файл. Для обработки итогового CSS используется CSS-построцессор [postcss](https://github.com/postcss/postcss). 8 | 9 | ### Опции 10 | 11 | * [target](#target) 12 | * [filesTarget](#filestarget) 13 | * [sourceSuffixes](#sourcesuffixes) 14 | * [url](#url) 15 | * [imports](#imports) 16 | * [sourcemap](#sourcemap) 17 | * [autoprefixer](#autoprefixer) 18 | * [prefix](#prefix) 19 | * [compress](#compress) 20 | * [comments](#comments) 21 | * [globals](#globals) 22 | * [includes](#includes) 23 | * [use](#use) 24 | * [useNib](#usenib) 25 | * [importPaths](#importpaths) 26 | * [inlineMaxSize](#inlinemaxsize) 27 | 28 | #### target 29 | 30 | Тип: `String`. По умолчанию: `?.css`. 31 | 32 | Имя файла, куда будет записан результат сборки необходимых `.styl`- и `.css`-файлов проекта. 33 | 34 | #### filesTarget 35 | 36 | Тип: `String`. По умолчанию: `?.files`. 37 | 38 | Имя таргета, откуда будет доступен список исходных файлов для сборки. Список файлов предоставляет технология [files](https://github.com/enb/enb-bem-techs/blob/master/docs/api/api.ru.md#files) пакета [enb-bem-techs](https://github.com/enb/enb-bem-techs/blob/master/README.md). 39 | 40 | #### sourceSuffixes 41 | 42 | Тип: `String | String[]`. По умолчанию: `['styl', 'css']`. 43 | 44 | Суффиксы, по которым отбираются файлы с стилей для дальнейшей сборки. 45 | 46 | #### url 47 | 48 | Тип: `String`. По умолчанию: `rebase`. 49 | 50 | Oбработка `url()` внутри файлов `.styl` и `.css`. 51 | 52 | *Допустимые значения:* 53 | 54 | * **inline** — содержимое файла будет закодировано в `base64`. 55 | 56 | > **Важно!** 57 | > 58 | > * Максимальный размер по умолчанию `14kb`. Можно изменить опцией `inlineMaxSize`. 59 | > * Не поддерживается кодирование `.svg`-файлов с хешем. Например: `url(image.svg#hash)`. Такие `url()` не будут обработаны. 60 | 61 | * **rebase** — изменение пути к содержимому относительно таргета. 62 | 63 | **Пример** 64 | 65 | ```bash 66 | blocks/ 67 | └── block/ 68 | ├── block.styl 69 | └── block.png 70 | bundle/ 71 | └── bundle.css # таргет 72 | ``` 73 | 74 | Исходный файл `block.styl`: 75 | 76 | ```css 77 | .block 78 | background-image: url(block.png) 79 | ``` 80 | 81 | Файл для подключения на страницу `bundle.css`: 82 | 83 | ```css 84 | .block 85 | { 86 | background-image: url(../../blocks/block/block.png); 87 | } 88 | ``` 89 | 90 | #### imports 91 | 92 | Тип: `String`. По умолчанию: `include`. 93 | 94 | Раскрытие CSS `@import`-ов. 95 | 96 | *Допустимые значения:* 97 | 98 | * **include** — `@import` будет удален, вместо него в собираемый файл будет добавлено его содержимое. 99 | 100 | #### sourcemap 101 | 102 | Тип: `String | Boolean`. По умолчанию: `false`. 103 | 104 | Построение карт кода (sourcemap) с информацией об исходных файлах. 105 | 106 | *Допустимые значения:* 107 | 108 | * **true** — карта хранится в отдельном файле с расширение `.map`. 109 | * **inline** — карта встраивается в скомпилированный файл в виде закодированной строки в формате `base64`. 110 | 111 | #### autoprefixer 112 | 113 | Тип: `Object | Boolean`. По умолчанию: `false`. 114 | 115 | Добавление вендорных префиксов с помощью [autoprefixer](https://github.com/postcss/autoprefixer). 116 | 117 | *Допустимые значения:* 118 | 119 | * **false** — отключает `autoprefixer`. 120 | * **true** — префиксы добавляются для самых актуальных версий браузеров на основании данных сервиса [caniuse.com](http://caniuse.com). 121 | * **options** — задание конфигурации в случае, если требуется передать точный список поддерживаемых браузеров. 122 | 123 | **Пример** 124 | 125 | ```js 126 | { 127 | autoprefixer: { browsers: ['Explorer 10', 'Opera 12'] } 128 | } 129 | ``` 130 | 131 | > **Примечание.** Подробнее в документации [autoprefixer](https://github.com/postcss/autoprefixer#options). 132 | 133 | #### prefix 134 | 135 | Тип: `String`. По умолчанию: `''`. 136 | 137 | Добавление префикса для CSS-классов. 138 | 139 | **Важно!** Опция работает только для файлов с расширением `.styl`. 140 | 141 | #### compress 142 | 143 | Тип: `Boolean`. По умолчанию: `false`. 144 | 145 | Минификация CSS-кода. Поддерживает карты кода (sourcemap). 146 | 147 | #### comments 148 | 149 | Тип: `Boolean`. По умолчанию: `true`. 150 | 151 | Обрамление комментариями CSS-кода в собранном файле. Комментарии cодержат относительный путь до исходного файла. Может быть использовано при разработке проекта. 152 | 153 | **Пример** 154 | 155 | ```css 156 | /* ../../blocks/block/block.styl:begin */ 157 | .block 158 | { 159 | background-image: url(../../blocks/block/block.png); 160 | } 161 | /* ../../blocks/block/block.styl:end */ 162 | ``` 163 | 164 | #### globals 165 | 166 | Тип: `String | String[]`. По умолчанию: `[]`. 167 | 168 | Подключает `.styl`-файлы с глобальными переменными, методами или миксинами в начало. 169 | 170 | #### includes 171 | 172 | Тип: `String | String[]`. По умолчанию: `[]`. 173 | 174 | Задает пути, которые будут использованы при обработке `@import` и `url()`. 175 | Может быть использовано при подключении сторонних библиотек, например, `nib`. 176 | 177 | **Важно!** Опция работает только для файлов с расширением `.styl`. 178 | 179 | ### use 180 | 181 | Тип: `Function | Function[]`. По умолчанию: `[]`. 182 | 183 | Подключение плагинов или одного плагина для Stylus [через use()](https://github.com/stylus/stylus/blob/dev/docs/js.md#usefn) 184 | 185 | **Важно!** Опция работает только для файлов с расширением `.styl`. 186 | 187 | ### useNib 188 | 189 | Тип: `Boolean`. По умолчанию: `false`. 190 | 191 | Подключение библиотеки CSS3-миксинов для Stylus – [nib](https://github.com/tj/nib). 192 | 193 | **Важно!** Опция работает только для файлов с расширением `.styl`. 194 | 195 | ### importPaths 196 | 197 | Тип: `String[]`. По умолчанию: `[]`. 198 | 199 | Подключение `.styl` файлов или директорий c `index.styl` [через import()](https://github.com/stylus/stylus/blob/dev/docs/js.md#importpath) 200 | 201 | **Важно!** Опция работает только для файлов с расширением `.styl`. 202 | 203 | ### inlineMaxSize 204 | 205 | Тип: `Number`. По умолчанию: `14`. 206 | 207 | Максимальный размер файла в килобайтах, который будет закодирован в base64 в режиме `inline`. 208 | 209 | **Пример** 210 | 211 | ```js 212 | var stylusTech = require('enb-stylus/techs/stylus'), 213 | FileProvideTech = require('enb/techs/file-provider'), 214 | nib = require('nib'), 215 | rupture = require('rupture'), 216 | bemTechs = require('enb-bem-techs'); 217 | 218 | module.exports = function(config) { 219 | config.node('bundle', function(node) { 220 | // Получаем имена файлов (FileList) 221 | node.addTechs([ 222 | [FileProvideTech, { target: '?.bemdecl.js' }], 223 | [bemTechs.levels, { levels: ['blocks'] }], 224 | bemTechs.deps, 225 | bemTechs.files 226 | ]); 227 | 228 | // Создаем CSS-файлы 229 | node.addTech([stylusTech, { 230 | use: [nib(), rupture()], 231 | importPaths: [nib.path + '/nib'] 232 | }]); 233 | node.addTarget('?.css'); 234 | }); 235 | }; 236 | ``` 237 | 238 | **Лицензия** 239 | 240 | © 2014 YANDEX LLC. Код лицензирован [Mozilla Public License 2.0](LICENSE.txt). 241 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | enb-stylus 2 | ========== 3 | 4 | [![NPM version](http://img.shields.io/npm/v/enb-stylus.svg?style=flat)](http://www.npmjs.org/package/enb-stylus) [![Build Status](http://img.shields.io/travis/enb/enb-stylus/master.svg?style=flat&label=tests)](https://travis-ci.org/enb/enb-stylus) [![Build status](https://img.shields.io/appveyor/ci/blond/enb-stylus.svg?style=flat&label=windows)](https://ci.appveyor.com/project/blond/enb-stylus) [![Coverage Status](https://img.shields.io/coveralls/enb/enb-stylus.svg?style=flat)](https://coveralls.io/r/enb/enb-stylus?branch=master) [![Dependency Status](http://img.shields.io/david/enb/enb-stylus.svg?style=flat)](https://david-dm.org/enb/enb-stylus) 5 | 6 | Пакет предоставляет [ENB](https://ru.bem.info/tools/bem/enb-bem/)-технологию для сборки CSS- и Stylus-файлов в проектах, построенных по [методологии БЭМ](https://ru.bem.info/method/). 7 | 8 | Принципы работы технологии и ее API описаны в документе [API технологии](api.ru.md). 9 | 10 | **Совместимость:** технология пакета `enb-stylus` поддерживает версию [CSS-препроцессора Stylus](https://github.com/stylus/stylus) `0.54.2`. 11 | 12 | Обзор документа 13 | --------------- 14 | 15 | * [Работа технологии `stylus`](#Работа-технологии-stylus) 16 | * [Как начать использовать?](#Как-начать-использовать) 17 | * [Особенности работы пакета](#Особенности-работы-пакета) 18 | * [Совместное использование Stylus и CSS](#Совместное-использование-stylus-и-css) 19 | * [Добавление вендорных префиксов](#Добавление-вендорных-префиксов) 20 | * [Минимизация CSS-кода](#Минимизация-css-кода) 21 | * [Сборка отдельного бандла для IE](#Сборка-отдельного-бандла-для-ie) 22 | 23 | Работа технологии `stylus` 24 | -------------------------- 25 | 26 | В [БЭМ-методологии](https://ru.bem.info/method/filesystem/) стили к каждому блоку хранятся в отдельных файлах в директориях блоков. 27 | 28 | ENB-технология `stylus` позволяет писать код как в синтаксисе Stylus, так и на чистом CSS. Для компиляции Stylus-кода в CSS используется CSS-препроцессор [Stylus](https://github.com/stylus/stylus). 29 | 30 | В результате сборки вы получите CSS-файл. Для обработки итогового CSS используется CSS-построцессор [postcss](https://github.com/postcss/postcss). 31 | 32 | Как начать использовать? 33 | ------------------------ 34 | 35 | **1.** Установите пакет `enb-stylus`: 36 | 37 | ```sh 38 | $ npm install --save-dev enb-stylus 39 | ``` 40 | 41 | **Требования:** зависимость от пакета `enb` версии `0.16.0` или выше. 42 | 43 | **2.** Опишите код стилей в файле с расширением `.styl`: 44 | ``` 45 | blocks/ 46 | └── block/ 47 | └── block.styl 48 | ``` 49 | 50 | **3.** Добавьте в конфигурационный файл `.enb/make.js` следующий код: 51 | 52 | ```js 53 | var stylusTech = require('enb-stylus/techs/stylus'), 54 | FileProvideTech = require('enb/techs/file-provider'), 55 | bemTechs = require('enb-bem-techs'); 56 | 57 | module.exports = function(config) { 58 | config.node('bundle', function(node) { 59 | // Получаем список файлов (FileList) 60 | node.addTechs([ 61 | [FileProvideTech, { target: '?.bemdecl.js' }], 62 | [bemTechs.levels, { levels: ['blocks'] }], 63 | bemTechs.deps, 64 | bemTechs.files 65 | ]); 66 | 67 | // Строим CSS-файл 68 | node.addTech([stylusTech, { 69 | // target: '?.css', 70 | // filesTarget: '?.files', 71 | // sourceSuffixes: ['.styl', '.css'], 72 | // url: 'rebase' 73 | // imports: 'include', 74 | // comments: true 75 | }]); 76 | node.addTarget('?.css'); 77 | }); 78 | }; 79 | ``` 80 | 81 | Особенности работы пакета 82 | ------------------------- 83 | 84 | ### Совместное использование Stylus и CSS 85 | 86 | В проекте допускается совместное использование `.css`- и `.styl`-файлов. Однако в рамках одного блока обе технологии не могут использоваться одновременно. Если стили блока реализованы и в CSS, и в Stylus, будет использоваться файл с расширением `.styl`. 87 | 88 | **Пример 1.** Если файл одного блока реализован в CSS-технологии, а файл другого — в Stylus: 89 | 90 | ``` 91 | blocks/ 92 | └── block1/ 93 | └── block1.styl 94 | └── block2/ 95 | └── block2.css 96 | bundle 97 | └── bundle.css 98 | ``` 99 | 100 | В сборку попадут оба файла: 101 | 102 | ```css 103 | @import "../blocks/block1/block1.styl"; 104 | @import "../blocks/block1/block2.css"; 105 | ``` 106 | 107 | **Пример 2.** Если у одного блока есть несколько реалиализаций: файл c расширением `.styl` и файл c расширением `.css`: 108 | 109 | ``` 110 | blocks/ 111 | └── block/ 112 | ├── block.styl 113 | └── block.css 114 | bundle 115 | └── bundle.css 116 | ``` 117 | 118 | В сборку попадет только Stylus-файл: 119 | 120 | ```css 121 | @import "../blocks/block/block.styl"; 122 | ``` 123 | 124 | **Пример 3.** Если у одного блока есть несколько реалиализаций, но на разных уровнях переопределения: 125 | 126 | ``` 127 | common.blocks/ 128 | └── block/ 129 | └── block.styl 130 | desktop.blocks/ 131 | └── block/ 132 | └── block.css 133 | bundle 134 | └── bundle.css 135 | ``` 136 | 137 | В сборку попадут оба файла: 138 | 139 | ```css 140 | @import "../common.blocks/block/block.styl"; 141 | @import "../desktop.blocks/block/block.css"; 142 | ``` 143 | 144 | ### Добавление вендорных префиксов 145 | 146 | Технология `stylus` поддерживает [Autoprefixer](https://github.com/postcss/autoprefixer). 147 | 148 | Для автоматического добавления вендорных префиксов в процессе сборки используйте опцию [autoprefixer](api.ru.md#autoprefixer). 149 | 150 | ### Минимизация CSS-кода 151 | 152 | Для минимизации CSS-кода используется [csswring](https://github.com/hail2u/node-csswring). 153 | 154 | Включить минимизацию можно с помощью опции [compress](api.ru.md#compress). 155 | 156 | ### Сборка отдельного бандла для IE 157 | 158 | Если в проекте есть стили, которые должны примениться только для IE, то их помещают в отдельный файл со специальным расширением `.ie*.styl`: 159 | 160 | * `.ie.styl` — стили для любого IE, ниже 9й версии. 161 | * `.ie6.styl` — стили для IE 6. 162 | * `.ie7.styl` — стили для IE 7. 163 | * `.ie8.styl` — стили для IE 8. 164 | * `.ie9.styl` — стили для IE 9. 165 | 166 | Чтобы собрать отдельный бандл для IE нужно: 167 | 168 | **1.** В папке блока создать один или несколько файлов c расширением `.ie*.styl`: 169 | 170 | ``` 171 | blocks/ 172 | └── block/ 173 | ├── block.styl 174 | ├── block.ie.styl 175 | └── block.ie6.styl 176 | ``` 177 | 178 | **2.** Добавить еще технологию `StylusTech`: 179 | 180 | ```js 181 | node.addTechs([ 182 | [stylusTech], // для основного CSS 183 | [stylusTech] // для IE 184 | ]); 185 | ``` 186 | 187 | **3.** Добавить новую цель сборки для IE файла — `?.ie6.css`: 188 | 189 | ```js 190 | node.addTechs([ 191 | [stylusTech], 192 | [stylusTech, { target: '?.ie6.css' }] // IE 6 193 | ]); 194 | 195 | node.addTargets(['?.css', '?.ie6.css']); 196 | ``` 197 | 198 | **4.** В БЭМ проектах принято подключать стили с помощью [условных комментариев](https://ru.wikipedia.org/wiki/Условный_комментарий). 199 | 200 | **Пример** 201 | 202 | ```html 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 213 | 214 | 215 | ``` 216 | 217 | Важно, чтобы файл, подключаемый для IE, содержал стили не только специфичные для него, но и общие для всей страницы. 218 | 219 | Чтобы собрать такой файл, нужно расширить список суффиксов с помощью опции [sourceSuffixed](api.ru.md#sourcesuffixes). 220 | 221 | ```js 222 | node.addTechs([ 223 | [stylusTech], 224 | [stylusTech, { 225 | target: '?.ie6.css', 226 | sourceSuffixes: [ 227 | 'styl', 'css', // Общие стили 228 | 'ie.styl', 'ie.css', // Стили для IE < 9 229 | 'ie6.styl', 'ie6.css' // Стили для IE 6 230 | ] 231 | }] 232 | ]); 233 | node.addTargets(['?.css', '?.ie.css']); 234 | ``` 235 | 236 | В итоге получаем следующий конфигурационный файл `.enb/make.js`: 237 | 238 | ```js 239 | var stylusTech = require('enb-stylus/techs/stylus'), 240 | FileProvideTech = require('enb/techs/file-provider'), 241 | bemTechs = require('enb-bem-techs'); 242 | 243 | module.exports = function(config) { 244 | config.node('bundle', function(node) { 245 | // получаем список файлов (FileList) 246 | node.addTechs([ 247 | [FileProvideTech, { target: '?.bemdecl.js' }], 248 | [bemTechs.levels, { levels: ['blocks'] }], 249 | bemTechs.deps, 250 | bemTechs.files 251 | ]); 252 | 253 | // Собираем CSS-файлы 254 | node.addTechs([ 255 | [stylusTech], 256 | [stylusTech, { 257 | target: '?.ie6.css', 258 | sourceSuffixes: [ 259 | 'styl', 'css', // Общие стили 260 | 'ie.styl', 'ie.css', // Стили для IE < 9 261 | 'ie6.styl', 'ie6.css' // Стили для IE 6 262 | ] 263 | }] 264 | ]); 265 | node.addTargets(['?.css', '?.ie6.css']); 266 | }); 267 | }; 268 | ``` 269 | 270 | Лицензия 271 | -------- 272 | 273 | © 2014 YANDEX LLC. Код лицензирован [Mozilla Public License 2.0](LICENSE.txt). 274 | -------------------------------------------------------------------------------- /techs/stylus.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | enb = require('enb'), 3 | vfs = enb.asyncFS || require('enb/lib/fs/async-fs'), 4 | FileList = enb.FileList || require('enb/lib/file-list'), 5 | buildFlow = enb.buildFlow || require('enb/lib/build-flow'), 6 | EOL = require('os').EOL; 7 | 8 | /** 9 | * @class StylusTech 10 | * @augments {BaseTech} 11 | * @classdesc 12 | * 13 | * Builds CSS from Stylus and CSS sources.

14 | * 15 | * Files processing in 3 steps:
16 | * 1. Prepares list of @import sources (contain .styl and .css source code).
17 | * 2. Expands Stylus @import and processes it with the [Stylus renderer]{@link https://github.com/stylus/stylus}.
18 | * 3. Expands the remaining CSS @import and processes the received common CSS using 19 | * [Postcss]{@link https://github.com/postcss/postcss}.

20 | * 21 | * Important: `prefix`, `includes`, `useNib` options are enabled only for Stylus source code.
22 | * 23 | * @param {Object} [options] Options 24 | * @param {String} [options.filesTarget='?.files'] Path to target with 25 | * [FileList]{@link http://bit.ly/1GTUOj0} 26 | * @param {String[]} [options.sourceSuffixes=['styl', 'css']] Files with specified suffixes involved in 27 | * the assembly. Default: `styl`, `css` 28 | * @param {String|Boolean} [options.url='rebase'] Rebases or inlines url():
29 | * - `rebase` – resolves a path relative to the 30 | * bundle directory.
31 | * - `inline` – inlines assets using base64 encoding. 32 | * @param {Boolean} [options.comments=true] Adds CSS comment with path to source to a code 33 | * block (above and below).
34 | * @param {String|Boolean} [options.imports='include'] Allows to include(expand) @import with css files 35 | * or leave without changes. 36 | * @param {Boolean|String} [options.sourcemap=false] Builds sourcemap:
37 | * - `true` – builds ?.css.map.
38 | * - `inline` – builds and inlining sourcemap into 39 | * bundled CSS file. 40 | * @param {Boolean|Object} [options.autoprefixer=false] Adds vendor prefixes using autoprefixer:
41 | * - `true` – enables autoprefixer and defines what 42 | * prefixes should be used based on 43 | * [CanIUse]{@link http://caniuse.com} data.
44 | * - `{browsers: ['last 2 versions']}` – allows to 45 | * set custom browsers. 46 | * @param {Boolean} [options.compress=false] Minifies styles. Supports sourcemap. 47 | * @param {String} [options.prefix=''] Adds prefix to CSS classes.
48 | * Important: Available for Stylus only. 49 | * @param {String[]} [options.globals=[]] Imports `.styl` files with global variables, 50 | * functions and mixins to the top. 51 | * @param {String[]} [options.importPaths=[]] Adds additional path to import 52 | * @param {String[]} [options.includes=[]] Adds additional path to resolve a path in @import 53 | * and url().
54 | * [Stylus: include]{@link http://bit.ly/1IpsoTh} 55 | *
56 | * Important: Available for Stylus only. 57 | * @param {Function[]} [options.use=[]] Allows to use plugins for Stylus.
58 | * @param {Boolean} [options.useNib=false] Allows to use Nib library for Stylus.
59 | * Important: Available for Stylus only. 60 | * 61 | * @example 62 | * // Styles in file system before build: 63 | * // blocks/ 64 | * // ├── block1.styl 65 | * // └── block2.css 66 | * // 67 | * // After build: 68 | * // bundle/ 69 | * // └── bundle.css 70 | * 71 | * var stylusTech = require('enb-stylus/techs/stylus'), 72 | * FileProvideTech = require('enb/techs/file-provider'), 73 | * bemTechs = require('enb-bem-techs'); 74 | * 75 | * module.exports = function(config) { 76 | * config.node('bundle', function(node) { 77 | * // get FileList 78 | * node.addTechs([ 79 | * [FileProvideTech, { target: '?.bemdecl.js' }], 80 | * [bemTechs.levels, { levels: ['blocks'] }], 81 | * bemTechs.deps, 82 | * bemTechs.files 83 | * ]); 84 | * 85 | * // build css file 86 | * node.addTech(stylusTech); 87 | * node.addTarget('?.css'); 88 | * }); 89 | * }; 90 | */ 91 | module.exports = buildFlow.create() 92 | .name('stylus') 93 | .target('target', '?.css') 94 | .defineOption('url', 'rebase') 95 | .defineOption('inlineMaxSize', 14) 96 | .defineOption('comments', true) 97 | .defineOption('imports', 'include') 98 | .defineOption('sourcemap', false) 99 | .defineOption('autoprefixer', false) 100 | .defineOption('compress', false) 101 | .defineOption('prefix', '') 102 | .defineOption('importPaths', []) 103 | .defineOption('includes', []) 104 | .defineOption('globals', []) 105 | .defineOption('useNib', false) 106 | .defineOption('use', []) 107 | .useFileList(['styl', 'css']) 108 | .saveCache(function (cache) { 109 | cache.cacheFileList('global', this._globalFiles); 110 | }) 111 | .needRebuild(function (cache) { 112 | this._globalFiles = this._filenamesToFileList(this._globals); 113 | 114 | return cache.needRebuildFileList('global', this._globalFiles); 115 | }) 116 | .builder(function (sourceFiles) { 117 | var node = this.node, 118 | filename = node.resolvePath(path.basename(this._target)), 119 | stylesImports = this._prepareImports(sourceFiles); 120 | 121 | return this._processStylus(filename, stylesImports) 122 | .then(([css, sourcemap]) => 123 | this._processCss(filename, css, sourcemap) 124 | ) 125 | .then(result => 126 | this._writeMap(filename + '.map', result.map).then(() => result.css) 127 | ); 128 | }) 129 | 130 | .methods(/** @lends StylusTech.prototype */{ 131 | /** 132 | * Imitates source files (FileList format). 133 | * 134 | * @param {String[]} filenames — paths to files 135 | * @see [FileList]{@link https://github.com/enb/enb/blob/master/lib/file-list.js} 136 | * @returns {FileList} 137 | */ 138 | _filenamesToFileList: function (filenames) { 139 | var node = this.node, 140 | nodeDir = node.getDir(); 141 | 142 | return filenames.map(function (filename) { 143 | var absolutePath = path.resolve(nodeDir, filename); 144 | 145 | return FileList.getFileInfo(absolutePath); 146 | }); 147 | }, 148 | 149 | /** 150 | * Filters source files. 151 | * 152 | * This is necessary when block has a lot of files that include styles. 153 | * 154 | * Case #1: 155 | * blocks/ 156 | * ├── block.styl 157 | * └── block.css 158 | * Will be used `.styl` 159 | * 160 | * Case #2: 161 | * blocks/ 162 | * ├── block.styl 163 | * ├── block.ie.styl 164 | * └── block.css 165 | * Will be used `.styl` and `.ie.styl` 166 | * 167 | * Case #3: 168 | * blocks/ 169 | * ├── block.css 170 | * ├── block.ie.css 171 | * Will be used `.css` and `.ie.css` 172 | * 173 | * @param {FileList} sourceFiles — Objects with paths to the files that contain styles for processing. 174 | * @see [FileList]{@link https://github.com/enb/enb/blob/master/lib/file-list.js} 175 | * @returns {FileList} 176 | */ 177 | _filterSourceFiles: function (sourceFiles) { 178 | var added = {}; 179 | 180 | return sourceFiles.filter(function (file) { 181 | var basename = file.fullname.substring(0, file.fullname.lastIndexOf('.')); 182 | 183 | if (added[basename]) { 184 | return false; 185 | } 186 | 187 | added[basename] = true; 188 | 189 | return true; 190 | }); 191 | }, 192 | 193 | /** 194 | * Returns CSS code with imports to specified files. 195 | * 196 | * @param {FileList} sourceFiles — Objects with paths to the files that contain styles for processing. 197 | * @see [FileList]{@link https://github.com/enb/enb/blob/master/lib/file-list.js} 198 | * @returns {String} 199 | */ 200 | _composeImports: function (sourceFiles) { 201 | var node = this.node; 202 | 203 | return sourceFiles.map(function (file) { 204 | var url = node.relativePath(file.fullname), 205 | pre = '', 206 | post = ''; 207 | 208 | if (this._comments) { 209 | pre = '/* ' + url + ':begin */' + EOL; 210 | post = '/* ' + url + ':end */' + EOL; 211 | } 212 | 213 | return pre + '@import "' + url + '";' + EOL + post; 214 | }, this).join(EOL); 215 | }, 216 | 217 | /** 218 | * Prepares the list of @import sources. 219 | * 220 | * @private 221 | * @param {FileList} sourceFiles — Objects with paths to the files that contain styles for processing. 222 | * @see [FileList]{@link https://github.com/enb/enb/blob/master/lib/file-list.js} 223 | * @returns {String} – list of @import 224 | */ 225 | _prepareImports: function (sourceFiles) { 226 | return this._composeImports([].concat( 227 | // add global files to the top 228 | this._globalFiles, 229 | // add source files after global files 230 | this._filterSourceFiles(sourceFiles) 231 | )); 232 | }, 233 | 234 | /** 235 | * Configure Stylus renderer. 236 | * Could be used for overriding render options. 237 | * 238 | * @protected 239 | * @param {Object} renderer – instance of Stylus renderer class 240 | * @returns {Object} – instance of Stylus renderer class 241 | */ 242 | _configureRenderer: function (renderer) { 243 | return renderer; 244 | }, 245 | 246 | /** 247 | * Process Stylus files. 248 | * 249 | * @private 250 | * @param {String} filename – filename of the target 251 | * @param {String} stylesImports – list of @import to process 252 | * @returns {Promise[]} – promise with sourcemap of Stylus file and content with expanded @import with 253 | * Stylus sources and list of @import with CSS sources, that would expand on next step 254 | */ 255 | _processStylus: function (filename, stylesImports) { 256 | var map = !!this._sourcemap; 257 | 258 | if (map) { 259 | map = { 260 | basePath: path.dirname(filename), 261 | inline: false, 262 | comment: false 263 | }; 264 | } 265 | 266 | var stylus = require('stylus'); 267 | 268 | var renderer = stylus(stylesImports) 269 | .set('prefix', this._prefix) 270 | .set('filename', filename) 271 | .set('sourcemap', map); 272 | 273 | // rebase url() in all cases on stylus level 274 | if (['rebase', 'inline'].indexOf(this._url) !== -1) { 275 | renderer 276 | .set('resolve url', true) 277 | // set `nocheck` for fixed github.com/stylus/stylus/issues/1951 278 | .define('url', stylus.resolver({ nocheck: true })); 279 | } 280 | 281 | if (this._includes) { 282 | this._includes.forEach(function (includePath) { 283 | renderer.include(includePath); 284 | }); 285 | } 286 | 287 | if (!Array.isArray(this._use)) { 288 | this._use = [this._use]; 289 | } 290 | 291 | if (this._useNib) { 292 | var nib = require('nib'); 293 | 294 | this._use.push(nib()); 295 | this._importPaths.push(path.join(nib.path, 'nib')); 296 | } 297 | 298 | this._use.forEach(function (func) { 299 | renderer.use(func); 300 | }); 301 | 302 | this._importPaths.forEach(function (importPath) { 303 | renderer.import(importPath); 304 | }); 305 | 306 | return new Promise((resolve, reject) => { 307 | this._configureRenderer(renderer).render(function (err, css) { 308 | err ? reject(err) : resolve([css, renderer.sourcemap]); 309 | }); 310 | }); 311 | }, 312 | 313 | /** 314 | * Process CSS files. 315 | * 316 | * @private 317 | * @param {String} filename – filename of the target 318 | * @param {String} css – list of CSS @import to process and CSS code received after rendering Stylus source code 319 | * @param {Object} sourcemap – sourcemap of Stylus source code 320 | * @returns {Promise} – promise with processed css and sourcemap (options) 321 | */ 322 | _processCss: function (filename, css, sourcemap) { 323 | var postcss = require('postcss'), 324 | postcssPlugins = [], 325 | urlMethod = this._url, 326 | 327 | // base opts to resolve urls 328 | opts = { 329 | from: filename, 330 | to: filename 331 | }; 332 | 333 | // add options to build sourcemap 334 | if (this._sourcemap) { 335 | opts.map = { 336 | prev: JSON.stringify(sourcemap), 337 | inline: this._sourcemap === 'inline', 338 | annotation: true 339 | }; 340 | } 341 | 342 | // expand imports with css 343 | if (this._imports === 'include') { 344 | postcssPlugins.push(require('postcss-import')()); 345 | } 346 | 347 | // rebase or inline urls in css 348 | if (['rebase', 'inline'].indexOf(urlMethod) > -1) { 349 | postcssPlugins.push(require('postcss-url')({ url: urlMethod, maxSize: this._inlineMaxSize })); 350 | } 351 | 352 | // use autoprefixer 353 | if (this._autoprefixer) { 354 | var autoprefixer = require('autoprefixer'); 355 | postcssPlugins.push( 356 | typeof this._autoprefixer === 'object' ? 357 | autoprefixer(this._autoprefixer) : 358 | autoprefixer 359 | ); 360 | } 361 | 362 | // compress css 363 | if (this._compress) { 364 | postcssPlugins.push(require('csswring')()); 365 | } 366 | 367 | return postcssPlugins.length || this._sourcemap ? 368 | postcss(postcssPlugins).process(css, opts) : 369 | { css: css }; 370 | }, 371 | 372 | /** 373 | * Write sourcemap to file system in target directory 374 | * 375 | * @private 376 | * @param {String} filename – filename for map, e.g. /Users/project/bundle/bundle.css.map 377 | * @param {Object} data – data of sourcemap 378 | * @returns {Promise} 379 | */ 380 | _writeMap: function (filename, data) { 381 | if (this._sourcemap && this._sourcemap !== 'inline') { 382 | return vfs.write(filename, JSON.stringify(data)); 383 | } 384 | 385 | return Promise.resolve(); 386 | } 387 | }) 388 | .createTech(); 389 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | © YANDEX LLC, 2014 2 | 3 | The Source Code called `enb-stylus` available at https://github.com/enb/enb-stylus is subject to the terms of the Mozilla Public License, v. 2.0 (hereinafter - MPL). The text of MPL is the following: 4 | 5 | Mozilla Public License, version 2.0 6 | 7 | 1. Definitions 8 | 9 | 1.1. "Contributor" 10 | 11 | means each individual or legal entity that creates, contributes to the 12 | creation of, or owns Covered Software. 13 | 14 | 1.2. "Contributor Version" 15 | 16 | means the combination of the Contributions of others (if any) used by a 17 | Contributor and that particular Contributor's Contribution. 18 | 19 | 1.3. "Contribution" 20 | 21 | means Covered Software of a particular Contributor. 22 | 23 | 1.4. "Covered Software" 24 | 25 | means Source Code Form to which the initial Contributor has attached the 26 | notice in Exhibit A, the Executable Form of such Source Code Form, and 27 | Modifications of such Source Code Form, in each case including portions 28 | thereof. 29 | 30 | 1.5. "Incompatible With Secondary Licenses" 31 | means 32 | 33 | a. that the initial Contributor has attached the notice described in 34 | Exhibit B to the Covered Software; or 35 | 36 | b. that the Covered Software was made available under the terms of 37 | version 1.1 or earlier of the License, but not also under the terms of 38 | a Secondary License. 39 | 40 | 1.6. "Executable Form" 41 | 42 | means any form of the work other than Source Code Form. 43 | 44 | 1.7. "Larger Work" 45 | 46 | means a work that combines Covered Software with other material, in a 47 | separate file or files, that is not Covered Software. 48 | 49 | 1.8. "License" 50 | 51 | means this document. 52 | 53 | 1.9. "Licensable" 54 | 55 | means having the right to grant, to the maximum extent possible, whether 56 | at the time of the initial grant or subsequently, any and all of the 57 | rights conveyed by this License. 58 | 59 | 1.10. "Modifications" 60 | 61 | means any of the following: 62 | 63 | a. any file in Source Code Form that results from an addition to, 64 | deletion from, or modification of the contents of Covered Software; or 65 | 66 | b. any new file in Source Code Form that contains any Covered Software. 67 | 68 | 1.11. "Patent Claims" of a Contributor 69 | 70 | means any patent claim(s), including without limitation, method, 71 | process, and apparatus claims, in any patent Licensable by such 72 | Contributor that would be infringed, but for the grant of the License, 73 | by the making, using, selling, offering for sale, having made, import, 74 | or transfer of either its Contributions or its Contributor Version. 75 | 76 | 1.12. "Secondary License" 77 | 78 | means either the GNU General Public License, Version 2.0, the GNU Lesser 79 | General Public License, Version 2.1, the GNU Affero General Public 80 | License, Version 3.0, or any later versions of those licenses. 81 | 82 | 1.13. "Source Code Form" 83 | 84 | means the form of the work preferred for making modifications. 85 | 86 | 1.14. "You" (or "Your") 87 | 88 | means an individual or a legal entity exercising rights under this 89 | License. For legal entities, "You" includes any entity that controls, is 90 | controlled by, or is under common control with You. For purposes of this 91 | definition, "control" means (a) the power, direct or indirect, to cause 92 | the direction or management of such entity, whether by contract or 93 | otherwise, or (b) ownership of more than fifty percent (50%) of the 94 | outstanding shares or beneficial ownership of such entity. 95 | 96 | 97 | 2. License Grants and Conditions 98 | 99 | 2.1. Grants 100 | 101 | Each Contributor hereby grants You a world-wide, royalty-free, 102 | non-exclusive license: 103 | 104 | a. under intellectual property rights (other than patent or trademark) 105 | Licensable by such Contributor to use, reproduce, make available, 106 | modify, display, perform, distribute, and otherwise exploit its 107 | Contributions, either on an unmodified basis, with Modifications, or 108 | as part of a Larger Work; and 109 | 110 | b. under Patent Claims of such Contributor to make, use, sell, offer for 111 | sale, have made, import, and otherwise transfer either its 112 | Contributions or its Contributor Version. 113 | 114 | 2.2. Effective Date 115 | 116 | The licenses granted in Section 2.1 with respect to any Contribution 117 | become effective for each Contribution on the date the Contributor first 118 | distributes such Contribution. 119 | 120 | 2.3. Limitations on Grant Scope 121 | 122 | The licenses granted in this Section 2 are the only rights granted under 123 | this License. No additional rights or licenses will be implied from the 124 | distribution or licensing of Covered Software under this License. 125 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 126 | Contributor: 127 | 128 | a. for any code that a Contributor has removed from Covered Software; or 129 | 130 | b. for infringements caused by: (i) Your and any other third party's 131 | modifications of Covered Software, or (ii) the combination of its 132 | Contributions with other software (except as part of its Contributor 133 | Version); or 134 | 135 | c. under Patent Claims infringed by Covered Software in the absence of 136 | its Contributions. 137 | 138 | This License does not grant any rights in the trademarks, service marks, 139 | or logos of any Contributor (except as may be necessary to comply with 140 | the notice requirements in Section 3.4). 141 | 142 | 2.4. Subsequent Licenses 143 | 144 | No Contributor makes additional grants as a result of Your choice to 145 | distribute the Covered Software under a subsequent version of this 146 | License (see Section 10.2) or under the terms of a Secondary License (if 147 | permitted under the terms of Section 3.3). 148 | 149 | 2.5. Representation 150 | 151 | Each Contributor represents that the Contributor believes its 152 | Contributions are its original creation(s) or it has sufficient rights to 153 | grant the rights to its Contributions conveyed by this License. 154 | 155 | 2.6. Fair Use 156 | 157 | This License is not intended to limit any rights You have under 158 | applicable copyright doctrines of fair use, fair dealing, or other 159 | equivalents. 160 | 161 | 2.7. Conditions 162 | 163 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in 164 | Section 2.1. 165 | 166 | 167 | 3. Responsibilities 168 | 169 | 3.1. Distribution of Source Form 170 | 171 | All distribution of Covered Software in Source Code Form, including any 172 | Modifications that You create or to which You contribute, must be under 173 | the terms of this License. You must inform recipients that the Source 174 | Code Form of the Covered Software is governed by the terms of this 175 | License, and how they can obtain a copy of this License. You may not 176 | attempt to alter or restrict the recipients' rights in the Source Code 177 | Form. 178 | 179 | 3.2. Distribution of Executable Form 180 | 181 | If You distribute Covered Software in Executable Form then: 182 | 183 | a. such Covered Software must also be made available in Source Code Form, 184 | as described in Section 3.1, and You must inform recipients of the 185 | Executable Form how they can obtain a copy of such Source Code Form by 186 | reasonable means in a timely manner, at a charge no more than the cost 187 | of distribution to the recipient; and 188 | 189 | b. You may distribute such Executable Form under the terms of this 190 | License, or sublicense it under different terms, provided that the 191 | license for the Executable Form does not attempt to limit or alter the 192 | recipients' rights in the Source Code Form under this License. 193 | 194 | 3.3. Distribution of a Larger Work 195 | 196 | You may create and distribute a Larger Work under terms of Your choice, 197 | provided that You also comply with the requirements of this License for 198 | the Covered Software. If the Larger Work is a combination of Covered 199 | Software with a work governed by one or more Secondary Licenses, and the 200 | Covered Software is not Incompatible With Secondary Licenses, this 201 | License permits You to additionally distribute such Covered Software 202 | under the terms of such Secondary License(s), so that the recipient of 203 | the Larger Work may, at their option, further distribute the Covered 204 | Software under the terms of either this License or such Secondary 205 | License(s). 206 | 207 | 3.4. Notices 208 | 209 | You may not remove or alter the substance of any license notices 210 | (including copyright notices, patent notices, disclaimers of warranty, or 211 | limitations of liability) contained within the Source Code Form of the 212 | Covered Software, except that You may alter any license notices to the 213 | extent required to remedy known factual inaccuracies. 214 | 215 | 3.5. Application of Additional Terms 216 | 217 | You may choose to offer, and to charge a fee for, warranty, support, 218 | indemnity or liability obligations to one or more recipients of Covered 219 | Software. However, You may do so only on Your own behalf, and not on 220 | behalf of any Contributor. You must make it absolutely clear that any 221 | such warranty, support, indemnity, or liability obligation is offered by 222 | You alone, and You hereby agree to indemnify every Contributor for any 223 | liability incurred by such Contributor as a result of warranty, support, 224 | indemnity or liability terms You offer. You may include additional 225 | disclaimers of warranty and limitations of liability specific to any 226 | jurisdiction. 227 | 228 | 4. Inability to Comply Due to Statute or Regulation 229 | 230 | If it is impossible for You to comply with any of the terms of this License 231 | with respect to some or all of the Covered Software due to statute, 232 | judicial order, or regulation then You must: (a) comply with the terms of 233 | this License to the maximum extent possible; and (b) describe the 234 | limitations and the code they affect. Such description must be placed in a 235 | text file included with all distributions of the Covered Software under 236 | this License. Except to the extent prohibited by statute or regulation, 237 | such description must be sufficiently detailed for a recipient of ordinary 238 | skill to be able to understand it. 239 | 240 | 5. Termination 241 | 242 | 5.1. The rights granted under this License will terminate automatically if You 243 | fail to comply with any of its terms. However, if You become compliant, 244 | then the rights granted under this License from a particular Contributor 245 | are reinstated (a) provisionally, unless and until such Contributor 246 | explicitly and finally terminates Your grants, and (b) on an ongoing 247 | basis, if such Contributor fails to notify You of the non-compliance by 248 | some reasonable means prior to 60 days after You have come back into 249 | compliance. Moreover, Your grants from a particular Contributor are 250 | reinstated on an ongoing basis if such Contributor notifies You of the 251 | non-compliance by some reasonable means, this is the first time You have 252 | received notice of non-compliance with this License from such 253 | Contributor, and You become compliant prior to 30 days after Your receipt 254 | of the notice. 255 | 256 | 5.2. If You initiate litigation against any entity by asserting a patent 257 | infringement claim (excluding declaratory judgment actions, 258 | counter-claims, and cross-claims) alleging that a Contributor Version 259 | directly or indirectly infringes any patent, then the rights granted to 260 | You by any and all Contributors for the Covered Software under Section 261 | 2.1 of this License shall terminate. 262 | 263 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user 264 | license agreements (excluding distributors and resellers) which have been 265 | validly granted by You or Your distributors under this License prior to 266 | termination shall survive termination. 267 | 268 | 6. Disclaimer of Warranty 269 | 270 | Covered Software is provided under this License on an "as is" basis, 271 | without warranty of any kind, either expressed, implied, or statutory, 272 | including, without limitation, warranties that the Covered Software is free 273 | of defects, merchantable, fit for a particular purpose or non-infringing. 274 | The entire risk as to the quality and performance of the Covered Software 275 | is with You. Should any Covered Software prove defective in any respect, 276 | You (not any Contributor) assume the cost of any necessary servicing, 277 | repair, or correction. This disclaimer of warranty constitutes an essential 278 | part of this License. No use of any Covered Software is authorized under 279 | this License except under this disclaimer. 280 | 281 | 7. Limitation of Liability 282 | 283 | Under no circumstances and under no legal theory, whether tort (including 284 | negligence), contract, or otherwise, shall any Contributor, or anyone who 285 | distributes Covered Software as permitted above, be liable to You for any 286 | direct, indirect, special, incidental, or consequential damages of any 287 | character including, without limitation, damages for lost profits, loss of 288 | goodwill, work stoppage, computer failure or malfunction, or any and all 289 | other commercial damages or losses, even if such party shall have been 290 | informed of the possibility of such damages. This limitation of liability 291 | shall not apply to liability for death or personal injury resulting from 292 | such party's negligence to the extent applicable law prohibits such 293 | limitation. Some jurisdictions do not allow the exclusion or limitation of 294 | incidental or consequential damages, so this exclusion and limitation may 295 | not apply to You. 296 | 297 | 8. Litigation 298 | 299 | Any litigation relating to this License may be brought only in the courts 300 | of a jurisdiction where the defendant maintains its principal place of 301 | business and such litigation shall be governed by laws of that 302 | jurisdiction, without reference to its conflict-of-law provisions. Nothing 303 | in this Section shall prevent a party's ability to bring cross-claims or 304 | counter-claims. 305 | 306 | 9. Miscellaneous 307 | 308 | This License represents the complete agreement concerning the subject 309 | matter hereof. If any provision of this License is held to be 310 | unenforceable, such provision shall be reformed only to the extent 311 | necessary to make it enforceable. Any law or regulation which provides that 312 | the language of a contract shall be construed against the drafter shall not 313 | be used to construe this License against a Contributor. 314 | 315 | 316 | 10. Versions of the License 317 | 318 | 10.1. New Versions 319 | 320 | Mozilla Foundation is the license steward. Except as provided in Section 321 | 10.3, no one other than the license steward has the right to modify or 322 | publish new versions of this License. Each version will be given a 323 | distinguishing version number. 324 | 325 | 10.2. Effect of New Versions 326 | 327 | You may distribute the Covered Software under the terms of the version 328 | of the License under which You originally received the Covered Software, 329 | or under the terms of any subsequent version published by the license 330 | steward. 331 | 332 | 10.3. Modified Versions 333 | 334 | If you create software not governed by this License, and you want to 335 | create a new license for such software, you may create and use a 336 | modified version of this License if you rename the license and remove 337 | any references to the name of the license steward (except to note that 338 | such modified license differs from this License). 339 | 340 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 341 | Licenses If You choose to distribute Source Code Form that is 342 | Incompatible With Secondary Licenses under the terms of this version of 343 | the License, the notice described in Exhibit B of this License must be 344 | attached. 345 | 346 | Exhibit A - Source Code Form License Notice 347 | 348 | This Source Code Form is subject to the 349 | terms of the Mozilla Public License, v. 350 | 2.0. If a copy of the MPL was not 351 | distributed with this file, You can 352 | obtain one at 353 | http://mozilla.org/MPL/2.0/. 354 | 355 | If it is not possible or desirable to put the notice in a particular file, 356 | then You may include the notice in a location (such as a LICENSE file in a 357 | relevant directory) where a recipient would be likely to look for such a 358 | notice. 359 | 360 | You may add additional accurate notices of copyright ownership. 361 | 362 | Exhibit B - "Incompatible With Secondary Licenses" Notice 363 | 364 | This Source Code Form is "Incompatible 365 | With Secondary Licenses", as defined by 366 | the Mozilla Public License, v. 2.0. 367 | 368 | 369 | A copy of the MPL is also available at http://mozilla.org/MPL/2.0/. 370 | -------------------------------------------------------------------------------- /test/stylus-tech.test.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | path = require('path'), 3 | deepExtend = require('deep-extend'), 4 | mockFs = require('mock-fs'), 5 | mockFsHelper = require(path.join(__dirname, 'lib', 'mock-fs-helper')), 6 | MockNode = require('mock-enb/lib/mock-node'), 7 | FileList = require('enb/lib/file-list'), 8 | loadDirSync = require('mock-enb/utils/dir-utils').loadDirSync, 9 | StylusTech = require('../techs/stylus'), 10 | nodeModules = mockFsHelper.duplicateFSInMemory(path.resolve('node_modules')), 11 | EOL = require('os').EOL; 12 | 13 | describe('stylus-tech', function () { 14 | afterEach(function () { 15 | mockFs.restore(); 16 | }); 17 | 18 | describe('@import', function () { 19 | it('must import .css file', function () { 20 | var scheme = { 21 | blocks: { 'block.styl': '@import "../plugins/file.css"\n' }, 22 | plugins: { 'file.css': 'body { color: #000; }' } 23 | }; 24 | 25 | return build(scheme).then(function (actual) { 26 | actual.must.equal('body{color:#000;}'); 27 | }); 28 | }); 29 | 30 | it('must import .styl file', function () { 31 | var scheme = { 32 | blocks: { 'block.styl': '@import "../plugins/file.styl"\n' }, 33 | plugins: { 'file.styl': 'body { color: #000; }' } 34 | }; 35 | 36 | return build(scheme).then(function (actual) { 37 | actual.must.equal('body{color:#000;}'); 38 | }); 39 | }); 40 | 41 | it('css imports can be left as is', function () { 42 | var scheme = { 43 | blocks: { 'block.styl': '@import "../plugins/file.css"\n' }, 44 | plugins: { 'file.css': 'body { color: #000; }' } 45 | }; 46 | 47 | return build(scheme, { imports: 'keep' }).then(function (actual) { 48 | actual.must.equal('@import"../plugins/file.css";'); 49 | }); 50 | }); 51 | 52 | it('css imports correctly processed after styl files', function () { 53 | var scheme = { 54 | blocks: { 55 | 'block.styl': [ 56 | '@import "../plugins/file.styl";', 57 | '@import "../plugins/file2.css";' 58 | ].join(EOL) 59 | }, 60 | plugins: { 61 | 'file.styl': 'body { margin: 0; }', 62 | 'file2.css': 'body { padding: 0; }' 63 | } 64 | }; 65 | 66 | return build(scheme).then(function (actual) { 67 | actual.must.equal('body{margin:0;}body{padding:0;}'); 68 | }); 69 | }); 70 | 71 | it('must import css without processing and resolve urls', function () { 72 | var scheme = { 73 | blocks: { 74 | 'block.styl': '@import "../plugins/file2.css";' 75 | }, 76 | plugins: { 77 | 'file2.css': '.transparent { background: url(./icon.png); filter: alpha(opacity=0); }' 78 | } 79 | }; 80 | 81 | return build(scheme).then(function (actual) { 82 | actual.must.equal('.transparent{background:url(../plugins/icon.png);filter:alpha(opacity=0);}'); 83 | }); 84 | }); 85 | }); 86 | 87 | describe('@require', function () { 88 | it('must require .styl file', function () { 89 | var scheme = { 90 | blocks: { 'block.styl': '@require "../plugins/file.styl"\n' }, 91 | plugins: { 'file.styl': 'body { color: #000; }' } 92 | }; 93 | 94 | return build(scheme).then(function (actual) { 95 | actual.must.equal('body{color:#000;}'); 96 | }); 97 | }); 98 | 99 | it('must require .styl file once', function () { 100 | var scheme = { 101 | blocks: { 102 | 'block.styl': [ 103 | '@require "../plugins/file.styl"', 104 | '@require "../plugins/file.styl"' 105 | ].join(EOL) 106 | }, 107 | plugins: { 'file.styl': 'body { color: #000; }' } 108 | }; 109 | 110 | return build(scheme).then(function (actual) { 111 | actual.must.equal('body{color:#000;}'); 112 | }); 113 | }); 114 | }); 115 | 116 | describe('imports', function () { 117 | it('must rebase url()', function () { 118 | var scheme = { 119 | blocks: { 120 | 'block.jpg': new Buffer('block image'), 121 | 'block.styl': 'body { background-image: url(block.jpg) }' 122 | } 123 | }; 124 | 125 | return build(scheme, { url: 'rebase' }).then(function (actual) { 126 | actual.must.equal('body{background-image:url("../blocks/block.jpg");}'); 127 | }); 128 | }); 129 | }); 130 | 131 | describe('url()', function () { 132 | it('must rebase url()', function () { 133 | var scheme = { 134 | blocks: { 135 | 'block.jpg': new Buffer('block image'), 136 | 'block.styl': 'body { background-image: url(block.jpg) }' 137 | } 138 | }; 139 | 140 | return build(scheme, { url: 'rebase' }).then(function (actual) { 141 | actual.must.equal('body{background-image:url("../blocks/block.jpg");}'); 142 | }); 143 | }); 144 | 145 | it('must inline url()', function () { 146 | var scheme = { 147 | blocks: { 148 | images: { 149 | 'block.jpg': new Buffer('block image'), 150 | 'block.png': new Buffer('block image'), 151 | 'block.gif': new Buffer('block image') 152 | }, 153 | 'block.styl': [ 154 | 'body { background-image: url(images/block.jpg) }', 155 | 'div { background-image: url(images/block.png) }', 156 | 'section { background-image: url(images/block.gif) }' 157 | ].join(EOL) 158 | } 159 | }, 160 | expected = [ 161 | 'body{background-image:url("data:image/jpeg;base64,YmxvY2sgaW1hZ2U=");}', 162 | 'div{background-image:url("data:image/png;base64,YmxvY2sgaW1hZ2U=");}', 163 | 'section{background-image:url("data:image/gif;base64,YmxvY2sgaW1hZ2U=");}' 164 | ].join(''); 165 | 166 | return build(scheme, { url: 'inline' }).then(function (actual) { 167 | actual.must.equal(expected); 168 | }); 169 | }); 170 | 171 | it('must inline svg in url()', function () { 172 | var scheme = { 173 | blocks: { 174 | images: { 175 | 'block.svg': new Buffer('block-image') 176 | }, 177 | 'block.styl': 'body { background-image: url(images/block.svg) }' 178 | } 179 | }; 180 | 181 | return build(scheme, { url: 'inline' }).then(function (actual) { 182 | actual.must.equal('body{background-image:url("data:image/svg+xml,block-image");}'); 183 | }); 184 | }); 185 | 186 | it('must not rebase/inline absolute url()', function () { 187 | var scheme = { 188 | blocks: { 189 | 'block.styl': [ 190 | 'body { background-image: url(http://foo.com/foo.css) }', 191 | 'div { background-image: url(https://foo.com/foo.css) }', 192 | 'section { background-image: url(//foo.com/foo.css) }' 193 | ].join(EOL) 194 | } 195 | }, 196 | expected = [ 197 | 'body{background-image:url("http://foo.com/foo.css");}', 198 | 'div{background-image:url("https://foo.com/foo.css");}', 199 | 'section{background-image:url("//foo.com/foo.css");}' 200 | ].join(''); 201 | 202 | return build(scheme, { url: 'rebase' }).then(function (actual) { 203 | actual.must.equal(expected); 204 | }); 205 | }); 206 | }); 207 | 208 | describe('globals', function () { 209 | it('must import global file by relative path', function () { 210 | var scheme = { 211 | blocks: { 212 | 'file.styl': '.col { width: $col-width; }' 213 | }, 214 | globals: { 215 | 'vars.styl': '$col-width = 30px' 216 | } 217 | }; 218 | 219 | return build(scheme, { globals: ['../globals/vars.styl'] }).then(function (actual) { 220 | actual.must.equal('.col{width:30px;}'); 221 | }); 222 | }); 223 | 224 | it('must import global file by absolute path', function () { 225 | var scheme = { 226 | blocks: { 227 | 'file.styl': '.col { width: $col-width; }' 228 | }, 229 | globals: { 230 | 'vars.styl': '$col-width = 30px' 231 | } 232 | }; 233 | 234 | return build(scheme, { globals: [path.resolve('./globals/vars.styl')] }).then(function (actual) { 235 | actual.must.equal('.col{width:30px;}'); 236 | }); 237 | }); 238 | }); 239 | 240 | describe('autoprefixer', function () { 241 | it('must add vendor prefixes from browserlist', function () { 242 | var scheme = { 243 | blocks: { 244 | 'block.styl': [ 245 | 'body { ', 246 | ' color: #000; ', 247 | ' display:-ms-flexbox;', 248 | ' display: flex;', 249 | '} ' 250 | ].join(EOL) 251 | } 252 | }, 253 | expected = [ 254 | 'body{', 255 | 'color:#000;', 256 | 'display:flex;', 257 | '}' 258 | ].join(''); 259 | 260 | return build(scheme, { autoprefixer: true }).then(function (actual) { 261 | actual.must.equal(expected); 262 | }); 263 | }); 264 | 265 | it('must add vendor prefixes from browser config', function () { 266 | var scheme = { 267 | blocks: { 268 | 'block.styl': [ 269 | 'body { ', 270 | ' color: #000; ', 271 | ' display: flex;', 272 | '} ' 273 | ].join(EOL) 274 | } 275 | }, 276 | expected = [ 277 | 'body{', 278 | 'color:#000;', 279 | 'display:-ms-flexbox;', 280 | 'display:flex;', 281 | '}' 282 | ].join(''); 283 | 284 | return build(scheme, { autoprefixer: { browsers: ['Explorer 10'] } }) 285 | .then(function (actual) { 286 | actual.must.equal(expected); 287 | }); 288 | }); 289 | 290 | it('must do not delete -webkit-box-orient for multiline overflow', function () { 291 | var scheme = { 292 | blocks: { 293 | 'block.styl': [ 294 | 'body { ', 295 | ' -webkit-line-clamp: 2; ', 296 | ' display: -webkit-box; ' + 297 | ' -webkit-box-orient: vertical;', 298 | '} ' 299 | ].join(EOL) 300 | } 301 | }, 302 | expected = [ 303 | 'body{', 304 | '-webkit-line-clamp:2;', 305 | 'display:-webkit-box;', 306 | '-webkit-box-orient:vertical;', 307 | '}' 308 | ].join(''); 309 | 310 | return build(scheme, { autoprefixer: { browsers: ['Chrome > 80'] } }) 311 | .then(function (actual) { 312 | actual.must.equal(expected); 313 | }); 314 | }); 315 | }); 316 | 317 | describe('sourcemap', function () { 318 | it('must create, save on fs and add link to sourcemap', function () { 319 | var scheme = { 320 | blocks: { 321 | 'block.styl': 'body { color: #000; }' 322 | } 323 | }; 324 | 325 | return build(scheme, { sourcemap: true }).then(function (actual) { 326 | var isMapExists = fs.existsSync('./bundle/bundle.css.map'); 327 | 328 | isMapExists.must.be.true(); 329 | actual.must.equal('body{color:#000;}/*#sourceMappingURL=bundle.css.map*/'); 330 | }); 331 | }); 332 | 333 | it('must create and inline sourcemap', function () { 334 | var scheme = { 335 | blocks: { 336 | 'block.styl': 'body { color: #000; }' 337 | } 338 | }, 339 | 340 | expected = 'body{color:#000;}/*#sourceMappingURL=data:application/json;base64,'; 341 | 342 | return build(scheme, { sourcemap: 'inline' }).then(function (actual) { 343 | actual.must.contain(expected); 344 | }); 345 | }); 346 | }); 347 | 348 | describe('nib', function () { 349 | it('must use mixins', function () { 350 | var scheme = { 351 | blocks: { 352 | 'block.styl': 'body { size: 5em 10em; }' 353 | } 354 | }, 355 | expected = [ 356 | 'body{', 357 | 'width:5em;', 358 | 'height:10em;', 359 | '}' 360 | ].join(''); 361 | 362 | return build(scheme, { useNib: true }).then(function (actual) { 363 | actual.must.equal(expected); 364 | }); 365 | }); 366 | }); 367 | 368 | describe('use', function () { 369 | var nibPlugin = require('nib'); 370 | 371 | it('must use nib plugin as a part of plugins list', function () { 372 | var scheme = { 373 | blocks: { 374 | 'block.styl': 'body { size: 5em 10em; }' 375 | } 376 | }, 377 | expected = [ 378 | 'body{', 379 | 'width:5em;', 380 | 'height:10em;', 381 | '}' 382 | ].join(''); 383 | 384 | return build(scheme, { use: [nibPlugin()] }).then(function (actual) { 385 | actual.must.equal(expected); 386 | }); 387 | }); 388 | 389 | it('must use single nib plugin identically as a part of plugins list', function () { 390 | var scheme = { 391 | blocks: { 392 | 'block.styl': 'body { size: 5em 10em; }' 393 | } 394 | }; 395 | 396 | return Promise.all([ 397 | build(scheme, { use: nibPlugin() }), 398 | build(scheme, { use: [nibPlugin()] }) 399 | ]).then(function (values) { 400 | values[0].must.equal(values[1]); 401 | }); 402 | }); 403 | }); 404 | 405 | describe('compress', function () { 406 | it('must compressed result css', function () { 407 | var scheme = { 408 | blocks: { 409 | 'block.styl': [ 410 | 'body { ', 411 | ' color: #000; ', 412 | '} ', 413 | 'div {} ', 414 | 'div { ', 415 | ' font-weight: normal; ', 416 | ' margin: 0px; ', 417 | ' padding: 5px 0 5px 0; ', 418 | ' background: hsl(134, 50%, 50%); ', 419 | ' padding: 5px 0 5px 0; ', 420 | '} ' 421 | ].join(EOL) 422 | } 423 | }, 424 | expected = 'body{color:#000}div{font-weight:400;margin:0;background:#40bf5e;padding:5px 0}'; 425 | 426 | return build(scheme, { compress: true }).then(function (actual) { 427 | actual.must.equal(expected); 428 | }); 429 | }); 430 | }); 431 | 432 | describe('comments', function () { 433 | it('must added comments for Stylus', function () { 434 | var scheme = { 435 | blocks: { 436 | 'block.styl': [ 437 | 'body {', 438 | ' color: #000;', 439 | '}' 440 | ].join(EOL) 441 | } 442 | }, 443 | 444 | expected = [ 445 | ['/* ..', 'blocks', 'block.styl:begin */'].join(path.sep), 446 | 'body {', 447 | ' color: #000;', 448 | '}', 449 | ['/* ..', 'blocks', 'block.styl:end */'].join(path.sep) 450 | ].join('\n'); // Stylus uses \n for line break 451 | 452 | return build(scheme, { comments: true }).then(function (actual) { 453 | actual.must.contain(expected); 454 | }); 455 | }); 456 | 457 | it('must added comments for CSS file', function () { 458 | var scheme = { 459 | blocks: { 460 | 'block.css': [ 461 | 'body {', 462 | ' color: #000;', 463 | '}' 464 | ].join(EOL) 465 | } 466 | }, 467 | 468 | expected = [ 469 | ['/* ..', 'blocks', 'block.css:begin */'].join(path.sep), 470 | scheme.blocks['block.css'], 471 | ['/* ..', 'blocks', 'block.css:end */'].join(path.sep) 472 | ].join('\n'); // Stylus uses \n for line break 473 | 474 | return build(scheme, { comments: true }).then(function (actual) { 475 | actual.must.contain(expected); 476 | }); 477 | }); 478 | 479 | it('must added comments for Stylus file with suffix', function () { 480 | var scheme = { 481 | blocks: { 482 | 'block.styl': [ 483 | 'body {', 484 | ' color: #000;', 485 | '}' 486 | ].join(EOL), 487 | 'block.ie.styl': [ 488 | 'body {', 489 | ' color: #fff;', 490 | '}' 491 | ].join(EOL) 492 | } 493 | }, 494 | 495 | expected = [ 496 | ['/* ..', 'blocks', 'block.styl:begin */'].join(path.sep), 497 | 'body {', 498 | ' color: #000;', 499 | '}', 500 | ['/* ..', 'blocks', 'block.styl:end */'].join(path.sep), 501 | ['/* ..', 'blocks', 'block.ie.styl:begin */'].join(path.sep), 502 | 'body {', 503 | ' color: #fff;', 504 | '}', 505 | ['/* ..', 'blocks', 'block.ie.styl:end */'].join(path.sep) 506 | ].join('\n'); // Stylus uses \n for line break 507 | 508 | return build(scheme, { comments: true, sourceSuffixes: ['styl', 'css', 'ie.styl'] }) 509 | .then(function (actual) { 510 | actual.must.contain(expected); 511 | }); 512 | }); 513 | }); 514 | 515 | describe('other', function () { 516 | it('must use only .styl file if an entity has multiple files', function () { 517 | var scheme = { 518 | blocks: { 519 | 'block.css': [ 520 | 'body {', 521 | ' color: #fff;', 522 | '}' 523 | ].join(EOL), 524 | 'block.styl': [ 525 | 'body {', 526 | ' color: #000;', 527 | '}' 528 | ].join(EOL) 529 | } 530 | }, 531 | 532 | expected = [ 533 | 'body{', 534 | 'color:#000;', 535 | '}' 536 | ].join(''); 537 | 538 | return build(scheme).then(function (actual) { 539 | actual.must.contain(expected); 540 | }); 541 | }); 542 | }); 543 | }); 544 | 545 | function build (scheme, options) { 546 | var baseScheme = { 547 | blocks: {}, 548 | bundle: {}, 549 | // jscs:disable 550 | node_modules: nodeModules 551 | // jscs:enable 552 | }, 553 | commonScheme = deepExtend(baseScheme, scheme), 554 | commonOptions = deepExtend({ comments: false }, options); 555 | 556 | mockFs(commonScheme); 557 | 558 | var bundle = new MockNode('bundle'), 559 | fileList = new FileList(); 560 | 561 | fileList.addFiles(loadDirSync('blocks')); 562 | bundle.provideTechData('?.files', fileList); 563 | 564 | return bundle.runTechAndGetContent(StylusTech, commonOptions).spread(function (content) { 565 | return (commonOptions.compress || commonOptions.comments) ? content : normalizeContent(content); 566 | }); 567 | } 568 | 569 | /** 570 | * Remove all /r from file for do more truth test 571 | * @param {String} str - source text 572 | * @returns {String} 573 | */ 574 | function normalizeContent(str) { 575 | return str.replace(/\s+/g, '').trim(); 576 | } 577 | --------------------------------------------------------------------------------