|
1 | 1 | import hook from './hook';
|
2 | 2 | import { readFileSync } from 'fs';
|
3 | 3 | import { dirname, sep, relative, resolve } from 'path';
|
| 4 | +import { identity, removeQuotes } from './fn'; |
4 | 5 | import postcss from 'postcss';
|
5 | 6 |
|
6 | 7 | import ExtractImports from 'postcss-modules-extract-imports';
|
7 | 8 | import LocalByDefault from 'postcss-modules-local-by-default';
|
8 | 9 | import Scope from 'postcss-modules-scope';
|
9 | 10 | import Parser from './parser';
|
10 | 11 |
|
| 12 | +// cache |
11 | 13 | let importNr = 0;
|
12 |
| -let plugins = [ExtractImports, LocalByDefault, Scope]; |
| 14 | +let tokensByFile = {}; |
| 15 | +// processing functions |
| 16 | +const preProcess = identity; |
| 17 | +let postProcess; |
| 18 | +// defaults |
| 19 | +let plugins = [LocalByDefault, ExtractImports, Scope]; |
13 | 20 | let rootDir = process.cwd();
|
14 | 21 |
|
15 |
| -export default function (opts = {}) { |
16 |
| - plugins = opts.use ? opts.use : [ExtractImports, LocalByDefault, Scope]; |
17 |
| - rootDir = opts.rootDir ? opts.rootDir : process.cwd(); |
| 22 | +/** |
| 23 | + * @param {object} opts |
| 24 | + * @param {function} opts.createImportedName |
| 25 | + * @param {function} opts.generateScopedName |
| 26 | + * @param {function} opts.processCss |
| 27 | + * @param {string} opts.rootDir |
| 28 | + * @param {array} opts.use |
| 29 | + */ |
| 30 | +export default function setup(opts = {}) { |
| 31 | + // clearing cache |
| 32 | + importNr = 0; |
| 33 | + tokensByFile = {}; |
| 34 | + |
| 35 | + if (opts.processCss && typeof opts.processCss !== 'function') { |
| 36 | + throw new Error('should specify function for processCss'); |
| 37 | + } |
| 38 | + |
| 39 | + postProcess = opts.processCss || null; |
| 40 | + |
| 41 | + if (opts.rootDir && typeof opts.rootDir !== 'string') { |
| 42 | + throw new Error('should specify string for rootDir'); |
| 43 | + } |
| 44 | + |
| 45 | + rootDir = opts.rootDir || process.cwd(); |
| 46 | + |
| 47 | + if (opts.use) { |
| 48 | + if (!Array.isArray(opts.use)) { |
| 49 | + throw new Error('should specify array for use'); |
| 50 | + } |
| 51 | + |
| 52 | + return void (plugins = opts.use); |
| 53 | + } |
| 54 | + |
| 55 | + plugins = []; |
| 56 | + |
| 57 | + if (opts.mode) { |
| 58 | + if (typeof opts.mode !== 'string') { |
| 59 | + throw new Error('should specify string for mode'); |
| 60 | + } |
| 61 | + |
| 62 | + plugins.push(new LocalByDefault({mode: opts.mode})); |
| 63 | + } else { |
| 64 | + plugins.push(LocalByDefault); |
| 65 | + } |
| 66 | + |
| 67 | + if (opts.createImportedName) { |
| 68 | + if (typeof opts.createImportedName !== 'function') { |
| 69 | + throw new Error('should specify function for createImportedName'); |
| 70 | + } |
| 71 | + |
| 72 | + plugins.push(new ExtractImports({createImportedName: opts.createImportedName})); |
| 73 | + } else { |
| 74 | + plugins.push(ExtractImports); |
| 75 | + } |
| 76 | + |
| 77 | + if (opts.generateScopedName) { |
| 78 | + if (typeof opts.generateScopedName !== 'function') { |
| 79 | + throw new Error('should specify function for generateScopedName'); |
| 80 | + } |
| 81 | + |
| 82 | + plugins.push(new Scope({generateScopedName: opts.generateScopedName})); |
| 83 | + } else { |
| 84 | + plugins.push(Scope); |
| 85 | + } |
18 | 86 | }
|
19 | 87 |
|
20 |
| -function pathFetcher(_newPath, sourcePath, _trace) { |
| 88 | +/** |
| 89 | + * @param {string} _newPath Absolute or relative path. Also can be path to the Node.JS module. |
| 90 | + * @param {string} _sourcePath Absolute path (relative to root). |
| 91 | + * @param {string} _trace |
| 92 | + * @return {object} |
| 93 | + */ |
| 94 | +function fetch(_newPath, _sourcePath, _trace) { |
21 | 95 | const trace = _trace || String.fromCharCode(importNr++);
|
22 |
| - const newPath = _newPath.replace(/^["']|["']$/g, ''); |
| 96 | + const newPath = removeQuotes(_newPath); |
| 97 | + // getting absolute path to the processing file |
23 | 98 | const filename = /\w/.test(newPath[0])
|
24 | 99 | ? require.resolve(newPath)
|
25 |
| - : resolve(rootDir + dirname(sourcePath), newPath); |
| 100 | + : resolve(rootDir + dirname(_sourcePath), newPath); |
| 101 | + |
| 102 | + // checking cache |
| 103 | + let tokens = tokensByFile[filename]; |
| 104 | + if (tokens) { |
| 105 | + return tokens; |
| 106 | + } |
| 107 | + |
26 | 108 | const rootRelativePath = sep + relative(rootDir, filename);
|
27 |
| - const source = readFileSync(filename, 'utf8'); |
| 109 | + const CSSSource = preProcess(readFileSync(filename, 'utf8')); |
28 | 110 |
|
29 |
| - const result = postcss(plugins.concat(new Parser({ pathFetcher, trace }))) |
30 |
| - // preprocess |
31 |
| - .process(source, {from: rootRelativePath}) |
| 111 | + const result = postcss(plugins.concat(new Parser({ fetch, trace }))) |
| 112 | + .process(CSSSource, {from: rootRelativePath}) |
32 | 113 | .root;
|
33 |
| - // postprocess |
34 | 114 |
|
35 |
| - return result.tokens; |
| 115 | + tokens = result.tokens; |
| 116 | + tokensByFile[filename] = tokens; |
| 117 | + |
| 118 | + if (postProcess) { |
| 119 | + postProcess(result.toResult().css); |
| 120 | + } |
| 121 | + |
| 122 | + return tokens; |
36 | 123 | }
|
37 | 124 |
|
38 |
| -hook(filename => pathFetcher(filename, sep + relative(rootDir, filename))); |
| 125 | +hook(filename => fetch(filename, sep + relative(rootDir, filename))); |
0 commit comments