├── .gitignore ├── .prettierrc ├── CHANGELOG.md ├── package.json ├── LICENSE ├── manifest.yml ├── index.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": false, 4 | "trailingComma": "none" 5 | } 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the “Inline Critical CSS” plugin will be documented in this file. 4 | 5 | ## [2.0.0] 6 | 7 | - Updated dependencies. 8 | - ⚠️ Breaking: the `minify` option has been removed. 9 | - ⚠️ Breaking: dropped support for node versions 11 and 13. 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "netlify-plugin-inline-critical-css", 3 | "version": "2.0.0", 4 | "description": "A Netlify plugin to extract and inline your critical CSS, built on top of the `critical` package.", 5 | "license": "MIT", 6 | "author": "Tom Bonnike (bonniketom@gmail.com)", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/Tom-Bonnike/netlify-plugin-inline-critical-css.git" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/Tom-Bonnike/netlify-plugin-inline-critical-css/issues" 13 | }, 14 | "homepage": "https://github.com/Tom-Bonnike/netlify-plugin-inline-critical-css#readme", 15 | "keywords": [ 16 | "netlify", 17 | "netlify-plugin", 18 | "inline", 19 | "critical", 20 | "css" 21 | ], 22 | "engines": { 23 | "node": "^10 || ^12 || >=14" 24 | }, 25 | "main": "index.js", 26 | "dependencies": { 27 | "critical": "^4.0.1", 28 | "readdirp": "^3.6.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Tom Bonnike 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /manifest.yml: -------------------------------------------------------------------------------- 1 | name: netlify-plugin-inline-critical-css 2 | inputs: 3 | - name: fileFilter 4 | description: A filter used to target specific files in the publish directory. Be sure to only target HTML files. This option is passed onto the readdirp library, see https://github.com/paulmillr/readdirp#options for more info. 5 | default: ['*.html'] 6 | - name: directoryFilter 7 | description: A filter used to target or ignore specific directories in the publish directory. This option is passed onto the readdirp library, see https://github.com/paulmillr/readdirp#options for more info. 8 | default: ['!node_modules'] 9 | - name: extract 10 | description: Whether to remove the inlined styles from any stylesheet referenced in the HTML. Use with caution. Removing the critical CSS per page results in a unique async loaded CSS file for every page, meaning you can’t rely on cache across multiple pages. 11 | default: false 12 | - name: dimensions 13 | description: An array of objects containing `width` and `height` properties to deliver critical CSS for multiple screen resolutions. 14 | default: 15 | - width: 414 16 | height: 896 17 | - width: 1920 18 | height: 1080 19 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const readdirp = require('readdirp') 2 | const critical = require('critical') 3 | 4 | const getHtmlFiles = async (directory, inputs = {}) => { 5 | const files = await readdirp.promise(directory, { 6 | fileFilter: inputs.fileFilter, 7 | directoryFilter: inputs.directoryFilter 8 | }) 9 | 10 | return files.map((file) => file.fullPath) 11 | } 12 | 13 | module.exports = { 14 | onPostBuild: async ({ inputs, constants, utils }) => { 15 | const htmlFiles = await getHtmlFiles(constants.PUBLISH_DIR, inputs) 16 | 17 | try { 18 | // Ignore penthouse/puppeteer max listener warnings. 19 | // See https://github.com/pocketjoso/penthouse/issues/250. 20 | // One penthouse call is made per page and per screen resolution. 21 | process.setMaxListeners(inputs.dimensions.length + 1) 22 | 23 | // Process each page in sequence to avoid lingering processes and memory 24 | // issues, at the cost of a slower execution. 25 | // `critical` might offer this feature at some point: 26 | // https://github.com/addyosmani/critical/issues/111 27 | for (const filePath of htmlFiles) { 28 | await critical.generate({ 29 | base: constants.PUBLISH_DIR, 30 | // Overwrite files by passing the same path for `src` and `target`. 31 | src: filePath, 32 | target: filePath, 33 | inline: true, 34 | extract: inputs.extract, 35 | dimensions: inputs.dimensions, 36 | // Force critical to run penthouse only on a single page at a time to 37 | // avoid timeout issues. 38 | concurrency: 1, 39 | // Bump penthouse’s page load timeout to 2 minutes to avoid crashes 40 | // which could cause lingering processes as it’s possible some pages 41 | // can take a long time to load. 42 | penthouse: { timeout: 120000 } 43 | }) 44 | } 45 | 46 | console.log('Critical CSS successfully inlined!') 47 | } catch (error) { 48 | return utils.build.failBuild('Failed to inline critical CSS.', { error }) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # netlify-plugin-inline-critical-css 2 | 3 | A Netlify Build plugin to extract and inline your [critical CSS](https://web.dev/extract-critical-css/), built on top of the [`critical` package](https://github.com/addyosmani/critical). It extracts the CSS for above-the-fold content and inlines it into the HTML document in order to render content to the user as fast as possible. 4 | 5 | Inlining the critical CSS directly into the HTML document eliminates additional requests and can be used to deliver a “one roundtrip” critical path where only the HTML is a blocking resource. You can use this plugin together with [`netlify-plugin-inline-source`](https://github.com/tom-bonnike/netlify-plugin-inline-source) to inline your other assets/sources such as small images, SVGs or render-blocking scripts. 6 | 7 | ## Usage and inputs 8 | 9 | To install the plugin in the Netlify UI, use this [direct in-app installation link](https://app.netlify.com/plugins/netlify-plugin-inline-critical-css/install) or go to the [Plugins directory](https://app.netlify.com/plugins). 10 | 11 | For file-based installation, add it to your `netlify.toml` file. 12 | 13 | ```toml 14 | [[plugins]] 15 | package = "netlify-plugin-inline-critical-css" 16 | 17 | # All inputs are optional, so you can omit this section. 18 | # Defaults are shown below. 19 | [plugins.inputs] 20 | # A filter used to target specific files in the publish directory. Be sure to only target HTML files. This option is passed onto the readdirp library, see https://github.com/paulmillr/readdirp#options for more info. 21 | fileFilter = ["*.html"] 22 | 23 | # A filter used to target or ignore specific directories in the publish directory. This option is passed onto the readdirp library, see https://github.com/paulmillr/readdirp#options for more info. 24 | directoryFilter = ["!node_modules"] 25 | 26 | # You can refer to `critical`’s documentation: https://github.com/addyosmani/critical for all options below. 27 | # Whether to remove the inlined styles from any stylesheet referenced in the HTML. Use with caution. Removing the critical CSS per page results in a unique async loaded CSS file for every page, meaning you can’t rely on cache across multiple pages. 28 | extract = false 29 | 30 | # An array of objects containing `width` and `height` properties to deliver critical CSS for multiple screen resolutions. 31 | dimensions = [ 32 | { width = 414, height = 896 }, 33 | { width = 1920, height = 1080 } 34 | ] 35 | ``` 36 | 37 | To complete file-based installation, from your project’s base directory, use npm, yarn, or any other Node.js package manager to add the plugin to `devDependencies` in `package.json`. 38 | 39 | ```bash 40 | npm install -D netlify-plugin-inline-critical-css 41 | ``` 42 | 43 | Once installed and configured, the plugin will automatically run for all of your site’s deploys. 44 | 45 | ### Testing locally 46 | 47 | To [test this plugin locally](https://docs.netlify.com/configure-builds/build-plugins/create-plugins/#local-plugins), you can use the [Netlify CLI](https://docs.netlify.com/cli/get-started/#run-builds-locally): 48 | 49 | ```bash 50 | # Install the Netlify CLI. 51 | npm install netlify-cli -g 52 | 53 | # In the project working directory, run the build as Netlify would with the build bot. 54 | netlify build 55 | ``` 56 | --------------------------------------------------------------------------------