There are four plugins for each of the phases which depend on the previous phase (except the clean
plugin), however each phase leaves the code in a functioning state.
These plugins rely on the dependency object generated by webpack --json
and rely on the common-chunks
module in the object.
The deprecation of CommonsChunkPlugin
in webpack 4 may change the structure of this object.
This adds the namespaces (suffixes) to all selectors and takes the following options:
An array of strings representing file paths which contain the top level directories which the plugin will recursive analyze for class selectors
A comma, delineated string representing file extensions that will be examined for class selectors within directories
An object with two keys (BLACKLIST_CLASSES
and BLACKLIST_PREFIXES
) whose values are both an array of strings which are class selectors (including the period!).
These selectors will be ignored (skipped from namespacing) in stylesheets. The selectors in BLACKLIST_PREFIXES
will ignore class selectors that begin with those strings.
For one off selectors that would be skipped, adding a comment next to the selector in the stylesheets will allow the plugin to ignore namespacing the selector like so
.test { /* squeaky-skip */
color: fuchsia;
}
An array of strings that represent regular expressions to target specific internal/helper method invocations within a codebase that can be targeted by the plugin. See some specs for an example.
With namespaced selectors across all files, there's a 1:1 correlation where a selector in a given view/template file has a selector in a stylesheet that defines it's styles. Since each namespaced selector has a unique key globally, the selector can directly target those elements which can happen if nesting is removed from the stylesheet. The major obstacle preventing such a simple solution is that with a sufficiently sized codebase, there will be many stylesheets which define nested selectors where a given DOM element can be targetted by multiple of such selectors. The flattening of a stylesheet will normalize the specificity of all selectors at the cost of losing which styles should have precedence over others.
To alleviate this issue, a heuristic is applied in an attempt to remove namespaced selectors from all files, except for those that it's used in. This is accomplished
by starting with all files that contain a given selector and walking the dependency chain (provided by webpack, assuming that the codebase has been sufficiently
modularized with CommonJS modules) to determine the file hierarchy which actually utilize the styles. There can be some false positives generated from this process
and shared components that could be chunked/split via other webpack plugins. For the most part, this helps wipe out a majority of the extra namespaced selectors
that are scattered from the clean
plugin (especially the generically named shared styles).
The following options can be passed in
A RegExp indicating the types of files to include under the common-chunks
module
An array of strings representing file paths which contain the top level directories which the plugin will recursive analyze for class selectors
An array of RegExps to blacklist file types to be analyzed for namespaced selectors within the directories
specified and filterInclude
below
An array of RegExps to whitelist file types to be analyzed for namespaced selectors within the directories
specified
A function to map the argument string path to the name of the feature the path is associated to. Assists with the dependency traversal to determine which files should be bucketize under the same feature
A string representing the stylesheet path to analyze
A RegExp to blacklist file types that are detected to have namespaced selectors. Typically, these target server-side files that are disconnected from the webpack
server such as .erb
or .rb
A string representing the path to the webpack JSON file or Object representing the contents of said file
A RegExp indicating the types of files that lack dependencies. Typically, these are view template files (such as .eco
or .hbs
)
A pattern string fed into the invert match (grep -v
) run on the set of whitelist files for a given namespace selector. Used to exclude "blessed" files
in a codebase, such as styleguide components
After running the heuristic
plugin, each namespaced selector in a stylesheet should have a 1:1 correspondence with the view template files in which they
are used in. This unique relationship means that the selectors in the stylesheet can be pulled out top level so that the specificity they have is just of
the class.
A caveat is that the a given DOM element may have multiple namespaced selectors which conflict since they all have the same single class specificity.
These can be highlighted and resolved with the specificity
plugin
In case there are extra new lines inserted between the first style brace and property values, the following shell command can be run to clean up the styles
cat <PATH> | awk '/{/ { printf("%s", $0); next } 1' | tee <PATH>
Using stylint
can also clean up any additional awkward spacing that's leftover
An example script demonstrates how the plugins can be hooked up with PostCSS. If placed in the directory scripts/node
, the clean
plugin can be executed with
node examples/scss-parser.js path/to/stylesheet.scss --clean
The heuristic
plugin can be run with
node examples/scss-parser.js path/to/stylesheet.scss --heuristic
The flatten
plugin can be run with (requires the heuristic
plugin to be run first)
node examples/scss-parser.js path/to/stylesheet.scss --flatten
The analytics
plugin can be run with
node examples/scss-parser.js path/to/stylesheet.scss --analyze
Selector specificity can be observed with
node examples/scss-parser.js path/to/stylesheet.scss --specify
Once styles have been flattened to a single level at the top (via the flatten
plugin above), the 1:1 relation between each namespaced selector in the stylesheet
and the view templates they are used in means the selectors can be extracted as dependency in said views as a CSS module. This is done for all view files that
are run through webpack, so files that are server side views (such as those similar to ERB
) will be left out. Selectors that belong in those types of files
will be alerted so they can be isolated into their own stylesheets in hopes that those views can be ported into the webpack asset server.
The following are the options it takes
A string representing the stylesheet path to analyze
An array of strings representing file paths which contain the top level directories which the plugin will recursive analyze for class selectors
A string indicating the temporary file path that will be used to store the intermediary stylesheet selectors such as duplication handling
A regular expression used to specify which view files belong to the server, disconnected from the webpack asset server
To check for any dangling namespaced selectors, the following command can be run
squeaky-lint --directoriesPath <FILE_PATH> --pathRoot <FILE_PATH> --composeDir <DIR_PATH> --ext <EXT_STR>
Here are further descriptions for the configuration flags
A string file path that points to a module which returns an array of strings denoting file directories to lint
A string file path that's the top level directory of the code to be analyzed
A string that's comma delineated of directories that have stylesheets implementing CSS composition
A string that's comma delineated to specify the extensions for view files to scan for squeaky selectors
There are two auxiliary plugins that help collect data to assist with the movement between the squeaky phases
After a stylesheet has been namespaced (run through phase 1, the clean
plugin), it can be checked for how "clean" the stylesheet is relative to the codebase.
This is determined by checking the base/original (without the suffix) selector and seeing if all occurrences of that selector have been converted
to CSS modules. This is done by checking if the selector processed in the current stylesheet is the only place that still has a namespace. A stylesheet that contains
only namespaced selectors that are present in the current stylesheet means that it's selectors have been completely isolated to a file.
The plugin takes the following options:
A newline separated string of SCSS files. This typically is the result of a Shell command (such as find
) to aggregate necessary files
If a given page layout pulls in a number of stylesheets, trying to flatten those selectors may lead to specificity conflicts because the selectors have the same level of nesting, however depend on the order of files being loaded to break ties. These issues can be detected by running this plugin which will check all namespaced selectors in a given stylesheet and output an object detailing the selectors + properties that have conflicts and the file they source from.
See the following image for a better sense of the structure:
The following options can be passed in
A string representing the stylesheet to analyze
An array of directory strings which should be given less precedence (lose specificity ties). Usually utility + reset files
An array of directory strings which should be given more precedence (win specificity ties). Usually exceptional cases
yarn test
will run the specs, yarn lint
will run eslint
with the airbnb
configuration
Specs can be debugged with node --inspect-brk node_modules/jasmine/bin/jasmine.js specs/**/*.js