Repository: tivac/modular-css (js)

Issue:
Aliases provided in a `<link>` tag for a Svelte 3 file aren't resolved by known build-system aliases
<!--- Provide a general summary of the issue in the Title above -->
O' LORD OF MODULAR CSS. 

I'm running into an issue where I'd like to use the aliases that my current rollup configuration knows about to point to a specific `css` file to use in a given Svelte3 component. 

like so:
`<link rel="stylesheet" href="shared/styles/target.css">` 

Normally the way I structure the css is by referencing a stylesheet in the same general directory as the component, such that referencing is as simple as 
`<link rel="stylesheet" href="./target.css">`

However, again, I'm running into the issue when I try to use aliases that my rollup configuration knows about. 

## Expected Behavior
<!--- If you're describing a bug, tell us what should happen -->
<!--- If you're suggesting a change/improvement, tell us how it should work -->
`<link rel="stylesheet" href="shared/styles/target.css">` is resolved properly to point to the correct file, where `shared/` is a convenient alias pointing to another folder in my project. 

## Current Behavior
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
<!--- If suggesting a change/improvement, explain the difference from current behavior -->
`<link rel="stylesheet" href="shared/styles/target.css">`  causes an error :

`[!] (plugin svelte) Error: Cannot find module 'shared/styles/target.css'`

## Possible Solution
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
<!--- or ideas how to implement the addition or change -->
The resolution of a given `href` should probably be injected with known build time aliases (or at least the resolver should expose an option to provide it with a pairing of aliases to paths to resolve against)

Pull request:
feat(processor): add processor.resolve()
<!--- Provide a general summary of your changes in the Title above -->

## Description
<!--- Describe your changes in detail -->
Exposed previously private(ish) `processor._resolve()` as `processor.resolve()` so it's obvious that it's fine and reasonable for consumers to use it.

## Motivation and Context
<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->
Fixes #679 by allowing the svelte preprocessor to use the exact same file resolution logic as the core `modular-css` package.

## How Has This Been Tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->
Added a new svelte test, all existing tests still pass.

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)

## Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [x] My code follows the code style of this project.
- [x] My change requires a change to the documentation.
- [x] I have updated the documentation accordingly.
- [x] I have read the **CONTRIBUTING** document.
- [x] I have added tests to cover my changes.
- [x] All new and existing tests passed.


Patch:
diff --git a/.travis.yml b/.travis.yml
index 4726a9e1..b40f4995 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,8 +8,8 @@ if: tag IS blank
 language: node_js
 node_js:
 - node
+- '12'
 - '10'
-- '8'
 
 # https://docs.travis-ci.com/user/customizing-the-build/#Fast-Finishing
 matrix:
