From b8f976df7ecda182ede0c810f7d763b5dc0d0a40 Mon Sep 17 00:00:00 2001 From: Nathan Reid Date: Tue, 10 Nov 2015 22:34:49 -0600 Subject: [PATCH 1/5] Fixed Windows issues: broken class naming (_absolute_path_to_file instead of _relative_path_to_file) and broken path resolution ("composes" didn't work) --- src/file-system-loader.js | 11 ++++++++--- src/index.js | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/file-system-loader.js b/src/file-system-loader.js index 2a3f47d..5369c49 100644 --- a/src/file-system-loader.js +++ b/src/file-system-loader.js @@ -26,6 +26,12 @@ export default class FileSystemLoader { this.importNr = 0 this.core = new Core(plugins) this.tokensByFile = {}; + + Core.scope.generateScopedName = function (exportedName, unsanitizedPath) { + let sanitizedPath = path.relative(root, unsanitizedPath).replace(/\.[^\.\/\\]+$/, '').replace(/[\W_]+/g, '_').replace(/^_|_$/g, ''); + return `_${sanitizedPath}__${exportedName}`; + }; + } fetch( _newPath, relativeTo, _trace ) { @@ -33,8 +39,7 @@ export default class FileSystemLoader { trace = _trace || String.fromCharCode( this.importNr++ ) return new Promise( ( resolve, reject ) => { let relativeDir = path.dirname( relativeTo ), - rootRelativePath = path.resolve( relativeDir, newPath ), - fileRelativePath = path.resolve( path.join( this.root, relativeDir ), newPath ) + fileRelativePath = path.resolve(path.isAbsolute(relativeDir) ? relativeDir : path.join( this.root, relativeDir ), newPath ) // if the path is not relative or absolute, try to resolve it in node_modules if (newPath[0] !== '.' && newPath[0] !== '/') { @@ -49,7 +54,7 @@ export default class FileSystemLoader { fs.readFile( fileRelativePath, "utf-8", ( err, source ) => { if ( err ) reject( err ) - this.core.load( source, rootRelativePath, trace, this.fetch.bind( this ) ) + this.core.load( source, fileRelativePath, trace, this.fetch.bind( this ) ) .then( ( { injectableSource, exportTokens } ) => { this.sources[trace] = injectableSource this.tokensByFile[fileRelativePath] = exportTokens diff --git a/src/index.js b/src/index.js index 92795bd..418409d 100644 --- a/src/index.js +++ b/src/index.js @@ -15,7 +15,7 @@ export default class Core { let parser = new Parser( pathFetcher, trace ) return postcss( this.plugins.concat( [parser.plugin] ) ) - .process( sourceString, { from: "/" + sourcePath } ) + .process( sourceString, { from: sourcePath } ) .then( result => { return { injectableSource: result.css, exportTokens: parser.exportTokens } } ) From 04576456cb4fdd50b3257e109873ccc42830b918 Mon Sep 17 00:00:00 2001 From: Nathan Reid Date: Tue, 10 Nov 2015 22:39:47 -0600 Subject: [PATCH 2/5] All tests except for "test-cases should compose node module" are working on Windows and Mac. --- test/test-cases.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/test-cases.js b/test/test-cases.js index c6adcff..dd238fa 100644 --- a/test/test-cases.js +++ b/test/test-cases.js @@ -23,8 +23,8 @@ Object.keys( pipelines ).forEach( dirname => { let expected = normalize( fs.readFileSync( path.join( testDir, testCase, "expected.css" ), "utf-8" ) ) let loader = new FileSystemLoader( testDir, pipelines[dirname] ) let expectedTokens = JSON.parse( fs.readFileSync( path.join( testDir, testCase, "expected.json" ), "utf-8" ) ) - loader.fetch( `${testCase}/source.css`, "/" ).then( tokens => { - assert.equal( loader.finalSource, expected ) + loader.fetch( `${testCase}/source.css`, "./" ).then( tokens => { + assert.equal( normalize(loader.finalSource), expected ) assert.equal( JSON.stringify( tokens ), JSON.stringify( expectedTokens ) ) } ).then( done, done ) } ); @@ -43,9 +43,9 @@ describe( 'multiple sources', () => { let expected = normalize( fs.readFileSync( path.join( testDir, testCase, "expected.css" ), "utf-8" ) ) let loader = new FileSystemLoader( testDir, pipelines[dirname] ) let expectedTokens = JSON.parse( fs.readFileSync( path.join( testDir, testCase, "expected.json" ), "utf-8" ) ) - loader.fetch( `${testCase}/source1.css`, "/" ).then( tokens1 => { - loader.fetch( `${testCase}/source2.css`, "/" ).then( tokens2 => { - assert.equal( loader.finalSource, expected ) + loader.fetch( `${testCase}/source1.css`, "./" ).then( tokens1 => { + loader.fetch( `${testCase}/source2.css`, "./" ).then( tokens2 => { + assert.equal( normalize(loader.finalSource), expected ) const tokens = Object.assign({}, tokens1, tokens2); assert.equal( JSON.stringify( tokens ), JSON.stringify( expectedTokens ) ) } ).then( done, done ) From 82476702e7fdf4f4c365cb9b1834264889174bf2 Mon Sep 17 00:00:00 2001 From: Nathan Reid Date: Tue, 10 Nov 2015 22:41:38 -0600 Subject: [PATCH 3/5] updated expected css & tokens for test "should compose node module" to better reflect the path to the imported style --- test/test-cases/compose-node-module/expected.css | 2 +- test/test-cases/compose-node-module/expected.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test-cases/compose-node-module/expected.css b/test/test-cases/compose-node-module/expected.css index 0667b94..f304ae0 100644 --- a/test/test-cases/compose-node-module/expected.css +++ b/test/test-cases/compose-node-module/expected.css @@ -1,4 +1,4 @@ -._compose_node_module_cool_styles_foo__example { +._node_modules_cool_styles_foo__example { color: #F00; } ._compose_node_module_source__foo { diff --git a/test/test-cases/compose-node-module/expected.json b/test/test-cases/compose-node-module/expected.json index a57448c..ce71efb 100644 --- a/test/test-cases/compose-node-module/expected.json +++ b/test/test-cases/compose-node-module/expected.json @@ -1,3 +1,3 @@ { - "foo": "_compose_node_module_source__foo _compose_node_module_cool_styles_foo__example" + "foo": "_compose_node_module_source__foo _node_modules_cool_styles_foo__example" } From 2ef5e570f703c89ed8b73a9b3126b9ad6129eddf Mon Sep 17 00:00:00 2001 From: Nathan Reid Date: Tue, 10 Nov 2015 23:03:31 -0600 Subject: [PATCH 4/5] replace path.isAbsolute with sindresorhus/path-is-absolute ponyfill for old versions of Node --- package.json | 1 + src/file-system-loader.js | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 2443f9a..10060c7 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ }, "dependencies": { "icss-replace-symbols": "1.0.2", + "path-is-absolute": "^1.0.0", "postcss": "5.0.10", "postcss-modules-values": "1.1.1", "postcss-modules-extract-imports": "1.0.0", diff --git a/src/file-system-loader.js b/src/file-system-loader.js index 5369c49..1076ca5 100644 --- a/src/file-system-loader.js +++ b/src/file-system-loader.js @@ -1,6 +1,7 @@ import Core from './index.js' import fs from 'fs' import path from 'path' +import pathIsAbsolute from 'path-is-absolute'; // Sorts dependencies in the following way: // AAA comes before AA and A @@ -39,7 +40,7 @@ export default class FileSystemLoader { trace = _trace || String.fromCharCode( this.importNr++ ) return new Promise( ( resolve, reject ) => { let relativeDir = path.dirname( relativeTo ), - fileRelativePath = path.resolve(path.isAbsolute(relativeDir) ? relativeDir : path.join( this.root, relativeDir ), newPath ) + fileRelativePath = path.resolve(pathIsAbsolute(relativeDir) ? relativeDir : path.join( this.root, relativeDir ), newPath ) // if the path is not relative or absolute, try to resolve it in node_modules if (newPath[0] !== '.' && newPath[0] !== '/') { From 9f969f73c7499f261f0722c153dd87ccf5c5fb4f Mon Sep 17 00:00:00 2001 From: stanislav Date: Wed, 19 Apr 2017 15:31:18 +0700 Subject: [PATCH 5/5] Add build directory to repository --- .gitignore | 1 - lib/file-system-loader.js | 123 ++++++++++++++++++++++++++++++++++++++ lib/index.js | 67 +++++++++++++++++++++ lib/parser.js | 107 +++++++++++++++++++++++++++++++++ 4 files changed, 297 insertions(+), 1 deletion(-) create mode 100644 lib/file-system-loader.js create mode 100644 lib/index.js create mode 100644 lib/parser.js diff --git a/.gitignore b/.gitignore index 017354f..8f5c351 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ node_modules/* !node_modules/cool-styles -lib diff --git a/lib/file-system-loader.js b/lib/file-system-loader.js new file mode 100644 index 0000000..31610c1 --- /dev/null +++ b/lib/file-system-loader.js @@ -0,0 +1,123 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +var _indexJs = require('./index.js'); + +var _indexJs2 = _interopRequireDefault(_indexJs); + +var _fs = require('fs'); + +var _fs2 = _interopRequireDefault(_fs); + +var _path = require('path'); + +var _path2 = _interopRequireDefault(_path); + +var _pathIsAbsolute = require('path-is-absolute'); + +var _pathIsAbsolute2 = _interopRequireDefault(_pathIsAbsolute); + +// Sorts dependencies in the following way: +// AAA comes before AA and A +// AB comes after AA and before A +// All Bs come after all As +// This ensures that the files are always returned in the following order: +// - In the order they were required, except +// - After all their dependencies +var traceKeySorter = function traceKeySorter(a, b) { + if (a.length < b.length) { + return a < b.substring(0, a.length) ? -1 : 1; + } else if (a.length > b.length) { + return a.substring(0, b.length) <= b ? -1 : 1; + } else { + return a < b ? -1 : 1; + } +}; + +var FileSystemLoader = (function () { + function FileSystemLoader(root, plugins) { + _classCallCheck(this, FileSystemLoader); + + this.root = root; + this.sources = {}; + this.traces = {}; + this.importNr = 0; + this.core = new _indexJs2['default'](plugins); + this.tokensByFile = {}; + + _indexJs2['default'].scope.generateScopedName = function (exportedName, unsanitizedPath) { + var sanitizedPath = _path2['default'].relative(root, unsanitizedPath).replace(/\.[^\.\/\\]+$/, '').replace(/[\W_]+/g, '_').replace(/^_|_$/g, ''); + return '_' + sanitizedPath + '__' + exportedName; + }; + } + + _createClass(FileSystemLoader, [{ + key: 'fetch', + value: function fetch(_newPath, relativeTo, _trace) { + var _this = this; + + var newPath = _newPath.replace(/^["']|["']$/g, ""), + trace = _trace || String.fromCharCode(this.importNr++); + return new Promise(function (resolve, reject) { + var relativeDir = _path2['default'].dirname(relativeTo), + fileRelativePath = _path2['default'].resolve((0, _pathIsAbsolute2['default'])(relativeDir) ? relativeDir : _path2['default'].join(_this.root, relativeDir), newPath); + + // if the path is not relative or absolute, try to resolve it in node_modules + if (newPath[0] !== '.' && newPath[0] !== '/') { + try { + fileRelativePath = require.resolve(newPath); + } catch (e) {} + } + + var tokens = _this.tokensByFile[fileRelativePath]; + if (tokens) { + return resolve(tokens); + } + + _fs2['default'].readFile(fileRelativePath, "utf-8", function (err, source) { + if (err) reject(err); + _this.core.load(source, fileRelativePath, trace, _this.fetch.bind(_this)).then(function (_ref) { + var injectableSource = _ref.injectableSource; + var exportTokens = _ref.exportTokens; + + _this.sources[fileRelativePath] = injectableSource; + _this.traces[trace] = fileRelativePath; + _this.tokensByFile[fileRelativePath] = exportTokens; + resolve(exportTokens); + }, reject); + }); + }); + } + }, { + key: 'finalSource', + get: function get() { + var traces = this.traces; + var sources = this.sources; + var written = new Set(); + + return Object.keys(traces).sort(traceKeySorter).map(function (key) { + var filename = traces[key]; + if (written.has(filename)) { + return null; + } + written.add(filename); + + return sources[filename]; + }).join(""); + } + }]); + + return FileSystemLoader; +})(); + +exports['default'] = FileSystemLoader; +module.exports = exports['default']; \ No newline at end of file diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..4aeb5c0 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,67 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +var _postcss = require('postcss'); + +var _postcss2 = _interopRequireDefault(_postcss); + +var _postcssModulesLocalByDefault = require('postcss-modules-local-by-default'); + +var _postcssModulesLocalByDefault2 = _interopRequireDefault(_postcssModulesLocalByDefault); + +var _postcssModulesExtractImports = require('postcss-modules-extract-imports'); + +var _postcssModulesExtractImports2 = _interopRequireDefault(_postcssModulesExtractImports); + +var _postcssModulesScope = require('postcss-modules-scope'); + +var _postcssModulesScope2 = _interopRequireDefault(_postcssModulesScope); + +var _postcssModulesValues = require('postcss-modules-values'); + +var _postcssModulesValues2 = _interopRequireDefault(_postcssModulesValues); + +var _parser = require('./parser'); + +var _parser2 = _interopRequireDefault(_parser); + +var Core = (function () { + function Core(plugins) { + _classCallCheck(this, Core); + + this.plugins = plugins || Core.defaultPlugins; + } + + // These four plugins are aliased under this package for simplicity. + + _createClass(Core, [{ + key: 'load', + value: function load(sourceString, sourcePath, trace, pathFetcher) { + var parser = new _parser2['default'](pathFetcher, trace); + + return (0, _postcss2['default'])(this.plugins.concat([parser.plugin])).process(sourceString, { from: sourcePath }).then(function (result) { + return { injectableSource: result.css, exportTokens: parser.exportTokens }; + }); + } + }]); + + return Core; +})(); + +exports['default'] = Core; +Core.values = _postcssModulesValues2['default']; +Core.localByDefault = _postcssModulesLocalByDefault2['default']; +Core.extractImports = _postcssModulesExtractImports2['default']; +Core.scope = _postcssModulesScope2['default']; + +Core.defaultPlugins = [_postcssModulesValues2['default'], _postcssModulesLocalByDefault2['default'], _postcssModulesExtractImports2['default'], _postcssModulesScope2['default']]; +module.exports = exports['default']; \ No newline at end of file diff --git a/lib/parser.js b/lib/parser.js new file mode 100644 index 0000000..efa5eee --- /dev/null +++ b/lib/parser.js @@ -0,0 +1,107 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var _icssReplaceSymbols = require('icss-replace-symbols'); + +var _icssReplaceSymbols2 = _interopRequireDefault(_icssReplaceSymbols); + +var importRegexp = /^:import\((.+)\)$/; + +var Parser = (function () { + function Parser(pathFetcher, trace) { + _classCallCheck(this, Parser); + + this.pathFetcher = pathFetcher; + this.plugin = this.plugin.bind(this); + this.exportTokens = {}; + this.translations = {}; + this.trace = trace; + } + + _createClass(Parser, [{ + key: "plugin", + value: function plugin(css, result) { + var _this = this; + + return Promise.all(this.fetchAllImports(css)).then(function (_) { + return _this.linkImportedSymbols(css); + }).then(function (_) { + return _this.extractExports(css); + }); + } + }, { + key: "fetchAllImports", + value: function fetchAllImports(css) { + var _this2 = this; + + var imports = []; + css.each(function (node) { + if (node.type == "rule" && node.selector.match(importRegexp)) { + imports.push(_this2.fetchImport(node, css.source.input.from, imports.length)); + } + }); + return imports; + } + }, { + key: "linkImportedSymbols", + value: function linkImportedSymbols(css) { + (0, _icssReplaceSymbols2["default"])(css, this.translations); + } + }, { + key: "extractExports", + value: function extractExports(css) { + var _this3 = this; + + css.each(function (node) { + if (node.type == "rule" && node.selector == ":export") _this3.handleExport(node); + }); + } + }, { + key: "handleExport", + value: function handleExport(exportNode) { + var _this4 = this; + + exportNode.each(function (decl) { + if (decl.type == 'decl') { + Object.keys(_this4.translations).forEach(function (translation) { + decl.value = decl.value.replace(translation, _this4.translations[translation]); + }); + _this4.exportTokens[decl.prop] = decl.value; + } + }); + exportNode.remove(); + } + }, { + key: "fetchImport", + value: function fetchImport(importNode, relativeTo, depNr) { + var _this5 = this; + + var file = importNode.selector.match(importRegexp)[1], + depTrace = this.trace + String.fromCharCode(depNr); + return this.pathFetcher(file, relativeTo, depTrace).then(function (exports) { + importNode.each(function (decl) { + if (decl.type == 'decl') { + _this5.translations[decl.prop] = exports[decl.value]; + } + }); + importNode.remove(); + }, function (err) { + return console.log(err); + }); + } + }]); + + return Parser; +})(); + +exports["default"] = Parser; +module.exports = exports["default"]; \ No newline at end of file