mrnocreativity / postcss-critical-split

A PostCSS plugin that takes existing CSS files and splits out the annotated critical styles into a seperate file.
MIT License
89 stars 6 forks source link

postcss-critical-split

A PostCSS plugin that takes existing CSS files and splits out the annotated critical styles into a separate file, inspired by https://github.com/wladston/postcss-split

A PostCSS plugin to split your Critical CSS from the rest

What exactly does this plugin do?

What does it NOT do?

Why should I tag my CSS rules myself?

For larger scale projects, automating critical-CSS detection is complicated, unprecise or damn-nearly impossible. Annotating your CSS with a simple comment gives your perfect control over which CSS rules are to be considered critical and which ones are not.

If you later decide to no longer support this workflow or switch to a different one (with different tools), the critical-comments are standard CSS and will not break your project.

Why split the files into 2 (or more) separate files? Why not immediately move it into HTML?

The idea here is that we want to generate our entire CSS file first and then split out what is considered 'critical'. Injecting it into an HTML file right away would be fairly dictative of your workflow. This allows for more flexible setups.

For example: during development you could <link> the critical-CSS file, while rendering it out into the HTML templates once you get ready for production (remember to adjust the URL's in the CSS file for the changed context of the CSS execution).

Install

npm install --save-dev postcss postcss-critical-split

Test

To run tests:

npm test

If you want to contribute to the project and write additional tests, look into the testsuite folder. You'll find a folder per test with input/output results, splitSettings and optional process tasks where you can write a custom test scenario.

Usage

gulp.src(['**/*.css','!**/*-critical.css'])
    .pipe(postcss(require('postcss-critical-split')));
/* before: main.css */

/* critical:start */
header{
    background-color: #1d1d1d;
    font-size: 2em;
}

.aside {
    text-decoration: underline;
}

/* critical:end */

p {
    font-size: 14px;
}

li {
    float: left;
    /* critical:start */
    color: red;
    /* critical:end */
}

footer{
    background-color: #1d1d1d;
    font-size: 1.1em;
}

a[rel=external]:before{
    content: '[!]';
    /* critical */
}

/* critical:start */
@media screen and (min-width: 400px) {
    footer {
        padding: 50px;
    }
}
/* critical:end */

@media screen and (min-width: 1400px) {
    header {
        height: 300px;
        /* critical:start */
        background-color: #FF0066;
        font-size: 3em;
        /* critical:end */
    }
}
/* after: main.css */

p {
    font-size: 14px;
}

li {
    float: left;
}

footer{
    background-color: #1d1d1d;
    font-size: 1.1em;
}

@media screen and (min-width: 1400px) {
    header {
        background-color: #FF0066;
        font-size: 3em;
    }
}
/* after main-critical.css */

header{
    background-color: #1d1d1d;
    font-size: 2em;
}

.aside {
    text-decoration: underline;
}

li {
    color: red;
}

a[rel=external]:before{
    content: '[!]';
}

@media screen and (min-width: 400px) {
    footer {
        padding: 50px;
    }
}

@media screen and (min-width: 1400px) {
    header {
        background-color: #FF0066;
        font-size: 3em;
    }
}

Important comments

This plugin supports important comments (e.g. /*! critical */). Any of the examples above should work using this comment style.

Options

The plugin accepts an object with additional options.

options.output

defaults to critical Allowed values: critical, rest or input to return either the critical-css, the rest-css or just the original input-css.

options.blockTag

defaults to critical

This is the comment text that is matched in every rule/atRule in the original CSS file. If the blockTag is encountered in a rule, the rule is appended to the critical-CSS file and removed in the original file. All declarations in the rule will be carried over.

/* gulpfile */
gulp.src(['**/*.css','!**/*-critical.css'])
    .pipe(postcss(require('postcss-critical-split')({
        'blockTag':'criticalCss'
    }));
/* before: main.css */
header{
    /* criticalCss */
    background-color: #1d1d1d;
    font-size: 2em;
}

footer{
    background-color: #1d1d1d;
    font-size: 1.1em;
}
/* after: main.css */
footer{
    background-color: #1d1d1d;
    font-size: 1.1em;
}
/* after: main-critical.css */
header{
    background-color: #1d1d1d;
    font-size: 2em;
}

options.startTag & options.endTag

startTag defaults to critical:start endTag defaults to critical:end

These are the comment texts that are matched throughout the original CSS file. If the startTag is encountered, every rule, declaration, atRule is carried into the critical-CSS until the endTag is encountered. All the rules that appy will be removed from the original CSS.

/* gulpfile */
gulp.src(['**/*.css','!**/*-critical.css'])
    .pipe(postcss(require('postcss-critical-split')({
        'startTag':'grab:open',
        'endTag':'grab:close',
    }));
/* before: main.css */

/* grab:open */
header{
    background-color: #1d1d1d;
    font-size: 2em;
}

.aside {
    text-decoration: underline;
}

/* grab:close */

footer{
    background-color: #1d1d1d;
    font-size: 1.1em;
}
/* after: main.css */
footer{
    background-color: #1d1d1d;
    font-size: 1.1em;
}
/* after: main-critical.css */
header{
    background-color: #1d1d1d;
    font-size: 2em;
}
.aside {
    text-decoration: underline;
}

options.modules

These are the modules you want to select from your css file into the critical file. This allows for targetting which parts of the CSS to include in the critical and which ones not. CSS rules that are not labeled by a module will be considered 'common' and thus will be added to the critical CSS at all times.

/* gulpfile */
gulp.src(['**/*.css','!**/*-critical.css'])
    .pipe(postcss(require('postcss-critical-split')({
        'modules': ['header', 'top-photo']
    }));
/* before: main.css */

/* critical:start:header */
header{
    background-color: #1d1d1d;
    font-size: 2em;
}
/* critical:end */

.login-button {
    display: block;
    border: red thin solid;
}

/* critical:start:top-photo */
.top-photo{
    background-color: #1d1d1d;
    font-size: 2em;
}
/* critical:end */

/* critical:start:preview-article */
.preview-article{
    color: #CCC;
}
/* critical:end */

/* critical:start */
.profile-picture{
    float: right;
    width: 20%;
    height: auto;
}
/* critical:end */

.aside {
    text-decoration: underline;
}

footer{
    background-color: #1d1d1d;
    font-size: 1.1em;
}
/* after: main.css */
.login-button {
    display: block;
    border: red thin solid;
}

.preview-article{
    color: #CCC;
}

.aside {
    text-decoration: underline;
}

footer{
    background-color: #1d1d1d;
    font-size: 1.1em;
}
/* after: main-critical.css */
header{
    background-color: #1d1d1d;
    font-size: 2em;
}

.top-photo{
    background-color: #1d1d1d;
    font-size: 2em;
}

.aside {
    text-decoration: underline;
}

footer{
    background-color: #1d1d1d;
    font-size: 1.1em;
}

options.separator

This is the separator used in your critical start-tag to tag a module.

/* gulpfile */
gulp.src(['**/*.css','!**/*-critical.css'])
    .pipe(postcss(require('postcss-critical-split')({
        'modules': ['header', 'top-photo'],
        'separator': '--'
    }));
/* critical:start--header */