You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

72 lines
3.3 KiB
JavaScript

"use strict";
const Promise = require("bluebird");
const path = require("path");
const mapObj = require("map-obj");
const stream = require("../stream");
const createFilePostprocessor = require("../postcss/postprocess");
const loaderShimPath = require("../loader-shim-path");
// NOTE: This stream is inserted directly after the 'dedupe' phase, and *not* before the 'pack' phase like in postcssify-icss, because that would break `extract-css` - which inserts itself before the 'label' phase that comes after the 'dedupe' phase, and by which time all the CSS needs to be prepared.
module.exports = function (state) {
const isCss = require("../is-css")(state);
return function createDedupeBundleCssStream(options) {
// TODO: Reuse instance?
let processFile = createFilePostprocessor(options);
let allCss = "";
let loaderItem;
let entryPoint = (options._flags != null && options._flags.entries != null)
// Get the (absolute!) path of the folder containing the initial entry file, as a reference point for relative paths in the output
? path.dirname(path.resolve(options._flags.entries[0]))
// ... or, if no entry file is specified, go off the current working directory
: process.cwd();
return stream((item) => {
// And the same for the loader shim path. All this relative-path stuff is to prevent absolute filesystem URLs from leaking into the output, as those might contain sensitive information.
let relativeLoaderPath = path.relative(path.dirname(item.file), loaderShimPath);
if (isCss(item)) {
return Promise.try(() => {
return processFile(item);
}).then(({ result, icssExports }) => {
// NOTE: This is a workaround, until we find a more robust solution to this. While composed classes should be delimited by dots in CSS, they should be delimited by *spaces* in HTML. In "double compose" cases, ie. when one class composes another class which was already composed with a third class, we can get a class string like "foo bar.baz" which is wrong; this replaces all the erroneous dots with spaces.
let icssExportsHTML = mapObj(icssExports, (key, value) => {
return [ key, value.replace(/\./g, " ") ];
});
allCss += `/* from ${path.relative(entryPoint, item.file)} */\n\n${result.css}\n\n`;
if (!item.__icssify__discardable) {
return {
... item,
source: `require(${JSON.stringify(relativeLoaderPath)}); module.exports = ${JSON.stringify(icssExportsHTML)};`
};
}
});
} else if (item.file === loaderShimPath) {
// Stockpile this item, to emit at the very end, right before the browser-pack operation (because by that time, we'll have processed and therefore concatenated all the CSS files)
loaderItem = item;
return null;
} else {
return item;
}
}, () => {
if (allCss.length > 0) {
if (loaderItem != null) {
return {
... loaderItem,
source: loaderItem.source.replace('"## CONTENT MARKER ##"', JSON.stringify(allCss))
};
} else {
// FIXME: Can occur if some dependency also gets processed and require()s a CSS file, but does not specify icssify as a transform (but the using project does) -- like with ui-lib + site-builder
throw new Error("Processed CSS, but global loader was not encountered. This should never happen, please report it as a bug!");
}
}
});
};
};