From e0c5eeefc61f669655f304781536909945094dba Mon Sep 17 00:00:00 2001 From: Pat Cavit Date: Tue, 5 Feb 2019 13:50:02 -0800 Subject: [PATCH] feat: processor.normalize() & corrected chunking logic (#562) * fix: functional CSS chunking algorithm Walks JS entries + CSS dep graph to determine ideal chunking locations and merges nodes together until it gets there. Probably a bit slower than before but *way* more accurate and safer to use. * fix: change asset naming to not depend on entries There were just too many issues around trying to parse out the source filename, so for now we use the CSS files instead. * feat: processor.normalize() Because sometimes you need external access to cleaned-up paths Fixes #559 Fixes #560 --- packages/namer/namer.js | 12 +- packages/processor/processor.js | 29 +- .../test/__snapshots__/api.test.js.snap | 6 + packages/processor/test/api.test.js | 28 +- packages/processor/test/getters.test.js | 11 + packages/rollup-rewriter/rewriter.js | 48 ++- .../test/__snapshots__/rewriter.test.js.snap | 80 ++--- packages/rollup/chunker.js | 136 +++++++++ packages/rollup/package.json | 5 +- packages/rollup/parser.js | 32 -- packages/rollup/rollup.js | 196 +++++------- .../test/__snapshots__/rollup.test.js.snap | 60 ++-- .../test/__snapshots__/splitting.test.js.snap | 282 ++++++++---------- .../test/__snapshots__/watch.test.js.snap | 222 ++++++++++---- packages/rollup/test/rollup.test.js | 5 +- .../test/specimens/css-dependencies/a.css | 5 + .../test/specimens/css-dependencies/a.js | 3 + .../test/specimens/css-dependencies/b.css | 5 + .../test/specimens/css-dependencies/b.js | 3 + .../test/specimens/css-dependencies/c.css | 3 + .../test/specimens/multiple-chunks/a.css | 4 +- .../test/specimens/multiple-chunks/a.js | 11 +- .../test/specimens/multiple-chunks/b.css | 2 +- .../test/specimens/multiple-chunks/b.js | 11 +- .../test/specimens/multiple-chunks/c.css | 5 - .../test/specimens/multiple-chunks/c.js | 4 - .../test/specimens/multiple-chunks/common.js | 1 - .../specimens/multiple-chunks/constants.css | 1 - .../test/specimens/multiple-chunks/d.css | 5 - .../test/specimens/multiple-chunks/d.js | 4 - .../test/specimens/multiple-chunks/e.js | 1 - .../test/specimens/multiple-chunks/shared.css | 5 - .../specimens/multiple-chunks/subfolder/a.css | 1 + packages/rollup/test/splitting.test.js | 29 ++ packages/rollup/test/watch.test.js | 38 +-- packages/scratchpad/.npmignore | 5 + packages/scratchpad/CHANGELOG.md | 5 + packages/scratchpad/LICENSE | 21 ++ packages/scratchpad/chunks.js | 135 +++++++++ packages/scratchpad/chunktest.js | 46 +++ packages/scratchpad/package.json | 10 + .../test/__snapshots__/chunks.test.js.snap | 135 +++++++++ packages/scratchpad/test/chunks.test.js | 60 ++++ packages/scratchpad/test/construct.js | 35 +++ packages/scratchpad/test/snapshot.js | 18 ++ 45 files changed, 1210 insertions(+), 553 deletions(-) create mode 100644 packages/rollup/chunker.js delete mode 100644 packages/rollup/parser.js create mode 100644 packages/rollup/test/specimens/css-dependencies/a.css create mode 100644 packages/rollup/test/specimens/css-dependencies/a.js create mode 100644 packages/rollup/test/specimens/css-dependencies/b.css create mode 100644 packages/rollup/test/specimens/css-dependencies/b.js create mode 100644 packages/rollup/test/specimens/css-dependencies/c.css delete mode 100644 packages/rollup/test/specimens/multiple-chunks/c.css delete mode 100644 packages/rollup/test/specimens/multiple-chunks/c.js delete mode 100644 packages/rollup/test/specimens/multiple-chunks/common.js delete mode 100644 packages/rollup/test/specimens/multiple-chunks/constants.css delete mode 100644 packages/rollup/test/specimens/multiple-chunks/d.css delete mode 100644 packages/rollup/test/specimens/multiple-chunks/d.js delete mode 100644 packages/rollup/test/specimens/multiple-chunks/e.js delete mode 100644 packages/rollup/test/specimens/multiple-chunks/shared.css create mode 100644 packages/rollup/test/specimens/multiple-chunks/subfolder/a.css create mode 100644 packages/scratchpad/.npmignore create mode 100644 packages/scratchpad/CHANGELOG.md create mode 100644 packages/scratchpad/LICENSE create mode 100644 packages/scratchpad/chunks.js create mode 100644 packages/scratchpad/chunktest.js create mode 100644 packages/scratchpad/package.json create mode 100644 packages/scratchpad/test/__snapshots__/chunks.test.js.snap create mode 100644 packages/scratchpad/test/chunks.test.js create mode 100644 packages/scratchpad/test/construct.js create mode 100644 packages/scratchpad/test/snapshot.js diff --git a/packages/namer/namer.js b/packages/namer/namer.js index 383812ef8..04ecd19dd 100644 --- a/packages/namer/namer.js +++ b/packages/namer/namer.js @@ -36,13 +36,13 @@ module.exports = () => { const { id, selectors } = meta.get(file); - // Has to use "in" because they can be 0 which is falsey - if(!selectors.has(selector)) { - selectors.set(selector, selectors.size); - } + // Don't need to cache this because repeats were caught by the cache up above + selectors.set(selector, selectors.size); + + const output = value(letters, id) + value(everything, selectors.get(selector)); - cache[key] = value(letters, id) + value(everything, selectors.get(selector)); + cache.set(key, output); - return cache[key]; + return output; }; }; diff --git a/packages/processor/processor.js b/packages/processor/processor.js index d6f40d186..1557924e7 100644 --- a/packages/processor/processor.js +++ b/packages/processor/processor.js @@ -145,6 +145,11 @@ class Processor { return files; } + // Return the corrected-path version of the file + normalize(file) { + return this._normalize(file); + } + // Check if a file exists in the currently-processed set has(input) { const file = this._normalize(input); @@ -167,7 +172,7 @@ class Processor { } const deps = this.dependents(source); - + deps.concat(source).forEach((file) => { this._log("invalidate()", file); @@ -176,29 +181,33 @@ class Processor { } // Get the dependency order for a file or the entire tree - dependencies(file) { + dependencies(file, options = false) { + const { leavesOnly } = options; + if(file) { const id = this._normalize(file); - return this._graph.dependenciesOf(id); + return this._graph.dependenciesOf(id, leavesOnly); } - return this._graph.overallOrder(); + return this._graph.overallOrder(leavesOnly); } // Get the dependant files for a file - dependents(file) { + dependents(file, options = false) { if(!file) { throw new Error("Must provide a file to processor.dependants()"); } const id = this._normalize(file); + const { leavesOnly } = options; - return this._graph.dependantsOf(id); + return this._graph.dependantsOf(id, leavesOnly); } // Get the ultimate output for specific files or the entire tree async output(args = false) { + const { to } = args; let { files } = args; if(!Array.isArray(files)) { @@ -225,7 +234,6 @@ class Processor { // Rewrite relative URLs before adding // Have to do this every time because target file might be different! - // const results = []; for(const dep of files) { @@ -240,7 +248,7 @@ class Processor { params(this, { from : dep, - to : args.to, + to, }) ); @@ -308,6 +316,11 @@ class Processor { return this._options; } + // Expose the dependency graph + get graph() { + return this._graph; + } + // Return all the compositions for the files loaded into the processor instance get compositions() { // Ensure all files are fully-processed first diff --git a/packages/processor/test/__snapshots__/api.test.js.snap b/packages/processor/test/__snapshots__/api.test.js.snap index 2152efd8f..080759ecc 100644 --- a/packages/processor/test/__snapshots__/api.test.js.snap +++ b/packages/processor/test/__snapshots__/api.test.js.snap @@ -145,6 +145,12 @@ exports[`/processor.js API .invalidate() should throw if an invalid file is pass exports[`/processor.js API .invalidate() should throw if no file is passed 1`] = `"invalidate() requires a file argument"`; +exports[`/processor.js API .normalize() should normalize inputs 1`] = ` +Array [ + "simple.css", +] +`; + exports[`/processor.js API .output() should allow for seperate source map output 1`] = ` Object { "file": "to.css", diff --git a/packages/processor/test/api.test.js b/packages/processor/test/api.test.js index e8847920b..49ff534ab 100644 --- a/packages/processor/test/api.test.js +++ b/packages/processor/test/api.test.js @@ -35,7 +35,6 @@ describe("/processor.js", () => { describe(".file()", () => { it("should process a relative file", async () => { const result = await processor.file("./packages/processor/test/specimens/simple.css"); - expect(result.exports).toMatchSnapshot(); expect(result.details.exports).toMatchSnapshot(); @@ -55,6 +54,33 @@ describe("/processor.js", () => { }); }); + describe(".has()", () => { + it("should return a boolean", async () => { + await processor.string( + "./simple.css", + ".wooga { }" + ); + + expect(processor.has("./simple.css")).toBe(true); + expect(processor.has("./nope.css")).toBe(false); + }); + + it("should normalize inputs before checking for existence", async () => { + await processor.string( + "./simple.css", + ".wooga { }" + ); + + expect(processor.has("../modular-css/simple.css")).toBe(true); + }); + }); + + describe(".normalize()", () => { + it("should normalize inputs", async () => { + expect(relative([ processor.normalize("../modular-css/simple.css") ])).toMatchSnapshot(); + }); + }); + describe(".remove()", () => { it("should remove a relative file", async () => { await processor.string( diff --git a/packages/processor/test/getters.test.js b/packages/processor/test/getters.test.js index 9d87cfe56..61dd149e5 100644 --- a/packages/processor/test/getters.test.js +++ b/packages/processor/test/getters.test.js @@ -1,5 +1,7 @@ "use strict"; +const { DepGraph } = require("dependency-graph"); + const namer = require("@modular-css/test-utils/namer.js"); const relative = require("@modular-css/test-utils/relative.js"); const Processor = require("../processor.js"); @@ -31,5 +33,14 @@ describe("/processor.js", () => { expect(typeof processor.options).toBe("object") ); }); + + describe(".graph", () => { + it("should return the dependency graph for added CSS files", async () => { + await processor.file("./packages/processor/test/specimens/start.css"); + await processor.file("./packages/processor/test/specimens/local.css"); + + expect(processor.graph).toBeInstanceOf(DepGraph); + }); + }); }); }); diff --git a/packages/rollup-rewriter/rewriter.js b/packages/rollup-rewriter/rewriter.js index 4797385a5..308667f0d 100644 --- a/packages/rollup-rewriter/rewriter.js +++ b/packages/rollup-rewriter/rewriter.js @@ -3,6 +3,7 @@ const MagicString = require("magic-string"); const dedent = require("dedent"); const escape = require("escape-string-regexp"); +const { DepGraph } = require("dependency-graph"); const formats = { es : require("./formats/es.js"), @@ -38,20 +39,34 @@ module.exports = (opts) => { this.error(`Unsupported format: ${format}. Supported formats are ${JSON.stringify([ ...supported.values() ])}`); } + const entries = new Map(); + const graph = new DepGraph({ circular : true }); + Object.entries(chunks).forEach(([ entry, chunk ]) => { - const { - isAsset = false, - code = "", - dynamicImports = [], - } = chunk; + const { isAsset, imports, dynamicImports } = chunk; - // Guard against https://github.com/rollup/rollup/issues/2659 - const deps = dynamicImports.filter(Boolean); - - if(isAsset || !deps.length) { + if(isAsset) { return; } + // Guard against https://github.com/rollup/rollup/issues/2659 + const imported = dynamicImports.filter(Boolean); + + if(imported.length) { + entries.set(entry, imported); + } + + graph.addNode(entry); + + imported.forEach((file) => { + graph.addNode(file); + graph.addDependency(entry, file); + }); + }); + + entries.forEach((deps, entry) => { + const { code } = chunks[entry]; + const { regex, loader, load } = formats[format]; const search = regex(deps.map(escape).join("|")); @@ -72,10 +87,17 @@ module.exports = (opts) => { const [ statement, file ] = result; const { index } = result; - const { dynamicAssets : assets = false } = chunks[file]; + // eslint-disable-next-line no-loop-func + const css = [ ...graph.dependenciesOf(file), file ].reduce((out, curr) => { + const { assets = [] } = chunks[curr]; + + assets.forEach((asset) => out.add(asset)); + + return out; + }, new Set()); - if(assets && assets.length) { - const imports = assets.map((dep) => + if(css.size) { + const imports = [ ...css ].map((dep) => `${options.loadfn}("./${dep}")` ); @@ -91,7 +113,7 @@ module.exports = (opts) => { log("Updating", entry); - chunk.code = str.toString(); + chunks[entry].code = str.toString(); }); }, }; diff --git a/packages/rollup-rewriter/test/__snapshots__/rewriter.test.js.snap b/packages/rollup-rewriter/test/__snapshots__/rewriter.test.js.snap index 2394265d1..133103921 100644 --- a/packages/rollup-rewriter/test/__snapshots__/rewriter.test.js.snap +++ b/packages/rollup-rewriter/test/__snapshots__/rewriter.test.js.snap @@ -29,7 +29,7 @@ define(['require'], function (require) { 'use strict'; console.log(css); new Promise(function (resolve, reject) { Promise.all([ - lazyload(\\"./assets/chunk.css\\"), + lazyload(\\"./assets/c.css\\"), new Promise(function (resolve, reject) { require(['./chunk.js'], resolve, reject) }) ]) .then((results) => resolve(results[results.length - 1])) @@ -45,7 +45,7 @@ define(['require'], function (require) { 'use strict'; /* packages/rollup-rewriter/test/specimens/no-asset-imports/b.css */ .b { color: blue; } ", - "assets/chunk.css": " + "assets/c.css": " /* packages/rollup-rewriter/test/specimens/no-asset-imports/c.css */ .c { color: crimson; } ", @@ -99,7 +99,7 @@ var css = { console.log(css); Promise.all([ - lazyload(\\"./assets/chunk.css\\"), + lazyload(\\"./assets/c.css\\"), import('./chunk.js') ]) .then((results) => results[results.length - 1]).then(console.log); @@ -112,7 +112,7 @@ Promise.all([ /* packages/rollup-rewriter/test/specimens/no-asset-imports/b.css */ .b { color: blue; } ", - "assets/chunk.css": " + "assets/c.css": " /* packages/rollup-rewriter/test/specimens/no-asset-imports/c.css */ .c { color: crimson; } ", @@ -154,7 +154,7 @@ var css = { console.log(css); Promise.all([ - lazyload(\\"./assets/chunk.css\\"), + lazyload(\\"./assets/c.css\\"), import('./chunk.js') ]) .then((results) => results[results.length - 1]).then(console.log); @@ -167,7 +167,7 @@ Promise.all([ /* packages/rollup-rewriter/test/specimens/no-asset-imports/b.css */ .b { color: blue; } ", - "assets/chunk.css": " + "assets/c.css": " /* packages/rollup-rewriter/test/specimens/no-asset-imports/c.css */ .c { color: crimson; } ", @@ -214,7 +214,7 @@ System.register([], function (exports, module) { console.log(css); Promise.all([ - lazyload(\\"./assets/chunk.css\\"), + lazyload(\\"./assets/c.css\\"), module.import('./chunk.js') ]) .then((results) => results[results.length - 1]).then(console.log); @@ -231,7 +231,7 @@ System.register([], function (exports, module) { /* packages/rollup-rewriter/test/specimens/no-asset-imports/b.css */ .b { color: blue; } ", - "assets/chunk.css": " + "assets/c.css": " /* packages/rollup-rewriter/test/specimens/no-asset-imports/c.css */ .c { color: crimson; } ", @@ -300,7 +300,7 @@ import lazyload from \\"./css.js\\"; console.log(css); new Promise(function (resolve, reject) { Promise.all([ - lazyload(\\"./assets/chunk.css\\"), + lazyload(\\"./assets/c.css\\"), new Promise(function (resolve, reject) { require(['./chunk.js'], resolve, reject) }) ]) .then((results) => resolve(results[results.length - 1])) @@ -323,13 +323,13 @@ import lazyload from \\"./css.js\\"; color: blue; } ", - "assets/chunk.css": " + "assets/c.css": " /* packages/rollup-rewriter/test/specimens/dynamic-imports/c.css */ .c { color: cyan; } ", - "assets/chunk2.css": " + "assets/d.css": " /* packages/rollup-rewriter/test/specimens/dynamic-imports/d.css */ .d { color: darkred; @@ -347,7 +347,7 @@ import lazyload from \\"./css.js\\"; console.log(css); new Promise(function (resolve, reject) { Promise.all([ - lazyload(\\"./assets/chunk2.css\\"), + lazyload(\\"./assets/d.css\\"), new Promise(function (resolve, reject) { require(['./chunk2.js'], resolve, reject) }) ]) .then((results) => resolve(results[results.length - 1])) @@ -399,7 +399,7 @@ function a$1() { console.log(css); Promise.all([ - lazyload(\\"./assets/chunk.css\\"), + lazyload(\\"./assets/c.css\\"), import('./chunk.js') ]) .then((results) => results[results.length - 1]).then(console.log); @@ -419,13 +419,13 @@ export default a$1; color: blue; } ", - "assets/chunk.css": " + "assets/c.css": " /* packages/rollup-rewriter/test/specimens/dynamic-imports/c.css */ .c { color: cyan; } ", - "assets/chunk2.css": " + "assets/d.css": " /* packages/rollup-rewriter/test/specimens/dynamic-imports/d.css */ .d { color: darkred; @@ -441,7 +441,7 @@ function b$1() { console.log(css); Promise.all([ - lazyload(\\"./assets/chunk2.css\\"), + lazyload(\\"./assets/d.css\\"), import('./chunk2.js') ]) .then((results) => results[results.length - 1]).then(console.log); @@ -482,7 +482,7 @@ function a$1() { console.log(css); Promise.all([ - lazyload(\\"./assets/chunk.css\\"), + lazyload(\\"./assets/c.css\\"), import('./chunk.js') ]) .then((results) => results[results.length - 1]).then(console.log); @@ -502,13 +502,13 @@ export default a$1; color: blue; } ", - "assets/chunk.css": " + "assets/c.css": " /* packages/rollup-rewriter/test/specimens/dynamic-imports/c.css */ .c { color: cyan; } ", - "assets/chunk2.css": " + "assets/d.css": " /* packages/rollup-rewriter/test/specimens/dynamic-imports/d.css */ .d { color: darkred; @@ -524,7 +524,7 @@ function b$1() { console.log(css); Promise.all([ - lazyload(\\"./assets/chunk2.css\\"), + lazyload(\\"./assets/d.css\\"), import('./chunk2.js') ]) .then((results) => results[results.length - 1]).then(console.log); @@ -572,7 +572,7 @@ import lazyload from \\"./css.js\\"; console.log(css); Promise.all([ - lazyload(\\"./assets/chunk.css\\"), + lazyload(\\"./assets/c.css\\"), module.import('./chunk.js') ]) .then((results) => results[results.length - 1]).then(console.log); @@ -594,13 +594,13 @@ import lazyload from \\"./css.js\\"; color: blue; } ", - "assets/chunk.css": " + "assets/c.css": " /* packages/rollup-rewriter/test/specimens/dynamic-imports/c.css */ .c { color: cyan; } ", - "assets/chunk2.css": " + "assets/d.css": " /* packages/rollup-rewriter/test/specimens/dynamic-imports/d.css */ .d { color: darkred; @@ -623,7 +623,7 @@ import lazyload from \\"./css.js\\"; console.log(css); Promise.all([ - lazyload(\\"./assets/chunk2.css\\"), + lazyload(\\"./assets/d.css\\"), module.import('./chunk2.js') ]) .then((results) => results[results.length - 1]).then(console.log); @@ -683,7 +683,7 @@ define(['require'], function (require) { 'use strict'; console.log(css); new Promise(function (resolve, reject) { Promise.all([ - lazyload(\\"./assets/chunk.css\\"), + lazyload(\\"./assets/c.css\\"), new Promise(function (resolve, reject) { require(['./chunk.js'], resolve, reject) }) ]) .then((results) => resolve(results[results.length - 1])) @@ -706,13 +706,13 @@ define(['require'], function (require) { 'use strict'; color: blue; } ", - "assets/chunk.css": " + "assets/c.css": " /* packages/rollup-rewriter/test/specimens/dynamic-imports/c.css */ .c { color: cyan; } ", - "assets/chunk2.css": " + "assets/d.css": " /* packages/rollup-rewriter/test/specimens/dynamic-imports/d.css */ .d { color: darkred; @@ -729,7 +729,7 @@ define(['require'], function (require) { 'use strict'; console.log(css); new Promise(function (resolve, reject) { Promise.all([ - lazyload(\\"./assets/chunk2.css\\"), + lazyload(\\"./assets/d.css\\"), new Promise(function (resolve, reject) { require(['./chunk2.js'], resolve, reject) }) ]) .then((results) => resolve(results[results.length - 1])) @@ -780,7 +780,7 @@ function a$1() { console.log(css); Promise.all([ - lazyload(\\"./assets/chunk.css\\"), + lazyload(\\"./assets/c.css\\"), import('./chunk.js') ]) .then((results) => results[results.length - 1]).then(console.log); @@ -800,13 +800,13 @@ export default a$1; color: blue; } ", - "assets/chunk.css": " + "assets/c.css": " /* packages/rollup-rewriter/test/specimens/dynamic-imports/c.css */ .c { color: cyan; } ", - "assets/chunk2.css": " + "assets/d.css": " /* packages/rollup-rewriter/test/specimens/dynamic-imports/d.css */ .d { color: darkred; @@ -821,7 +821,7 @@ function b$1() { console.log(css); Promise.all([ - lazyload(\\"./assets/chunk2.css\\"), + lazyload(\\"./assets/d.css\\"), import('./chunk2.js') ]) .then((results) => results[results.length - 1]).then(console.log); @@ -861,7 +861,7 @@ function a$1() { console.log(css); Promise.all([ - lazyload(\\"./assets/chunk.css\\"), + lazyload(\\"./assets/c.css\\"), import('./chunk.js') ]) .then((results) => results[results.length - 1]).then(console.log); @@ -881,13 +881,13 @@ export default a$1; color: blue; } ", - "assets/chunk.css": " + "assets/c.css": " /* packages/rollup-rewriter/test/specimens/dynamic-imports/c.css */ .c { color: cyan; } ", - "assets/chunk2.css": " + "assets/d.css": " /* packages/rollup-rewriter/test/specimens/dynamic-imports/d.css */ .d { color: darkred; @@ -902,7 +902,7 @@ function b$1() { console.log(css); Promise.all([ - lazyload(\\"./assets/chunk2.css\\"), + lazyload(\\"./assets/d.css\\"), import('./chunk2.js') ]) .then((results) => results[results.length - 1]).then(console.log); @@ -949,7 +949,7 @@ System.register([], function (exports, module) { console.log(css); Promise.all([ - lazyload(\\"./assets/chunk.css\\"), + lazyload(\\"./assets/c.css\\"), module.import('./chunk.js') ]) .then((results) => results[results.length - 1]).then(console.log); @@ -971,13 +971,13 @@ System.register([], function (exports, module) { color: blue; } ", - "assets/chunk.css": " + "assets/c.css": " /* packages/rollup-rewriter/test/specimens/dynamic-imports/c.css */ .c { color: cyan; } ", - "assets/chunk2.css": " + "assets/d.css": " /* packages/rollup-rewriter/test/specimens/dynamic-imports/d.css */ .d { color: darkred; @@ -999,7 +999,7 @@ System.register([], function (exports, module) { console.log(css); Promise.all([ - lazyload(\\"./assets/chunk2.css\\"), + lazyload(\\"./assets/d.css\\"), module.import('./chunk2.js') ]) .then((results) => results[results.length - 1]).then(console.log); diff --git a/packages/rollup/chunker.js b/packages/rollup/chunker.js new file mode 100644 index 000000000..a01d5d312 --- /dev/null +++ b/packages/rollup/chunker.js @@ -0,0 +1,136 @@ +/* eslint-disable no-loop-func */ +"use strict"; + +const merge = (graph, original, target) => { + const incoming = graph.incomingEdges[original]; + const outgoing = graph.outgoingEdges[original]; + + graph.getNodeData(target).unshift(original); + + incoming.forEach((src) => { + graph.removeDependency(src, original); + + if(src !== target) { + graph.addDependency(src, target); + } + }); + + outgoing.forEach((dest) => { + graph.removeDependency(original, dest); + + if(dest !== target) { + graph.addDependency(target, dest); + } + }); + + // Bye bye + graph.removeNode(original); +}; + +const setsContain = (a, b) => { + for(const val of a) { + if(!b.has(val)) { + return false; + } + } + + return true; +}; + +const setsMatch = (a, b) => { + if(a.size !== b.size) { + return false; + } + + return setsContain(a, b); +}; + +const chunks = ({ entries, graph }) => { + const result = graph.clone(); + + // Lookups + const nodeToEntries = new Map(); + const entryToNodes = new Map(); + + // Keeping track of nodes we've already handled + const chunked = new Set(); + + // First build up a map of entry -> set of deps for comparisons + entries.forEach((entry) => { + const deps = result.dependenciesOf(entry).reverse(); + + if(!entryToNodes.has(entry)) { + entryToNodes.set(entry, new Set()); + } + + deps.forEach((node) => { + if(!nodeToEntries.has(node)) { + nodeToEntries.set(node, new Set()); + } + + nodeToEntries.get(node).add(entry); + entryToNodes.get(entry).add(node); + }); + }); + + // Keep looping until all nodes have been chunked (even if that's just into themselves) + while(chunked.size < nodeToEntries.size) { + // Iterate the nodes associated with each entry, + // and figure out if the node can be collapsed + entryToNodes.forEach((nodes, entry) => { + // Nodes that will be merged at the end of this iteration + const queued = new Set(); + + // This will be the first set of entries found in the walk, + // any subsequent nodes in this pass must match this to be combined + let branches; + + // If all the nodes for this branch are handled, skip it + if(setsContain(nodes, chunked)) { + return; + } + + // Walk the dependencies of the current entry in order + const branch = result.dependenciesOf(entry).reverse(); + + for(const node of branch) { + if(chunked.has(node)) { + continue; + } + + // Figure out the entries that reference this node + const containers = nodeToEntries.get(node); + + if(!branches) { + branches = containers; + } else if(!setsMatch(branches, containers)) { + // TODO: Can we get the order to have leaf nodes first so we could stop + // TODO: iteration on this branch at this point? Might have to implement BFS + // TODO: to get that info + continue; + } + + queued.add(node); + } + + // Merge all the queued nodes into the first node in the queue + let base; + + queued.forEach((node) => { + chunked.add(node); + + if(!base) { + base = node; + + return; + } + + merge(result, node, base); + }); + }); + } + + return result; +}; + +module.exports = chunks; diff --git a/packages/rollup/package.json b/packages/rollup/package.json index b3b5c6107..46b27807c 100644 --- a/packages/rollup/package.json +++ b/packages/rollup/package.json @@ -23,12 +23,9 @@ "dependencies": { "@modular-css/processor": "file:../processor", "dedent": "0.7.0", - "dependency-graph": "^0.8.0", "esutils": "^2.0.2", - "mkdirp": "^0.5.1", "rollup-pluginutils": "^2.0.1", - "slash": "^2.0.0", - "xregexp": "^4.2.4" + "slash": "^2.0.0" }, "peerDependencies": { "rollup": "^1.1.2" diff --git a/packages/rollup/parser.js b/packages/rollup/parser.js deleted file mode 100644 index a380044e7..000000000 --- a/packages/rollup/parser.js +++ /dev/null @@ -1,32 +0,0 @@ -"use strict"; - -const xregexp = require("xregexp"); - -// Parse a rollup template like "[name]-[hash][extname]" & a filename generated -// from that template into its constituent parts using a bunch of regex nonsense. -// Using xregexp because it allows for supporting node < 10 - -const patterns = new Map([ - [ "extname", "(?\\.\\w+)" ], - [ "ext", "(?\\w+)" ], - [ "hash", "(?[a-f0-9]{8})" ], - [ "name", "(?\\w+)" ], -]); - -const patternsRegex = new RegExp( - `\\[(${[ ...patterns.keys() ].join("|")})\\]`, - "ig" -); - -exports.parse = (template, name) => { - const marked = xregexp.escape( - template.replace(patternsRegex, (match, key) => `!!${key}!!`) - ); - - const parser = xregexp( - marked.replace(/!!(.+?)!!/g, (match, key) => patterns.get(key)), - "i" - ); - - return xregexp.exec(name, parser); -}; diff --git a/packages/rollup/rollup.js b/packages/rollup/rollup.js index 770d4b745..b3a0c05df 100644 --- a/packages/rollup/rollup.js +++ b/packages/rollup/rollup.js @@ -7,12 +7,11 @@ const { keyword } = require("esutils"); const utils = require("rollup-pluginutils"); const dedent = require("dedent"); const slash = require("slash"); -const Graph = require("dependency-graph").DepGraph; const Processor = require("@modular-css/processor"); const output = require("@modular-css/processor/lib/output.js"); -const { parse } = require("./parser.js"); +const chunker = require("./chunker.js"); // sourcemaps for css-to-js don't make much sense, so always return nothing // https://github.com/rollup/rollup/wiki/Plugins#conventions @@ -35,7 +34,6 @@ module.exports = (opts) => { const filter = utils.createFilter(options.include, options.exclude); const { - common, dev, done, map, @@ -97,6 +95,7 @@ module.exports = (opts) => { const relative = path.relative(processor.options.cwd, id); const out = [ + // Need to include this, watch mode doesn't catch all changes otherwise ಠ_ಠ ...processor.dependencies(id).map((dep) => `import ${JSON.stringify(dep)};`), dev ? dedent(` @@ -133,7 +132,7 @@ module.exports = (opts) => { out.push(`export var styles = ${JSON.stringify(details.result.css)};`); } - processor.dependencies(id).forEach((dependency) => this.addWatchFile(dependency)); + processor.dependencies(id).forEach((dep) => this.addWatchFile(dep)); return { code : out.join("\n"), @@ -149,111 +148,54 @@ module.exports = (opts) => { // Really wish rollup would provide these defaults somehow const { - chunkFileNames = "[name]-[hash].js", - entryFileNames = "[name].js", assetFileNames = "assets/[name]-[hash][extname]", } = outputOptions; // Determine the correct to option for PostCSS by doing a bit of a dance - let to; - - if(!outputOptions.file && !outputOptions.dir) { - to = path.join(processor.options.cwd, assetFileNames); - } else { - to = path.join( + const to = (!outputOptions.file && !outputOptions.dir) ? + path.join(processor.options.cwd, assetFileNames) : + path.join( outputOptions.dir ? outputOptions.dir : path.dirname(outputOptions.file), assetFileNames ); - } + + // Store an easy-to-use Set that maps all the entry files + const entries = new Set(); - // Build chunk dependency graph - const usage = new Graph({ circular : true }); + // Clone the processor graph so we can chunk it w/o making things crazy + const graph = processor.graph.clone(); + // Convert the graph over to a chunking-amenable format + graph.overallOrder().forEach((node) => graph.setNodeData(node, [ node ])); + + // Walk all bundle entries and add them to the dependency graph Object.entries(bundle).forEach(([ entry, chunk ]) => { - const { imports, dynamicImports } = chunk; - - const statics = imports.filter((dep) => dep in bundle); - const dynamics = dynamicImports.filter((dep) => dep in bundle); - - // Add all the nodes first, tagging them with their type for later - statics.forEach((dep) => usage.addNode(dep, "static")); - dynamics.forEach((dep) => usage.addNode(dep, "dynamic")); + const { isAsset, modules } = chunk; - // Then tag the entry node - usage.addNode(entry, "entry"); - - // And then add all the dependency links - [ ...dynamics, ...statics ].forEach((dep) => - usage.addDependency(entry, dep) - ); - }); - - // Output CSS chunks - const out = new Map(); - - // Keep track of files that are queued to be written - const queued = new Set(); - - usage.overallOrder().forEach((entry) => { - const css = new Set(); - const { modules, name, fileName, isEntry } = bundle[entry]; + /* istanbul ignore if */ + if(isAsset) { + return; + } // Get CSS files being used by this chunk - const styles = Object.keys(modules).filter((file) => processor.has(file)); - - // Get dependency chains for each css file & record them into the usage graph - styles.forEach((style) => { - processor - .dependencies(style) - .forEach((file) => css.add(file)); - - css.add(style); - }); - - // How does Set not have .filter yet ಠ_ಠ - const included = [ ...css ].filter((file) => !queued.has(file)); - - if(!included.length) { + const css = Object.keys(modules).filter((file) => processor.has(file)); + + if(!css.length) { return; } - // Parse out the name part from the resulting filename, - // based on the module's template (either entry or chunk) - let dest; - const template = isEntry ? entryFileNames : chunkFileNames; - - if(template.includes("[hash]")) { - const parts = parse(template, fileName); + entries.add(entry); - dest = parts.name; - } else { - // Want to use source chunk name when code-splitting, otherwise match bundle name - dest = outputOptions.dir ? name : path.basename(entry, path.extname(entry)); - } + graph.addNode(entry, [ entry ]); - out.set(entry, { - name : dest, - files : included - }); - - // Flag all the files that are queued for writing so they don't get double-output - css.forEach((file) => queued.add(file)); + css.forEach((file) => graph.addDependency(entry, processor.normalize(file))); }); - // Figure out if there were any CSS files that the JS didn't reference - const unused = processor - .dependencies() - .filter((css) => !queued.has(css)); - - // Shove any unreferenced CSS files onto the beginning of the first chunk - if(unused.length) { - out.set("unused", { - // .css extension is added automatically down below, so strip in case it was specified - name : common.replace(path.extname(common), ""), - files : unused, - dependencies : [], - }); - } + // Output CSS chunks + const chunked = chunker({ + graph, + entries : [ ...entries ], + }); // If assets are being hashed then the automatic annotation has to be disabled // because it won't include the hashed value and will lead to badness @@ -267,27 +209,37 @@ module.exports = (opts) => { ); } - for(const [ entry, value ] of out.entries()) { - const { name, files } = value; + // Track specified name -> output name for writing out metadata later + const names = new Map(); - const id = this.emitAsset(`${name}.css`); + for(const node of chunked.overallOrder()) { + // Only want to deal with CSS currently + if(entries.has(node)) { + continue; + } + + const { name, ext, base } = path.parse(node); + const id = this.emitAsset(base); /* eslint-disable-next-line no-await-in-loop */ const result = await processor.output({ // Can't use this.getAssetFileName() here, because the source hasn't been set yet // Have to do our best to come up with a valid final location though... - to : to.replace(/\[(name|extname)\]/g, (match, field) => (field === "name" ? name : ".css")), + to : to.replace(/\[(name|extname)\]/g, (match, field) => (field === "name" ? name : ext)), map : mapOpt, - files, + files : graph.getNodeData(node), }); - log("css output", `${name}.css`); - + this.setAssetSource(id, result.css); - + // Save off the final name of this asset for later use const dest = this.getAssetFileName(id); + + names.set(node, dest); + + 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. @@ -295,7 +247,7 @@ module.exports = (opts) => { if(result.map) { // Make sure to use the rollup name as the base, otherwise it won't // automatically handle duplicate names correctly - const fileName = dest.replace(".css", ".css.map"); + const fileName = dest.replace(ext, `${ext}.map`); log("map output", fileName); @@ -311,20 +263,6 @@ module.exports = (opts) => { bundle[dest].source += `\n/*# sourceMappingURL=${path.basename(fileName)} */`; } } - - if(entry in bundle) { - // Attach info about this asset to the bundle - const { assets = [], dynamicAssets = [] } = bundle[entry]; - - if(usage.getNodeData(entry) === "dynamic") { - dynamicAssets.push(dest); - } else { - assets.push(dest); - } - - bundle[entry].assets = assets; - bundle[entry].dynamicAssets = dynamicAssets; - } } if(options.json) { @@ -337,25 +275,31 @@ module.exports = (opts) => { this.emitAsset(dest, JSON.stringify(compositions, null, 4)); } - if(options.meta) { - const dest = typeof options.meta === "string" ? options.meta : "metadata.json"; + const meta = {}; - log("metadata output", dest); + Object.entries(bundle).forEach(([ entry, chunk ]) => { + const { isAsset } = chunk; - const meta = {}; + if(isAsset || !entries.has(entry)) { + return; + } - out.forEach((value, entry) => { - if(!bundle[entry]) { - return; - } + // Attach info about this asset to the bundle + const { assets = [] } = chunk; - const { assets, dynamicAssets } = bundle[entry]; + chunked.dependenciesOf(entry).forEach((dep) => assets.push(names.get(dep))); - meta[entry] = { - assets, - dynamicAssets, - }; - }); + chunk.assets = assets; + + meta[entry] = { + assets, + }; + }); + + if(options.meta) { + const dest = typeof options.meta === "string" ? options.meta : "metadata.json"; + + log("metadata output", dest); this.emitAsset(dest, JSON.stringify(meta, null, 4)); } diff --git a/packages/rollup/test/__snapshots__/rollup.test.js.snap b/packages/rollup/test/__snapshots__/rollup.test.js.snap index 2281f6960..744cec396 100644 --- a/packages/rollup/test/__snapshots__/rollup.test.js.snap +++ b/packages/rollup/test/__snapshots__/rollup.test.js.snap @@ -3,7 +3,7 @@ exports[`/rollup.js case sensitivity tests should remove repeated references that point at the same files 1`] = ` Array [ Object { - "file": "assets/main.css", + "file": "assets/foo.css", "text": "/* packages/rollup/test/specimens/casing/bar.css */ .mc79ab9c62_bar { display: none; @@ -30,7 +30,7 @@ console.log({ foo: foo$1, bar: bar$3, bar2: bar$1 }); exports[`/rollup.js should accept an existing processor instance (no css in bundle) 1`] = ` Array [ Object { - "file": "common.css", + "file": "fake.css", "text": "/* packages/rollup/test/specimens/fake.css */ .fake { color: yellow; @@ -42,14 +42,14 @@ Array [ exports[`/rollup.js should accept an existing processor instance 1`] = ` Array [ Object { - "file": "common.css", + "file": "fake.css", "text": "/* packages/rollup/test/specimens/fake.css */ .fake { color: yellow; }", }, Object { - "file": "existing-processor.css", + "file": "simple.css", "text": "/* packages/rollup/test/specimens/simple.css */ .fooga { color: red; @@ -83,7 +83,7 @@ console.log(fooga); exports[`/rollup.js should correctly handle hashed output 1`] = ` Array [ Object { - "file": "assets/hashes-8d1bcf7b.css", + "file": "assets/simple-8d1bcf7b.css", "text": "/* packages/rollup/test/specimens/simple.css */ .fooga { color: red; @@ -115,17 +115,17 @@ Array [ }", }, Object { - "file": "assets/hashes-8d1bcf7b.css", + "file": "assets/simple-8d1bcf7b.css", "text": "/* packages/rollup/test/specimens/simple.css */ .fooga { color: red; } -/*# sourceMappingURL=hashes-8d1bcf7b.css.map */", +/*# sourceMappingURL=simple-8d1bcf7b.css.map */", }, Object { - "file": "assets/hashes-8d1bcf7b.css.map", - "text": "{\\"version\\":3,\\"sources\\":[\\"../../../specimens/simple.css\\"],\\"names\\":[],\\"mappings\\":\\"AAAA,8CAAA;AAEA;IACI,UAAU;AACd\\",\\"file\\":\\"hashes-[hash].css\\",\\"sourcesContent\\":[\\"@value str: \\\\\\"string\\\\\\";\\\\n\\\\n.fooga {\\\\n color: red;\\\\n}\\\\n\\"]}", + "file": "assets/simple-8d1bcf7b.css.map", + "text": "{\\"version\\":3,\\"sources\\":[\\"../../../specimens/simple.css\\"],\\"names\\":[],\\"mappings\\":\\"AAAA,8CAAA;AAEA;IACI,UAAU;AACd\\",\\"file\\":\\"simple-[hash].css\\",\\"sourcesContent\\":[\\"@value str: \\\\\\"string\\\\\\";\\\\n\\\\n.fooga {\\\\n color: red;\\\\n}\\\\n\\"]}", }, Object { "file": "hashes.js", @@ -263,7 +263,7 @@ Array [ Array [ "[rollup]", "css output", - "simple.css", + "assets/simple.css", ], Array [ "[processor]", @@ -397,6 +397,26 @@ console.log(css); } `; +exports[`/rollup.js should output unreferenced CSS 1`] = ` +Array [ + Object { + "file": "fake.css", + "text": "/* packages/rollup/test/specimens/fake.css */ +.fake { + color: yellow; +}", + }, + Object { + "file": "simple.css", + "text": "/* packages/rollup/test/specimens/simple.css */ +.fooga { + color: red; +} +", + }, +] +`; + exports[`/rollup.js should provide named exports 1`] = ` Object { "named": "var str = \\"\\\\\\"string\\\\\\"\\"; @@ -440,26 +460,6 @@ exports[`/rollup.js should respect the CSS dependency tree 2`] = ` " `; -exports[`/rollup.js should use the common arg for unreferenced CSS 1`] = ` -Array [ - Object { - "file": "simple.css", - "text": "/* packages/rollup/test/specimens/simple.css */ -.fooga { - color: red; -} -", - }, - Object { - "file": "unreferenced.css", - "text": "/* packages/rollup/test/specimens/fake.css */ -.fake { - color: yellow; -}", - }, -] -`; - exports[`/rollup.js should warn & not export individual keys when they are not valid identifiers 1`] = ` Object { "code": "PLUGIN_WARNING", diff --git a/packages/rollup/test/__snapshots__/splitting.test.js.snap b/packages/rollup/test/__snapshots__/splitting.test.js.snap index 8f3304e61..ef0e8485f 100644 --- a/packages/rollup/test/__snapshots__/splitting.test.js.snap +++ b/packages/rollup/test/__snapshots__/splitting.test.js.snap @@ -1,71 +1,71 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`/rollup.js code splitting should dedupe chunk names using rollup's incrementing counter logic (hashed) 1`] = ` +exports[`/rollup.js code splitting should correctly chunk up CSS files 1`] = ` Array [ Object { - "file": "a-8dc3c49a.css", - "text": "/* packages/rollup/test/specimens/multiple-chunks/a.css */ + "file": "a.css", + "text": "/* packages/rollup/test/specimens/css-dependencies/a.css */ .a { color: aqua; } - -/*# sourceMappingURL=a-8dc3c49a.css.map */", - }, - Object { - "file": "a-8dc3c49a.css.map", - "text": "{\\"version\\":3,\\"sources\\":[\\"../../../specimens/multiple-chunks/a.css\\"],\\"names\\":[],\\"mappings\\":\\"AAAA,yDAAA;AAAA;IAGI,WAAW;AACf\\",\\"file\\":\\"a-[hash].css\\",\\"sourcesContent\\":[\\".a {\\\\n composes: shared from \\\\\\"./shared.css\\\\\\";\\\\n\\\\n color: aqua;\\\\n}\\\\n\\"]}", +", }, Object { - "file": "b-fc0ef588.css", - "text": "/* packages/rollup/test/specimens/multiple-chunks/b.css */ + "file": "b.css", + "text": "/* packages/rollup/test/specimens/css-dependencies/b.css */ .b { color: blue; } - -/*# sourceMappingURL=b-fc0ef588.css.map */", +", }, Object { - "file": "b-fc0ef588.css.map", - "text": "{\\"version\\":3,\\"sources\\":[\\"../../../specimens/multiple-chunks/b.css\\"],\\"names\\":[],\\"mappings\\":\\"AAAA,yDAAA;AAAA;IAGI,WAAW;AACf\\",\\"file\\":\\"b-[hash].css\\",\\"sourcesContent\\":[\\".b {\\\\n composes: shared from \\\\\\"./shared.css\\\\\\";\\\\n\\\\n color: blue;\\\\n}\\\\n\\"]}", + "file": "c.css", + "text": "/* packages/rollup/test/specimens/css-dependencies/c.css */ +.c { + color: cyan; +} +", }, - Object { - "file": "chunk-2f7464a0.css", - "text": "/* packages/rollup/test/specimens/multiple-chunks/constants.css */ /* packages/rollup/test/specimens/multiple-chunks/shared.css */ -.shared { color: salmon; } +] +`; -.other { color: blue; } +exports[`/rollup.js code splitting should dedupe chunk names using rollup's incrementing counter logic (hashed) 1`] = ` +Array [ + Object { + "file": "a-8dc3c49a.css", + "text": "/* packages/rollup/test/specimens/multiple-chunks/a.css */ +.a { + color: aqua; +} -/*# sourceMappingURL=chunk-2f7464a0.css.map */", +/*# sourceMappingURL=a-8dc3c49a.css.map */", }, Object { - "file": "chunk-2f7464a0.css.map", - "text": "{\\"version\\":3,\\"sources\\":[\\"../../../specimens/multiple-chunks/constants.css\\",\\"../../../specimens/multiple-chunks/shared.css\\"],\\"names\\":[],\\"mappings\\":\\"AAAA,iEAAA,ECAA,8DAAA;AAEA,UAAU,aAAa,EAAE;;AAEzB,SDJA,WAAiB,ECIK\\",\\"file\\":\\"chunk-[hash].css\\",\\"sourcesContent\\":[\\"@value main: blue;\\\\n\\",\\"@value main from \\\\\\"./constants.css\\\\\\";\\\\n\\\\n.shared { color: salmon; }\\\\n\\\\n.other { color: main; }\\\\n\\"]}", + "file": "a-8dc3c49a.css.map", + "text": "{\\"version\\":3,\\"sources\\":[\\"../../../specimens/multiple-chunks/a.css\\"],\\"names\\":[],\\"mappings\\":\\"AAAA,yDAAA;AAAA;IAGI,WAAW;AACf\\",\\"file\\":\\"a-[hash].css\\",\\"sourcesContent\\":[\\".a {\\\\n composes: a from \\\\\\"./subfolder/a.css\\\\\\";\\\\n \\\\n color: aqua;\\\\n}\\\\n\\"]}", }, Object { - "file": "chunk-48a593d7.css", - "text": "/* packages/rollup/test/specimens/multiple-chunks/c.css */ -.c { - color: coral; -} - -/*# sourceMappingURL=chunk-48a593d7.css.map */", + "file": "a-bfb0bddb.css", + "text": "/* packages/rollup/test/specimens/multiple-chunks/subfolder/a.css */ +.a { color: azure; } +/*# sourceMappingURL=a-bfb0bddb.css.map */", }, Object { - "file": "chunk-48a593d7.css.map", - "text": "{\\"version\\":3,\\"sources\\":[\\"../../../specimens/multiple-chunks/packages/rollup/test/specimens/multiple-chunks/b.css\\"],\\"names\\":[],\\"mappings\\":\\"AAAA,yDAAA;AAAA;IAGI,YAAW;AACf\\",\\"file\\":\\"chunk-[hash].css\\",\\"sourcesContent\\":[\\".b {\\\\n composes: shared from \\\\\\"./shared.css\\\\\\";\\\\n\\\\n color: blue;\\\\n}\\\\n\\"]}", + "file": "a-bfb0bddb.css.map", + "text": "{\\"version\\":3,\\"sources\\":[\\"../../../specimens/multiple-chunks/subfolder/a.css\\"],\\"names\\":[],\\"mappings\\":\\"AAAA,mEAAA;AAAA,KAAK,YAAY,EAAE\\",\\"file\\":\\"a-[hash].css\\",\\"sourcesContent\\":[\\".a { color: azure; }\\"]}", }, Object { - "file": "chunk-ee76f9f0.css", - "text": "/* packages/rollup/test/specimens/multiple-chunks/d.css */ -.d { - color: deepskyblue; + "file": "b-fc0ef588.css", + "text": "/* packages/rollup/test/specimens/multiple-chunks/b.css */ +.b { + color: blue; } -/*# sourceMappingURL=chunk-ee76f9f0.css.map */", +/*# sourceMappingURL=b-fc0ef588.css.map */", }, Object { - "file": "chunk-ee76f9f0.css.map", - "text": "{\\"version\\":3,\\"sources\\":[\\"../../../specimens/multiple-chunks/packages/rollup/test/specimens/multiple-chunks/b.css\\"],\\"names\\":[],\\"mappings\\":\\"AAAA,yDAAA;AAAA;IAGI,kBAAW;AACf\\",\\"file\\":\\"chunk-[hash].css\\",\\"sourcesContent\\":[\\".b {\\\\n composes: shared from \\\\\\"./shared.css\\\\\\";\\\\n\\\\n color: blue;\\\\n}\\\\n\\"]}", + "file": "b-fc0ef588.css.map", + "text": "{\\"version\\":3,\\"sources\\":[\\"../../../specimens/multiple-chunks/b.css\\"],\\"names\\":[],\\"mappings\\":\\"AAAA,yDAAA;AAAA;IAGI,WAAW;AACf\\",\\"file\\":\\"b-[hash].css\\",\\"sourcesContent\\":[\\".b {\\\\n composes: a from \\\\\\"./subfolder/a.css\\\\\\";\\\\n\\\\n color: blue;\\\\n}\\\\n\\"]}", }, ] `; @@ -74,6 +74,16 @@ exports[`/rollup.js code splitting should dedupe chunk names using rollup's incr Array [ Object { "file": "a.css", + "text": "/* packages/rollup/test/specimens/multiple-chunks/subfolder/a.css */ +.a { color: azure; } +/*# sourceMappingURL=a.css.map */", + }, + Object { + "file": "a.css.map", + "text": "{\\"version\\":3,\\"sources\\":[\\"../../../specimens/multiple-chunks/subfolder/a.css\\"],\\"names\\":[],\\"mappings\\":\\"AAAA,mEAAA;AAAA,KAAK,YAAY,EAAE\\",\\"file\\":\\"a.css\\",\\"sourcesContent\\":[\\".a { color: azure; }\\"]}", + }, + Object { + "file": "a2.css", "text": "/* packages/rollup/test/specimens/multiple-chunks/a.css */ .a { color: aqua; @@ -82,8 +92,8 @@ Array [ /*# sourceMappingURL=a.css.map */", }, Object { - "file": "a.css.map", - "text": "{\\"version\\":3,\\"sources\\":[\\"../../../specimens/multiple-chunks/a.css\\"],\\"names\\":[],\\"mappings\\":\\"AAAA,yDAAA;AAAA;IAGI,WAAW;AACf\\",\\"file\\":\\"a.css\\",\\"sourcesContent\\":[\\".a {\\\\n composes: shared from \\\\\\"./shared.css\\\\\\";\\\\n\\\\n color: aqua;\\\\n}\\\\n\\"]}", + "file": "a2.css.map", + "text": "{\\"version\\":3,\\"sources\\":[\\"../../../specimens/multiple-chunks/a.css\\"],\\"names\\":[],\\"mappings\\":\\"AAAA,yDAAA;AAAA;IAGI,WAAW;AACf\\",\\"file\\":\\"a.css\\",\\"sourcesContent\\":[\\".a {\\\\n composes: a from \\\\\\"./subfolder/a.css\\\\\\";\\\\n \\\\n color: aqua;\\\\n}\\\\n\\"]}", }, Object { "file": "b.css", @@ -96,58 +106,13 @@ Array [ }, Object { "file": "b.css.map", - "text": "{\\"version\\":3,\\"sources\\":[\\"../../../specimens/multiple-chunks/b.css\\"],\\"names\\":[],\\"mappings\\":\\"AAAA,yDAAA;AAAA;IAGI,WAAW;AACf\\",\\"file\\":\\"b.css\\",\\"sourcesContent\\":[\\".b {\\\\n composes: shared from \\\\\\"./shared.css\\\\\\";\\\\n\\\\n color: blue;\\\\n}\\\\n\\"]}", - }, - Object { - "file": "chunk.css", - "text": "/* packages/rollup/test/specimens/multiple-chunks/constants.css */ /* packages/rollup/test/specimens/multiple-chunks/shared.css */ -.shared { color: salmon; } - -.other { color: blue; } - -/*# sourceMappingURL=chunk.css.map */", - }, - Object { - "file": "chunk.css.map", - "text": "{\\"version\\":3,\\"sources\\":[\\"../../../specimens/multiple-chunks/constants.css\\",\\"../../../specimens/multiple-chunks/shared.css\\"],\\"names\\":[],\\"mappings\\":\\"AAAA,iEAAA,ECAA,8DAAA;AAEA,UAAU,aAAa,EAAE;;AAEzB,SDJA,WAAiB,ECIK\\",\\"file\\":\\"chunk.css\\",\\"sourcesContent\\":[\\"@value main: blue;\\\\n\\",\\"@value main from \\\\\\"./constants.css\\\\\\";\\\\n\\\\n.shared { color: salmon; }\\\\n\\\\n.other { color: main; }\\\\n\\"]}", - }, - Object { - "file": "chunk2.css", - "text": "/* packages/rollup/test/specimens/multiple-chunks/d.css */ -.d { - color: deepskyblue; -} -", - }, - Object { - "file": "chunk2.css.map", - "text": "{\\"version\\":3,\\"sources\\":[\\"../../../specimens/multiple-chunks/packages/rollup/test/specimens/multiple-chunks/b.css\\"],\\"names\\":[],\\"mappings\\":\\"AAAA,yDAAA;AAAA;IAGI,kBAAW;AACf\\",\\"file\\":\\"chunk.css\\",\\"sourcesContent\\":[\\".b {\\\\n composes: shared from \\\\\\"./shared.css\\\\\\";\\\\n\\\\n color: blue;\\\\n}\\\\n\\"]}", - }, - Object { - "file": "chunk3.css", - "text": "/* packages/rollup/test/specimens/multiple-chunks/c.css */ -.c { - color: coral; -} -", - }, - Object { - "file": "chunk3.css.map", - "text": "{\\"version\\":3,\\"sources\\":[\\"../../../specimens/multiple-chunks/packages/rollup/test/specimens/multiple-chunks/b.css\\"],\\"names\\":[],\\"mappings\\":\\"AAAA,yDAAA;AAAA;IAGI,YAAW;AACf\\",\\"file\\":\\"chunk.css\\",\\"sourcesContent\\":[\\".b {\\\\n composes: shared from \\\\\\"./shared.css\\\\\\";\\\\n\\\\n color: blue;\\\\n}\\\\n\\"]}", + "text": "{\\"version\\":3,\\"sources\\":[\\"../../../specimens/multiple-chunks/b.css\\"],\\"names\\":[],\\"mappings\\":\\"AAAA,yDAAA;AAAA;IAGI,WAAW;AACf\\",\\"file\\":\\"b.css\\",\\"sourcesContent\\":[\\".b {\\\\n composes: a from \\\\\\"./subfolder/a.css\\\\\\";\\\\n\\\\n color: blue;\\\\n}\\\\n\\"]}", }, ] `; exports[`/rollup.js code splitting should ouput only 1 JSON file 1`] = ` Array [ - Object { - "file": "chunk.css", - "text": "/* packages/rollup/test/specimens/simple.css */ -.fooga { - color: red; -} -", - }, Object { "file": "dependencies.css", "text": "/* packages/rollup/test/specimens/dependencies.css */ @@ -168,6 +133,14 @@ Array [ } }", }, + Object { + "file": "simple.css", + "text": "/* packages/rollup/test/specimens/simple.css */ +.fooga { + color: red; +} +", + }, ] `; @@ -186,36 +159,33 @@ Array [ ", }, Object { - "file": "chunk.css", + "file": "d.css", "text": "/* packages/rollup/test/specimens/metadata/d.css */ .mc7de0d66b_d { color: darkkhaki; } ", }, Object { - "file": "common.css", + "file": "fake.css", "text": "/* fake.css */ .mc2c838439_fake { color: red; }", }, Object { "file": "metadata.json", "text": "{ - \\"chunk2.js\\": { - \\"assets\\": [], - \\"dynamicAssets\\": [ - \\"assets/chunk.css\\" - ] - }, \\"a.js\\": { \\"assets\\": [ \\"assets/a.css\\" - ], - \\"dynamicAssets\\": [] + ] }, \\"b.js\\": { \\"assets\\": [ \\"assets/b.css\\" - ], - \\"dynamicAssets\\": [] + ] + }, + \\"chunk2.js\\": { + \\"assets\\": [ + \\"assets/d.css\\" + ] } }", }, @@ -240,10 +210,14 @@ export default a$1; ", }, Object { - "file": "assets/common.css", + "file": "assets/a.css", "text": "/* packages/rollup/test/specimens/circular-dependencies/a.css */ .a { color: aqua; } -/* packages/rollup/test/specimens/circular-dependencies/b.css */ +", + }, + Object { + "file": "assets/b.css", + "text": "/* packages/rollup/test/specimens/circular-dependencies/b.css */ .b { color: blue; } ", }, @@ -269,15 +243,7 @@ exports[`/rollup.js code splitting should support dynamic imports 1`] = ` Array [ Object { "file": "a.css", - "text": "/* packages/rollup/test/specimens/dynamic-imports/f.css */ -.f { - color: floralwhite; -} -/* packages/rollup/test/specimens/dynamic-imports/d.css */ -.d { - color: darkred; -} -/* packages/rollup/test/specimens/dynamic-imports/a.css */ + "text": "/* packages/rollup/test/specimens/dynamic-imports/a.css */ .a { color: aqua; } @@ -296,11 +262,23 @@ Array [ ", }, Object { - "file": "chunk.css", + "file": "c.css", "text": "/* packages/rollup/test/specimens/dynamic-imports/c.css */ .c { color: cyan; } +", + }, + Object { + "file": "d.css", + "text": "/* packages/rollup/test/specimens/dynamic-imports/f.css */ +.f { + color: floralwhite; +} +/* packages/rollup/test/specimens/dynamic-imports/d.css */ +.d { + color: darkred; +} ", }, ] @@ -325,7 +303,7 @@ Array [ ", }, Object { - "file": "shared.css", + "file": "c.css", "text": "/* packages/rollup/test/specimens/manual-chunks/d.css */ /* packages/rollup/test/specimens/manual-chunks/c.css */ .c { @@ -352,7 +330,7 @@ Array [ ", }, Object { - "file": "chunk.css", + "file": "d.css", "text": "/* packages/rollup/test/specimens/metadata/d.css */ .d { color: darkkhaki; } ", @@ -360,23 +338,20 @@ Array [ Object { "file": "metadata.json", "text": "{ - \\"chunk2.js\\": { - \\"assets\\": [], - \\"dynamicAssets\\": [ - \\"assets/chunk.css\\" - ] - }, \\"a.js\\": { \\"assets\\": [ \\"assets/a.css\\" - ], - \\"dynamicAssets\\": [] + ] }, \\"b.js\\": { \\"assets\\": [ \\"assets/b.css\\" - ], - \\"dynamicAssets\\": [] + ] + }, + \\"chunk2.js\\": { + \\"assets\\": [ + \\"assets/d.css\\" + ] } }", }, @@ -395,56 +370,53 @@ Array [ "file": "b.css", "text": "/* packages/rollup/test/specimens/metadata/b.css */ .b { color: blue; } -", - }, - Object { - "file": "chunk.css", - "text": "/* packages/rollup/test/specimens/metadata/d.css */ -.d { color: darkkhaki; } ", }, Object { "file": "chunks.json", "text": "{ - \\"chunk2.js\\": { - \\"assets\\": [], - \\"dynamicAssets\\": [ - \\"assets/chunk.css\\" - ] - }, \\"a.js\\": { \\"assets\\": [ \\"assets/a.css\\" - ], - \\"dynamicAssets\\": [] + ] }, \\"b.js\\": { \\"assets\\": [ \\"assets/b.css\\" - ], - \\"dynamicAssets\\": [] + ] + }, + \\"chunk2.js\\": { + \\"assets\\": [ + \\"assets/d.css\\" + ] } }", }, + Object { + "file": "d.css", + "text": "/* packages/rollup/test/specimens/metadata/d.css */ +.d { color: darkkhaki; } +", + }, ] `; exports[`/rollup.js code splitting should support splitting up CSS files 1`] = ` Array [ - Object { - "file": "chunk.css", - "text": "/* packages/rollup/test/specimens/simple.css */ -.fooga { - color: red; -} -", - }, Object { "file": "dependencies.css", "text": "/* packages/rollup/test/specimens/dependencies.css */ .wooga { background: blue; } +", + }, + Object { + "file": "simple.css", + "text": "/* packages/rollup/test/specimens/simple.css */ +.fooga { + color: red; +} ", }, ] @@ -469,15 +441,19 @@ Array [ ", }, Object { - "file": "chunk.css", + "file": "c.css", + "text": "/* packages/rollup/test/specimens/css-chunks/c.css */ +.c { + color: cyan; +} +", + }, + Object { + "file": "shared.css", "text": "/* packages/rollup/test/specimens/css-chunks/shared.css */ .shared { color: snow; } -/* packages/rollup/test/specimens/css-chunks/c.css */ -.c { - color: cyan; -} ", }, ] @@ -488,7 +464,7 @@ exports[`/rollup.js code splitting shouldn't break when dynamic imports are tree exports[`/rollup.js code splitting shouldn't put bundle-specific CSS in common.css 1`] = ` Array [ Object { - "file": "a.css", + "file": "b.css", "text": "/* packages/rollup/test/specimens/common-splitting/shared.css */ .shared { color: snow; diff --git a/packages/rollup/test/__snapshots__/watch.test.js.snap b/packages/rollup/test/__snapshots__/watch.test.js.snap index 281554bb5..3a44327a6 100644 --- a/packages/rollup/test/__snapshots__/watch.test.js.snap +++ b/packages/rollup/test/__snapshots__/watch.test.js.snap @@ -1,21 +1,34 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`/rollup.js watch mode should generate updated output 1`] = ` -"/* packages/rollup/test/output/watch/change/watched.css */ +Array [ + Object { + "file": "watched.css", + "text": "/* packages/rollup/test/output/watch/change/watched.css */ .mc5f9237d4_one { color: red; -}" +}", + }, +] `; exports[`/rollup.js watch mode should generate updated output 2`] = ` -"/* packages/rollup/test/output/watch/change/watched.css */ +Array [ + Object { + "file": "watched.css", + "text": "/* packages/rollup/test/output/watch/change/watched.css */ .mc5f9237d4_two { color: blue; -}" +}", + }, +] `; exports[`/rollup.js watch mode should generate updated output for composes changes 1`] = ` -"/* packages/rollup/test/output/watch/change-composes/watched.css */ +Array [ + Object { + "file": "assets/watched.css", + "text": "/* packages/rollup/test/output/watch/change-composes/watched.css */ .mc0f3c579a_one { color: red; } @@ -26,22 +39,47 @@ exports[`/rollup.js watch mode should generate updated output for composes chang .mc0f3c579a_three { color: teal; -}" -`; - -exports[`/rollup.js watch mode should generate updated output for composes changes 2`] = ` -"var css = { +}", + }, + Object { + "file": "watch-output.js", + "text": "var css = { \\"one\\": \\"mc0f3c579a_one\\", \\"two\\": \\"mc0f3c579a_one mc0f3c579a_two\\", \\"three\\": \\"mc0f3c579a_three\\" }; console.log(css); -" +", + }, + Object { + "file": "watched.css", + "text": ".one { + color: red; +} + +.two { + composes: one; + background: blue; +} + +.three { + color: teal; +}", + }, + Object { + "file": "watched.js", + "text": "import css from \\"./watched.css\\"; +console.log(css);", + }, +] `; -exports[`/rollup.js watch mode should generate updated output for composes changes 3`] = ` -"/* packages/rollup/test/output/watch/change-composes/watched.css */ +exports[`/rollup.js watch mode should generate updated output for composes changes 2`] = ` +Array [ + Object { + "file": "assets/watched.css", + "text": "/* packages/rollup/test/output/watch/change-composes/watched.css */ .mc0f3c579a_one { color: green; } @@ -52,22 +90,48 @@ exports[`/rollup.js watch mode should generate updated output for composes chang .mc0f3c579a_three { color: teal; -}" -`; - -exports[`/rollup.js watch mode should generate updated output for composes changes 4`] = ` -"var css = { +}", + }, + Object { + "file": "watch-output.js", + "text": "var css = { \\"one\\": \\"mc0f3c579a_one\\", \\"two\\": \\"mc0f3c579a_one mc0f3c579a_two\\", \\"three\\": \\"mc0f3c579a_one mc0f3c579a_three\\" }; console.log(css); -" +", + }, + Object { + "file": "watched.css", + "text": ".one { + color: green; +} + +.two { + composes: one; + background: blue; +} + +.three { + composes: one; + color: teal; +}", + }, + Object { + "file": "watched.js", + "text": "import css from \\"./watched.css\\"; +console.log(css);", + }, +] `; exports[`/rollup.js watch mode should update when a dependency changes 1`] = ` -"/* packages/rollup/test/output/watch/dep-graph/two.css */ +Array [ + Object { + "file": "one.css", + "text": "/* packages/rollup/test/output/watch/dep-graph/two.css */ .mc6f857f1c_two { color: blue; } @@ -78,11 +142,16 @@ exports[`/rollup.js watch mode should update when a dependency changes 1`] = ` /* packages/rollup/test/output/watch/dep-graph/one.css */ .mca3136a1b_one { color: red; -}" +}", + }, +] `; exports[`/rollup.js watch mode should update when a dependency changes 2`] = ` -"/* packages/rollup/test/output/watch/dep-graph/two.css */ +Array [ + Object { + "file": "one.css", + "text": "/* packages/rollup/test/output/watch/dep-graph/two.css */ .mc6f857f1c_two { color: green; } @@ -93,11 +162,16 @@ exports[`/rollup.js watch mode should update when a dependency changes 2`] = ` /* packages/rollup/test/output/watch/dep-graph/one.css */ .mca3136a1b_one { color: red; -}" +}", + }, +] `; exports[`/rollup.js watch mode should update when a shared dependency changes 1`] = ` -"/* packages/rollup/test/output/watch/shared-deps/two.css */ +Array [ + Object { + "file": "three.css", + "text": "/* packages/rollup/test/output/watch/shared-deps/two.css */ .mc5067d789_two { color: green; } @@ -108,11 +182,16 @@ exports[`/rollup.js watch mode should update when a shared dependency changes 1` /* packages/rollup/test/output/watch/shared-deps/three.css */ .mc4ec9318b_three { color: teal; -}" +}", + }, +] `; exports[`/rollup.js watch mode should update when a shared dependency changes 2`] = ` -"/* packages/rollup/test/output/watch/shared-deps/two.css */ +Array [ + Object { + "file": "three.css", + "text": "/* packages/rollup/test/output/watch/shared-deps/two.css */ .mc5067d789_two { color: yellow; } @@ -123,64 +202,75 @@ exports[`/rollup.js watch mode should update when a shared dependency changes 2` /* packages/rollup/test/output/watch/shared-deps/three.css */ .mc4ec9318b_three { color: teal; -}" +}", + }, +] `; -exports[`/rollup.js watch mode should update when adding new css files 1`] = ` -"/* packages/rollup/test/output/watch/new-file/one.css */ +exports[`/rollup.js watch mode should update when adding new css files 1`] = `Array []`; + +exports[`/rollup.js watch mode should update when adding new css files 2`] = ` +Array [ + Object { + "file": "one.css", + "text": "/* packages/rollup/test/output/watch/new-file/one.css */ .mc54f12712_one { color: red; -}" +}", + }, +] `; exports[`/rollup.js watch mode should watch when using code splitting 1`] = ` -"/* packages/rollup/test/output/watch/code-splitting/one.css */ -.mc204ad279_one { - color: red; -}" -`; - -exports[`/rollup.js watch mode should watch when using code splitting 2`] = ` -"/* packages/rollup/test/output/watch/code-splitting/two.css */ -.mc3861d3af_two { - color: green; -}" -`; - -exports[`/rollup.js watch mode should watch when using code splitting 3`] = ` -"/* packages/rollup/test/output/watch/code-splitting/values.css */ +Array [ + Object { + "file": "one.css", + "text": "/* packages/rollup/test/output/watch/code-splitting/values.css */ /* packages/rollup/test/output/watch/code-splitting/shared.css */ .mc4002e81f_shared { color: blue; -}" -`; - -exports[`/rollup.js watch mode should watch when using code splitting 4`] = ` -"/* packages/rollup/test/output/watch/code-splitting/one.css */ +} +/* packages/rollup/test/output/watch/code-splitting/one.css */ .mc204ad279_one { color: red; -}" -`; - -exports[`/rollup.js watch mode should watch when using code splitting 5`] = ` -"/* packages/rollup/test/output/watch/code-splitting/two.css */ +}", + }, + Object { + "file": "two.css", + "text": "/* packages/rollup/test/output/watch/code-splitting/two.css */ .mc3861d3af_two { color: green; -}" +}", + }, +] `; -exports[`/rollup.js watch mode should watch when using code splitting 6`] = ` -"/* packages/rollup/test/output/watch/code-splitting/one.css */ +exports[`/rollup.js watch mode should watch when using code splitting 2`] = ` +Array [ + Object { + "file": "one.css", + "text": "/* packages/rollup/test/output/watch/code-splitting/one.css */ .mc204ad279_one { color: red; -} -/* packages/rollup/test/output/watch/code-splitting/two.css */ -.mc3861d3af_two { - color: green; -} -/* packages/rollup/test/output/watch/code-splitting/shared.css */ +}", + }, + Object { + "file": "shared.css", + "text": "/* packages/rollup/test/output/watch/code-splitting/shared.css */ .mc4002e81f_shared { color: blue; -} -/* packages/rollup/test/output/watch/code-splitting/values.css */" +}", + }, + Object { + "file": "two.css", + "text": "/* packages/rollup/test/output/watch/code-splitting/two.css */ +.mc3861d3af_two { + color: green; +}", + }, + Object { + "file": "values.css", + "text": "/* packages/rollup/test/output/watch/code-splitting/values.css */", + }, +] `; diff --git a/packages/rollup/test/rollup.test.js b/packages/rollup/test/rollup.test.js index ed82d31d0..cb7676d6e 100644 --- a/packages/rollup/test/rollup.test.js +++ b/packages/rollup/test/rollup.test.js @@ -247,7 +247,7 @@ describe("/rollup.js", () => { expect(read("./json-named/assets/custom.json")).toMatchSnapshot(); }); - it("should use the common arg for unreferenced CSS", async () => { + it("should output unreferenced CSS", async () => { const processor = new Processor({ namer, map, @@ -265,7 +265,6 @@ describe("/rollup.js", () => { plugin({ namer, processor, - common : "unreferenced.css", }), ], }); @@ -436,7 +435,7 @@ describe("/rollup.js", () => { file : prefix(`./output/no-maps/no-maps.js`), }); - expect(read("./no-maps/assets/no-maps.css")).toMatchSnapshot(); + expect(read("./no-maps/assets/simple.css")).toMatchSnapshot(); }); it("should respect the CSS dependency tree", async () => { diff --git a/packages/rollup/test/specimens/css-dependencies/a.css b/packages/rollup/test/specimens/css-dependencies/a.css new file mode 100644 index 000000000..4e4bfadb9 --- /dev/null +++ b/packages/rollup/test/specimens/css-dependencies/a.css @@ -0,0 +1,5 @@ +.a { + composes: c from "./c.css"; + + color: aqua; +} diff --git a/packages/rollup/test/specimens/css-dependencies/a.js b/packages/rollup/test/specimens/css-dependencies/a.js new file mode 100644 index 000000000..a6d22eff2 --- /dev/null +++ b/packages/rollup/test/specimens/css-dependencies/a.js @@ -0,0 +1,3 @@ +import css from "./a.css"; + +console.log(css); diff --git a/packages/rollup/test/specimens/css-dependencies/b.css b/packages/rollup/test/specimens/css-dependencies/b.css new file mode 100644 index 000000000..cecf28776 --- /dev/null +++ b/packages/rollup/test/specimens/css-dependencies/b.css @@ -0,0 +1,5 @@ +.b { + composes: c from "./c.css"; + + color: blue; +} diff --git a/packages/rollup/test/specimens/css-dependencies/b.js b/packages/rollup/test/specimens/css-dependencies/b.js new file mode 100644 index 000000000..539146027 --- /dev/null +++ b/packages/rollup/test/specimens/css-dependencies/b.js @@ -0,0 +1,3 @@ +import css from "./b.css"; + +console.log(css); diff --git a/packages/rollup/test/specimens/css-dependencies/c.css b/packages/rollup/test/specimens/css-dependencies/c.css new file mode 100644 index 000000000..6ffbc8359 --- /dev/null +++ b/packages/rollup/test/specimens/css-dependencies/c.css @@ -0,0 +1,3 @@ +.c { + color: cyan; +} diff --git a/packages/rollup/test/specimens/multiple-chunks/a.css b/packages/rollup/test/specimens/multiple-chunks/a.css index b2a281c34..072f3bc75 100644 --- a/packages/rollup/test/specimens/multiple-chunks/a.css +++ b/packages/rollup/test/specimens/multiple-chunks/a.css @@ -1,5 +1,5 @@ .a { - composes: shared from "./shared.css"; - + composes: a from "./subfolder/a.css"; + color: aqua; } diff --git a/packages/rollup/test/specimens/multiple-chunks/a.js b/packages/rollup/test/specimens/multiple-chunks/a.js index 96818f1a1..a6d22eff2 100644 --- a/packages/rollup/test/specimens/multiple-chunks/a.js +++ b/packages/rollup/test/specimens/multiple-chunks/a.js @@ -1,12 +1,3 @@ import css from "./a.css"; -import common from "./common.js"; -console.log(css, common); - -const d = import("./d.js"); - -d.then(console.log); - -const e = import("./e.js"); - -e.then(console.log); +console.log(css); diff --git a/packages/rollup/test/specimens/multiple-chunks/b.css b/packages/rollup/test/specimens/multiple-chunks/b.css index 16e87cdb5..3cf0c244f 100644 --- a/packages/rollup/test/specimens/multiple-chunks/b.css +++ b/packages/rollup/test/specimens/multiple-chunks/b.css @@ -1,5 +1,5 @@ .b { - composes: shared from "./shared.css"; + composes: a from "./subfolder/a.css"; color: blue; } diff --git a/packages/rollup/test/specimens/multiple-chunks/b.js b/packages/rollup/test/specimens/multiple-chunks/b.js index c678206dd..539146027 100644 --- a/packages/rollup/test/specimens/multiple-chunks/b.js +++ b/packages/rollup/test/specimens/multiple-chunks/b.js @@ -1,12 +1,3 @@ import css from "./b.css"; -import common from "./common.js"; -console.log(css, common); - -const c = import("./c.js"); - -c.then(console.log); - -const e = import("./e.js"); - -e.then(console.log); +console.log(css); diff --git a/packages/rollup/test/specimens/multiple-chunks/c.css b/packages/rollup/test/specimens/multiple-chunks/c.css deleted file mode 100644 index eec134a9f..000000000 --- a/packages/rollup/test/specimens/multiple-chunks/c.css +++ /dev/null @@ -1,5 +0,0 @@ -.c { - composes: shared from "./shared.css"; - - color: coral; -} diff --git a/packages/rollup/test/specimens/multiple-chunks/c.js b/packages/rollup/test/specimens/multiple-chunks/c.js deleted file mode 100644 index bd48d857b..000000000 --- a/packages/rollup/test/specimens/multiple-chunks/c.js +++ /dev/null @@ -1,4 +0,0 @@ -import css from "./c.css"; -import common from "./common.js"; - -console.log(css, common); diff --git a/packages/rollup/test/specimens/multiple-chunks/common.js b/packages/rollup/test/specimens/multiple-chunks/common.js deleted file mode 100644 index c735fc8c8..000000000 --- a/packages/rollup/test/specimens/multiple-chunks/common.js +++ /dev/null @@ -1 +0,0 @@ -export default "common"; diff --git a/packages/rollup/test/specimens/multiple-chunks/constants.css b/packages/rollup/test/specimens/multiple-chunks/constants.css deleted file mode 100644 index 96ba5d092..000000000 --- a/packages/rollup/test/specimens/multiple-chunks/constants.css +++ /dev/null @@ -1 +0,0 @@ -@value main: blue; diff --git a/packages/rollup/test/specimens/multiple-chunks/d.css b/packages/rollup/test/specimens/multiple-chunks/d.css deleted file mode 100644 index f0fe87445..000000000 --- a/packages/rollup/test/specimens/multiple-chunks/d.css +++ /dev/null @@ -1,5 +0,0 @@ -.d { - composes: shared from "./shared.css"; - - color: deepskyblue; -} diff --git a/packages/rollup/test/specimens/multiple-chunks/d.js b/packages/rollup/test/specimens/multiple-chunks/d.js deleted file mode 100644 index dc16ae407..000000000 --- a/packages/rollup/test/specimens/multiple-chunks/d.js +++ /dev/null @@ -1,4 +0,0 @@ -import css from "./d.css"; -import common from "./common.js"; - -console.log(css, common); diff --git a/packages/rollup/test/specimens/multiple-chunks/e.js b/packages/rollup/test/specimens/multiple-chunks/e.js deleted file mode 100644 index d97e38b22..000000000 --- a/packages/rollup/test/specimens/multiple-chunks/e.js +++ /dev/null @@ -1 +0,0 @@ -export default "e"; diff --git a/packages/rollup/test/specimens/multiple-chunks/shared.css b/packages/rollup/test/specimens/multiple-chunks/shared.css deleted file mode 100644 index 296f11c8f..000000000 --- a/packages/rollup/test/specimens/multiple-chunks/shared.css +++ /dev/null @@ -1,5 +0,0 @@ -@value main from "./constants.css"; - -.shared { color: salmon; } - -.other { color: main; } diff --git a/packages/rollup/test/specimens/multiple-chunks/subfolder/a.css b/packages/rollup/test/specimens/multiple-chunks/subfolder/a.css new file mode 100644 index 000000000..71928249e --- /dev/null +++ b/packages/rollup/test/specimens/multiple-chunks/subfolder/a.css @@ -0,0 +1 @@ +.a { color: azure; } \ No newline at end of file diff --git a/packages/rollup/test/splitting.test.js b/packages/rollup/test/splitting.test.js index 17c86e932..c359afa68 100644 --- a/packages/rollup/test/splitting.test.js +++ b/packages/rollup/test/splitting.test.js @@ -58,6 +58,35 @@ describe("/rollup.js", () => { expect(dir("./splitting/assets")).toMatchSnapshot(); }); + it("should correctly chunk up CSS files", async () => { + const bundle = await rollup({ + input : [ + require.resolve("./specimens/css-dependencies/a.js"), + require.resolve("./specimens/css-dependencies/b.js"), + ], + + plugins : [ + plugin({ + namer, + map, + // verbose : true, + }), + ], + }); + + await bundle.write({ + format, + sourcemap, + + assetFileNames, + chunkFileNames, + + dir : prefix(`./output/css-dependencies`), + }); + + expect(dir("./css-dependencies/assets")).toMatchSnapshot(); + }); + it("should support outputting metadata about CSS dependencies", async () => { const bundle = await rollup({ input : [ diff --git a/packages/rollup/test/watch.test.js b/packages/rollup/test/watch.test.js index f49859eed..1a50bf3e9 100644 --- a/packages/rollup/test/watch.test.js +++ b/packages/rollup/test/watch.test.js @@ -6,8 +6,8 @@ const shell = require("shelljs"); const read = require("@modular-css/test-utils/read.js")(__dirname); const write = require("@modular-css/test-utils/write.js")(__dirname); -const exists = require("@modular-css/test-utils/exists.js")(__dirname); const prefix = require("@modular-css/test-utils/prefix.js")(__dirname); +const dir = require("@modular-css/test-utils/read-dir.js")(__dirname); const watching = require("@modular-css/test-utils/rollup-watching.js"); const plugin = require("../rollup.js"); @@ -54,7 +54,7 @@ describe("/rollup.js", () => { watcher.on("event", watching((builds) => { if(builds === 1) { - expect(read("./watch/change/assets/watch-output.css")).toMatchSnapshot(); + expect(dir("./watch/change/assets/")).toMatchSnapshot(); setTimeout(() => write(`./watch/change/watched.css`, dedent(` .two { @@ -66,7 +66,7 @@ describe("/rollup.js", () => { return; } - expect(read("./watch/change/assets/watch-output.css")).toMatchSnapshot(); + expect(dir("./watch/change/assets/")).toMatchSnapshot(); return done(); })); @@ -111,8 +111,7 @@ describe("/rollup.js", () => { watcher.on("event", watching((builds) => { if(builds === 1) { - expect(read("./watch/change-composes/assets/watch-output.css")).toMatchSnapshot(); - expect(read("./watch/change-composes/watch-output.js")).toMatchSnapshot(); + expect(dir("./watch/change-composes/")).toMatchSnapshot(); setTimeout(() => write(`./watch/change-composes/watched.css`, dedent(` .one { @@ -134,8 +133,7 @@ describe("/rollup.js", () => { return; } - expect(read("./watch/change-composes/assets/watch-output.css")).toMatchSnapshot(); - expect(read("./watch/change-composes/watch-output.js")).toMatchSnapshot(); + expect(dir("./watch/change-composes/")).toMatchSnapshot(); return done(); })); @@ -185,7 +183,7 @@ describe("/rollup.js", () => { watcher.on("event", watching((builds) => { if(builds === 1) { - expect(read("./watch/dep-graph/assets/watch-output.css")).toMatchSnapshot(); + expect(dir("./watch/dep-graph/assets/")).toMatchSnapshot(); setTimeout(() => write(`./watch/dep-graph/two.css`, dedent(` .two { @@ -197,7 +195,7 @@ describe("/rollup.js", () => { return; } - expect(read("./watch/dep-graph/assets/watch-output.css")).toMatchSnapshot(); + expect(dir("./watch/dep-graph/assets/")).toMatchSnapshot(); return done(); })); @@ -232,7 +230,7 @@ describe("/rollup.js", () => { watcher.on("event", watching((builds) => { if(builds === 1) { - expect(exists("./new-file/assets/watch-output.css")).toBe(false); + expect(dir("./new-file/assets/")).toMatchSnapshot(); setTimeout(() => write(`./watch/new-file/watch.js`, dedent(` import css from "./one.css"; @@ -244,7 +242,7 @@ describe("/rollup.js", () => { return; } - expect(read("./watch/new-file/assets/watch-output.css")).toMatchSnapshot(); + expect(dir("./watch/new-file/assets/")).toMatchSnapshot(); return done(); })); @@ -296,7 +294,7 @@ describe("/rollup.js", () => { watcher.on("event", watching((builds) => { if(builds === 1) { - expect(read("./watch/shared-deps/assets/watch-output.css")).toMatchSnapshot(); + expect(dir("./watch/shared-deps/assets/")).toMatchSnapshot(); setTimeout(() => write(`./watch/shared-deps/two.css`, dedent(` .two { @@ -308,14 +306,14 @@ describe("/rollup.js", () => { return; } - expect(read("./watch/shared-deps/assets/watch-output.css")).toMatchSnapshot(); + expect(dir("./watch/shared-deps/assets/")).toMatchSnapshot(); return done(); })); }); // TODO: causing jest to hang but say the test has completed weirdly - it.skip("should watch when using code splitting", (done) => { + it("should watch when using code splitting", (done) => { // Create v1 of the files write(`./watch/code-splitting/one.css`, dedent(` .one { @@ -379,22 +377,18 @@ describe("/rollup.js", () => { watcher.on("event", watching((builds) => { if(builds === 1) { - expect(read("./watch/code-splitting/assets/one.css")).toMatchSnapshot(); - expect(read("./watch/code-splitting/assets/two.css")).toMatchSnapshot(); - expect(read("./watch/code-splitting/assets/common.css")).toMatchSnapshot(); + expect(dir("./watch/code-splitting/assets/")).toMatchSnapshot(); // Create v2 of the file we want to change setTimeout(() => write(`./watch/code-splitting/values.css`, dedent(` - @value baloo: aqua; + @value baloo: aqua; `)), 100); - + // continue watching return; } - expect(read("./watch/code-splitting/assets/one.css")).toMatchSnapshot(); - expect(read("./watch/code-splitting/assets/two.css")).toMatchSnapshot(); - expect(read("./watch/code-splitting/assets/common.css")).toMatchSnapshot(); + expect(dir("./watch/code-splitting/assets/")).toMatchSnapshot(); return done(); })); diff --git a/packages/scratchpad/.npmignore b/packages/scratchpad/.npmignore new file mode 100644 index 000000000..84a60e421 --- /dev/null +++ b/packages/scratchpad/.npmignore @@ -0,0 +1,5 @@ +coverage/ +profiling/ +test/ +.* +CHANGELOG.md diff --git a/packages/scratchpad/CHANGELOG.md b/packages/scratchpad/CHANGELOG.md new file mode 100644 index 000000000..767840ada --- /dev/null +++ b/packages/scratchpad/CHANGELOG.md @@ -0,0 +1,5 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + diff --git a/packages/scratchpad/LICENSE b/packages/scratchpad/LICENSE new file mode 100644 index 000000000..76aab6f21 --- /dev/null +++ b/packages/scratchpad/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Pat Cavit + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/scratchpad/chunks.js b/packages/scratchpad/chunks.js new file mode 100644 index 000000000..fd1582010 --- /dev/null +++ b/packages/scratchpad/chunks.js @@ -0,0 +1,135 @@ +/* eslint-disable no-loop-func */ +"use strict"; + +const merge = (graph, original, target) => { + const incoming = graph.incomingEdges[original]; + const outgoing = graph.outgoingEdges[original]; + + graph.getNodeData(target).unshift(original); + + incoming.forEach((src) => { + graph.removeDependency(src, original); + + if(src !== target) { + graph.addDependency(src, target); + } + }); + + outgoing.forEach((dest) => { + graph.removeDependency(original, dest); + + if(dest !== target) { + graph.addDependency(target, dest); + } + }); + + // Bye bye + graph.removeNode(original); +}; + +const setsContain = (a, b) => { + for(const val of a) { + if(!b.has(val)) { + return false; + } + } + + return true; +}; + +const setsMatch = (a, b) => { + if(a.size !== b.size) { + return false; + } + + return setsContain(a, b); +}; + +const chunks = ({ entries, graph }) => { + const result = graph.clone(); + + // Lookups + const nodeToEntries = new Map(); + const entryToNodes = new Map(); + + // Keeping track of nodes we've already handled + const chunked = new Set(); + + // First build up a map of entry -> set of deps for comparisons + entries.forEach((entry) => { + const deps = result.dependenciesOf(entry).reverse(); + + if(!entryToNodes.has(entry)) { + entryToNodes.set(entry, new Set()); + } + + deps.forEach((node) => { + if(!nodeToEntries.has(node)) { + nodeToEntries.set(node, new Set()); + } + + nodeToEntries.get(node).add(entry); + entryToNodes.get(entry).add(node); + }); + }); + + // Keep looping until all nodes have been chunked (even if that's just into themselves) + while(chunked.size < nodeToEntries.size) { + // Iterate the nodes associated with each entry, + // and figure out if the node can be collapsed + entryToNodes.forEach((nodes, entry) => { + // Nodes that will be merged at the end of this iteration + const queued = new Set(); + + // This will be the first set of entries found in the walk, + // any subsequent nodes in this pass must match this to be combined + let branches; + + // If all the nodes for this branch are handled, skip it + if(setsContain(nodes, chunked)) { + return; + } + + // Walk the dependencies of the current entry in order + const branch = result.dependenciesOf(entry).reverse(); + + for(const node of branch) { + if(chunked.has(node)) { + continue; + } + + // Figure out the entries that reference this node + const containers = nodeToEntries.get(node); + + if(!branches) { + branches = containers; + } else if(!setsMatch(branches, containers)) { + // TODO: Can we get the order to have leaf nodes first so we could stop + // TODO: iteration on this branch at this point + continue; + } + + queued.add(node); + } + + // Merge all the queued nodes into the first node in the queue + let base; + + queued.forEach((node) => { + chunked.add(node); + + if(!base) { + base = node; + + return; + } + + merge(result, node, base); + }); + }); + } + + return result; +}; + +module.exports = chunks; diff --git a/packages/scratchpad/chunktest.js b/packages/scratchpad/chunktest.js new file mode 100644 index 000000000..46b2b53cb --- /dev/null +++ b/packages/scratchpad/chunktest.js @@ -0,0 +1,46 @@ +const chunks = require("./chunks.js"); +const construct = require("./test/construct.js"); + +// const { entries, graph } = construct([ "a", "b", "c" ], ` +// a -> A +// b -> B +// c -> C +// A -> B +// B -> C +// `); + +// const { entries, graph } = construct([ "a", "b" ], ` +// a -> A +// b -> B +// A -> C +// A -> D +// C -> E +// E -> F +// B -> F +// F -> G +// `); + +const { entries, graph } = construct([ "a", "b" ], ` +a -> A +b -> B +A -> D +A -> C +B -> D +D -> E +E -> F +E -> G +C -> F +`); + +const result = chunks({ entries, graph }); + +console.log("FILE OUTPUT"); + +entries.forEach((entry) => { + console.log("ENTRY:", entry); + + result.dependenciesOf(entry) + .map((node) => console.log(node, " ", result.getNodeData(node))); +}); + +console.log("===="); diff --git a/packages/scratchpad/package.json b/packages/scratchpad/package.json new file mode 100644 index 000000000..58b7749cd --- /dev/null +++ b/packages/scratchpad/package.json @@ -0,0 +1,10 @@ +{ + "name": "@modular-css/scratchpad", + "version": "22.0.0", + "private": true, + "description": "Testing area for new ideas modular-css", + "main": "compare.js", + "author": "Pat Cavit ", + "repository": "tivac/modular-css", + "license": "MIT" +} diff --git a/packages/scratchpad/test/__snapshots__/chunks.test.js.snap b/packages/scratchpad/test/__snapshots__/chunks.test.js.snap new file mode 100644 index 000000000..10a70372f --- /dev/null +++ b/packages/scratchpad/test/__snapshots__/chunks.test.js.snap @@ -0,0 +1,135 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`chunking algorithm should handle more complicated chunking 1`] = ` +Array [ + Object { + "node": "F", + "nodes": Array [ + "G", + "F", + ], + }, + Object { + "node": "A", + "nodes": Array [ + "E", + "C", + "D", + "A", + ], + }, + Object { + "node": "a", + "nodes": null, + }, + Object { + "node": "B", + "nodes": Array [ + "B", + ], + }, + Object { + "node": "b", + "nodes": null, + }, +] +`; + +exports[`chunking algorithm should handle more simple chunking 1`] = ` +Array [ + Object { + "node": "D", + "nodes": Array [ + "D", + ], + }, + Object { + "node": "A", + "nodes": Array [ + "A", + ], + }, + Object { + "node": "a", + "nodes": null, + }, + Object { + "node": "B", + "nodes": Array [ + "C", + "B", + ], + }, + Object { + "node": "b", + "nodes": null, + }, +] +`; + +exports[`chunking algorithm should handle unmergable chunks 1`] = ` +Array [ + Object { + "node": "C", + "nodes": Array [ + "C", + ], + }, + Object { + "node": "B", + "nodes": Array [ + "B", + ], + }, + Object { + "node": "A", + "nodes": Array [ + "A", + ], + }, + Object { + "node": "a", + "nodes": null, + }, + Object { + "node": "b", + "nodes": null, + }, + Object { + "node": "c", + "nodes": null, + }, +] +`; + +exports[`chunking algorithm should handle very simple chunking 1`] = ` +Array [ + Object { + "node": "D", + "nodes": Array [ + "D", + ], + }, + Object { + "node": "A", + "nodes": Array [ + "C", + "A", + ], + }, + Object { + "node": "a", + "nodes": null, + }, + Object { + "node": "B", + "nodes": Array [ + "B", + ], + }, + Object { + "node": "b", + "nodes": null, + }, +] +`; diff --git a/packages/scratchpad/test/chunks.test.js b/packages/scratchpad/test/chunks.test.js new file mode 100644 index 000000000..a120ab9a0 --- /dev/null +++ b/packages/scratchpad/test/chunks.test.js @@ -0,0 +1,60 @@ +"use strict"; + +const construct = require("./construct.js"); + +require("./snapshot.js"); + +const chunks = require("../chunks.js"); + +describe("chunking algorithm", () => { + it("should handle very simple chunking", () => { + const { entries, graph } = construct([ "a", "b" ], ` + a -> A + A -> C + A -> D + b -> B + B -> D + `); + + expect(chunks({ entries, graph })).toMatchChunksSnapshot(); + }); + + it("should handle more simple chunking", () => { + const { entries, graph } = construct([ "a", "b" ], ` + a -> A + A -> D + b -> B + B -> C + C -> D + `); + + expect(chunks({ entries, graph })).toMatchChunksSnapshot(); + }); + + it("should handle more complicated chunking", () => { + const { entries, graph } = construct([ "a", "b" ], ` + a -> A + b -> B + A -> C + A -> D + C -> E + E -> F + B -> F + F -> G + `); + + expect(chunks({ entries, graph })).toMatchChunksSnapshot(); + }); + + it("should handle unmergable chunks", () => { + const { entries, graph } = construct([ "a", "b", "c" ], ` + a -> A + b -> B + c -> C + A -> B + B -> C + `); + + expect(chunks({ entries, graph })).toMatchChunksSnapshot(); + }); +}); diff --git a/packages/scratchpad/test/construct.js b/packages/scratchpad/test/construct.js new file mode 100644 index 000000000..f6c170df8 --- /dev/null +++ b/packages/scratchpad/test/construct.js @@ -0,0 +1,35 @@ +"use strict"; + +const dedent = require("dedent"); +const { DepGraph } = require("dependency-graph"); + +const construct = (entries, tmpl) => { + const graph = new DepGraph(); + + const rows = dedent(tmpl).split(/\r?\n/); + + rows.forEach((row) => { + const [ src, tgt ] = row.trim().split(/\s*->\s*/); + + if(entries.indexOf(src) > -1) { + graph.addNode(src, null); + } else { + graph.addNode(src, [ src ]); + } + + if(entries.indexOf(tgt) > -1) { + graph.addNode(tgt, null); + } else { + graph.addNode(tgt, [ tgt ]); + } + + graph.addDependency(src, tgt); + }); + + return { + entries, + graph, + }; +}; + +module.exports = construct; diff --git a/packages/scratchpad/test/snapshot.js b/packages/scratchpad/test/snapshot.js new file mode 100644 index 000000000..bfb25bc17 --- /dev/null +++ b/packages/scratchpad/test/snapshot.js @@ -0,0 +1,18 @@ +"use strict"; + +const { toMatchSnapshot } = require("jest-snapshot"); + +expect.extend({ + toMatchChunksSnapshot(graph, ...args) { + const nodes = graph.overallOrder(); + + return toMatchSnapshot.call( + this, + nodes.map((node) => ({ + node, + nodes : graph.getNodeData(node), + })), + ...args, + ); + } +});