Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

require('postcss-plugin')() vs require('postcss-plugin') (without invocation) #225

Open
andreavaccari opened this issue Oct 6, 2021 · 13 comments

Comments

@andreavaccari
Copy link

Hi @ai, thank you for your work on postcss and autoprefixer (and your other packages). We're using them in combination with Vite, Svelte, and Tailwind, and we'd appreciate a clarification on how to specify plugins in postcss.config.cjs.

In this repo (ref), you instruct to require and then invoke a plugin:

module.exports = ({ env }) => ({
  plugins: [
    env === 'production' ? require('postcss-plugin')() : false    // <- with invocation
  ]
})

In autoprefixer (ref), you instruct to require a plugin without invoking it:

module.exports = {
  plugins: [
    require('autoprefixer')                                       // <- without invocation
  ]
}

While investigating performance issues with VS Code, I added a console.log to tailwind.config.cjs and found that:

  • With require('tailwindcss'), Tailwind's config file is loaded several times
  • With require('tailwindcss')(), Tailwind's config file is loaded only once

Question: Could you clarify what is the preferred way to specify plugins?

Thank you for your help!

@ai
Copy link
Member

ai commented Oct 6, 2021

Both ways are valid in PostCSS. The logic is:

  1. If you have no options, you can reduce () by writing only require('autoprefixer')
  2. If you have options, then you pass it to () require('autoprefixer')({ option: 1 })

PostCSS will detect, that () was not called and will call it automatically https://github.com/postcss/postcss/blob/main/lib/postcss.d.ts#L215-L216

@andreavaccari
Copy link
Author

Thank you for the quick reply! I assumed this was the intended behavior.

Could I ask you to reproduce the following simple set up?

npm init --yes @svelte-add/kit@latest -- --with tailwindcss test
cd test
sed -i "" "1s/^/console.log('inside tailwind config')\n/" tailwind.config.cjs
npm run dev -- --open

At this point you should see the string inside tailwind config printed twice to console.

Now please kill the dev server and repeat the following steps.

sed -i "" "s/tailwindcss()/tailwindcss/" postcss.config.cjs
npm run dev -- --open

Now you should see the string inside tailwind config printed several times to console.

Do you have any insight into why this might be happening?

@ai
Copy link
Member

ai commented Oct 6, 2021

I got this error during npm init call.

Can you show me the PostCSS config? Can you show me how do you load this config?

➜ npm init --yes @svelte-add/kit@latest -- --with tailwindcss test
➕ Svelte Add's SvelteKit app initializer (Version 2021.10.02.00)
file:///home/ai/.npm/_npx/9320fb454a84dc6d/node_modules/@svelte-add/create-kit/__init.js:33
		if (code !== 0) throw new Error(body);
		                ^

