Repository: tivac/modular-css
Pull request #666 (2019-09-11 06:38:37)

fix: use this.emitFile() API from rollup
<!--- Provide a general summary of your changes in the Title above -->

## Description
Using `this.emitFile()` for all file emission from the rollup plugin.

## Motivation and Context
`this.emitFile()` is the correct future-proof way to emit files from rollup plugins. `this.emitAsset()` and `this.emitChunk()` are being deprecated, and it was never *really* supported to add files directly to the bundle the way they were.

## How Has This Been Tested?
Normal test suite

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] 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.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [x] I have read the **CONTRIBUTING** document.
- [ ] I have added tests to cover my changes.
- [x] All new and existing tests passed.



Patch:
diff --git a/package-lock.json b/package-lock.json
index b6097e49..ddd03d89 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1569,6 +1569,7 @@
     "@modular-css/browserify": {
       "version": "file:packages/browserify",
       "requires": {
+        "@modular-css/processor": "file:packages/processor",
         "mkdirp": "^0.5.1",
         "p-each-series": "^2.0.0",
         "sink-transform": "^2.0.0",
@@ -1578,6 +1579,8 @@
     "@modular-css/cli": {
       "version": "file:packages/cli",
       "requires": {
+        "@modular-css/glob": "file:packages/glob",
+        "@modular-css/processor": "file:packages/processor",
         "meow": "^5.0.0",
         "mkdirp": "^0.5.1"
       }
@@ -1585,6 +1588,7 @@
     "@modular-css/glob": {
       "version": "file:packages/glob",
       "requires": {
+        "@modular-css/processor": "file:packages/processor",
         "globule": "^1.1.0"
       }
     },
@@ -1603,6 +1607,7 @@
     "@modular-css/postcss": {
       "version": "file:packages/postcss",
       "requires": {
+        "@modular-css/processor": "file:packages/processor",
         "mkdirp": "^0.5.1",
         "postcss": "^7.0.0"
       }
@@ -1624,6 +1629,7 @@
     "@modular-css/rollup": {
       "version": "file:packages/rollup",
       "requires": {
+        "@modular-css/processor": "file:packages/processor",
         "dedent": "0.7.0",
         "esutils": "^2.0.2",
         "rollup-pluginutils": "^2.0.1",
@@ -1647,6 +1653,7 @@
     "@modular-css/svelte": {
       "version": "file:packages/svelte",
       "requires": {
+        "@modular-css/processor": "file:packages/processor",
         "escape-string-regexp": "^2.0.0",
         "is-url": "^1.2.4",
         "resolve-from": "^5.0.0"
@@ -1659,6 +1666,7 @@
     "@modular-css/webpack": {
       "version": "file:packages/webpack",
       "requires": {
+        "@modular-css/processor": "file:packages/processor",
         "esutils": "^2.0.2",
         "loader-utils": "^1.1.0",
         "lodash": "^4.17.0",
@@ -1669,6 +1677,10 @@
       "version": "file:packages/www",
       "dev": true,
       "requires": {
+        "@modular-css/processor": "file:packages/processor",
+        "@modular-css/rollup": "file:packages/rollup",
+        "@modular-css/shortnames": "file:packages/namer",
+        "@modular-css/svelte": "file:packages/svelte",
         "babel-eslint": "^10.0.1",
         "codemirror": "^5.42.2",
         "cssnano": "^4.1.8",
@@ -1700,7 +1712,7 @@
         "rollup-plugin-node-globals": "^1.4.0",
         "rollup-plugin-node-resolve": "^5.2.0",
         "rollup-plugin-svelte": "^5.0.1",
-        "rollup-plugin-terser": "^5.0.0",
+        "rollup-plugin-terser": "^5.1.1",
         "rollup-pluginutils": "^2.8.1",
         "svelte": "^2.16.0"
       }
@@ -5095,9 +5107,9 @@
       }
     },
     "eslint-plugin-jest": {
-      "version": "22.15.1",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-22.15.1.tgz",
-      "integrity": "sha512-CWq/RR/3tLaKFB+FZcCJwU9hH5q/bKeO3rFP8G07+q7hcDCFNqpvdphVbEbGE6o6qo1UbciEev4ejUWv7brUhw==",
+      "version": "22.16.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-22.16.0.tgz",
+      "integrity": "sha512-eBtSCDhO1k7g3sULX/fuRK+upFQ7s548rrBtxDyM1fSoY7dTWp/wICjrJcDZKVsW7tsFfH22SG+ZaxG5BZodIg==",
       "dev": true,
       "requires": {
         "@typescript-eslint/experimental-utils": "^1.13.0"
@@ -12648,20 +12660,26 @@
       }
     },
     "rollup": {
-      "version": "1.19.4",
-      "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.19.4.tgz",
-      "integrity": "sha512-G24w409GNj7i/Yam2cQla6qV2k6Nug8bD2DZg9v63QX/cH/dEdbNJg8H4lUm5M1bRpPKRUC465Rm9H51JTKOfQ==",
+      "version": "1.21.3",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.21.3.tgz",
+      "integrity": "sha512-43CgeUtHhfiqBOUd0uJo5NEOg2FuheF3SqGN8BqgvnqB4xM2TbfPdudeSdllDcMKpagHb//qtpaAADBurT4GzA==",
       "dev": true,
       "requires": {
         "@types/estree": "0.0.39",
-        "@types/node": "^12.6.9",
-        "acorn": "^6.2.1"
+        "@types/node": "^12.7.5",
+        "acorn": "^7.0.0"
       },
       "dependencies": {
+        "@types/node": {
+          "version": "12.7.5",
+          "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.5.tgz",
+          "integrity": "sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w==",
+          "dev": true
+        },
         "acorn": {
-          "version": "6.3.0",
-          "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz",
-          "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==",
+          "version": "7.0.0",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.0.0.tgz",
+          "integrity": "sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==",
           "dev": true
         }
       }
diff --git a/package.json b/package.json
index d3bd69b8..ab18efe2 100644
--- a/package.json
+++ b/package.json
@@ -44,7 +44,7 @@
     "lerna": "^3.16.4",
     "pegjs": "0.10.0",
     "read-dir-deep": "^6.0.0",
-    "rollup": "^1.11.3",
+    "rollup": "^1.21.3",
     "rollup-plugin-hypothetical": "^2.1.0",
     "rollup-plugin-svelte": "^5.0.3",
     "shelljs": "^0.8.3",
diff --git a/packages/rollup-rewriter/README.md b/packages/rollup-rewriter/README.md
index c3148f8a..336a26a1 100644
--- a/packages/rollup-rewriter/README.md
+++ b/packages/rollup-rewriter/README.md
@@ -84,9 +84,9 @@ export default {
 
 ## Options
 
-### `loader` (string)
+### `loader` (string|function)
 
-The `loader` option can be set if you want the plugin to inject a reference to a CSS loader that returns a promise (I like [`lazyload-css`](https://npmjs.com/lazyload-css)). This loader must be available statically, so this option is most useful in `es`/`esm` mode where it can be loaded via `import`.
+The `loader` option can be set if you want the plugin to inject a reference to a CSS loader that returns a promise (I like [`lazyload-css`](https://npmjs.com/lazyload-css)). This loader must be available statically, so this option is most useful in `es`/`esm` mode where it can be loaded via `import`. If given a function instead of a string that function will be called once per chunk being modified, it gets passed a single argument of the form `{ chunks }` where `chunks` is the raw rollup output chunks.
 
 ### `loadfn` (string)
 
diff --git a/packages/rollup-rewriter/formats/amd.js b/packages/rollup-rewriter/formats/amd.js
index 36ffbd8f..61248460 100644
--- a/packages/rollup-rewriter/formats/amd.js
+++ b/packages/rollup-rewriter/formats/amd.js
@@ -17,7 +17,7 @@ exports.regex = (deps) => {
     );
 };
 
-exports.loader = (options, str) => {
+exports.loader = ({ content, str }) => {
     const s = str.toString();
     const i = s.indexOf(search);
 
@@ -28,7 +28,7 @@ exports.loader = (options, str) => {
     // + 1 is for the newline...
     str.appendRight(
         i + search.length + 1,
-        `${options.loader}\n`
+        `${content}\n`
     );
 };
 
diff --git a/packages/rollup-rewriter/formats/es.js b/packages/rollup-rewriter/formats/es.js
index 390edc8a..d2283479 100644
--- a/packages/rollup-rewriter/formats/es.js
+++ b/packages/rollup-rewriter/formats/es.js
@@ -11,7 +11,7 @@ exports.regex = (deps) => {
     );
 };
 
-exports.loader = (options, str) => str.prepend(`${options.loader}\n`);
+exports.loader = ({ content, str }) => str.prepend(`${content}\n`);
 
 exports.load = (options, imports, statement) => `
     Promise.all([
diff --git a/packages/rollup-rewriter/formats/system.js b/packages/rollup-rewriter/formats/system.js
index a3b2e597..544495d9 100644
--- a/packages/rollup-rewriter/formats/system.js
+++ b/packages/rollup-rewriter/formats/system.js
@@ -13,7 +13,7 @@ exports.regex = (deps) => {
     );
 };
 
-exports.loader = (options, str) => {
+exports.loader = ({ content, str }) => {
     const s = str.toString();
     const i = s.indexOf(search);
 
@@ -24,7 +24,7 @@ exports.loader = (options, str) => {
     // + 1 is for the newline...
     str.appendRight(
         i + search.length + 1,
-        `${options.loader}\n`
+        `${content}\n`
     );
 };
 
diff --git a/packages/rollup-rewriter/rewriter.js b/packages/rollup-rewriter/rewriter.js
index 155afe2c..3e0a95c7 100644
--- a/packages/rollup-rewriter/rewriter.js
+++ b/packages/rollup-rewriter/rewriter.js
@@ -75,7 +75,11 @@ module.exports = (opts) => {
                 const str = new MagicString(code);
 
                 if(options.loader) {
-                    loader(options, str);
+                    const content = typeof options.loader === "function" ?
+                        options.loader({ chunks, options }) :
+                        options.loader;
+
+                    loader({ content, str });
                 }
 
                 // Yay stateful regexes
diff --git a/packages/rollup/rollup.js b/packages/rollup/rollup.js
index f15f093a..1d58e105 100644
--- a/packages/rollup/rollup.js
+++ b/packages/rollup/rollup.js
@@ -257,7 +257,11 @@ module.exports = (opts) => {
                     continue;
                 }
 
-                const id = this.emitAsset(`${name}${ext}`, result.css);
+                const id = this.emitFile({
+                    type   : "asset",
+                    name   : `${name}${ext}`,
+                    source : result.css,
+                });
 
                 // Save off the final name of this asset for later use
                 const dest = this.getAssetFileName(id);
@@ -266,9 +270,6 @@ module.exports = (opts) => {
 
                 log("css output", dest);
 
-                // Maps can't be written out via the asset APIs becuase they shouldn't ever be hashed.
-                // They shouldn't be hashed because they simply follow the name of their parent .css asset.
-                // So add them to the bundle directly.
                 if(result.map) {
                     // Make sure to use the rollup name as the base, otherwise it won't
                     // automatically handle duplicate names correctly
@@ -276,11 +277,14 @@ module.exports = (opts) => {
 
                     log("map output", fileName);
 
-                    bundle[fileName] = {
-                        isAsset : true,
-                        source  : result.map.toString(),
+                    this.emitFile({
+                        type   : "asset",
+                        source : result.map.toString(),
+
+                        // Use fileName instead of name because this has to follow the parent
+                        // file naming and can't be double-hashed
                         fileName,
-                    };
+                    });
 
                     // Had to re-add the map annotation to the end of the source files
                     // if the filename had a hash, since we stripped it out up above
@@ -297,7 +301,11 @@ module.exports = (opts) => {
 
                 const compositions = await processor.compositions;
 
-                this.emitAsset(dest, JSON.stringify(compositions, null, 4));
+                this.emitFile({
+                    type   : "asset",
+                    name   : dest,
+                    source : JSON.stringify(compositions, null, 4),
+                });
             }
 
             const meta = {};
@@ -328,7 +336,11 @@ module.exports = (opts) => {
 
                 log("metadata output", dest);
 
-                this.emitAsset(dest, JSON.stringify(meta, null, 4));
+                this.emitFile({
+                    type   : "asset",
+                    source : JSON.stringify(meta, null, 4),
+                    name   : dest,
+                });
             }
         },
     };
diff --git a/packages/www/src/guide/feature-composing-styles.md b/packages/www/src/guide/feature-composing-styles.md
index e5d1e27e..ffe5c600 100644
--- a/packages/www/src/guide/feature-composing-styles.md
+++ b/packages/www/src/guide/feature-composing-styles.md
@@ -1,6 +1,6 @@
 ### Style Composition
 
-Selector limitations mean that it's difficult to use complicated selectors, so to enable building anything of complexity you can compose selectors. These compositions can be within a file or even pull in classes defined in other files.
+Selector limitations mean that it's difficult to use complicated selectors, so to enable building anything of complexity you can compose selectors. These compositions can be within a file, reference global CSS class, or even pull in classes defined in other files.
 
 ::: repl
 ```css
@@ -10,7 +10,7 @@ Selector limitations mean that it's difficult to use complicated selectors, so t
 
 .local {
     composes: composable;
-    
+
     color: red;
 }
 
@@ -25,20 +25,53 @@ Selector limitations mean that it's difficult to use complicated selectors, so t
 When this file is required the JS object will contain the expected keys, but the arrays will now contain more values.
 
 ```js
-var css = require("./styles.css");
+var css = require("./style.css");
 
 // css is:
 /*
 {
-    single   : "dafdfcc_other aeacf0c_single",
-    // Since .multiple is only a singular composes: declaration there's no need
-    // for it to be rewritten, it's left out of the output
-    multiple : "dafdfcc_more f5507abd_than aeacf0c_one",
-    local    : "dafdfcc_other aeacf0c_single"
+    composable : "dafdfcc_composable",
+    local      : "dafdfcc_composable aeacf0c_local",
+    removed    : "dafdfcc_composable aeacf0c_local aeacf0c_removed"
 }
 */
 ```
 
+Composition also works between files, by providing the source file.
+
+::: repl
+```css
+/* === style-guide.css === */
+
+.body {
+    margin: 10px;
+    height: 100%;
+}
+
+/* === home-page.css === */
+
+.body {
+    composes: body from "/style-guide.css";
+
+    padding: 10px;
+}
+```
+::::
+
+Styles can also be composed directly from the global scope to help with interoperability
+with CSS frameworks or other non-module styles.
+
+::: repl
+```css
+.box {
+    composes: d-flex, px-4, py-3 from global;
+
+    color: blue;
+}
+```
+::::
+
+
 If you're going to be doing a lot of composition with another file you can store the filename into a value for ease of referencing.
 
 ::: repl
@@ -58,13 +91,13 @@ If you're going to be doing a lot of composition with another file you can store
 
 .head {
     composes: heading from guide;
-    
+
     font-size: 120%;
 }
 
 .body {
     composes: body from guide;
-    
+
     padding: 10px;
 }
 ```
diff --git a/parsers/composes.pegjs b/parsers/composes.pegjs
index 6f76a113..7624e5b5 100644
--- a/parsers/composes.pegjs
+++ b/parsers/composes.pegjs
@@ -1,5 +1,6 @@
 start
     = composition
+    / global
     / namespaced
     / simple
 
@@ -14,6 +15,16 @@ composition
         };
     }
 
+// fooga from global
+// fooga, wooga from global
+global
+    = refs:references _ "from" _ global_keyword {
+        return {
+            type : "simple",
+            refs: refs.map(({ name }) => ({ name, global : true }))
+        };
+    }
+
 // fooga.booga
 namespaced
     = ns:ident "." ref:ident {
diff --git a/parsers/shared.pegjs b/parsers/shared.pegjs
index c59d3611..a3486a87 100644
--- a/parsers/shared.pegjs
+++ b/parsers/shared.pegjs
@@ -5,19 +5,22 @@ _ "whitespace"
 s "string"
     = "\"" chars:[^\"\r\n]* "\"" { return chars.join(""); }
     / "\'" chars:[^\'\r\n]* "\'" { return chars.join(""); }
-  
+
 // Partials
 
 // wooga
 ident "identifier"
     = chars:[a-z0-9-_]i+ { return chars.join(""); }
 
+global_keyword "global"
+    = "global"
+
 // wooga
 // global(wooga)
 reference "reference"
-    = "global(" _ ref:ident _ ")" { return { name : ref, global : true }; }
+    = global_keyword "(" _ ref:ident _ ")" { return { name : ref, global : true }; }
     / ref:ident { return { name : ref }; }
- 
+
 // wooga
 // wooga, tooga
 references "references"


Test patch:
diff --git a/packages/processor/test/__snapshots__/composition.test.js.snap b/packages/processor/test/__snapshots__/composition.test.js.snap
index e182270b..03be107a 100644
--- a/packages/processor/test/__snapshots__/composition.test.js.snap
+++ b/packages/processor/test/__snapshots__/composition.test.js.snap
@@ -28,6 +28,14 @@ Object {
 }
 `;
 
+exports[`/processor.js composition should compose from global keyword 1`] = `
+Object {
+  "global-compose.css": Object {
+    "a": "b c d a",
+  },
+}
+`;
+
 exports[`/processor.js composition should compose from globals 1`] = `
 Object {
   "global-compose.css": Object {
diff --git a/packages/processor/test/composition.test.js b/packages/processor/test/composition.test.js
index 37e7f332..b58e8d65 100644
--- a/packages/processor/test/composition.test.js
+++ b/packages/processor/test/composition.test.js
@@ -8,7 +8,7 @@ const Processor = require("../processor.js");
 describe("/processor.js", () => {
     describe("composition", () => {
         let processor;
-        
+
         beforeEach(() => {
             processor = new Processor({
                 namer,
@@ -22,10 +22,10 @@ describe("/processor.js", () => {
                     ".a { composes: b from nowhere.css; }"
                 );
             } catch({ message }) {
-                expect(message).toMatch(`SyntaxError: Expected source but "n" found.`);
+                expect(message).toMatch(`SyntaxError: Expected global or source but "n" found.`);
             }
         });
-        
+
         it("should fail if a composition references a non-existant class", async () => {
             try {
                 await processor.string(
@@ -36,7 +36,7 @@ describe("/processor.js", () => {
                 expect(message).toMatch(`Invalid composes reference`);
             }
         });
-        
+
         it("should fail if a composition references a non-existant file", async () => {
             try {
                 await processor.string(
@@ -108,12 +108,12 @@ describe("/processor.js", () => {
                     }
                 `)
             );
-            
+
             const { compositions } = await processor.output();
-            
+
             expect(compositions).toMatchSnapshot();
         });
-        
+
         it("should allow composes anywhere", async () => {
             await processor.string(
                 "./multiple-composes.css",
@@ -131,9 +131,9 @@ describe("/processor.js", () => {
                     }
                 `)
             );
-            
+
             const { compositions } = await processor.output();
-            
+
             expect(compositions).toMatchSnapshot();
         });
 
@@ -144,9 +144,22 @@ describe("/processor.js", () => {
                     .a { composes: global(b); }
                 `)
             );
-            
+
+            const { compositions } = await processor.output();
+
+            expect(compositions).toMatchSnapshot();
+        });
+
+        it("should compose from global keyword", async () => {
+            await processor.string(
+                "./global-compose.css",
+                dedent(`
+                    .a { composes: b, c, d from global; }
+                `)
+            );
+
             const { compositions } = await processor.output();
-            
+
             expect(compositions).toMatchSnapshot();
         });
 
@@ -162,17 +175,17 @@ describe("/processor.js", () => {
                     }
                 `)
             );
-            
+
             const { compositions } = await processor.output();
-            
+
             expect(compositions).toMatchSnapshot();
         });
-        
+
         it("should compose from other files", async () => {
             await processor.file(require.resolve("./specimens/composes.css"));
-            
+
             const { compositions } = await processor.output();
-            
+
             expect(compositions).toMatchSnapshot();
         });
     });
diff --git a/packages/rollup-rewriter/test/__snapshots__/rewriter.test.js.snap b/packages/rollup-rewriter/test/__snapshots__/rewriter.test.js.snap
index 225a2d17..ae6a96e4 100644
--- a/packages/rollup-rewriter/test/__snapshots__/rewriter.test.js.snap
+++ b/packages/rollup-rewriter/test/__snapshots__/rewriter.test.js.snap
@@ -1031,6 +1031,294 @@ System.register([], function (exports) {
 }
 `;
 
+exports[`rollup-rewriter should support loader being a function ("amd") 1`] = `
+Object {
+  "a": "
+define(['require'], function (require) { 'use strict';
+import chunkCountIs8 from \\"./css.js\\";
+
+    var css = {
+        \\"a\\": \\"a\\"
+    };
+
+    function a() {
+        console.log(css);
+
+        new Promise(function (resolve, reject) { Promise.all([
+    lazyload(\\"./assets/c.css\\"),
+    new Promise(function (resolve, reject) { require(['./c'], resolve, reject) })
+])
+.then((results) => resolve(results[results.length - 1]))
+.catch(reject) }).then(console.log);
+    }
+
+    return a;
+
+});
+",
+  "b": "
+define(['require'], function (require) { 'use strict';
+import chunkCountIs8 from \\"./css.js\\";
+
+    var css = {
+        \\"b\\": \\"b\\"
+    };
+
+    function b() {
+        console.log(css);
+
+        new Promise(function (resolve, reject) { Promise.all([
+    lazyload(\\"./assets/d.css\\"),
+    new Promise(function (resolve, reject) { require(['./d'], resolve, reject) })
+])
+.then((results) => resolve(results[results.length - 1]))
+.catch(reject) }).then(console.log);
+    }
+
+    return b;
+
+});
+",
+  "c": "
+define(['exports'], function (exports) { 'use strict';
+
+    var css = {
+        \\"c\\": \\"c\\"
+    };
+
+    console.log(css);
+
+    exports.default = css;
+
+});
+",
+  "d": "
+define(['exports'], function (exports) { 'use strict';
+
+    var css = {
+        \\"d\\": \\"d\\"
+    };
+
+    console.log(css);
+
+    exports.default = css;
+
+});
+",
+}
+`;
+
+exports[`rollup-rewriter should support loader being a function ("es") 1`] = `
+Object {
+  "a": "
+import chunkCountIs8 from \\"./css.js\\";
+var css = {
+    \\"a\\": \\"a\\"
+};
+
+function a() {
+    console.log(css);
+
+    Promise.all([
+    lazyload(\\"./assets/c.css\\"),
+    import('./c.js')
+])
+.then((results) => results[results.length - 1]).then(console.log);
+}
+
+export default a;
+",
+  "b": "
+import chunkCountIs8 from \\"./css.js\\";
+var css = {
+    \\"b\\": \\"b\\"
+};
+
+function b() {
+    console.log(css);
+
+    Promise.all([
+    lazyload(\\"./assets/d.css\\"),
+    import('./d.js')
+])
+.then((results) => results[results.length - 1]).then(console.log);
+}
+
+export default b;
+",
+  "c": "
+var css = {
+    \\"c\\": \\"c\\"
+};
+
+console.log(css);
+
+export default css;
+",
+  "d": "
+var css = {
+    \\"d\\": \\"d\\"
+};
+
+console.log(css);
+
+export default css;
+",
+}
+`;
+
+exports[`rollup-rewriter should support loader being a function ("esm") 1`] = `
+Object {
+  "a": "
+import chunkCountIs8 from \\"./css.js\\";
+var css = {
+    \\"a\\": \\"a\\"
+};
+
+function a() {
+    console.log(css);
+
+    Promise.all([
+    lazyload(\\"./assets/c.css\\"),
+    import('./c.js')
+])
+.then((results) => results[results.length - 1]).then(console.log);
+}
+
+export default a;
+",
+  "b": "
+import chunkCountIs8 from \\"./css.js\\";
+var css = {
+    \\"b\\": \\"b\\"
+};
+
+function b() {
+    console.log(css);
+
+    Promise.all([
+    lazyload(\\"./assets/d.css\\"),
+    import('./d.js')
+])
+.then((results) => results[results.length - 1]).then(console.log);
+}
+
+export default b;
+",
+  "c": "
+var css = {
+    \\"c\\": \\"c\\"
+};
+
+console.log(css);
+
+export default css;
+",
+  "d": "
+var css = {
+    \\"d\\": \\"d\\"
+};
+
+console.log(css);
+
+export default css;
+",
+}
+`;
+
+exports[`rollup-rewriter should support loader being a function ("system") 1`] = `
+Object {
+  "a": "
+System.register([], function (exports, module) {
+    'use strict';
+import chunkCountIs8 from \\"./css.js\\";
+    return {
+        execute: function () {
+
+            exports('default', a);
+
+            var css = {
+                \\"a\\": \\"a\\"
+            };
+
+            function a() {
+                console.log(css);
+
+                Promise.all([
+    lazyload(\\"./assets/c.css\\"),
+    module.import('./c.js')
+])
+.then((results) => results[results.length - 1]).then(console.log);
+            }
+
+        }
+    };
+});
+",
+  "b": "
+System.register([], function (exports, module) {
+    'use strict';
+import chunkCountIs8 from \\"./css.js\\";
+    return {
+        execute: function () {
+
+            exports('default', b);
+
+            var css = {
+                \\"b\\": \\"b\\"
+            };
+
+            function b() {
+                console.log(css);
+
+                Promise.all([
+    lazyload(\\"./assets/d.css\\"),
+    module.import('./d.js')
+])
+.then((results) => results[results.length - 1]).then(console.log);
+            }
+
+        }
+    };
+});
+",
+  "c": "
+System.register([], function (exports) {
+    'use strict';
+    return {
+        execute: function () {
+
+            var css = {
+                \\"c\\": \\"c\\"
+            };
+
+            console.log(css);
+            exports('default', css);
+
+        }
+    };
+});
+",
+  "d": "
+System.register([], function (exports) {
+    'use strict';
+    return {
+        execute: function () {
+
+            var css = {
+                \\"d\\": \\"d\\"
+            };
+
+            console.log(css);
+            exports('default', css);
+
+        }
+    };
+});
+",
+}
+`;
+
 exports[`rollup-rewriter shouldn't require a loader ("amd") 1`] = `
 Object {
   "a.js": "
diff --git a/packages/rollup-rewriter/test/rewriter.test.js b/packages/rollup-rewriter/test/rewriter.test.js
index 6274097a..c8e16b7d 100644
--- a/packages/rollup-rewriter/test/rewriter.test.js
+++ b/packages/rollup-rewriter/test/rewriter.test.js
@@ -111,6 +111,35 @@ describe("rollup-rewriter", () => {
 
         expect(result).toMatchRollupSnapshot();
     });
+    
+    it.each(formats)("should support loader being a function (%p)", async (format) => {
+        const bundle = await rollup({
+            input : [
+                require.resolve("./specimens/dynamic-imports/a.js"),
+                require.resolve("./specimens/dynamic-imports/b.js"),
+            ],
+            plugins : [
+                css({
+                    namer,
+                    map,
+                }),
+                rewriter({
+                    loader : ({ chunks }) => `import chunkCountIs${Object.keys(chunks).length} from "./css.js";`,
+                    loadfn : "lazyload",
+                }),
+            ],
+        });
+
+        const result = await bundle.generate({
+            format,
+            sourcemap,
+
+            assetFileNames,
+            chunkFileNames,
+        });
+
+        expect(result).toMatchRollupCodeSnapshot();
+    });
 
     it.each(formats)("should only rewrite when necessary (%p)", async (format) => {
         const bundle = await rollup({
diff --git a/packages/rollup/test/__snapshots__/splitting.test.js.snap b/packages/rollup/test/__snapshots__/splitting.test.js.snap
index 7385765c..dfc13fa1 100644
--- a/packages/rollup/test/__snapshots__/splitting.test.js.snap
+++ b/packages/rollup/test/__snapshots__/splitting.test.js.snap
@@ -408,7 +408,7 @@ Object {
     color: red;
 }
 ",
-  "simple.96e4896f.js": "
+  "simple.336b919d.js": "
 var css = {
     \\"str\\": \\"\\\\\\"string\\\\\\"\\",
     \\"fooga\\": \\"fooga\\"