Skip to content

Api.updates #18

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 28, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
"mocha": true
},
"rules": {
"no-use-before-define": [2, "nofunc"]
}
}
57 changes: 48 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,55 @@ $ npm i css-modules-require-hook
require('css-modules-require-hook');
```

## Specifying options
## Available options

Providing additional options allows you to get advanced experience. See the variants below.

```javascript
var hook = require('css-modules-require-hook');
hook({ /* options */ });
```

### `rootDir` option

Aliases are `root`, `d`.

Absolute path to your project's root directory. This is optional but providing it will result in better generated classnames. It can be obligatory, if you run require hook and build tools, like [css-modulesify](https://github.com/css-modules/css-modulesify) from different working directories.

### `use` option

Alias is `u`.

Custom list of plugins. This is optional but helps you to extend list of basic [postcss](https://github.com/postcss/postcss) plugins. Also helps to specify options for particular plugins.

### `createImportedName` option

Alias for the `createImportedName` option from the [postcss-modules-extract-imports](https://github.com/css-modules/postcss-modules-extract-imports) plugin. This is optional. Won't work if you `use` option.

### `generateScopedName` option

Custom function to generate class names. This is optional. Alias for the `generateScopedName` option from the [postcss-modules-scope](https://github.com/css-modules/postcss-modules-scope) plugin. Won't work if you `use` option.

### `mode` option

Alias for the `mode` option from the [postcss-modules-local-by-default](https://github.com/css-modules/postcss-modules-local-by-default) plugin. This is optional. Won't work if you `use` option.

## Examples

If you want to add custom functionality, for example [CSS Next](http://cssnext.io/setup/#as-a-postcss-plugin) plugin, you should provide the `use` option.

```javascript
require('css-modules-require-hook')({
// If you run css-modulesify and require-hook from different directories,
// you have to specify similar root directories
// in order to get the same class names
root: '...', // please, use the absolute path here. It's process.cwd() by default
// Setting this allows you to specify custom PostCSS plugins
// You may use functions or strings, which match to the modules with the same name
use: [] // may use `u` for short
var hook = require('css-modules-require-hook');

hook({
use: [
// adding CSS Next plugin
require('cssnext')(),

// adding basic css-modules plugins
require('postcss-modules-extract-imports'),
require('postcss-modules-local-by-default'),
require('postcss-modules-scope')
]
});
```
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
"engines": {
"node": ">=0.12"
},
"dependencies": {
"lodash.isplainobject": "^3.2.0"
},
"devDependencies": {
"babel": "^5.8.20",
"babel-eslint": "^4.0.5",
Expand Down
105 changes: 79 additions & 26 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,84 @@ import hook from './hook';
import postcss from 'postcss';
import { dirname, join, parse, relative, resolve, sep } from 'path';
import { readFileSync } from 'fs';
import isPlainObject from 'lodash.isplainobject';

import ExtractImports from 'postcss-modules-extract-imports';
import LocalByDefault from 'postcss-modules-local-by-default';
import Scope from 'postcss-modules-scope';
import Parser from './parser';

let processCss;
let rootDir;
let plugins;

/**
* @param {object} opts
* @param {function} opts.generateScopedName
* @param {function} opts.processCss|.p
* @param {string} opts.rootDir|.root|.d
* @param {array} opts.use|.u
*/
export default function buildOptions(opts = {}) {
if (!isPlainObject(opts)) {
throw new Error('Use plain object');
}

processCss = get(opts, 'processCss|p');
rootDir = get(opts, 'rootDir|root|d');
rootDir = rootDir ? resolve(rootDir) : process.cwd();

const customPlugins = get(opts, 'use|u');
if (Array.isArray(customPlugins)) {
return void (plugins = customPlugins);
}

plugins = [];

plugins.push(
opts.mode
? new LocalByDefault({mode: opts.mode})
: LocalByDefault
);

plugins.push(
opts.createImportedName
? new ExtractImports({createImportedName: opts.createImportedName})
: ExtractImports
);

plugins.push(
opts.generateScopedName
? new Scope({generateScopedName: opts.generateScopedName})
: Scope
);
}

const escapedSeparator = sep.replace(/(.)/g, '\\$1');
const relativePathPattern = new RegExp(`^.{1,2}$|^.{1,2}${escapedSeparator}`);

const defaultRoot = process.cwd();
const tokensByFile = {};
let plugins = [LocalByDefault, ExtractImports, Scope];
let root = defaultRoot;
let importNr = 0;

/**
* @param {object} object
* @param {string} keys 'a|b|c'
* @return {*}
*/
function get(object, keys) {
let key;

keys.split('|').some(k => {
if (!object[k]) {
return false;
}

key = k;
return true;
});

return key ? object[key] : null;
}

/**
* @param {string} pathname
* @return {boolean}
Expand All @@ -35,11 +98,10 @@ function isModule(pathname) {
* @return {object}
*/
function load(sourceString, sourcePath, trace, pathFetcher) {
const result = postcss(plugins.concat(new Parser({ pathFetcher, trace })))
.process(sourceString, {from: sourcePath})
.root;
const lazyResult = postcss(plugins.concat(new Parser({ pathFetcher, trace })))
.process(sourceString, {from: sourcePath});

return result.tokens;
return { injectableSource: lazyResult.css, exportTokens: lazyResult.root.tokens };
}

/**
Expand All @@ -54,7 +116,7 @@ function fetch(_newPath, _relativeTo, _trace) {

const relativeDir = dirname(_relativeTo);
const rootRelativePath = resolve(relativeDir, newPath);
let fileRelativePath = resolve(join(root, relativeDir), newPath);
let fileRelativePath = resolve(join(rootDir, relativeDir), newPath);

if (isModule(newPath)) {
fileRelativePath = require.resolve(newPath);
Expand All @@ -66,27 +128,18 @@ function fetch(_newPath, _relativeTo, _trace) {
}

const source = readFileSync(fileRelativePath, 'utf-8');
const exportTokens = load(source, rootRelativePath, trace, fetch);
const { exportTokens, injectableSource } = load(source, rootRelativePath, trace, fetch);

tokensByFile[fileRelativePath] = exportTokens;

if (typeof processCss === 'function') {
processCss(injectableSource);
}

return exportTokens;
}

hook(filename => fetch(`.${sep}${relative(root, filename)}`, '/'));
// setting defaults
buildOptions();

/**
* @param {object} opts
* @param {array} opts.u
* @param {array} opts.use
*/
export default function configure(opts = {}) {
const customPlugins = opts.u || opts.use;
plugins = Array.isArray(customPlugins)
? customPlugins
: [LocalByDefault, ExtractImports, Scope];

root = opts.root && typeof opts.root === 'string'
? opts.root
: defaultRoot;
}
hook(filename => fetch(`.${sep}${relative(rootDir, filename)}`, '/'));