Error: error An unexpected error occurred: "Can't answer a question unless a user TTY".

    at ChildProcess.<anonymous> (file:///home/ai/.npm/_npx/9320fb454a84dc6d/node_modules/@svelte-add/create-kit/__init.js:33:25)
    at ChildProcess.emit (node:events:390:28)
    at maybeClose (node:internal/child_process:1064:16)
    at Socket.<anonymous> (node:internal/child_process:450:11)
    at Socket.emit (node:events:390:28)
    at Pipe.<anonymous> (node:net:672:12)

@andreavaccari
Copy link
Author

The error you get seems related to yarn (ref). The same command should work with npm (ref).

To answer your question, the command generates this default configuration:

  • postcss.config.cjs
const tailwindcss = require("tailwindcss");
const autoprefixer = require("autoprefixer");
const cssnano = require("cssnano");

const mode = process.env.NODE_ENV;
const dev = mode === "development";

const config = {
	plugins: [
        //Some plugins, like tailwindcss/nesting, need to run before Tailwind,
        tailwindcss(),
        //But others, like autoprefixer, need to run after,
        autoprefixer(),
        !dev && cssnano({
			preset: "default",
		})
    ],
};

module.exports = config;
  • tailwind.config.cjs
const config = {
  mode: "jit",
  purge: ["./src/**/*.{html,js,svelte,ts}"],

  theme: {
    extend: {}
  },

  plugins: []
};

module.exports = config;

@ai
Copy link
Member

ai commented Oct 7, 2021

The error you get seems related to yarn (ref). The same command should work with npm (ref).

I used npm init there. Maybe script found yarn in my system and called it?

How I can fix this issue to call code reproducion?

To answer your question, the command generates this default configuration:

Another questions:

  1. How inside tailwind config double call affect end-users?
  2. Where do you print this line?

@andreavaccari
Copy link
Author

I create a simple repo so you can avoid the npm init problem:

https://github.com/andreavaccari/postcss-load-config-tailwind

Could you try the following:

  • Test 1: Clone the repo and run npm run dev -- --open.
  • Test 2: Open postcss.config.cjs, comment the line under option 1, uncomment the line under option 2, and rerun.

You should be able to see that in the second case the tailwind.config.cjs file is loaded several times.

I'm investigating this issue because I have a monorepo with a sluggish DX and I'm trying to figure out if this is part of the issue.

@ai
Copy link
Member

ai commented Oct 7, 2021

Is it just a performance problem of file multiple loading or it causes some bug? (Don't worry I will not ignore performance issue 😅)

@andreavaccari
Copy link
Author

I believe it's only a performance issue. Thank you for being so responsive! 👍

@WilhelmYakunin
Copy link

WilhelmYakunin commented Nov 2, 2021

Hello,

the scope of calling tailwindcss should be the tailwind-config object itself. Like here:

`console.log(">>> Loading postcss.config.cjs");

const tailwindConfig = require('./tailwind.config.cjs');
const tailwindcss = require("tailwindcss");
const autoprefixer = require("autoprefixer");
const cssnano = require("cssnano");
const mode = process.env.NODE_ENV;
const dev = mode === "development";

const config = {
  plugins: [
    tailwindcss(tailwindConfig),
    autoprefixer(),
    !dev &&
      cssnano({
        preset: "default",
      }),
  ],
};

module.exports = config;
console.log(">>> Loading postcss.config.cjs");

I suppose that then tailwindcss called without confing in its scope so the default behaviour of tailwinds is to load the tailwindcss-confing for all its plugins separately. And that's more, the config file is finded by the tailwinds themself.

Do you mind if I pull the request?

@andreavaccari
Copy link
Author

Hi @ai, it looks like @WilhelmYakunin is on the right track here. How would you like to proceed?

@WilhelmYakunin
Copy link

WilhelmYakunin commented Nov 15, 2021

Hi, @andreavaccari, maybe I can evaporate the question you posed.

The official docs of Tailwindcss prescribe that by default Tailwindcss will look for a tailwind.config.js in the root of a project (please visit here).

Also, postCSS and Tailwindcss work together with having no search for config if the developer clearly puts the config (as an object) or path to config (as string) into tailwindcss function as the first argument. Just read the code of tailwindcss function that is taken from ‘.\node_modules\tailwindcss\lib\index.js’:

module.exports = function tailwindcss(config) {
  const resolvedConfigPath = resolveConfigPath(config);
  const getConfig = getConfigFunction(resolvedConfigPath || config);
  const mode = _lodash.default.get(getConfig(), 'mode', 'aot');

  if (mode === 'jit') {
    return {
      postcssPlugin: 'tailwindcss',
      plugins: (0, _jit.default)(config)
    };
  }

  const plugins = [];

  if (!_lodash.default.isUndefined(resolvedConfigPath)) {
    plugins.push((0, _registerConfigAsDependency.default)(resolvedConfigPath));
  }

  return {
    postcssPlugin: 'tailwindcss',
    plugins: [...plugins, (0, _processTailwindFeatures.default)(getConfig), _formatCSS.default]
  };
};

module.exports.postCSS = true;

But what happens when tailwindcss runs with and without invocation?

1st or with the invocation of tailwindcss in postCSS.config.js

The tailwindcss after invocation is the object, which has the property ‘postCSSplugin’. With the invocation of this object (as it is commonly known in JS functions are objects) a config is ‘undefined’ so the tailwindcss searches into the root folder and finds the config. In this case, the config is loaded once because the function calls only once. Why once? See the second;

2nd or WITHOUT the invocation of tailwindcss in postCSS.config.js

When the tailwindcss is written without invocation it is a function (and at the same time a first-class object) but has no property ‘postCSSplugin’. The property ‘postCSSplugin’ will reveal after invocation. So postCSS processor runs it as not an object (case ref) but as a function (case ref). And to process input CSS the postCSS processor needs to run (invocate) tailwindcss as a function. Afterwards, tailwindcss searches for a config because it did not pass into his first arguments (it is the default of tailwindcss). Actually, the posctCSS processor uses the tailwindcss plugin to process not only one css code but also:
the code here
here
and so on.
So in the second case, every time postCSS processor works with each of the input CSS it invokes the tailwindcss as the function which searches for config and loads it for every time of its invocation.


Postscript. Maybe I should also add.

If the project root folder wouldn't contain a file which name matches to substring 'tailwindcss-config' (with some of its variations) the tailwindcss will load the defaults config of its own (from stubs folder in tailwindcss node modules, alike here ones ref)

@andreavaccari
Copy link
Author

Hi @WilhelmYakunin, thank you so much for taking the time to write this detailed explanation.

I now always invoke tailwind in the config file to avoid the subtly magical behavior of the alternative.

@chenjiahan
Copy link

Actually postcss-load-config has already called the tailwindcss function, see: https://github.com/postcss/postcss-load-config/blob/main/src/plugins.js#L67

To avoid repeatedly initializing plugin functions, postcss-load-config could consider returning the return value of plugin() instead of returning the plugin itself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants