diff --git a/index.js b/index.js index c30624d9..1adf6fc4 100755 --- a/index.js +++ b/index.js @@ -5,12 +5,12 @@ var fs = require("fs") var path = require("path") var assign = require("object-assign") -var resolve = require("resolve") var postcss = require("postcss") var helpers = require("postcss-message-helpers") var glob = require("glob") var parseImports = require("./lib/parse-imports") var resolveMedia = require("./lib/resolve-media") +var resolveId = require("./lib/resolve-id") /** * Constants @@ -257,41 +257,50 @@ function readAtImport( } addInputToPath(options) - var resolvedFilename = resolveFilename( - parsedAtImport.uri, - options.root, - options.path, - atRule.source, - options.resolve - ) + var base = atRule.source && atRule.source.input && atRule.source.input.file + ? path.dirname(path.resolve(options.root, atRule.source.input.file)) + : options.root - if (options.skipDuplicates) { - // skip files already imported at the same scope - if ( - state.importedFiles[resolvedFilename] && - state.importedFiles[resolvedFilename][media] - ) { - atRule.remove() - return Promise.resolve() + return Promise.resolve().then(function() { + if (options.resolve) { + return options.resolve(parsedAtImport.uri, base, options) } + return resolveId( + parsedAtImport.uri, + base, + options.path + ) + }).then(function(resolvedFilename) { + if (options.skipDuplicates) { + // skip files already imported at the same scope + if ( + state.importedFiles[resolvedFilename] && + state.importedFiles[resolvedFilename][media] + ) { + atRule.remove() + return Promise.resolve() + } - // save imported files to skip them next time - if (!state.importedFiles[resolvedFilename]) { - state.importedFiles[resolvedFilename] = {} + // save imported files to skip them next time + if (!state.importedFiles[resolvedFilename]) { + state.importedFiles[resolvedFilename] = {} + } + state.importedFiles[resolvedFilename][media] = true } - state.importedFiles[resolvedFilename][media] = true - } - return readImportedContent( - result, - atRule, - parsedAtImport, - assign({}, options), - resolvedFilename, - state, - media, - processor - ) + return readImportedContent( + result, + atRule, + parsedAtImport, + assign({}, options), + resolvedFilename, + state, + media, + processor + ) + }).catch(function(err) { + result.warn(err.message, { node: atRule }) + }) } /** @@ -414,63 +423,6 @@ function insertRules(atRule, parsedAtImport, newStyles) { atRule.replaceWith(newNodes) } -/** - * Check if a file exists - * - * @param {String} name - */ -function resolveFilename(name, root, paths, source, resolver) { - var dir = source && source.input && source.input.file - ? path.dirname(path.resolve(root, source.input.file)) - : root - - try { - var resolveOpts = { - basedir: dir, - moduleDirectory: moduleDirectories.concat(paths), - paths: paths, - extensions: [ ".css" ], - packageFilter: function processPackage(pkg) { - pkg.main = pkg.style || "index.css" - return pkg - }, - } - var file - resolver = resolver || resolve.sync - try { - file = resolver(name, resolveOpts) - } - catch (e) { - // fix to try relative files on windows with "./" - // if it's look like it doesn't start with a relative path already - // if (name.match(/^\.\.?/)) {throw e} - try { - file = resolver("./" + name, resolveOpts) - } - catch (err) { - // LAST HOPE - if (!paths.some(function(dir2) { - file = path.join(dir2, name) - return fs.existsSync(file) - })) { - throw err - } - } - } - - return path.normalize(file) - } - catch (e) { - throw new Error( - "Failed to find '" + name + "' from " + root + - "\n in [ " + - "\n " + paths.join(",\n ") + - "\n ]", - source - ) - } -} - /** * Read the contents of a file * diff --git a/lib/resolve-id.js b/lib/resolve-id.js new file mode 100644 index 00000000..45c28a73 --- /dev/null +++ b/lib/resolve-id.js @@ -0,0 +1,82 @@ +var fs = require("fs") +var path = require("path") +var resolveModule = require("resolve") + +var moduleDirectories = [ + "web_modules", + "node_modules", +] + +function isFile(path) { + return new Promise(function(resolve, reject) { + fs.stat(path, function(err, stat) { + if (err) { + if (err.code === "ENOENT") { + return resolve(false) + } + return reject(err) + } + resolve(stat.isFile() || stat.isFIFO()) + }) + }) +} + +function getFirstExistingFile(files) { + return files.reduce(function(promise, file) { + return promise.then(function(resolvedFile) { + if (resolvedFile) { + return resolvedFile + } + return isFile(file).then(function(is) { + if (is) { + return file + } + }) + }) + }, Promise.resolve()) +} + +module.exports = function(id, base, paths) { + var files = [ base ].concat(paths).reduce(function(files, p) { + files.push(path.resolve(p, id)) + files.push(path.resolve(p, id + ".css")) + files.push(path.resolve(p, id, "index.css")) + return files + }, []) + + return getFirstExistingFile(files).then(function(existing) { + if (existing) { + return existing + } + var opts = { + basedir: path.resolve(base), + moduleDirectory: moduleDirectories, + extensions: [ ".css" ], + packageFilter: function(pkg) { + if (!/\.css$/.test(pkg.main)) { + pkg.main = pkg.style || "index.css" + } + return pkg + }, + } + + return new Promise(function(resolve) { + resolveModule(id, opts, function(err, existing) { + if (err) { + return resolve() + } + resolve(existing) + }) + }) + }).then(function(existing) { + if (existing) { + return existing + } + throw new Error([ + "Failed to find '" + id + "'", + "in [ ", + " " + paths.join(",\n "), + "]", + ].join("\n ")) + }) +} diff --git a/test/fixtures/ignore.expected.css b/test/fixtures/ignore.expected.css index 804b9950..9187131a 100644 --- a/test/fixtures/ignore.expected.css +++ b/test/fixtures/ignore.expected.css @@ -1,6 +1,3 @@ -@import "http://css" (min-width: 25em); - -@import "http://css-screen" (min-width: 25em) and screen; @import "http://css"; @@ -28,6 +25,10 @@ @import url(//css); +@import "http://css" (min-width: 25em); + +@import "http://css-screen" (min-width: 25em) and screen; + @media (min-width: 25em){ ignore{} } diff --git a/test/fixtures/imports/local-module/main.css b/test/fixtures/imports/local-module/main.css deleted file mode 100644 index 68c41250..00000000 --- a/test/fixtures/imports/local-module/main.css +++ /dev/null @@ -1 +0,0 @@ -.local-module{} diff --git a/test/fixtures/imports/local-module/package.json b/test/fixtures/imports/local-module/package.json deleted file mode 100644 index ae14fa6b..00000000 --- a/test/fixtures/imports/local-module/package.json +++ /dev/null @@ -1 +0,0 @@ -{"style": "main.css"} diff --git a/test/fixtures/modules.css b/test/fixtures/modules.css deleted file mode 100644 index cf3c881e..00000000 --- a/test/fixtures/modules.css +++ /dev/null @@ -1,19 +0,0 @@ -@import "fake"; -@import "auto"; -@import "nest"; -@import "by-hand/style.css"; -@import "use-dep"; -@import "use-dep-too"; -@import "use-dep" screen; - -@import "web-fake"; -@import "web-auto"; -@import "web-nest"; -@import "web-by-hand/style.css"; -@import "web-use-dep"; -@import "web-use-dep-too"; -@import "web-use-dep" screen; - -@import "local-module"; - -content{} \ No newline at end of file diff --git a/test/fixtures/modules.expected.css b/test/fixtures/modules.expected.css deleted file mode 100644 index 4fa34101..00000000 --- a/test/fixtures/modules.expected.css +++ /dev/null @@ -1,21 +0,0 @@ -.fake{} -.auto{} -.nested{} -.byHand{} -.dep{} -@media screen{ -.dep{} -} - -.web-fake{} -.web-auto{} -.web-nested{} -.web-byHand{} -.web-dep{} -@media screen{ -.web-dep{} -} - -.local-module{} - -content{} \ No newline at end of file diff --git a/test/import.js b/test/import.js index 4dccc1cb..a9a1139d 100644 --- a/test/import.js +++ b/test/import.js @@ -1,4 +1,5 @@ import test from "ava" +import path from "path" import { readFileSync } from "fs" import postcss from "postcss" import atImport from ".." @@ -54,12 +55,6 @@ test("should not need `path` option if `source` option has been passed", t => { }) }) -test("should be able to consume npm package or local modules", t => { - return compareFixtures(t, "modules", { - root: ".", - }) -}) - test("should not fail with only one absolute import", t => { var base = "@import url(http://)" return postcss() @@ -85,13 +80,11 @@ test("should output readable trace", t => { return postcss() .use(atImport()) .process(readFileSync(file), { from: file }) - .catch(error => { - t.throws( - () => { - throw error - }, + .then(result => { + t.is( + result.warnings()[0].text, /* eslint-disable max-len */ - /import-missing.css:2:5: Failed to find 'missing-file.css' from .*\n\s+in \[/gm + "Failed to find 'missing-file.css'\n in [ \n " + path.resolve("fixtures/imports") + "\n ]" /* eslint-enabme max-len */ ) }) @@ -155,14 +148,16 @@ test("should work with no styles without throwing an error", t => { test("should be able to consume modules in the custom-resolve way", t => { const resolve = require("resolve") - const sassResolve = (file, opts) => { - opts = opts || {} - opts.extensions = [ ".scss", ".css" ] - opts.packageFilter = pkg => { - pkg.main = pkg.sass || pkg.style || "index" - return pkg - } - return resolve.sync(file, opts) + const sassResolve = (id, base, importOptions) => { + return resolve.sync(id, { + basedir: base, + extensions: [ ".scss", ".css" ], + paths: importOptions.path, + packageFilter: pkg => { + pkg.main = pkg.sass || pkg.style || "index" + return pkg + }, + }) } return compareFixtures(t, "custom-resolve-modules", { root: ".",