├── loader.js ├── plugins ├── babel │ ├── index.js │ ├── package.json │ ├── src │ │ ├── babel-preset-build.js │ │ └── plugin.js │ └── README.md ├── next │ ├── package.json │ ├── src │ │ └── document-head-tags-server.js │ └── README.md └── webpack │ ├── FixOutputOptionsPlugin.js │ ├── FixAutoDLLPluginPlugin.js │ ├── NextCriticalPlugin.js │ └── CriticalCompiler.js ├── critical.d.ts ├── jest.config.json ├── critical.js ├── babel.config.js ├── package.json ├── index.js ├── .gitignore └── README.md /loader.js: -------------------------------------------------------------------------------- 1 | module.exports = function () {}; 2 | -------------------------------------------------------------------------------- /plugins/babel/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { dir: __dirname }; 2 | -------------------------------------------------------------------------------- /critical.d.ts: -------------------------------------------------------------------------------- 1 | export default (props: { src: string }) => null; 2 | -------------------------------------------------------------------------------- /jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "testEnvironment": "node", 3 | "testRunner": "jest-circus/runner" 4 | } 5 | -------------------------------------------------------------------------------- /critical.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ src }) => { 2 | console.warn('You need to add the next-critical plugin to next'); 3 | return null; 4 | }; 5 | -------------------------------------------------------------------------------- /plugins/babel/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-critical-babel-preset-plugin", 3 | "main": "index.js", 4 | "files": [ 5 | "src/babel-preset-build.js" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /plugins/next/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-critical-next-plugin", 3 | "nextjs": { 4 | "name": "Next Critical", 5 | "required-env": [] 6 | }, 7 | "files": [ 8 | "src/*.js" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /plugins/babel/src/babel-preset-build.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | 3 | module.exports = function plugin(config) { 4 | const plugin = resolve(__dirname, './plugin.js'); 5 | config.plugins.push([plugin]); 6 | }; 7 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | // 📌 this config is only for jest. 2 | // Unfortunately jest does not bubble up the packages for a root `babel.config.js`. 3 | // See: https://github.com/facebook/jest/issues/7359 4 | module.exports = { 5 | extends: '../../babel.config.js', 6 | }; 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-critical", 3 | "version": "1.0.0", 4 | "author": "Lukas Bombach", 5 | "license": "MIT", 6 | "main": "index.js", 7 | "files": [ 8 | "loader.js", 9 | "critical.js", 10 | "plugins/**/*(.js|.json)" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /plugins/webpack/FixOutputOptionsPlugin.js: -------------------------------------------------------------------------------- 1 | const { dirname } = require('path'); 2 | const NodeOutputFileSystem = require('webpack/lib/node/NodeOutputFileSystem'); 3 | const NodeWatchFileSystem = require('webpack/lib/node/NodeWatchFileSystem'); 4 | 5 | class FixOutputOptionsPlugin { 6 | pluginName = this.constructor.name; 7 | outputFile = undefined; 8 | 9 | constructor({ outputFile }) { 10 | this.outputFile = outputFile; 11 | } 12 | 13 | apply(compiler) { 14 | compiler.outputFileSystem = new NodeOutputFileSystem(); 15 | compiler.watchFileSystem = new NodeWatchFileSystem(); 16 | compiler.outputPath = dirname(this.outputFile); 17 | } 18 | } 19 | 20 | module.exports = FixOutputOptionsPlugin; 21 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const merge = require('deepmerge'); 2 | const NextCriticalPlugin = require('./plugins/webpack/NextCriticalPlugin'); 3 | const BabelPresetPlugin = require('./plugins/babel'); 4 | 5 | module.exports = (nextConfig = {}) => 6 | merge(nextConfig, { 7 | experimental: { 8 | plugins: true, 9 | }, 10 | plugins: ['next-critical/plugins/next'], 11 | webpack: (config, options) => { 12 | config.plugins.push(new NextCriticalPlugin(options.buildId)); 13 | options.defaultLoaders.babel.options.babelPresetPlugins.push(BabelPresetPlugin); 14 | 15 | if (typeof nextConfig.webpack === 'function') { 16 | return nextConfig.webpack(config, options); 17 | } 18 | 19 | return config; 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /plugins/babel/README.md: -------------------------------------------------------------------------------- 1 | # Next Critical Babel Preset Plugin 2 | 3 | This folder needs a short explanation why it is here and why it has a `src` subfolder. 4 | 5 | `Next.js` uses its own `Babel` preset1 and allows adding `BabelPresetPlugin`s via config. 6 | 7 | To add a `BabelPresetPlugin` you need to add an object that in its minimal form contains a `dir` field 8 | pointing to a folder. 9 | 10 | ```json 11 | { 12 | "dir": "/path/to/a/folder" 13 | } 14 | ``` 15 | 16 | that folder _must_ contain a `src` folder with a file called `babel-preset-build.js`. In that file 17 | you may export a function that modifies a config with which you can add a `Babel` plugin. So that is 18 | why there is this folder structure. 19 | 20 | 1 I think 21 | -------------------------------------------------------------------------------- /plugins/webpack/FixAutoDLLPluginPlugin.js: -------------------------------------------------------------------------------- 1 | const { SyncHook } = require('tapable'); 2 | 3 | /** 4 | * Patches a bug where the AutoDLLPlugin is missing this hook 5 | * It will throw an error in this line, saying "cannot call .call of undefined" 6 | * 7 | * https://github.com/asfktz/autodll-webpack-plugin/blob/546ccc450b13e4aad7da95d694e3c0a174b9ccfd/src/plugin.js#L87 8 | * 9 | * because it has not been initialized with this child compiler. So this plugin augments this line+ 10 | * 11 | * https://github.com/asfktz/autodll-webpack-plugin/blob/546ccc450b13e4aad7da95d694e3c0a174b9ccfd/src/plugin.js#L57 12 | * 13 | * todo this may a more thorough investigation & bugifx, but the AutoDLLPlugin will be deprecated with Webpack 5 anyway 14 | **/ 15 | class FixOutputOptionsPlugin { 16 | pluginName = this.constructor.name; 17 | 18 | apply(compiler) { 19 | compiler.hooks.autodllStatsRetrieved = new SyncHook(['stats', 'source']); 20 | } 21 | } 22 | 23 | module.exports = FixOutputOptionsPlugin; 24 | -------------------------------------------------------------------------------- /plugins/next/src/document-head-tags-server.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import React from 'react'; 4 | 5 | export default async function headTags() { 6 | const __html = await getCriticalJS(); 7 | return