Tailwindcss: Error: Expected an opening square bracket (using postcss-purgecss and postcss-preset-env)

Created on 26 Oct 2019  路  3Comments  路  Source: tailwindlabs/tailwindcss

I've encountered this error when trying to build styles for production. I have a create-react-app with tailwindcss and postcss. I'm not sure where the problem lies exactly but I've boiled it down to this config to recreate it:

package.json:

{
  ...
  "scripts": {
    "build:css": "postcss src/tailwind.css -o src/index.css",
    ...
  },
  "browserslist": [
    ">0.2%",
    "not dead",
    "not op_mini all"
  ],
  "devDependencies": {
    "@fullhuman/postcss-purgecss": "^1.3.0",
    "postcss-cli": "^6.1.3",
    "postcss-preset-env": "^6.7.0",
    "tailwindcss": "^1.1.3"
  }
}

postcss.config.js

const purgecss = require("@fullhuman/postcss-purgecss")({
  // paths to all of the template files in the project
  content: ["./src/**/*.html", "./src/**/*.tsx", "./public/**/*.html"],

  // default extractor including tailwind's special characters
  defaultExtractor: content => content.match(/[A-Za-z0-9-_:/]+/g) || []
});

module.exports = {
  plugins: [
    require("tailwindcss"),
    require("postcss-preset-env")({ stage: 1 }),
    ...(process.env.NODE_ENV === "production" ? [purgecss] : [])
  ]
};

tailwind.config.js
鈿狅笍 The error arises whenever responsive and focus-within variants are added together to a property.

module.exports = {
  theme: {
    extend: {}
  },
  variants: {
    backgroundColor: ["responsive", "focus-within"]
  },
  plugins: []
};

I get this error when building the styles for a production environment (so with purgecss enabled):
yarn build:css --env=production

$ postcss src/tailwind.css -o src/index.css --env=production
{ Error: Expected an opening square bracket.
    at tailwind-build-error\src\tailwind.css:5:1
    at Root._error (tailwind-build-error\node_modules\purgecss\node_modules\postcss-selector-parser\dist\parser.js:160:16)
    at Root.error (tailwind-build-error\node_modules\purgecss\node_modules\postcss-selector-parser\dist\selectors\root.js:43:19)
    at Parser.error (tailwind-build-error\node_modules\purgecss\node_modules\postcss-selector-parser\dist\parser.js:726:21)
    at Parser.expected (tailwind-build-error\node_modules\purgecss\node_modules\postcss-selector-parser\dist\parser.js:1113:19)
    at Parser.missingSquareBracket (tailwind-build-error\node_modules\purgecss\node_modules\postcss-selector-parser\dist\parser.js:740:17)
    at Parser.parse (tailwind-build-error\node_modules\purgecss\node_modules\postcss-selector-parser\dist\parser.js:1090:14)
    at Parser.loop (tailwind-build-error\node_modules\purgecss\node_modules\postcss-selector-parser\dist\parser.js:1023:12)
    at new Parser (tailwind-build-error\node_modules\purgecss\node_modules\postcss-selector-parser\dist\parser.js:150:10)
    at Processor._root (tailwind-build-error\node_modules\purgecss\node_modules\postcss-selector-parser\dist\processor.js:55:18)
    at Processor._runSync (tailwind-build-error\node_modules\purgecss\node_modules\postcss-selector-parser\dist\processor.js:102:21)
  postcssNode:
   Rule {
     raws: { before: '\n\n  ', after: '\n  ' },
     type: 'rule',
     nodes: [ [Declaration] ],
     selector: '.sm\\[focus-within]\\:bg-transparent[focus-within]',
     lastEach: 53,
     indexes: {},
     source: { start: [Object], input: [Input], end: [Object] },
     parent:
      AtRule {
        raws: [Object],
        name: 'media',
        params: '(min-width: 640px)',
        type: 'atrule',
        nodes: [Array],
        parent: [Root],
        lastEach: 46,
        indexes: [Object],
        _autoprefixerDisabled: false,
        _autoprefixerPrefix: false,
        _autoprefixerGridStatus: false },
     _autoprefixerDisabled: false,
     _autoprefixerGridStatus: false } }
error Command failed with exit code 1.

So, I've checked the generated css, and some of it appears to be broken:

.sm\[focus-within]\:bg-transparent[focus-within] {
    background-color: transparent;
}

I've created a repo with the minimal config displaying the error: https://github.com/yassinedoghri/tailwind-build-error

