├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .travis.yml ├── docs ├── platforms.png └── products.png ├── lib ├── index.js ├── inject │ ├── loader.js │ └── output.js ├── output.js ├── runner.js ├── transform.js └── utils.js ├── license.md ├── package.json ├── readme.md └── test ├── .eslintrc ├── fixtures ├── ?class │ ├── import-factory │ │ ├── layer-0 │ │ │ ├── target │ │ │ │ └── index.js │ │ │ └── test │ │ │ │ └── index.js │ │ └── result.js │ ├── simple │ │ ├── layer-0 │ │ │ ├── target │ │ │ │ └── index.js │ │ │ └── test │ │ │ │ └── index.js │ │ └── result.js │ └── with-styles │ │ ├── layer-0 │ │ ├── target │ │ │ ├── index.js │ │ │ └── styles.less │ │ └── test │ │ │ └── index.js │ │ └── result.js ├── ?inject │ ├── ?class │ │ ├── layer-0 │ │ │ ├── target │ │ │ │ └── index.js │ │ │ └── test │ │ │ │ └── index.js │ │ └── result.js │ ├── ?styles │ │ ├── layer-0 │ │ │ ├── target │ │ │ │ └── styles.less │ │ │ └── test │ │ │ │ └── index.js │ │ └── result.js │ ├── no-injects │ │ ├── import-factory │ │ │ ├── layer-0 │ │ │ │ ├── target │ │ │ │ │ └── index.js │ │ │ │ └── test │ │ │ │ │ └── index.js │ │ │ └── result.js │ │ └── simple │ │ │ ├── layer-0 │ │ │ ├── target │ │ │ │ └── index.js │ │ │ └── test │ │ │ │ └── index.js │ │ │ └── result.js │ ├── simple │ │ ├── layer-0 │ │ │ ├── target │ │ │ │ └── index.js │ │ │ └── test │ │ │ │ └── index.js │ │ └── result.js │ └── with-styles │ │ ├── layer-0 │ │ ├── target │ │ │ ├── index.js │ │ │ └── styles.less │ │ └── test │ │ │ └── index.js │ │ └── result.js ├── ?styles │ ├── error │ │ ├── layer-0 │ │ │ └── test │ │ │ │ └── index.js │ │ └── result.js │ └── simple │ │ ├── layer-0 │ │ ├── target │ │ │ └── styles.less │ │ └── test │ │ │ └── index.js │ │ └── result.js ├── error │ ├── no-hash │ │ ├── layer-0 │ │ │ └── test │ │ │ │ └── index.js │ │ └── result.js │ ├── not-found │ │ ├── layer-0 │ │ │ └── test │ │ │ │ └── index.js │ │ └── result.js │ ├── only-styles │ │ ├── layer-0 │ │ │ ├── target │ │ │ │ └── styles.less │ │ │ └── test │ │ │ │ └── index.js │ │ └── result.js │ └── self-not-found │ │ ├── layer-0 │ │ └── target │ │ │ └── index.js │ │ └── result.js ├── simple │ ├── cache │ │ ├── layer-0 │ │ │ ├── target │ │ │ │ └── index.js │ │ │ └── test │ │ │ │ └── index.js │ │ └── result.js │ ├── exclude │ │ ├── result.js │ │ └── test │ │ │ └── index.js │ ├── multiple-layers │ │ ├── middle │ │ │ ├── layer-0 │ │ │ │ └── target │ │ │ │ │ └── index.js │ │ │ ├── layer-1 │ │ │ │ ├── target │ │ │ │ │ └── index.js │ │ │ │ └── test │ │ │ │ │ └── index.js │ │ │ ├── layer-2 │ │ │ │ └── target │ │ │ │ │ └── index.js │ │ │ └── result.js │ │ ├── nearest │ │ │ ├── layer-0 │ │ │ │ └── target │ │ │ │ │ └── index.js │ │ │ ├── layer-1 │ │ │ │ ├── target │ │ │ │ │ └── index.js │ │ │ │ └── test │ │ │ │ │ └── index.js │ │ │ └── result.js │ │ ├── same-folder-name │ │ │ ├── layer-0 │ │ │ │ └── target │ │ │ │ │ └── index.js │ │ │ ├── layer-1 │ │ │ │ ├── subfolder │ │ │ │ │ └── target │ │ │ │ │ │ └── index.js │ │ │ │ └── target │ │ │ │ │ └── index.js │ │ │ ├── layer-2 │ │ │ │ └── target │ │ │ │ │ └── index.js │ │ │ └── result.js │ │ ├── self-middle │ │ │ ├── layer-0 │ │ │ │ └── target │ │ │ │ │ └── index.js │ │ │ ├── layer-1 │ │ │ │ └── target │ │ │ │ │ └── index.js │ │ │ ├── layer-2 │ │ │ │ └── target │ │ │ │ │ └── index.js │ │ │ └── result.js │ │ ├── self │ │ │ ├── layer-0 │ │ │ │ └── target │ │ │ │ │ └── index.js │ │ │ ├── layer-1 │ │ │ │ └── target │ │ │ │ │ └── index.js │ │ │ └── result.js │ │ └── through │ │ │ ├── layer-0 │ │ │ └── target │ │ │ │ └── index.js │ │ │ ├── layer-1 │ │ │ └── test │ │ │ │ └── index.js │ │ │ └── result.js │ └── single-layer │ │ ├── layer-0 │ │ ├── target │ │ │ └── index.js │ │ └── test │ │ │ └── index.js │ │ └── result.js └── with-styles │ ├── multiple-layers │ ├── layer-0 │ │ └── target │ │ │ └── styles.less │ ├── layer-1 │ │ ├── target │ │ │ ├── index.js │ │ │ └── styles.less │ │ └── test │ │ │ └── index.js │ └── result.js │ └── single-layer │ ├── layer-0 │ ├── target │ │ ├── index.js │ │ └── styles.less │ └── test │ │ └── index.js │ └── result.js ├── helpers └── index.js └── lib └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ "es2015", "stage-2" ], 3 | "plugins": [ "add-module-exports" ] 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org/ 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 4 11 | 12 | [*.{json,yml}] 13 | indent_size = 2 14 | 15 | [{.babelrc,.eslintrc}] 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "rebem/configs/common", 4 | "rebem/configs/babel" 5 | ], 6 | "rules": { 7 | "max-len": 0, 8 | "quotes": 0 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | tmp/ 3 | build/ 4 | coverage/ 5 | *.sublime-* 6 | *.log 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # https://docs.travis-ci.com/user/customizing-the-build/ 2 | 3 | sudo: false 4 | 5 | language: node_js 6 | 7 | node_js: 8 | - "0.12" 9 | - "4" 10 | - "5" 11 | 12 | branches: 13 | only: 14 | - master 15 | 16 | matrix: 17 | fast_finish: true 18 | 19 | before_install: 20 | - npm install -g npm 21 | - npm --version 22 | 23 | script: npm start ci 24 | -------------------------------------------------------------------------------- /docs/platforms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/docs/platforms.png -------------------------------------------------------------------------------- /docs/products.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/docs/products.png -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | import loaderUtils from 'loader-utils'; 2 | 3 | import transform from './transform'; 4 | 5 | export default function(source) { 6 | if (this.cacheable) { 7 | this.cacheable(); 8 | } 9 | 10 | const callback = this.async(); 11 | const options = loaderUtils.parseQuery(this.query); 12 | 13 | transform.call(this, source, this.resourcePath, options) 14 | .then(result => { 15 | callback(null, result); 16 | }) 17 | .catch(err => { 18 | callback(err); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /lib/inject/loader.js: -------------------------------------------------------------------------------- 1 | import falafel from 'falafel'; 2 | 3 | export default function(source) { 4 | if (this.cacheable) { 5 | this.cacheable(); 6 | } 7 | 8 | const result = falafel(source, node => { 9 | if ( 10 | node.type === 'CallExpression' && 11 | node.callee.type === 'Identifier' && 12 | node.callee.name === 'require' 13 | ) { 14 | const value = node.arguments[0].value; 15 | 16 | node.update(`typeof __inject__('${value}') !== 'undefined' ? __inject__('${value}') : require('${value}')`); 17 | } 18 | }); 19 | 20 | return ` 21 | exports['default'] = function(__injections__) { 22 | var exports = {}; 23 | 24 | function __inject__(value) { 25 | return __injections__[module.__rebem__ ? module.__rebem__[value] : value]; 26 | } 27 | 28 | ${result.toString()} 29 | 30 | return exports['default']; 31 | }; 32 | `; 33 | } 34 | -------------------------------------------------------------------------------- /lib/inject/output.js: -------------------------------------------------------------------------------- 1 | import { shouldImportClass } from '../utils'; 2 | 3 | export default function(result, options) { 4 | const createFactory = `require('react').createFactory`; 5 | let styles = result.styles 6 | .map(stylesPath => { 7 | return `require('${stylesPath}')`; 8 | }) 9 | .join('; '); 10 | const inject = `module.__rebem__ = module.__rebem__ || {}; module.__rebem__['${result.component}'] = '${result.raw}'`; 11 | 12 | if (result.opts.styles === true) { 13 | return `/* ${result.raw} */ ${styles}`; 14 | } 15 | 16 | styles = styles ? '; ' + styles : styles; 17 | 18 | if (result.opts.inject === true) { 19 | if (shouldImportClass(result, options)) { 20 | return `/* ${result.raw} */ function(injects) { return require('rebem-layers-loader/build/inject/loader!${result.component}')['default'](injects); }${styles}`; 21 | } 22 | 23 | return `/* ${result.raw} */ function(injects) { return ${createFactory}(require('rebem-layers-loader/build/inject/loader!${result.component}')['default'](injects)); }${styles}`; 24 | } 25 | 26 | if (shouldImportClass(result, options)) { 27 | return `/* ${result.raw} */ (function() { ${inject}; var out = require('${result.component}')['default']${styles}; return out; })()`; 28 | } 29 | 30 | return `/* ${result.raw} */ (function() { ${inject}; var out = ${createFactory}(require('${result.component}')['default'])${styles}; return out; })()`; 31 | } 32 | -------------------------------------------------------------------------------- /lib/output.js: -------------------------------------------------------------------------------- 1 | import { shouldImportClass } from './utils'; 2 | 3 | export default function(result, options) { 4 | const createFactory = `require('react').createFactory`; 5 | let styles = result.styles 6 | .map(stylesPath => { 7 | return `require('${stylesPath}')`; 8 | }) 9 | .join('; '); 10 | 11 | if (result.opts.styles === true) { 12 | return `/* ${result.raw} */ ${styles}`; 13 | } 14 | 15 | styles = styles ? '; ' + styles : styles; 16 | 17 | if (shouldImportClass(result, options)) { 18 | return `/* ${result.raw} */ require('${result.component}')['default']${styles}`; 19 | } 20 | 21 | return `/* ${result.raw} */ ${createFactory}(require('${result.component}')['default'])${styles}`; 22 | } 23 | -------------------------------------------------------------------------------- /lib/runner.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import pathExists from 'path-exists'; 3 | import loaderUtils from 'loader-utils'; 4 | import 'core-js/fn/array/find'; 5 | import 'core-js/fn/string/starts-with'; 6 | 7 | const cache = {}; 8 | 9 | function parseRequiredString(requiredString) { 10 | const matched = requiredString.match(/^#(.+?)(\?.+)?$/); 11 | const component = matched[1]; 12 | const opts = loaderUtils.parseQuery(matched[2]); 13 | 14 | return { 15 | component, 16 | opts 17 | }; 18 | } 19 | 20 | function generateCacheKey(required, currentPath, layers) { 21 | const layersCache = loaderUtils.getHashDigest(layers.toString()); 22 | const currentLayerIndex = getCurrentLayerIndex(currentPath, layers); 23 | const currentLayer = layers[currentLayerIndex]; 24 | const requiredFile = path.join(required.component, currentLayer.files.main); 25 | const result = layersCache + '-' + currentLayerIndex + 26 | '#' + required.component + JSON.stringify(required.opts); 27 | 28 | if (currentPath.length - currentPath.lastIndexOf(requiredFile) === requiredFile.length) { 29 | return result + currentPath; 30 | } 31 | 32 | return result; 33 | } 34 | 35 | function getCurrentLayerIndex(currentPath, layers) { 36 | return layers.reduce((result, layer, index) => { 37 | if (currentPath.indexOf(layer.path) === 0) { 38 | return index; 39 | } 40 | 41 | return result; 42 | }, layers.length - 1); 43 | } 44 | 45 | function getCurrentLayerConfig(currentPath, layers) { 46 | return layers.find(layer => currentPath.startsWith(layer.path)); 47 | } 48 | 49 | function getRemainingLayers(currentPath, component, layers) { 50 | const currentLayerIndex = getCurrentLayerIndex(currentPath, layers); 51 | const currentLayer = layers[currentLayerIndex]; 52 | const requiredFilePath = path.join(currentLayer.path, component, currentLayer.files.main); 53 | 54 | if (currentPath === requiredFilePath) { 55 | return layers.slice(0, currentLayerIndex + 1); 56 | } 57 | 58 | return layers; 59 | } 60 | 61 | function getPossibleMainPaths(currentPath, component, layers) { 62 | const remainingLayers = getRemainingLayers(currentPath, component, layers); 63 | 64 | return remainingLayers 65 | .filter(layer => ('main' in layer.files)) 66 | .map(layer => path.join(layer.path, component, layer.files.main)) 67 | .filter(possiblePath => possiblePath !== currentPath); 68 | } 69 | 70 | function getPossibleStylesPaths(component, layers) { 71 | return layers 72 | .filter(layer => ('styles' in layer.files)) 73 | .map(layer => path.join(layer.path, component, layer.files.styles)); 74 | } 75 | 76 | function filterPaths(paths) { 77 | const pathsPromises = paths.map(item => { 78 | return pathExists(item).then(exists => { 79 | if (exists) { 80 | return item; 81 | } 82 | }); 83 | }); 84 | 85 | return Promise.all(pathsPromises).then(result => { 86 | return result.filter(item => item); 87 | }); 88 | } 89 | 90 | function filterMainPath(paths) { 91 | return filterPaths(paths).then(result => result[result.length - 1]); 92 | } 93 | 94 | function filterStylesPaths(paths) { 95 | return filterPaths(paths); 96 | } 97 | 98 | export default function(requiredString, currentPath, layers) { 99 | const currentLayerConfig = getCurrentLayerConfig(currentPath, layers); 100 | const required = parseRequiredString(requiredString); 101 | const cacheKey = generateCacheKey(required, currentPath, layers); 102 | 103 | if (cacheKey in cache) { 104 | return cache[cacheKey]; 105 | } 106 | 107 | if (required.opts.styles === true) { 108 | const possibleStylesPaths = getPossibleStylesPaths(required.component, layers); 109 | const stylesPathsPromise = filterStylesPaths(possibleStylesPaths); 110 | 111 | cache[cacheKey] = stylesPathsPromise.then(stylesPaths => { 112 | if (stylesPaths.length === 0) { 113 | throw new Error(`Styles for component "${requiredString}" were not found.`); 114 | } 115 | 116 | return { 117 | raw: requiredString, 118 | opts: required.opts, 119 | layer: currentLayerConfig, 120 | component: null, 121 | styles: stylesPaths 122 | }; 123 | }); 124 | 125 | return cache[cacheKey]; 126 | } 127 | 128 | const possibleMainPaths = getPossibleMainPaths(currentPath, required.component, layers); 129 | const possibleStylesPaths = getPossibleStylesPaths(required.component, layers); 130 | const mainPathPromise = filterMainPath(possibleMainPaths); 131 | const stylesPathsPromise = filterStylesPaths(possibleStylesPaths); 132 | 133 | cache[cacheKey] = Promise 134 | .all([ mainPathPromise, stylesPathsPromise ]) 135 | .then(([ mainPath, stylesPaths ]) => { 136 | if (!mainPath) { 137 | throw new Error(`Component "${requiredString}" was not found.`); 138 | } 139 | 140 | return { 141 | raw: requiredString, 142 | opts: required.opts, 143 | layer: currentLayerConfig, 144 | component: mainPath, 145 | styles: stylesPaths 146 | }; 147 | }); 148 | 149 | return cache[cacheKey]; 150 | } 151 | -------------------------------------------------------------------------------- /lib/transform.js: -------------------------------------------------------------------------------- 1 | import falafel from 'falafel'; 2 | 3 | import getResults from './runner'; 4 | import getOutput from './output'; 5 | import getOutputWithInjects from './inject/output'; 6 | import { stringifyRequest } from 'loader-utils'; 7 | 8 | export default function(source, file, options) { 9 | const consumerPaths = options.layers.map(layer => layer.path).concat(options.consumers || []); 10 | const isFileInheritable = consumerPaths.some(layerPath => { 11 | return file.indexOf(layerPath) === 0; 12 | }); 13 | 14 | if (!isFileInheritable) { 15 | return Promise.resolve(source); 16 | } 17 | 18 | const requiresPromises = []; 19 | const out = falafel(source, node => { 20 | if ( 21 | node.type === 'CallExpression' && 22 | node.callee.type === 'Identifier' && 23 | node.callee.name === 'require' && 24 | node.arguments[0].value.charAt(0) === '#' 25 | ) { 26 | const rawRequireValue = node.arguments[0].value; 27 | 28 | requiresPromises.push( 29 | getResults(rawRequireValue, file, options.layers).then(origResult => { 30 | const result = { 31 | ...origResult 32 | }; 33 | 34 | if (result.component) { 35 | result.component = stringifyRequest(this, result.component).replace(/"/g, ''); 36 | } 37 | 38 | result.styles = result.styles.map((style) => { 39 | return stringifyRequest(this, style).replace(/"/g, ''); 40 | }); 41 | 42 | if (options.injectable) { 43 | node.update(getOutputWithInjects(result, options)); 44 | } else { 45 | node.update(getOutput(result, options)); 46 | } 47 | }) 48 | ); 49 | } 50 | }); 51 | 52 | // no promised requires 53 | if (!requiresPromises.length) { 54 | return Promise.resolve(source); 55 | } 56 | 57 | // wait for every promised require 58 | return Promise.all(requiresPromises).then(() => { 59 | return out.toString(); 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | // decide if should import class instead of factory 2 | export function shouldImportClass(result, options) { 3 | // importFactory is false and (we are not inside layer or importFactory in this layer is false/undefined) 4 | if (options.importFactory === false && (!result.layer || !result.layer.importFactory)) { 5 | return true; 6 | } 7 | 8 | // OR we are inside layer with importFactory false 9 | if (result.layer && result.layer.importFactory === false) { 10 | return true; 11 | } 12 | 13 | // OR there is `class` paramater in require string 14 | if (result.opts.class === true) { 15 | return true; 16 | } 17 | 18 | return false; 19 | } 20 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | * Copyright (c) 2015–present Kir Belevich 4 | * Copyright (c) 2015–present Denis Koltsov 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rebem-layers-loader", 3 | "version": "0.5.3", 4 | "description": "reBEM components layers via webpack loader", 5 | "keywords": [ "rebem", "webpack", "loader" ], 6 | "homepage": "https://github.com/rebem/layers-loader", 7 | "repository": "rebem/layers-loader", 8 | "maintainers": [ 9 | "Kir Belevich (https://github.com/deepsweet)", 10 | "Denis Koltsov (https://github.com/mistadikay)" 11 | ], 12 | "main": "build/index.js", 13 | "files": [ "build/" ], 14 | "peerDependencies": { 15 | "webpack": "*" 16 | }, 17 | "dependencies": { 18 | "falafel": "1.2.x", 19 | "loader-utils": "0.2.x", 20 | "path-exists": "2.1.x", 21 | "core-js": "2.3.x" 22 | }, 23 | "devDependencies": { 24 | "start-babel-cli": "1.x.x", 25 | "start-rebem-preset": "0.x.x", 26 | 27 | "babel-preset-es2015": "6.6.x", 28 | "babel-preset-stage-2": "6.5.x", 29 | "babel-plugin-add-module-exports": "0.1.x", 30 | 31 | "babel-eslint": "6.0.x", 32 | "eslint-plugin-babel": "3.2.x", 33 | "eslint-config-rebem": "1.1.x", 34 | 35 | "babel-istanbul": "0.8.x", 36 | "husky": "0.11.x" 37 | }, 38 | "scripts": { 39 | "start": "start-runner start-rebem-preset", 40 | "prepush": "npm start prepush", 41 | "prepublish": "npm start build" 42 | }, 43 | "engines": { 44 | "node": ">=0.12.0", 45 | "npm": ">=2.7.0" 46 | }, 47 | "license": "MIT" 48 | } 49 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![maintenance](https://img.shields.io/badge/maintained-no-red.svg?style=flat-square)](http://unmaintained.tech) 2 | [![npm](https://img.shields.io/npm/v/rebem-layers-loader.svg?style=flat-square)](https://www.npmjs.com/package/rebem-layers-loader) 3 | [![travis](http://img.shields.io/travis/rebem/layers-loader.svg?style=flat-square)](https://travis-ci.org/rebem/layers-loader) 4 | [![coverage](https://img.shields.io/codecov/c/github/rebem/layers-loader.svg?style=flat-square)](https://codecov.io/github/rebem/layers-loader) 5 | [![deps](https://img.shields.io/gemnasium/rebem/layers-loader.svg?style=flat-square)](https://gemnasium.com/rebem/layers-loader) 6 | [![gitter](https://img.shields.io/badge/gitter-join_chat_%E2%86%92-46bc99.svg?style=flat-square)](https://gitter.im/rebem/rebem) 7 | 8 | [Webpack](https://webpack.github.io/) loader for composing sets (layers) of React components. It allows you to easily create themes and share entire component libraries. A couple of use-cases: 9 | 10 | ### products 11 | 12 | ![](docs/products.png) 13 | 14 | ### platforms 15 | 16 | ![](docs/platforms.png) 17 | 18 | ## Usage 19 | 20 | ### `#` 21 | 22 | Components from layers are imported with a special `#`-character along with their styles. So instead of: 23 | ```js 24 | import Button from '../../some-layer/button/'; 25 | import from '../../some-theme/button/styles.css'; 26 | import from '../../another-theme/button/styles.css'; 27 | ``` 28 | 29 | you just write: 30 | 31 | ```js 32 | import Button from '#button'; 33 | ``` 34 | 35 | It imports **component** from the **nearest layer** and **styles** from **all layers**. 36 | 37 | ### With [reBEM](https://github.com/rebem/rebem) 38 | 39 | Button is imported as factory (we just wrap it with `React.createFactory(Button)`), so we can use function calls instead of `React.createElement(Button)`: 40 | ```js 41 | import { Component } from 'react'; 42 | import { BEM } from 'rebem'; 43 | import Button from '#button'; 44 | 45 | class SomeComponent extends Component { 46 | render() { 47 | return BEM({ block: 'some-block' }, 48 | Button({ block: 'some-block', elem: 'button' }, 'Click me'); 49 | ) 50 | } 51 | } 52 | ``` 53 | 54 | ### With JSX 55 | 56 | Button is imported as is (see [`importFactory`](#importfactory-1) option in webpack config): 57 | ```js 58 | import React from 'react'; 59 | import Button from '#button'; 60 | 61 | class SomeComponent extends React.Component { 62 | render() { 63 | return ( 64 |
65 | ; 66 |
67 | ); 68 | } 69 | } 70 | ``` 71 | 72 | ## Example 73 | 74 | ### `core-components` 75 | 76 | Initiate the component 77 | 78 | ``` 79 | . 80 | └── core-components/ 81 | └── button/ 82 | └── index.js 83 | ``` 84 | 85 | 86 | ```js 87 | export default class extends React.Component { 88 | render() {//...} 89 | } 90 | ``` 91 | 92 | ### `theme-reset` 93 | 94 | Reset browser specific styles 95 | 96 | ``` 97 | . 98 | └── theme-reset/ 99 | └── button/ 100 | └── styles.less 101 | ``` 102 | 103 | ### `custom-components` 104 | 105 | At some point we can extend our component in a separate layer. For example, add an icon to a button: 106 | 107 | ``` 108 | . 109 | └── custom-components/ 110 | └── button 111 | └── index.js 112 | ``` 113 | 114 | ```js 115 | // import Button from 'core-components/button/index.js'; 116 | // import from 'theme-reset/button/styles.less'; 117 | import Button from '#button'; 118 | 119 | export default class extends React.Component { 120 | renderIcon() { /*...*/ } 121 | render() { 122 | return ( 123 | 127 | ); 128 | } 129 | } 130 | ``` 131 | 132 | ### `product-theme` 133 | 134 | Now we may need to apply some theme styles: 135 | 136 | ``` 137 | . 138 | └── product-theme/ 139 | └── button/ 140 | └── index.js 141 | ``` 142 | 143 | ```js 144 | // import Button from 'custom-components/button/index.js'; 145 | // import from 'theme-reset/button/styles.less'; 146 | // import from './styles.less'; 147 | import Button from '#button'; 148 | 149 | export default class extends React.Component { 150 | return ( 151 | 155 | ); 156 | } 157 | ``` 158 | 159 | ``` 160 | . 161 | └── product-theme/ 162 | └── button/ 163 | └── styles.less 164 | ``` 165 | 166 | 167 | ```less 168 | .button { 169 | // ... 170 | 171 | &__mask { 172 | position: absolute; 173 | background: #f00; 174 | border: 2px solid #000; 175 | } 176 | } 177 | ``` 178 | 179 | ### `app` 180 | 181 | And finally we can use this button in our app with the optional local styles 182 | 183 | ``` 184 | . 185 | └── app/ 186 | └── somewhere.js 187 | ``` 188 | 189 | 190 | ```js 191 | // import Button from 'product-theme/button/index.js'; 192 | // import from 'theme-reset/button/styles.less'; 193 | // import from 'product-theme/button/styles.less'; 194 | // import from 'app/components/button/styles.less'; 195 | import Button from '#button'; 196 | 197 | class SomeAppComponent extends React.Component { 198 | // ... 199 | return ( 200 | //... 201 | 206 | //... 207 | ); 208 | } 209 | ``` 210 | 211 | ## Creating a layer 212 | 213 | You can use any technologies in your layers (css-preprocessors, babel, etc.). A good practice in this case is to prebuild it, so consumer of your layer wouldn't have to do it for you. Some examples of prebuilded layers: 214 | * [core components](https://github.com/rebem/core-components) with Babel 215 | * [theme-reset](https://github.com/rebem/theme-reset) with LESS 216 | 217 | ### folders structure 218 | 219 | You can use any structure you want, the example below is just a guideline: 220 | 221 | ``` 222 | . 223 | └── custom-layer/ 224 | ├── index.js 225 | └── components/ 226 | ├── button/ 227 | │ ├── index.js 228 | │ └── styles.css 229 | ├── checkbox/ 230 | ├── input/ 231 | │ ├── index.js 232 | │ └── styles.css 233 | ├── radio/ 234 | └── ... 235 | ``` 236 | 237 | ### layer config 238 | 239 | Consumers of your layer need to know how to work with it, so a good practice is to create a layer config like this: 240 | ```js 241 | // custom-layer/index.js 242 | var path = require('path'); 243 | 244 | module.exports = { 245 | path: path.resolve(__dirname, 'components/'), 246 | files: { 247 | main: 'index.js', 248 | styles: 'styles.css' 249 | }, 250 | importFactory: true 251 | }; 252 | ``` 253 | 254 | #### `path` 255 | 256 | Path to the components folder (it can be named `components`, `lib`, `src`, `build`, whatever). 257 | 258 | #### `files` 259 | 260 | File names to use when importing components from this layer. 261 | 262 | * `main` — component source: it can be optional if you are creating just css-theme 263 | * `styles` — component styles: always optional. You can have entire layer (*theme*) made only with styles. But actually you can extend your components in themes too — for example if you want to add some presentation element in children (like we did in the [`Button`](#product-theme) example above) 264 | 265 | #### `importFactory` 266 | 267 | If you use `#`-requires inside your layer, it's better to specify if you use factories there or not. For more details please see the [`importFactory`](#importfactory-1) option below. 268 | 269 | 270 | ## Webpack config 271 | 272 | In your app you need to configure how layers should be composed, where you app components are, etc. Example: 273 | 274 | ```js 275 | // ... 276 | preLoaders: [ 277 | { 278 | test: /\.js$/, 279 | loader: 'rebem-layers', 280 | query: { 281 | layers: [ 282 | // shared layers 283 | require('core-components'), 284 | require('theme-reset'), 285 | require('../custom-layer'), 286 | 287 | // app components 288 | { 289 | path: path.resolve('src/components/'), 290 | files: { 291 | main: 'index.js', 292 | styles: 'styles.less' 293 | } 294 | } 295 | ], 296 | // app source 297 | consumers: [ 298 | path.resolve('src/') 299 | ] 300 | } 301 | } 302 | ], 303 | // ... 304 | ``` 305 | 306 | ### `layers` 307 | 308 | Array of layer configs. If some layers already have config, you can just import it. 309 | 310 | ### `consumers` 311 | 312 | Array of paths where you want to use (_consume_) components from the layers (with `#`-imports). For example, files outside your app component folder or in a unit-tests folder. 313 | 314 | ### `importFactory` 315 | 316 | default: `true` 317 | 318 | By default when you use `#`-imports, all components are importing wrapped with React factories (`React.createFactory(...)`), but you can disable it by setting this option to `false` in root config or in specific layer config. 319 | 320 | When you set `importFactory` option in root config it will be applied to consumers and all layers where `importFactory` option is not specified. 321 | 322 | In the example below consumers and `src/components` layer will have `importFactory: true`, but `src/containers` will have `importFactory: false`, because it is specified there explicetely: 323 | 324 | ```js 325 | preLoaders: [ 326 | { 327 | test: /\.js$/, 328 | loader: 'rebem-layers', 329 | query: { 330 | layers: [ 331 | { 332 | path: path.resolve('src/components/'), 333 | files: { 334 | main: 'index.js', 335 | styles: 'styles.less' 336 | } 337 | }, 338 | { 339 | path: path.resolve('src/containers/'), 340 | files: { 341 | main: 'index.js', 342 | styles: 'styles.less' 343 | }, 344 | importFactory: false 345 | } 346 | ], 347 | importFactory: true, 348 | consumers: [ 349 | path.resolve('src/') 350 | ] 351 | } 352 | } 353 | ], 354 | ``` 355 | 356 | However if you chose to leave it as `true`, for example if you use reBEM without JSX, you may encounter with a situation when you need class in unit-tests. In this case you can use `?class` option: 357 | 358 | ```js 359 | import Button from '#button?class'; 360 | 361 | it('is a component', function() { 362 | expect(ReactTestUtils.isCompositeComponent(Button)).to.be.true; 363 | }); 364 | ``` 365 | 366 | ### `inject` 367 | 368 | default: `false` 369 | 370 | If you want to mock dependencies of your components for unit-tests, set this option to `true`. It will allow you to do this: 371 | 372 | ```js 373 | // `inject` option in import path makes component injectable 374 | import AppComponentInjector from '#app?inject'; 375 | 376 | const App = AppInjector({ 377 | '~/some-flux-store': (function() { 378 | return class MockedStore { 379 | // ... 380 | }; 381 | })() 382 | }); 383 | 384 | TestUtils.renderIntoDocument(App); 385 | // ... tests 386 | ``` 387 | 388 | You can use `inject` along with `?class`-option as well: 389 | 390 | ```js 391 | // injectable component imported as React class 392 | import AppComponentInjector from '#app?class&inject'; 393 | ``` 394 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "rebem/configs/test" 4 | ], 5 | "rules": { 6 | "no-param-reassign": 0 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/?class/import-factory/layer-0/target/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/?class/import-factory/layer-0/target/index.js -------------------------------------------------------------------------------- /test/fixtures/?class/import-factory/layer-0/test/index.js: -------------------------------------------------------------------------------- 1 | require('#target'); 2 | -------------------------------------------------------------------------------- /test/fixtures/?class/import-factory/result.js: -------------------------------------------------------------------------------- 1 | /* #target */ require('./layer-0/target/index.js')['default']; 2 | -------------------------------------------------------------------------------- /test/fixtures/?class/simple/layer-0/target/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/?class/simple/layer-0/target/index.js -------------------------------------------------------------------------------- /test/fixtures/?class/simple/layer-0/test/index.js: -------------------------------------------------------------------------------- 1 | require('#target?class'); 2 | -------------------------------------------------------------------------------- /test/fixtures/?class/simple/result.js: -------------------------------------------------------------------------------- 1 | /* #target?class */ require('./layer-0/target/index.js')['default']; 2 | -------------------------------------------------------------------------------- /test/fixtures/?class/with-styles/layer-0/target/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/?class/with-styles/layer-0/target/index.js -------------------------------------------------------------------------------- /test/fixtures/?class/with-styles/layer-0/target/styles.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/?class/with-styles/layer-0/target/styles.less -------------------------------------------------------------------------------- /test/fixtures/?class/with-styles/layer-0/test/index.js: -------------------------------------------------------------------------------- 1 | require('#target?class'); 2 | -------------------------------------------------------------------------------- /test/fixtures/?class/with-styles/result.js: -------------------------------------------------------------------------------- 1 | /* #target?class */ require('./layer-0/target/index.js')['default']; require('./layer-0/target/styles.less'); 2 | -------------------------------------------------------------------------------- /test/fixtures/?inject/?class/layer-0/target/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/?inject/?class/layer-0/target/index.js -------------------------------------------------------------------------------- /test/fixtures/?inject/?class/layer-0/test/index.js: -------------------------------------------------------------------------------- 1 | require('#target?class&inject'); 2 | -------------------------------------------------------------------------------- /test/fixtures/?inject/?class/result.js: -------------------------------------------------------------------------------- 1 | /* #target?class&inject */ function(injects) { return require('rebem-layers-loader/build/inject/loader!./layer-0/target/index.js')['default'](injects); }; 2 | -------------------------------------------------------------------------------- /test/fixtures/?inject/?styles/layer-0/target/styles.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/?inject/?styles/layer-0/target/styles.less -------------------------------------------------------------------------------- /test/fixtures/?inject/?styles/layer-0/test/index.js: -------------------------------------------------------------------------------- 1 | require('#target?styles&inject'); 2 | -------------------------------------------------------------------------------- /test/fixtures/?inject/?styles/result.js: -------------------------------------------------------------------------------- 1 | /* #target?styles&inject */ require('./layer-0/target/styles.less'); 2 | -------------------------------------------------------------------------------- /test/fixtures/?inject/no-injects/import-factory/layer-0/target/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/?inject/no-injects/import-factory/layer-0/target/index.js -------------------------------------------------------------------------------- /test/fixtures/?inject/no-injects/import-factory/layer-0/test/index.js: -------------------------------------------------------------------------------- 1 | require('#target'); 2 | -------------------------------------------------------------------------------- /test/fixtures/?inject/no-injects/import-factory/result.js: -------------------------------------------------------------------------------- 1 | /* #target */ (function() { module.__rebem__ = module.__rebem__ || {}; module.__rebem__['./layer-0/target/index.js'] = '#target'; var out = require('./layer-0/target/index.js')['default']; return out; })(); 2 | -------------------------------------------------------------------------------- /test/fixtures/?inject/no-injects/simple/layer-0/target/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/?inject/no-injects/simple/layer-0/target/index.js -------------------------------------------------------------------------------- /test/fixtures/?inject/no-injects/simple/layer-0/test/index.js: -------------------------------------------------------------------------------- 1 | require('#target'); 2 | -------------------------------------------------------------------------------- /test/fixtures/?inject/no-injects/simple/result.js: -------------------------------------------------------------------------------- 1 | /* #target */ (function() { module.__rebem__ = module.__rebem__ || {}; module.__rebem__['./layer-0/target/index.js'] = '#target'; var out = require('react').createFactory(require('./layer-0/target/index.js')['default']); return out; })(); 2 | -------------------------------------------------------------------------------- /test/fixtures/?inject/simple/layer-0/target/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/?inject/simple/layer-0/target/index.js -------------------------------------------------------------------------------- /test/fixtures/?inject/simple/layer-0/test/index.js: -------------------------------------------------------------------------------- 1 | require('#target?inject'); 2 | -------------------------------------------------------------------------------- /test/fixtures/?inject/simple/result.js: -------------------------------------------------------------------------------- 1 | /* #target?inject */ function(injects) { return require('react').createFactory(require('rebem-layers-loader/build/inject/loader!./layer-0/target/index.js')['default'](injects)); }; 2 | -------------------------------------------------------------------------------- /test/fixtures/?inject/with-styles/layer-0/target/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/?inject/with-styles/layer-0/target/index.js -------------------------------------------------------------------------------- /test/fixtures/?inject/with-styles/layer-0/target/styles.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/?inject/with-styles/layer-0/target/styles.less -------------------------------------------------------------------------------- /test/fixtures/?inject/with-styles/layer-0/test/index.js: -------------------------------------------------------------------------------- 1 | require('#target?inject'); 2 | -------------------------------------------------------------------------------- /test/fixtures/?inject/with-styles/result.js: -------------------------------------------------------------------------------- 1 | /* #target?inject */ function(injects) { return require('react').createFactory(require('rebem-layers-loader/build/inject/loader!./layer-0/target/index.js')['default'](injects)); }; require('./layer-0/target/styles.less'); 2 | -------------------------------------------------------------------------------- /test/fixtures/?styles/error/layer-0/test/index.js: -------------------------------------------------------------------------------- 1 | require('#target?styles'); 2 | -------------------------------------------------------------------------------- /test/fixtures/?styles/error/result.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/?styles/error/result.js -------------------------------------------------------------------------------- /test/fixtures/?styles/simple/layer-0/target/styles.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/?styles/simple/layer-0/target/styles.less -------------------------------------------------------------------------------- /test/fixtures/?styles/simple/layer-0/test/index.js: -------------------------------------------------------------------------------- 1 | require('#target?styles'); 2 | -------------------------------------------------------------------------------- /test/fixtures/?styles/simple/result.js: -------------------------------------------------------------------------------- 1 | /* #target?styles */ require('./layer-0/target/styles.less'); 2 | -------------------------------------------------------------------------------- /test/fixtures/error/no-hash/layer-0/test/index.js: -------------------------------------------------------------------------------- 1 | require('target'); 2 | -------------------------------------------------------------------------------- /test/fixtures/error/no-hash/result.js: -------------------------------------------------------------------------------- 1 | require('target'); 2 | -------------------------------------------------------------------------------- /test/fixtures/error/not-found/layer-0/test/index.js: -------------------------------------------------------------------------------- 1 | require('#target'); 2 | -------------------------------------------------------------------------------- /test/fixtures/error/not-found/result.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/error/not-found/result.js -------------------------------------------------------------------------------- /test/fixtures/error/only-styles/layer-0/target/styles.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/error/only-styles/layer-0/target/styles.less -------------------------------------------------------------------------------- /test/fixtures/error/only-styles/layer-0/test/index.js: -------------------------------------------------------------------------------- 1 | require('#target'); 2 | -------------------------------------------------------------------------------- /test/fixtures/error/only-styles/result.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/error/only-styles/result.js -------------------------------------------------------------------------------- /test/fixtures/error/self-not-found/layer-0/target/index.js: -------------------------------------------------------------------------------- 1 | require('#target'); 2 | -------------------------------------------------------------------------------- /test/fixtures/error/self-not-found/result.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/error/self-not-found/result.js -------------------------------------------------------------------------------- /test/fixtures/simple/cache/layer-0/target/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/simple/cache/layer-0/target/index.js -------------------------------------------------------------------------------- /test/fixtures/simple/cache/layer-0/test/index.js: -------------------------------------------------------------------------------- 1 | require('#target'); 2 | require('#target'); 3 | -------------------------------------------------------------------------------- /test/fixtures/simple/cache/result.js: -------------------------------------------------------------------------------- 1 | /* #target */ require('react').createFactory(require('./layer-0/target/index.js')['default']); 2 | /* #target */ require('react').createFactory(require('./layer-0/target/index.js')['default']); 3 | -------------------------------------------------------------------------------- /test/fixtures/simple/exclude/result.js: -------------------------------------------------------------------------------- 1 | require('#target'); 2 | -------------------------------------------------------------------------------- /test/fixtures/simple/exclude/test/index.js: -------------------------------------------------------------------------------- 1 | require('#target'); 2 | -------------------------------------------------------------------------------- /test/fixtures/simple/multiple-layers/middle/layer-0/target/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/simple/multiple-layers/middle/layer-0/target/index.js -------------------------------------------------------------------------------- /test/fixtures/simple/multiple-layers/middle/layer-1/target/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/simple/multiple-layers/middle/layer-1/target/index.js -------------------------------------------------------------------------------- /test/fixtures/simple/multiple-layers/middle/layer-1/test/index.js: -------------------------------------------------------------------------------- 1 | require('#target'); 2 | -------------------------------------------------------------------------------- /test/fixtures/simple/multiple-layers/middle/layer-2/target/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/simple/multiple-layers/middle/layer-2/target/index.js -------------------------------------------------------------------------------- /test/fixtures/simple/multiple-layers/middle/result.js: -------------------------------------------------------------------------------- 1 | /* #target */ require('react').createFactory(require('./layer-2/target/index.js')['default']); 2 | -------------------------------------------------------------------------------- /test/fixtures/simple/multiple-layers/nearest/layer-0/target/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/simple/multiple-layers/nearest/layer-0/target/index.js -------------------------------------------------------------------------------- /test/fixtures/simple/multiple-layers/nearest/layer-1/target/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/simple/multiple-layers/nearest/layer-1/target/index.js -------------------------------------------------------------------------------- /test/fixtures/simple/multiple-layers/nearest/layer-1/test/index.js: -------------------------------------------------------------------------------- 1 | require('#target'); 2 | -------------------------------------------------------------------------------- /test/fixtures/simple/multiple-layers/nearest/result.js: -------------------------------------------------------------------------------- 1 | /* #target */ require('react').createFactory(require('./layer-1/target/index.js')['default']); 2 | -------------------------------------------------------------------------------- /test/fixtures/simple/multiple-layers/same-folder-name/layer-0/target/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/simple/multiple-layers/same-folder-name/layer-0/target/index.js -------------------------------------------------------------------------------- /test/fixtures/simple/multiple-layers/same-folder-name/layer-1/subfolder/target/index.js: -------------------------------------------------------------------------------- 1 | require('#target'); 2 | -------------------------------------------------------------------------------- /test/fixtures/simple/multiple-layers/same-folder-name/layer-1/target/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/simple/multiple-layers/same-folder-name/layer-1/target/index.js -------------------------------------------------------------------------------- /test/fixtures/simple/multiple-layers/same-folder-name/layer-2/target/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/simple/multiple-layers/same-folder-name/layer-2/target/index.js -------------------------------------------------------------------------------- /test/fixtures/simple/multiple-layers/same-folder-name/result.js: -------------------------------------------------------------------------------- 1 | /* #target */ require('react').createFactory(require('./layer-2/target/index.js')['default']); 2 | -------------------------------------------------------------------------------- /test/fixtures/simple/multiple-layers/self-middle/layer-0/target/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/simple/multiple-layers/self-middle/layer-0/target/index.js -------------------------------------------------------------------------------- /test/fixtures/simple/multiple-layers/self-middle/layer-1/target/index.js: -------------------------------------------------------------------------------- 1 | require('#target'); 2 | -------------------------------------------------------------------------------- /test/fixtures/simple/multiple-layers/self-middle/layer-2/target/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/simple/multiple-layers/self-middle/layer-2/target/index.js -------------------------------------------------------------------------------- /test/fixtures/simple/multiple-layers/self-middle/result.js: -------------------------------------------------------------------------------- 1 | /* #target */ require('react').createFactory(require('./layer-0/target/index.js')['default']); 2 | -------------------------------------------------------------------------------- /test/fixtures/simple/multiple-layers/self/layer-0/target/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/simple/multiple-layers/self/layer-0/target/index.js -------------------------------------------------------------------------------- /test/fixtures/simple/multiple-layers/self/layer-1/target/index.js: -------------------------------------------------------------------------------- 1 | require('#target'); 2 | -------------------------------------------------------------------------------- /test/fixtures/simple/multiple-layers/self/result.js: -------------------------------------------------------------------------------- 1 | /* #target */ require('react').createFactory(require('./layer-0/target/index.js')['default']); 2 | -------------------------------------------------------------------------------- /test/fixtures/simple/multiple-layers/through/layer-0/target/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/simple/multiple-layers/through/layer-0/target/index.js -------------------------------------------------------------------------------- /test/fixtures/simple/multiple-layers/through/layer-1/test/index.js: -------------------------------------------------------------------------------- 1 | require('#target'); 2 | -------------------------------------------------------------------------------- /test/fixtures/simple/multiple-layers/through/result.js: -------------------------------------------------------------------------------- 1 | /* #target */ require('react').createFactory(require('./layer-0/target/index.js')['default']); 2 | -------------------------------------------------------------------------------- /test/fixtures/simple/single-layer/layer-0/target/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/simple/single-layer/layer-0/target/index.js -------------------------------------------------------------------------------- /test/fixtures/simple/single-layer/layer-0/test/index.js: -------------------------------------------------------------------------------- 1 | require('#target'); 2 | -------------------------------------------------------------------------------- /test/fixtures/simple/single-layer/result.js: -------------------------------------------------------------------------------- 1 | /* #target */ require('react').createFactory(require('./layer-0/target/index.js')['default']); 2 | -------------------------------------------------------------------------------- /test/fixtures/with-styles/multiple-layers/layer-0/target/styles.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/with-styles/multiple-layers/layer-0/target/styles.less -------------------------------------------------------------------------------- /test/fixtures/with-styles/multiple-layers/layer-1/target/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/with-styles/multiple-layers/layer-1/target/index.js -------------------------------------------------------------------------------- /test/fixtures/with-styles/multiple-layers/layer-1/target/styles.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/with-styles/multiple-layers/layer-1/target/styles.less -------------------------------------------------------------------------------- /test/fixtures/with-styles/multiple-layers/layer-1/test/index.js: -------------------------------------------------------------------------------- 1 | require('#target'); 2 | -------------------------------------------------------------------------------- /test/fixtures/with-styles/multiple-layers/result.js: -------------------------------------------------------------------------------- 1 | /* #target */ require('react').createFactory(require('./layer-1/target/index.js')['default']); require('./layer-0/target/styles.less'); require('./layer-1/target/styles.less'); 2 | -------------------------------------------------------------------------------- /test/fixtures/with-styles/single-layer/layer-0/target/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/with-styles/single-layer/layer-0/target/index.js -------------------------------------------------------------------------------- /test/fixtures/with-styles/single-layer/layer-0/target/styles.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebem/layers-loader/e16e8b8ac813fd58ad085adf8863a5913e3119a3/test/fixtures/with-styles/single-layer/layer-0/target/styles.less -------------------------------------------------------------------------------- /test/fixtures/with-styles/single-layer/layer-0/test/index.js: -------------------------------------------------------------------------------- 1 | require('#target'); 2 | -------------------------------------------------------------------------------- /test/fixtures/with-styles/single-layer/result.js: -------------------------------------------------------------------------------- 1 | /* #target */ require('react').createFactory(require('./layer-0/target/index.js')['default']); require('./layer-0/target/styles.less'); 2 | -------------------------------------------------------------------------------- /test/helpers/index.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import pify from 'pify'; 4 | 5 | const readFile = pify(fs.readFile); 6 | 7 | export default function(params) { 8 | delete require.cache[require.resolve('../../lib/transform')]; 9 | delete require.cache[require.resolve('../../lib/runner')]; 10 | 11 | const transform = require('../../lib/transform'); 12 | const testPath = path.resolve('./test/fixtures/', params.path); 13 | const loaderContext = { 14 | context: testPath 15 | }; 16 | const componentPath = path.resolve(testPath, params.test); 17 | const resultPath = path.resolve(testPath, 'result.js'); 18 | const options = { 19 | ...params.options, 20 | layers: params.options.layers.map(layer => { 21 | return { 22 | ...layer, 23 | path: path.resolve(testPath, layer.path) 24 | }; 25 | }) 26 | }; 27 | 28 | return readFile(componentPath, 'utf-8').then(sourceData => { 29 | return readFile(resultPath, 'utf-8').then(resultData => { 30 | return transform.call(loaderContext, sourceData, componentPath, options).then(result => { 31 | if (result !== resultData) { 32 | console.log('actual:', result); 33 | console.log('expected:', resultData); 34 | 35 | throw new Error('oops'); 36 | } 37 | }); 38 | }); 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /test/lib/index.js: -------------------------------------------------------------------------------- 1 | import test from '../helpers/'; 2 | 3 | describe('transform', function() { 4 | describe('simple component', function() { 5 | it('single layer', function() { 6 | return test({ 7 | path: 'simple/single-layer/', 8 | test: 'layer-0/test/index.js', 9 | options: { 10 | layers: [ 11 | { 12 | path: 'layer-0/', 13 | files: { 14 | main: 'index.js' 15 | } 16 | } 17 | ] 18 | } 19 | }); 20 | }); 21 | 22 | it('cache', function() { 23 | return test({ 24 | path: 'simple/cache/', 25 | test: 'layer-0/test/index.js', 26 | options: { 27 | layers: [ 28 | { 29 | path: 'layer-0/', 30 | files: { 31 | main: 'index.js' 32 | } 33 | } 34 | ] 35 | } 36 | }); 37 | }); 38 | 39 | it('exclude', function() { 40 | return test({ 41 | path: 'simple/exclude/', 42 | test: 'test/index.js', 43 | options: { 44 | layers: [] 45 | } 46 | }); 47 | }); 48 | 49 | describe('multiple layers', function() { 50 | it('nearest', function() { 51 | return test({ 52 | path: 'simple/multiple-layers/nearest/', 53 | test: 'layer-1/test/index.js', 54 | options: { 55 | layers: [ 56 | { 57 | path: 'layer-0/', 58 | files: { 59 | main: 'index.js' 60 | } 61 | }, 62 | { 63 | path: 'layer-1/', 64 | files: { 65 | main: 'index.js' 66 | } 67 | } 68 | ] 69 | } 70 | }); 71 | }); 72 | 73 | it('through', function() { 74 | return test({ 75 | path: 'simple/multiple-layers/through/', 76 | test: 'layer-1/test/index.js', 77 | options: { 78 | layers: [ 79 | { 80 | path: 'layer-0/', 81 | files: { 82 | main: 'index.js' 83 | } 84 | }, 85 | { 86 | path: 'layer-1/', 87 | files: { 88 | main: 'index.js' 89 | } 90 | } 91 | ] 92 | } 93 | }); 94 | }); 95 | 96 | it('middle', function() { 97 | return test({ 98 | path: 'simple/multiple-layers/middle/', 99 | test: 'layer-1/test/index.js', 100 | options: { 101 | layers: [ 102 | { 103 | path: 'layer-0/', 104 | files: { 105 | main: 'index.js' 106 | } 107 | }, 108 | { 109 | path: 'layer-1/', 110 | files: { 111 | main: 'index.js' 112 | } 113 | }, 114 | { 115 | path: 'layer-2/', 116 | files: { 117 | main: 'index.js' 118 | } 119 | } 120 | ] 121 | } 122 | }); 123 | }); 124 | 125 | it('self', function() { 126 | return test({ 127 | path: 'simple/multiple-layers/self/', 128 | test: 'layer-1/target/index.js', 129 | options: { 130 | layers: [ 131 | { 132 | path: 'layer-0/', 133 | files: { 134 | main: 'index.js' 135 | } 136 | }, 137 | { 138 | path: 'layer-1/', 139 | files: { 140 | main: 'index.js' 141 | } 142 | } 143 | ] 144 | } 145 | }); 146 | }); 147 | 148 | it('self middle', function() { 149 | return test({ 150 | path: 'simple/multiple-layers/self-middle/', 151 | test: 'layer-1/target/index.js', 152 | options: { 153 | layers: [ 154 | { 155 | path: 'layer-0/', 156 | files: { 157 | main: 'index.js' 158 | } 159 | }, 160 | { 161 | path: 'layer-1/', 162 | files: { 163 | main: 'index.js' 164 | } 165 | }, 166 | { 167 | path: 'layer-2/', 168 | files: { 169 | main: 'index.js' 170 | } 171 | } 172 | ] 173 | } 174 | }); 175 | }); 176 | 177 | it('same folder name', function() { 178 | return test({ 179 | path: 'simple/multiple-layers/same-folder-name/', 180 | test: 'layer-1/subfolder/target/index.js', 181 | options: { 182 | layers: [ 183 | { 184 | path: 'layer-0/', 185 | files: { 186 | main: 'index.js' 187 | } 188 | }, 189 | { 190 | path: 'layer-1/', 191 | files: { 192 | main: 'index.js' 193 | } 194 | }, 195 | { 196 | path: 'layer-2/', 197 | files: { 198 | main: 'index.js' 199 | } 200 | } 201 | ] 202 | } 203 | }); 204 | }); 205 | }); 206 | }); 207 | 208 | describe('error', function() { 209 | it('not found', function() { 210 | return test({ 211 | path: 'error/not-found/', 212 | test: 'layer-0/test/index.js', 213 | options: { 214 | layers: [ 215 | { 216 | path: 'layer-0/', 217 | files: { 218 | main: 'index.js' 219 | } 220 | } 221 | ] 222 | } 223 | }) 224 | .catch(function(err) { 225 | if (err.message !== 'Component "#target" was not found.') { 226 | throw new Error(err.message); 227 | } 228 | }); 229 | }); 230 | 231 | it('self not found', function() { 232 | return test({ 233 | path: 'error/self-not-found/', 234 | test: 'layer-0/target/index.js', 235 | options: { 236 | layers: [ 237 | { 238 | path: 'layer-0/', 239 | files: { 240 | main: 'index.js' 241 | } 242 | } 243 | ] 244 | } 245 | }) 246 | .catch(function(err) { 247 | if (err.message !== 'Component "#target" was not found.') { 248 | throw new Error(err.message); 249 | } 250 | }); 251 | }); 252 | 253 | it('no hash-requires', function() { 254 | return test({ 255 | path: 'error/no-hash/', 256 | test: 'layer-0/test/index.js', 257 | options: { 258 | layers: [ 259 | { 260 | path: 'layer-0/', 261 | files: { 262 | main: 'index.js' 263 | } 264 | } 265 | ] 266 | } 267 | }); 268 | }); 269 | 270 | it('only styles', function() { 271 | return test({ 272 | path: 'error/only-styles/', 273 | test: 'layer-0/test/index.js', 274 | options: { 275 | layers: [ 276 | { 277 | path: 'layer-0/', 278 | files: { 279 | main: 'index.js', 280 | styles: 'styles.less' 281 | } 282 | } 283 | ] 284 | } 285 | }) 286 | .catch(function(err) { 287 | if (err.message !== 'Component "#target" was not found.') { 288 | throw new Error(err.message); 289 | } 290 | }); 291 | }); 292 | }); 293 | 294 | describe('component with styles', function() { 295 | it('single layer', function() { 296 | return test({ 297 | path: 'with-styles/single-layer/', 298 | test: 'layer-0/test/index.js', 299 | options: { 300 | layers: [ 301 | { 302 | path: 'layer-0/', 303 | files: { 304 | main: 'index.js', 305 | styles: 'styles.less' 306 | } 307 | } 308 | ] 309 | } 310 | }); 311 | }); 312 | 313 | it('multiple layers', function() { 314 | return test({ 315 | path: 'with-styles/multiple-layers/', 316 | test: 'layer-1/test/index.js', 317 | options: { 318 | layers: [ 319 | { 320 | path: 'layer-0/', 321 | files: { 322 | main: 'index.js', 323 | styles: 'styles.less' 324 | } 325 | }, 326 | { 327 | path: 'layer-1/', 328 | files: { 329 | main: 'index.js', 330 | styles: 'styles.less' 331 | } 332 | } 333 | ] 334 | } 335 | }); 336 | }); 337 | }); 338 | 339 | describe('?styles', function() { 340 | it('simple', function() { 341 | return test({ 342 | path: '?styles/simple/', 343 | test: 'layer-0/test/index.js', 344 | options: { 345 | layers: [ 346 | { 347 | path: 'layer-0/', 348 | files: { 349 | main: 'index.js', 350 | styles: 'styles.less' 351 | } 352 | } 353 | ] 354 | } 355 | }); 356 | }); 357 | 358 | it('error', function() { 359 | return test({ 360 | path: '?styles/error/', 361 | test: 'layer-0/test/index.js', 362 | options: { 363 | layers: [ 364 | { 365 | path: 'layer-0/', 366 | files: { 367 | main: 'index.js', 368 | styles: 'styles.less' 369 | } 370 | } 371 | ] 372 | } 373 | }) 374 | .catch(function(err) { 375 | if (err.message !== 'Styles for component "#target?styles" were not found.') { 376 | throw new Error(err.message); 377 | } 378 | }); 379 | }); 380 | }); 381 | 382 | describe('?class', function() { 383 | it('simple', function() { 384 | return test({ 385 | path: '?class/simple/', 386 | test: 'layer-0/test/index.js', 387 | options: { 388 | layers: [ 389 | { 390 | path: 'layer-0/', 391 | files: { 392 | main: 'index.js' 393 | } 394 | } 395 | ] 396 | } 397 | }); 398 | }); 399 | 400 | it('with styles', function() { 401 | return test({ 402 | path: '?class/with-styles/', 403 | test: 'layer-0/test/index.js', 404 | options: { 405 | layers: [ 406 | { 407 | path: 'layer-0/', 408 | files: { 409 | main: 'index.js', 410 | styles: 'styles.less' 411 | } 412 | } 413 | ] 414 | } 415 | }); 416 | }); 417 | 418 | it('importFactory = false', function() { 419 | return test({ 420 | path: '?class/import-factory/', 421 | test: 'layer-0/test/index.js', 422 | options: { 423 | layers: [ 424 | { 425 | path: 'layer-0/', 426 | files: { 427 | main: 'index.js' 428 | } 429 | } 430 | ], 431 | importFactory: false 432 | } 433 | }); 434 | }); 435 | }); 436 | 437 | describe('?inject', function() { 438 | it('simple', function() { 439 | return test({ 440 | path: '?inject/simple/', 441 | test: 'layer-0/test/index.js', 442 | options: { 443 | layers: [ 444 | { 445 | path: 'layer-0/', 446 | files: { 447 | main: 'index.js' 448 | } 449 | } 450 | ], 451 | injectable: true 452 | } 453 | }); 454 | }); 455 | 456 | it('with styles', function() { 457 | return test({ 458 | path: '?inject/with-styles/', 459 | test: 'layer-0/test/index.js', 460 | options: { 461 | layers: [ 462 | { 463 | path: 'layer-0/', 464 | files: { 465 | main: 'index.js', 466 | styles: 'styles.less' 467 | } 468 | } 469 | ], 470 | injectable: true 471 | } 472 | }); 473 | }); 474 | 475 | it('no injects', function() { 476 | return test({ 477 | path: '?inject/no-injects/simple/', 478 | test: 'layer-0/test/index.js', 479 | options: { 480 | layers: [ 481 | { 482 | path: 'layer-0/', 483 | files: { 484 | main: 'index.js' 485 | } 486 | } 487 | ], 488 | injectable: true 489 | } 490 | }); 491 | }); 492 | 493 | it('no injects + importFactory: false', function() { 494 | return test({ 495 | path: '?inject/no-injects/import-factory/', 496 | test: 'layer-0/test/index.js', 497 | options: { 498 | layers: [ 499 | { 500 | path: 'layer-0/', 501 | files: { 502 | main: 'index.js' 503 | } 504 | } 505 | ], 506 | importFactory: false, 507 | injectable: true 508 | } 509 | }); 510 | }); 511 | 512 | it('?class', function() { 513 | return test({ 514 | path: '?inject/?class/', 515 | test: 'layer-0/test/index.js', 516 | options: { 517 | layers: [ 518 | { 519 | path: 'layer-0/', 520 | files: { 521 | main: 'index.js' 522 | } 523 | } 524 | ], 525 | injectable: true 526 | } 527 | }); 528 | }); 529 | 530 | it('?styles', function() { 531 | return test({ 532 | path: '?inject/?styles/', 533 | test: 'layer-0/test/index.js', 534 | options: { 535 | layers: [ 536 | { 537 | path: 'layer-0/', 538 | files: { 539 | main: 'index.js', 540 | styles: 'styles.less' 541 | } 542 | } 543 | ], 544 | injectable: true 545 | } 546 | }); 547 | }); 548 | }); 549 | }); 550 | --------------------------------------------------------------------------------