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.

76 lines
4.6 KiB
JavaScript

"use strict";
const { validateArguments } = require("@validatem/core");
const ValidationError = require("@validatem/error");
const isBoolean = require("@validatem/is-boolean");
const isPostcssPlugin = require("@validatem/is-postcss-plugin");
const isString = require("@validatem/is-string");
const required = require("@validatem/required");
const arrayOf = require("@validatem/array-of");
const oneOf = require("@validatem/one-of");
const allowExtraProperties = require("@validatem/allow-extra-properties");
// FIXME: When there's >1 icssify instance set up on the Browserify instance (can this happen when a module specifies its own dependency on icssify?), either throw an error (if incompatible?) or merge the two somehow, so that there's only one resulting .css file
module.exports = function (browserify, options) {
validateArguments(arguments, [
[ "browserify", required ],
[ "options", allowExtraProperties({
mode: oneOf([ "local", "global" ]),
before: arrayOf([ required, isPostcssPlugin ]),
after: arrayOf([ required, isPostcssPlugin ]),
extensions: arrayOf([ required, isString ]),
ignoreCycles: isBoolean
})]
]);
let state = {
extensions: options.extensions,
ignoreCycles: options.ignoreCycles
};
const createTransform = require("./src/transform")(state);
const createCssDepsStream = require("./src/phase-streams/deps-css")(state);
const createSyntaxHideStream = require("./src/phase-streams/syntax-hide")(state);
const createSyntaxUnhideStream = require("./src/phase-streams/syntax-unhide")(state);
const createKahnSortingStream = require("./src/phase-streams/sort-kahn")(state);
const createDedupeBundleCssStream = require("./src/phase-streams/dedupe-bundle-css")(state);
function setupPipeline() {
/* Not shown here: the default 'deps' phase stream will process all the CSS files through our custom transform (defined elsewhere). That transform handles the discovery of CSS dependencies, by pre-processing the CSS files (normalizing 'composes' statements etc. into :import/:export statements), and then extracting all the imports from them, emitting them to Browserify as additional dependencies to fetch. */
let depsPhase = browserify.pipeline.get("deps");
/* This stream marks CSS files as 'discardable' when they are only required by other CSS files and not by any JS files; this allows for their to be stripped from the final bundle, saving space. */
depsPhase.push(createCssDepsStream());
let syntaxPhase = browserify.pipeline.get("syntax");
/* These streams sneak the CSS past the JS syntax checker contained in Browserify, by encoding it in a "JS" file that contains a comment (which will always pass validation), and unpacking it into CSS again after the syntax check has occurred. This effectively opts the CSS files out of the syntax check, which only knows about JS. */
syntaxPhase.unshift(createSyntaxHideStream());
syntaxPhase.push(createSyntaxUnhideStream());
let sortPhase = browserify.pipeline.get("sort");
/* This stream re-sorts the dependencies into dependency order using Kahn's algorithm; that is to say, each dependency always appears before the file(s) that depend on it. This is needed for our final CSS processing (when we resolve the exported/imported identifiers between them), to make sure that we've processed all the dependencies of a file before processing the file itself (as otherwise we wouldn't know all the resolve identifiers to use). This is a deterministic algorith, which means it *does not* break the determinism guarantee of Browserify's own hash-sorting mechanism, and shouldn't interfere with anything else. It could be broken by other plugins adding their own sorting mechanism after this one, though; so this plugin should be loaded last. */
sortPhase.push(createKahnSortingStream());
let dedupePhase = browserify.pipeline.get("dedupe");
/* This stream does the actual bundling of CSS files. It does the final processing of all CSS files (ICSS :import/:export resolution, identifier replacing, etc.), then throws away all the files that were marked as 'discardable' earlier, converts the remaining files into JS-formatted class name exports (for use with eg. JSX), and finally inserts the fully-bundled CSS into the loader shim. Ta-da! */
dedupePhase.push(createDedupeBundleCssStream(options));
}
// NOTE: This is global because otherwise the transform will not run when processing node_modules. If this causes problems for you, please file an issue!
// FIXME: Figure out if there's a better solution for this
browserify.transform(createTransform, { ... options, global: true });
browserify.on("reset", () => {
setupPipeline();
});
setupPipeline();
return browserify;
};