Most helpful comment

Hey thanks for the excellently constructed issue!

I looked into this and the source of the problem appears to be a bug in the postcss-focus-within plugin used by postcss-preset-env.

It is duplicating your focus-within selectors and replacing the pseudo-class (:focus-within) with an attribute selector ([focus-within]). That results in duplicate declarations like this:

.focus-within\:bg-transparent[focus-within] {
  background-color: transparent;
}

.focus-within\:bg-transparent:focus-within {
  background-color: transparent;
}

The problem is it's also replacing the focus-within part of the class name in responsive selectors:

  .md\[focus-within]\:bg-transparent[focus-within] {
    background-color: transparent;
  }

  .md\:focus-within\:bg-transparent:focus-within {
    background-color: transparent;
  }

This is because the plugin is naively doing a search and replace on :focus-within, which is ignoring the fact that the : in the class name is escaped, and should not be replaced with an attribute selector:

https://github.com/jonathantneal/postcss-focus-within/blob/master/index.js#L3

The fix will be similar to what was done on the postcss-selector-not plugin a while back here:

https://github.com/postcss/postcss-selector-not/pull/11

I'll open an issue on that plugin but in the mean time if you aren't actually using the polyfill you can just disable that plugin in postcss-preset-env in your postcss.config.js file:

const purgecss = require("@fullhuman/postcss-purgecss")({
  // paths to all of the template files in the project
  content: ["./src/**/*.html", "./src/**/*.tsx", "./public/**/*.html"],

  // default extractor including tailwind's special characters
  defaultExtractor: content => content.match(/[A-Za-z0-9-_:/]+/g) || []
});

module.exports = {
  plugins: [
    require("tailwindcss"),
    require("postcss-preset-env")({
      stage: 1,
      features: {
        'focus-within-pseudo-class': false
      }
    }),
    ...(process.env.NODE_ENV === "production" ? [purgecss] : [])
  ]
};

All 3 comments

Hey thanks for the excellently constructed issue!

I looked into this and the source of the problem appears to be a bug in the postcss-focus-within plugin used by postcss-preset-env.

It is duplicating your focus-within selectors and replacing the pseudo-class (:focus-within) with an attribute selector ([focus-within]). That results in duplicate declarations like this:

.focus-within\:bg-transparent[focus-within] {
  background-color: transparent;
}

.focus-within\:bg-transparent:focus-within {
  background-color: transparent;
}

The problem is it's also replacing the focus-within part of the class name in responsive selectors:

  .md\[focus-within]\:bg-transparent[focus-within] {
    background-color: transparent;
  }

  .md\:focus-within\:bg-transparent:focus-within {
    background-color: transparent;
  }

This is because the plugin is naively doing a search and replace on :focus-within, which is ignoring the fact that the : in the class name is escaped, and should not be replaced with an attribute selector:

https://github.com/jonathantneal/postcss-focus-within/blob/master/index.js#L3

The fix will be similar to what was done on the postcss-selector-not plugin a while back here:

https://github.com/postcss/postcss-selector-not/pull/11

I'll open an issue on that plugin but in the mean time if you aren't actually using the polyfill you can just disable that plugin in postcss-preset-env in your postcss.config.js file:

const purgecss = require("@fullhuman/postcss-purgecss")({
  // paths to all of the template files in the project
  content: ["./src/**/*.html", "./src/**/*.tsx", "./public/**/*.html"],

  // default extractor including tailwind's special characters
  defaultExtractor: content => content.match(/[A-Za-z0-9-_:/]+/g) || []
});

module.exports = {
  plugins: [
    require("tailwindcss"),
    require("postcss-preset-env")({
      stage: 1,
      features: {
        'focus-within-pseudo-class': false
      }
    }),
    ...(process.env.NODE_ENV === "production" ? [purgecss] : [])
  ]
};

Opened issue on the plugin repo, will try to fix myself soon if I can:

https://github.com/jonathantneal/postcss-focus-within/issues/3

Closing here since it's not something we can fix from inside Tailwind but thanks for helping us discover this and track it down.

Awesome, thank you for the fast reply! And thank you for your work on Tailwind :)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

divdax picture divdax  路  3Comments

AlexVipond picture AlexVipond  路  3Comments

jbardnz picture jbardnz  路  3Comments

ouun picture ouun  路  3Comments

chintanbanugaria picture chintanbanugaria  路  3Comments