diff --git a/package-lock.json b/package-lock.json
index b2ecf7f5..b44eaa21 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2120,8 +2120,7 @@
       "requires": {
         "@modular-css/processor": "file:packages/processor",
         "escape-string-regexp": "^2.0.0",
-        "is-url": "^1.2.4",
-        "resolve-from": "^5.0.0"
+        "is-url": "^1.2.4"
       }
     },
     "@modular-css/test-utils": {
diff --git a/package.json b/package.json
index ba2ebd7a..7c6970be 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,6 @@
     "prepublishOnly": "npm run parsers",
     "pretest": "npm run parsers",
     "test": "jest",
-    "posttest": "npm run lint",
     "watch": "jest --watch",
     "www:build": "lerna run --stream build packages/www",
     "www:start": "lerna run --stream start packages/www",
diff --git a/packages/processor/processor.js b/packages/processor/processor.js
index e617a7d5..b8b966c5 100644
--- a/packages/processor/processor.js
+++ b/packages/processor/processor.js
@@ -9,12 +9,12 @@ const postcss   = require("postcss");
 const slug      = require("unique-slug");
 const mapValues = require("lodash/mapValues");
 
-const output    = require("./lib/output.js");
-const message   = require("./lib/message.js");
-const relative  = require("./lib/relative.js");
-const tiered    = require("./lib/graph-tiers.js");
-const resolve   = require("./lib/resolve.js");
-const normalize = require("./lib/normalize.js");
+const output        = require("./lib/output.js");
+const message       = require("./lib/message.js");
+const relative      = require("./lib/relative.js");
+const tiered        = require("./lib/graph-tiers.js");
+const normalize     = require("./lib/normalize.js");
+const { resolvers } = require("./lib/resolve.js");
 
 const noop = () => true;
 
@@ -70,8 +70,7 @@ class Processor {
             // eslint-disable-next-line no-empty-function
             () => {};
 
-        this._resolve = resolve.resolvers(options.resolvers);
-
+        this._resolve = resolvers(options.resolvers);
         this._normalize = normalize.bind(null, this._options.cwd);
 
         this._files = Object.create(null);
@@ -155,6 +154,11 @@ class Processor {
         return this._normalize(file);
     }
 
+    // Resolve a file from a src using the configured resolvers
+    resolve(src, file) {
+        return this._resolve(src, file);
+    }
+
     // Check if a file exists in the currently-processed set
     has(input) {
         const file = this._normalize(input);
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 3ff56ffc..5d810034 100644
--- a/packages/svelte/package.json
+++ b/packages/svelte/package.json
@@ -31,8 +31,7 @@
   "dependencies": {
     "@modular-css/processor": "file:../processor",
     "escape-string-regexp": "^2.0.0",
-    "is-url": "^1.2.4",
-    "resolve-from": "^5.0.0"
+    "is-url": "^1.2.4"
   },
   "peerDependencies": {
     "svelte": ">1"
diff --git a/packages/svelte/svelte.js b/packages/svelte/svelte.js
index 55a5272b..2d3fa598 100644
--- a/packages/svelte/svelte.js
+++ b/packages/svelte/svelte.js
@@ -2,7 +2,6 @@
 
 const path = require("path");
 
-const resolve = require("resolve-from");
 const isUrl = require("is-url");
 const escape = require("escape-string-regexp");
 
@@ -143,7 +142,7 @@ module.exports = (config = false) => {
             // Assign to file for later usage in logging
             css = href;
 
-            const external = resolve(path.dirname(html), css);
+            const external = processor.resolve(html, css);
 
             log("extract <link>", external);
 
diff --git a/packages/www/src/api/usage-js.md b/packages/www/src/api/usage-js.md
index 2f4bda29..98d12b0f 100644
--- a/packages/www/src/api/usage-js.md
+++ b/packages/www/src/api/usage-js.md
@@ -225,3 +225,18 @@ Remove files from the `Processor` instance. Accepts a single file or array of fi
 
 Returns an array of file paths. Accepts a single file argument to get the dependencies for, will return entire dependency graph in order if argument is omitted.
 
+##### `.invalidate(file)`
+
+Marks a file as stale, if that file is re-added either directly or as a dependency it will be reloaded from disk instead of the Processor cache.
+
+##### `.has(file)`
+
+Checks if the Processor instance knows about a file.
+
+##### `.normalize(file)`
+
+Uses the built-in path normalization settings to normalize the case of a file path.
+
+##### `.resolve(src, file)`
+
+Resolves `file` from `src`, using any of the specified [`resolvers`](#resolvers).


Test patch:
diff --git a/packages/processor/test/__snapshots__/api.test.js.snap b/packages/processor/test/__snapshots__/api.test.js.snap
index 468e50d6..d46b0e4a 100644
--- a/packages/processor/test/__snapshots__/api.test.js.snap
+++ b/packages/processor/test/__snapshots__/api.test.js.snap
@@ -1,12 +1,12 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`/processor.js API ._resolve() should fall back to a default resolver 1`] = `
+exports[`/processor.js API .resolve() should fall back to a default resolver 1`] = `
 Array [
   "packages/processor/test/specimens/local.css",
 ]
 `;
 
-exports[`/processor.js API ._resolve() should run resolvers until a match is found 1`] = `
+exports[`/processor.js API .resolve() should run resolvers until a match is found 1`] = `
 Array [
   "packages/processor/test/specimens/local.css",
 ]
diff --git a/packages/processor/test/api.test.js b/packages/processor/test/api.test.js
index c8b2d4f9..067159c7 100644
--- a/packages/processor/test/api.test.js
+++ b/packages/processor/test/api.test.js
@@ -336,7 +336,7 @@ describe("/processor.js", () => {
             });
         });
 
-        describe("._resolve()", () => {
+        describe(".resolve()", () => {
             it("should run resolvers until a match is found", () => {
                 let ran = false;
 
@@ -350,7 +350,7 @@ describe("/processor.js", () => {
                 
                 expect(
                     relative([
-                        processor._resolve(
+                        processor.resolve(
                             require.resolve("./specimens/start.css"),
                             "./local.css"
                         ),
@@ -370,7 +370,7 @@ describe("/processor.js", () => {
                 
                 expect(
                     relative([
-                        processor._resolve(
+                        processor.resolve(
                             require.resolve("./specimens/start.css"),
                             "./local.css"
                         ),
diff --git a/packages/svelte/test/__snapshots__/svelte.test.js.snap b/packages/svelte/test/__snapshots__/svelte.test.js.snap
index 2925fbde..b9063f8f 100644
--- a/packages/svelte/test/__snapshots__/svelte.test.js.snap
+++ b/packages/svelte/test/__snapshots__/svelte.test.js.snap
@@ -658,6 +658,19 @@ exports[`/svelte.js should use an already-created processor 2`] = `
 }"
 `;
 
+exports[`/svelte.js should use modular-css's file resolver 1`] = `
+"<div class=\\"fooga\\">fooga</div>
+<script>import css from \\"./does-not-exist.css\\";</script>"
+`;
+
+exports[`/svelte.js should use modular-css's file resolver 2`] = `
+"/* packages/svelte/test/specimens/simple.css */
+.fooga {
+    color: red;
+}
+"
+`;
+
 exports[`/svelte.js should wait for files to finish 1`] = `
 Array [
   "<style>/* replaced by modular-css */</style>
diff --git a/packages/svelte/test/specimens/link-resolving.html b/packages/svelte/test/specimens/link-resolving.html
new file mode 100644
index 00000000..b5a6d6f5
--- /dev/null
+++ b/packages/svelte/test/specimens/link-resolving.html
@@ -0,0 +1,3 @@
+<link rel="stylesheet" href="./does-not-exist.css" />
+
+<div class="{css.fooga}">fooga</div>
diff --git a/packages/svelte/test/svelte.test.js b/packages/svelte/test/svelte.test.js
index 5c51f640..1e65f286 100644
--- a/packages/svelte/test/svelte.test.js
+++ b/packages/svelte/test/svelte.test.js
@@ -370,4 +370,30 @@ describe("/svelte.js", () => {
 
         expect(results.map((result) => result.toString())).toMatchSnapshot();
     });
+
+    it("should use modular-css's file resolver", async () => {
+        const processor = new Processor({
+            namer,
+            resolvers : [
+                // Force all paths to resolve to a different file
+                () => require.resolve("./specimens/simple.css"),
+            ],
+        });
+
+        const filename = require.resolve("./specimens/link-resolving.html");
+        const { preprocess } = plugin({
+            processor,
+        });
+
+        const processed = await svelte.preprocess(
+            fs.readFileSync(filename, "utf8"),
+            Object.assign({}, preprocess, { filename })
+        );
+
+        expect(processed.toString()).toMatchSnapshot();
+
+        const output = await processor.output();
+
+        expect(output.css).toMatchSnapshot();
+    });
 });