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, + ); + } +});