cyrilwanner / next-compose-plugins

💡next-compose-plugins provides a cleaner API for enabling and configuring plugins for next.js
MIT License
736 stars 12 forks source link

Could you document a little bit of theory? #14

Closed realtebo closed 5 years ago

realtebo commented 5 years ago

I'm here to ask devleoper help to understand what must be done and where and how when adding 'translating' the default configuration of a plugin of webpack to your plugin syntax.

Example:

I can simply run sass using

const withPlugins = require('next-compose-plugins');

const nextConfig = ({
  webpack: async (config, options) => {
    return config;
  },
});

const plugins = [

  [NextSassPlugin],

];

// plugins: array
// nextConfig: un oggetto
module.exports = withPlugins (
  [...plugins], 
  nextConfig 
);

is this right?

I see that this single step create (in dev mode) .next/static/css/style.chunk.css and its complementar .map.

Then I'd like to add for example Optimize Css Assets Plugi

I tried with a simple


const NextSassPlugin = require("@zeit/next-sass");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const withPlugins    = require('next-compose-plugins');

const nextConfig = ({
  webpack: async (config, options) => {
    return config;
  },
});

const plugins = [

  [NextSassPlugin],
  [OptimizeCssAssetsPlugin]

];

// plugins: array
// nextConfig: un oggetto
module.exports = withPlugins (
  [...plugins], 
  nextConfig 
);

But I got a crash:

TypeError: Class constructor OptimizeCssAssetsWebpackPlugin cannot be invoked without 'new' at plugins.forEach.plugin (.....\node_modules\next-compose-plugins\lib\compose.js:102:23) at Array.forEach () at composePlugins (.....\node_modules\next-compose-plugins\lib\compose.js:79:11) at .....\node_modules\next-compose-plugins\lib\index.js:22:38 at loadConfig (.....\node_modules\next\dist\server\config.js:54:10)

I lloked at other plugin docs and tried using new()

const plugins = [

  [NextSassPlugin],
  [new OptimizeCSSAssetsPlugin()]

];

Now npm run dev doesn't crash, but nothing happe to the target file, styles.chunk.css.

I exited crazy trying to understand how to simple add a test and add a loader to webpack config. I ended in this, but I do not see any difference, so I cannot understand where/how myconfig must be fixed.


const NextSassPlugin = require("@zeit/next-sass");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const withPlugins    = require('next-compose-plugins');

const nextConfig = ({
  webpack: async (config, options) => {
    config.module.rules.push({
      test: /\.css$/,
      loader: MiniCssExtractPlugin.loader
    });
    return config;
  },
});

const plugins = [

  [NextSassPlugin],
  [new OptimizeCSSAssetsPlugin()]

];

// plugins: array
// nextConfig: un oggetto
module.exports = withPlugins (
  [...plugins], 
  nextConfig 
);

So this is my actual problem, but I widened the broad of my request

The title of this issue is due to a request I'd open to you: please, document the logical process of understand where/how to add test, loader, optimizations and other webpack configs, explaining when to use

[pluginName, {pluginConfig}]

and when/how/where add these configs to

const nextConfig = ({
  webpack: async (config, options) => {
    .... here .... ? where, when, how ?
    return config;
  },
});
cyrilwanner commented 5 years ago

Hi @realtebo I'm sorry that it may not be clear from the readme and try to explain it more detailed (and add it to the readme afterwards).

There is a difference between a next plugin (like next-sass) and a webpack plugin (like the optimize css assets).

A next plugin is specifically made for next.js (and mostly starts with a next- prefix in their name). All next plugins get the basic next.js configuration and can modify them in any way they want. In this configuration, they also get the webpack configuration which next.js will use in the end so they can add/modify any webpack loader or plugin. All next plugins can simply be added to your plugins array (in the [pluginName, {pluginConfig}] style) because they have the right signature for next and so can receive its configuration correctly.

A webpack plugin is a general plugin for webpack and not specifically made for next.js. Those can not be added to the same plugin array as the next plugins are, because they have a different signature which next will not understand. Next doesn't check whether it is a next or webpack plugin, it just assumes every plugin is a next plugin. So it tries to invoke your webpack plugin as if it were a next plugin and resulted in an error. But next still provides a way to use such webpack plugins - like you have in your examples - within the webpack: (config, options) => config function.

So if your plugin is a next plugin, you can simply add it to the array, otherwise you need to add it within the webpack function.


Now to your examples. next-sass is a next plugin, so it is right to put it into the plugins array. Adding OptimizeCSSAssetsPlugin to the same array won't work as you already have noticed, because it is a webpack plugin and not a next plugin.

The solution could be a different one (I'm not sure and didn't try it out, but when looking at the documentation, I think it could work). next-sass already registers a webpack loader for sass files which isn't easy to override. So just adding a new new loader for the optimization plugin also won't work. When I now look a the readme of next-sass, they say something about PostCSS Options so I assume they use node-sass + postcss. PostCSS is a postprocessor for css. So your sass files first get compiled into css by node-sass and then postcss can do additional stuff with your css files. And if I understood the OptimizeCSSAssetsPlugin, it uses cssnano internally and just wraps it in a webpack loader. And it looks like cssnano supports PostCSS.

So we end up with the conclusion that next-sass uses postcss and cssnano can be implemented with postcss which leads to my proposed solution which can maybe work:

I hope that this explanation was clear and the solution will work. If not or if anything wasn't clear, please just ask back :)

realtebo commented 5 years ago

First of all, thanks: for reply, for big explanation effort and for the plugin itself.

I appreciated your explanation about next.js plugins vs webpack 'native' plugins, this difference was totally not clear to me.

But you focus so much on my specific problem that you loose my goal, understand how to integrate an external, no-nextjs, plugin, a webpack plugin, when using your plugin.

Imagine I want to use a totally different one, one not 'overlapping' the one I am already using....

My question is still valid, sorry. How to do this?

cyrilwanner commented 5 years ago

Sorry that I didn't cover that enough.

If you want to integrate a non-next plugin, you have to do it in the webpack function which you can provide:

const withPlugins = require('next-compose-plugins');

const nextConfig = ({
  webpack: async (config, options) => {
    // v                         v //
    // add any webpack plugin here // 
    // ^                         ^ //
    return config;
  },
});

const plugins = [
  [NextSassPlugin],
];

module.exports = withPlugins (
  [...plugins], 
  nextConfig 
);

The first param you receive (config) contains the whole webpack config and you may want to modify it. The second one (options) contains some options which next.js provides you (buildId, environment, ..).

Adding a webpack plugin can now vary, but you can mostly follow their install instructions and just do everything with this config param which you would do in a webpack.config.js without next.js.

For example, if the webpack plugin is a full plugin and needs to be added to the plugins array, we can do it like this:

const nextConfig = ({
  webpack: async (config, options) => {
    config.plugins.push(new MinifyPlugin(minifyOpts, pluginOpts));
    return config;
  },
});
// rest of the file

Or if your plugin needs to add a loader, do it like this:

const nextConfig = ({
  webpack: async (config, options) => {
    config.module.loaders.push({
      test: /\.json$/,
      loader: 'json-loader'
    });
    return config;
  },
});
// rest of the file

And so on. As said, it really varies between plugins, but all configuration should be done with the config param which the webpack function gets (it has exactly the same structure as when you would configure webpack in a file without next.js). Just make sure you never override the webpack config but only add elements to the arrays (like with push in the examples above). Otherwise, you would override configs which next.js needs or another next plugin adds.

realtebo commented 5 years ago

Ok, that's very usefull, thanks.