From 67d7bff52524a0d3241a32e2fbe07462fc08e888 Mon Sep 17 00:00:00 2001 From: Alexey Litvinov Date: Thu, 29 Oct 2015 22:41:30 +0300 Subject: [PATCH 1/6] added a small wrapper for the generateScopedName to normalize the filepath --- package.json | 1 + src/index.js | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index e2535f7..bbeaf73 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ }, "dependencies": { "debug": "^2.2.0", + "generic-names": "^1.0.0-beta", "icss-replace-symbols": "^1.0.2", "lodash.assign": "^3.2.0", "lodash.identity": "^3.0.0", diff --git a/src/index.js b/src/index.js index a34dc5a..15ecf86 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,5 @@ import debug from 'debug'; +import genericNames from 'generic-names'; import hook from './hook'; import { readFileSync } from 'fs'; import { dirname, sep, relative, resolve } from 'path'; @@ -25,8 +26,10 @@ let preProcess = identity; let postProcess; // defaults let lazyResultOpts = {}; -let plugins = [LocalByDefault, ExtractImports, Scope]; +let plugins = [Values, LocalByDefault, ExtractImports]; +let terminalPlugins = []; let rootDir = process.cwd(); +let generateScopedName = genericNames('[name]__[local]___[hash:base64:5]', {context: rootDir}); /** * @param {object} opts @@ -64,11 +67,12 @@ export default function setup(opts = {}) { return void (plugins = customPlugins); } + terminalPlugins = get('append', null, 'array', opts) || []; + generateScopedName = get('generateScopedName', null, 'function', opts) + || genericNames('[name]__[local]___[hash:base64:5]', {context: rootDir}); const prepend = get('prepend', null, 'array', opts) || []; - const append = get('append', null, 'array', opts) || []; const mode = get('mode', null, 'string', opts); const createImportedName = get('createImportedName', null, 'function', opts); - const generateScopedName = get('generateScopedName', null, 'function', opts); plugins = [ ...prepend, @@ -79,10 +83,6 @@ export default function setup(opts = {}) { createImportedName ? new ExtractImports({createImportedName: opts.createImportedName}) : ExtractImports, - generateScopedName - ? new Scope({generateScopedName: opts.generateScopedName}) - : Scope, - ...append, ]; } @@ -111,8 +111,11 @@ function fetch(_to, _from, _trace) { const rootRelativePath = sep + relative(rootDir, filename); const CSSSource = preProcess(readFileSync(filename, 'utf8'), filename); - const lazyResult = postcss(plugins.concat(new Parser({ fetch, filename, trace }))) - .process(CSSSource, assign(lazyResultOpts, {from: rootRelativePath})); + const lazyResult = postcss(plugins.concat( + new Scope({generateScopedName: (name, _, css) => generateScopedName(name, filename, css)}), + terminalPlugins, + new Parser({ fetch, filename, trace })) + ).process(CSSSource, assign(lazyResultOpts, {from: rootRelativePath})); lazyResult.warnings().forEach(message => console.warn(message.text)); From d84d2e664692484a88655ad5baadb31b1ab31d6d Mon Sep 17 00:00:00 2001 From: Alexey Litvinov Date: Fri, 30 Oct 2015 01:13:54 +0300 Subject: [PATCH 2/6] possibility to specify the string pattern --- src/index.js | 7 +++++-- src/utility.js | 8 ++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index 15ecf86..bcc210d 100644 --- a/src/index.js +++ b/src/index.js @@ -68,11 +68,14 @@ export default function setup(opts = {}) { } terminalPlugins = get('append', null, 'array', opts) || []; - generateScopedName = get('generateScopedName', null, 'function', opts) - || genericNames('[name]__[local]___[hash:base64:5]', {context: rootDir}); const prepend = get('prepend', null, 'array', opts) || []; const mode = get('mode', null, 'string', opts); const createImportedName = get('createImportedName', null, 'function', opts); + const scopeOption = get('generateScopedName', null, ['function', 'string'], opts) + || genericNames('[name]__[local]___[hash:base64:5]', {context: rootDir}); + generateScopedName = typeof scopeOption === 'string' + ? genericNames(scopeOption, {context: rootDir}) + : scopeOption; plugins = [ ...prepend, diff --git a/src/utility.js b/src/utility.js index f110c5b..91928cb 100644 --- a/src/utility.js +++ b/src/utility.js @@ -18,6 +18,14 @@ const check = { */ export function get(prop, aliases, type, source) { if (source[prop]) { + if (isArray(type)) { + if (type.some(tp => is(tp, source[prop]))) { + return source[prop]; + } + + throw new Error(format('should specify %s for %s', type.join('|'), prop)); + } + if (!is(type, source[prop])) { throw new Error(format('should specify %s for %s', type, prop)); } From f646e4758c78f710367315f3a269d1d01ba1b258 Mon Sep 17 00:00:00 2001 From: Alexey Litvinov Date: Sat, 31 Oct 2015 23:01:20 +0300 Subject: [PATCH 3/6] standalone version of the plugins api --- src/extractor.js | 49 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/extractor.js diff --git a/src/extractor.js b/src/extractor.js new file mode 100644 index 0000000..62231e5 --- /dev/null +++ b/src/extractor.js @@ -0,0 +1,49 @@ +import postcss from 'postcss'; +import genericNames from 'generic-names'; + +import Values from 'postcss-modules-values'; +import LocalByDefault from 'postcss-modules-local-by-default'; +import ExtractImports from 'postcss-modules-extract-imports'; +import Scope from 'postcss-modules-scope'; +import Parser from './parser'; + +/** + * @param {array} options.append + * @param {array} options.prepend + * @param {array} options.use + * @param {function} options.createImportedName + * @param {function|string} options.generateScopedName + * @param {string} options.mode + * @param {string} options.rootDir + * @param {function} fetch + * @return {object} + */ +export function get({ + append = [], + prepend = [], + createImportedName, + generateScopedName, + mode, + use, + rootDir: context = process.cwd(), +} = {}, fetch) { + if (typeof generateScopedName !== 'function') { + generateScopedName = genericNames(generateScopedName || '[name]__[local]___[hash:base64:5]', {context}); + } + + const plugins = use || [ + ...prepend, + Values, + mode + ? new LocalByDefault({mode}) + : LocalByDefault, + createImportedName + ? new ExtractImports({createImportedName}) + : ExtractImports, + new Scope({generateScopedName}), + ...append, + ]; + + plugins.push(new Parser({fetch})); + return postcss(plugins); +} From 94e21895454621ee35ec6c6a9a3eac840d78d144 Mon Sep 17 00:00:00 2001 From: Alexey Litvinov Date: Sat, 31 Oct 2015 23:06:09 +0300 Subject: [PATCH 4/6] simplified version of the parser plugin --- src/parser.js | 68 +++++++++++++++++---------------------------------- 1 file changed, 23 insertions(+), 45 deletions(-) diff --git a/src/parser.js b/src/parser.js index 15c47e9..4e320b3 100644 --- a/src/parser.js +++ b/src/parser.js @@ -1,60 +1,38 @@ import { plugin } from 'postcss'; +import forEach from 'lodash.foreach'; import replaceSymbols from 'icss-replace-symbols'; - const importRegexp = /^:import\((.+)\)$/; +const exportRegexp = /^:export$/; -export default plugin('parser', function parser(opts = {}) { - const exportTokens = {}; - const translations = {}; - - const fetchImport = (importNode, relativeTo, depNr) => { - const file = importNode.selector.match(importRegexp)[1]; - const depTrace = opts.trace + String.fromCharCode(depNr); - const exports = opts.fetch(file, opts.filename, depTrace); - - importNode.each(decl => { - if (decl.type === 'decl') { - translations[decl.prop] = exports[decl.value]; - } - }); - - importNode.removeSelf(); - }; +/** + * @param {function} options.fetch + * @return {function} + */ +export default plugin('parser', function parser({ fetch } = {}) { + return css => { + // https://github.com/postcss/postcss/blob/master/docs/api.md#inputfile + const file = css.source.input.file; + const translations = {}; + const exportTokens = {}; - const fetchAllImports = css => { - let imports = 0; + css.walkRules(importRegexp, rule => { + const exports = fetch(RegExp.$1, file); - css.each(node => { - if (node.type === 'rule' && node.selector.match(importRegexp)) { - fetchImport(node, css.source.input.from, imports++); - } + rule.walkDecls(decl => translations[decl.prop] = exports[decl.value]); + rule.remove(); }); - }; - - const linkImportedSymbols = css => replaceSymbols(css, translations); - const handleExport = exportNode => { - exportNode.each(decl => { - if (decl.type === 'decl') { - Object.keys(translations).forEach(translation => { - decl.value = decl.value.replace(translation, translations[translation]); - }); + replaceSymbols(css, translations); + css.walkRules(exportRegexp, rule => { + rule.walkDecls(decl => { + forEach(translations, (value, key) => decl.value = decl.value.replace(key, value)); exportTokens[decl.prop] = decl.value; - } - }); + }); - exportNode.removeSelf(); - }; - - const extractExports = css => css.each(node => { - if (node.type === 'rule' && node.selector === ':export') handleExport(node); - }); + rule.remove(); + }); - return css => { - fetchAllImports(css); - linkImportedSymbols(css); - extractExports(css); css.tokens = exportTokens; }; }); From 49372483d5865dea791e9e97a0d86554fafaf3d9 Mon Sep 17 00:00:00 2001 From: Alexey Litvinov Date: Sat, 31 Oct 2015 23:44:53 +0300 Subject: [PATCH 5/6] small refactoring --- src/index.js | 126 ++++++++++++++------------------------------------- 1 file changed, 34 insertions(+), 92 deletions(-) diff --git a/src/index.js b/src/index.js index bcc210d..a35418c 100644 --- a/src/index.js +++ b/src/index.js @@ -1,107 +1,52 @@ import debug from 'debug'; -import genericNames from 'generic-names'; import hook from './hook'; -import { readFileSync } from 'fs'; -import { dirname, sep, relative, resolve } from 'path'; -import { get, removeQuotes } from './utility'; -import assign from 'lodash.assign'; import identity from 'lodash.identity'; -import pick from 'lodash.pick'; -import postcss from 'postcss'; - -import Values from 'postcss-modules-values'; -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'; - -const debugFetch = debug('css-modules:fetch'); -const debugSetup = debug('css-modules:setup'); +import { get } from './extractor'; +import { readFileSync } from 'fs'; +import { dirname, resolve } from 'path'; +import { removeQuotes } from './utility'; // cache -let importNr = 0; let tokensByFile = {}; -// processing functions +// global +let extractorOptions; +let processorOptions = {}; let preProcess = identity; let postProcess; -// defaults -let lazyResultOpts = {}; -let plugins = [Values, LocalByDefault, ExtractImports]; -let terminalPlugins = []; -let rootDir = process.cwd(); -let generateScopedName = genericNames('[name]__[local]___[hash:base64:5]', {context: rootDir}); + +const debugFetch = debug('css-modules:fetch'); +const debugSetup = debug('css-modules:setup'); /** - * @param {object} opts - * @param {function} opts.createImportedName - * @param {function} opts.generateScopedName - * @param {function} opts.preprocessCss - * @param {function} opts.processCss - * @param {string} opts.rootDir - * @param {string} opts.to - * @param {array} opts.use - * @param {array} opts.extensions + * @param {array} options.extensions + * @param {function} options.preprocessCss + * @param {function} options.processCss + * @param {string} options.to + * @param {object} options.rest */ -export default function setup(opts = {}) { - debugSetup(opts); - // clearing cache - importNr = 0; - tokensByFile = {}; +export default function setup({ extensions: extraExtensions, preprocessCss, processCss, to, ...rest } = {}) { + debugSetup(arguments[0]); + extractorOptions = rest; + processorOptions = {to}; + preProcess = preprocessCss || identity; + postProcess = processCss || null; - preProcess = get('preprocessCss', null, 'function', opts) || identity; - postProcess = get('processCss', null, 'function', opts) || null; - rootDir = get('rootDir', ['root', 'd'], 'string', opts) || process.cwd(); - // https://github.com/postcss/postcss/blob/master/docs/api.md#processorprocesscss-opts - lazyResultOpts = pick(opts, ['to']); - - const extraExtensions = get('extensions', null, 'array', opts); if (extraExtensions) { - extraExtensions.forEach((extension) => { - hook(filename => fetch(filename, filename), extension); - }); - } - - // Warning. Options, which aren't affected by plugins, should be processed above. - const customPlugins = get('use', ['u'], 'array', opts); - if (customPlugins) { - return void (plugins = customPlugins); + extraExtensions.forEach((extension) => hook(filename => fetch(filename, filename), extension)); } - - terminalPlugins = get('append', null, 'array', opts) || []; - const prepend = get('prepend', null, 'array', opts) || []; - const mode = get('mode', null, 'string', opts); - const createImportedName = get('createImportedName', null, 'function', opts); - const scopeOption = get('generateScopedName', null, ['function', 'string'], opts) - || genericNames('[name]__[local]___[hash:base64:5]', {context: rootDir}); - generateScopedName = typeof scopeOption === 'string' - ? genericNames(scopeOption, {context: rootDir}) - : scopeOption; - - plugins = [ - ...prepend, - Values, - mode - ? new LocalByDefault({mode: opts.mode}) - : LocalByDefault, - createImportedName - ? new ExtractImports({createImportedName: opts.createImportedName}) - : ExtractImports, - ]; } /** - * @param {string} _to Absolute or relative path. Also can be path to the Node.JS module. - * @param {string} _from Absolute path (relative to root). - * @param {string} _trace + * @param {string} _to Absolute or relative path. Also can be path to the Node.JS module. + * @param {string} from Absolute path. * @return {object} */ -function fetch(_to, _from, _trace) { - const trace = _trace || String.fromCharCode(importNr++); - const newPath = removeQuotes(_to); +function fetch(_to, from) { + const to = removeQuotes(_to); // getting absolute path to the processing file - const filename = /\w/.test(newPath[0]) - ? require.resolve(newPath) - : resolve(dirname(_from), newPath); + const filename = /\w/i.test(to[0]) + ? require.resolve(to) + : resolve(dirname(from), to); // checking cache let tokens = tokensByFile[filename]; @@ -111,19 +56,16 @@ function fetch(_to, _from, _trace) { } debugFetch({cache: false, filename}); - const rootRelativePath = sep + relative(rootDir, filename); const CSSSource = preProcess(readFileSync(filename, 'utf8'), filename); + // https://github.com/postcss/postcss/blob/master/docs/api.md#processorprocesscss-opts + const lazyResult = get(extractorOptions, fetch) + .process(CSSSource, Object.assign(processorOptions, {from: filename})); - const lazyResult = postcss(plugins.concat( - new Scope({generateScopedName: (name, _, css) => generateScopedName(name, filename, css)}), - terminalPlugins, - new Parser({ fetch, filename, trace })) - ).process(CSSSource, assign(lazyResultOpts, {from: rootRelativePath})); - + // https://github.com/postcss/postcss/blob/master/docs/api.md#lazywarnings lazyResult.warnings().forEach(message => console.warn(message.text)); - tokens = lazyResult.root.tokens; // updating cache + tokens = lazyResult.root.tokens; tokensByFile[filename] = tokens; if (postProcess) { From 0d3a35d4d52eb026494704d06f1c106c32d03763 Mon Sep 17 00:00:00 2001 From: Alexey Litvinov Date: Sat, 31 Oct 2015 23:48:34 +0300 Subject: [PATCH 6/6] added lodash.foreach --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index bbeaf73..e3e9a4e 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "generic-names": "^1.0.0-beta", "icss-replace-symbols": "^1.0.2", "lodash.assign": "^3.2.0", + "lodash.foreach": "^3.0.3", "lodash.identity": "^3.0.0", "lodash.isarray": "^3.0.4", "lodash.isfunction": "^3.0.6", @@ -29,7 +30,7 @@ "isparta": "^3.0.3", "lodash": "^3.10.1", "mocha": "^2.2.5", - "postcss": "^5.x", + "postcss": "^5.0.10", "postcss-modules-extract-imports": "^1.0.0", "postcss-modules-local-by-default": "^1.0.0", "postcss-modules-scope": "^1.0.0",