Use parametric modules for passing around state, and use that to implement a custom extensions option

pull/1/head
Sven Slootweg 4 years ago
parent 3cd5ff620a
commit 3840a1f8d3

@ -70,12 +70,18 @@ For further usage examples, refer to the usual Browserify documentation. `icssif
Plugin options (all optional):
* __extensions:__ An array of extensions (without the leading dot!) that should be considered "CSS files"; this is useful when you eg. name your files `.postcss` to indicate that you are using non-standard syntax. This list of extensions will *replace* the default list of extensions, so you will need to explicitly specify `"css"` in the list, if you want to keep parsing `.css` files. Defaults to `[ "css" ]`.
* __mode:__ Whether to assume that untagged class names in your CSS (ie. those without a `:local` or `:global` tag) are local or global. Defaults to `"local"`, but you can set this to `"global"` if you want to make the class name mangling *opt-in*. You'll generally want to leave this at the default setting.
* __before:__ PostCSS transforms to run *before* the ICSS transforms, ie. before imports/exports are analyzed. This is usually where you want custom PostCSS plugins to go.
* __after:__ PostCSS transforms to run *after* the ICSS transforms, ie. after mangling the class names, but before bundling it all together into a single file. You'll rarely need to use this.
## Changelog
### v1.2.0 (March 6, 2020)
- __Feature:__ You can now specify custom `extensions` that should be treated as CSS files, besides `".css"`.
### v1.1.1 (February 16, 2020)
- __Bug__:__ Removed stray console.log call.

@ -1,13 +1,8 @@
"use strict";
const { validateArguments, required, oneOf, arrayOf, allowExtraProperties, ValidationError } = require("validatem");
const { validateArguments, required, oneOf, arrayOf, isString, allowExtraProperties, ValidationError } = require("validatem");
const createTransform = require("./src/transform");
const createCssDepsStream = require("./src/phase-streams/deps-css");
const createSyntaxHideStream = require("./src/phase-streams/syntax-hide");
const createSyntaxUnhideStream = require("./src/phase-streams/syntax-unhide");
const createKahnSortingStream = require("./src/phase-streams/sort-kahn");
const createDedupeBundleCssStream = require("./src/phase-streams/dedupe-bundle-css");
function isPostcssPlugin(value) {
if (value.postcssPlugin == null || value.postcssVersion == null || typeof value !== "function") {
@ -17,7 +12,26 @@ function isPostcssPlugin(value) {
// 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
function setupPipeline(browserify, options) {
module.exports = function (browserify, options) {
validateArguments(arguments, [
[ "browserify" ],
[ "options", allowExtraProperties({
mode: oneOf([ "local", "global" ]),
before: arrayOf([ required, isPostcssPlugin ]),
after: arrayOf([ required, isPostcssPlugin ]),
extensions: arrayOf([ required, isString ])
})]
]);
let state = { extensions: options.extensions };
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 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");
@ -40,25 +54,15 @@ function setupPipeline(browserify, options) {
/* 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));
}
module.exports = function (browserify, options) {
validateArguments(arguments, [
[ "browserify" ],
[ "options", allowExtraProperties({
mode: oneOf([ "local", "global" ]),
before: arrayOf([ required, isPostcssPlugin ]),
after: arrayOf([ required, isPostcssPlugin ])
})]
]);
}
browserify.transform(createTransform, options);
browserify.on("reset", () => {
setupPipeline(browserify, options);
setupPipeline();
});
setupPipeline(browserify, options);
setupPipeline();
return browserify;
};

@ -1,9 +1,11 @@
"use strict";
const stream = require("./stream");
const isCss = require("./is-css");
module.exports = function cssOnlyStream(handler, flushHandler) {
module.exports = function (state) {
const isCss = require("./is-css")(state);
return function cssOnlyStream(handler, flushHandler) {
return stream((item) => {
if (isCss(item)) {
return handler(item);
@ -11,4 +13,5 @@ module.exports = function cssOnlyStream(handler, flushHandler) {
return [ item ];
}
}, flushHandler);
}
};

@ -1,5 +1,16 @@
"use strict";
module.exports = function isCss(item) {
return /\.css$/.test(item.file);
const defaultValue = require("default-value");
const path = require("path");
// Yes, there is a good reason this is a separate module. In the future we may need to add more heuristics to determine whether a "CSS file" *really* is a CSS file, rather than just going off the filename, and this centralizes the checking logic we'd need to change for that. If this ever occurs, though, keep in mind that this function doesn't always get a *full* file object (sometimes it just gets an object with a filename), so any such heuristics would need to be able to deal with that.
module.exports = function (state) {
let targetExtensions = new Set(defaultValue(state.extensions, [ "css" ]));
return function isCss(item) {
let itemExtension = path.extname(item.file).replace(/^\./, "");
return targetExtensions.has(itemExtension);
};
};

@ -5,12 +5,14 @@ const path = require("path");
const stream = require("../stream");
const createFilePostprocessor = require("../postcss/postprocess");
const isCss = require("../is-css");
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 createDedupeBundleCssStream(options) {
module.exports = function (state) {
const isCss = require("../is-css")(state);
return function createDedupeBundleCssStream(options) {
// TODO: Reuse instance?
let processFile = createFilePostprocessor(options);
let allCss = "";
@ -58,4 +60,5 @@ module.exports = function createDedupeBundleCssStream(options) {
}
}
});
};
};

@ -1,9 +1,11 @@
"use strict";
const stream = require("../stream");
const isCss = require("../is-css");
module.exports = function createCssDepsStream() {
module.exports = function (state) {
const isCss = require("../is-css")(state);
return function createCssDepsStream() {
let dependents = new Map();
let depsPhaseItems = [];
@ -35,4 +37,5 @@ module.exports = function createCssDepsStream() {
}
return stream(handleItem, flush);
};
};

@ -1,13 +1,16 @@
"use strict";
const cssOnlyStream = require("../css-only-stream");
const hideCss = require("../hide-css");
module.exports = function createSyntaxHideStream() {
module.exports = function (state) {
const cssOnlyStream = require("../css-only-stream")(state);
return function createSyntaxHideStream() {
return cssOnlyStream((file) => {
return {
... file,
source: hideCss(file.source)
};
});
};
};

@ -1,13 +1,16 @@
"use strict";
const cssOnlyStream = require("../css-only-stream");
const unhideCss = require("../unhide-css");
module.exports = function createSyntaxHideStream() {
module.exports = function (state) {
const cssOnlyStream = require("../css-only-stream")(state);
return function createSyntaxHideStream() {
return cssOnlyStream((file) => {
return {
... file,
source: unhideCss(file.source)
};
});
};
};

@ -5,11 +5,13 @@ const bl = require("bl");
const through2 = require("through2");
const path = require("path");
const isCss = require("./is-css");
const createFilePreprocessor = require("./postcss/preprocess");
const loaderShimPath = require("./loader-shim-path");
module.exports = function createTransform(file, options) {
module.exports = function (state) {
const isCss = require("./is-css")(state);
return function createTransform(file, options) {
// TODO: Reuse instance?
let preprocessFile = createFilePreprocessor(options);
@ -58,4 +60,5 @@ module.exports = function createTransform(file, options) {
} else {
return through2.obj();
}
};
};

Loading…
Cancel
Save