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

Add support for target: 'ie11' config option #1635

Merged
merged 8 commits into from Apr 29, 2020
Merged

Conversation

adamwathan
Copy link
Member

This PR introduces a new target key to the config that plugins can inspect to make decisions about what styles they add to Tailwind:

// tailwind.config.js
module.exports = {
  target: 'ie11',
  theme: {},
  variants: {},
  plugins: [],
}

Currently the only target that core plugins look for is ie11, but in the future we could add more options like modern for just evergreen browsers, email to make sure all the output works well with tools like Maizzle, or browserslist to tell plugins to respect the user's browserslist config.

Motivation

The primary motivation behind this feature is to help people who need to support IE11 avoid accidentally using Tailwind features that IE11 doesn't support, like:

  • Utilities like object-fit, object-position, flow-root, etc. that represent CSS features that don't exist in IE 11
  • Our grid utilities, which are designed for the modern grid API and can't be polyfilled to work in IE 11
  • Utilities that depend on CSS custom properties, like translate, scale, or the new text-opacity utilities

I've heard from several users for example who have wanted to disable CSS Grid in Tailwind because they need to support IE 11 and found it a bit cumbersome because they had to manually disable like 9 different plugins, and even then the grid utility still exists because it's in the display plugin which they can't disable.

So this feature aims to be a universal solution to the general problem, "how do I disable Tailwind features I can't use anyways".

Implementation

The Tailwind "engine" is actually entirely unchanged, all we've done is update a few core plugins to look for the new target key using the config function that plugins already receive.

For example, this is what the objectFit plugin looks like now:

// objectFit.js
export default function() {
  return function({ addUtilities, variants, config }) {
    if (config('target') === 'ie11') {
      return
    }

    addUtilities(
      {
        '.object-contain': { 'object-fit': 'contain' },
        '.object-cover': { 'object-fit': 'cover' },
        '.object-fill': { 'object-fit': 'fill' },
        '.object-none': { 'object-fit': 'none' },
        '.object-scale-down': { 'object-fit': 'scale-down' },
      },
      variants('objectFit')
    )
  }
}

It simply checks the target key to see if it matches ie11, and if so it just doesn't add any utilities.

Other plugins like display still add most of their utilities, but certain ones are conditionally added based on the target:

// display.js
export default function() {
  return function({ addUtilities, variants, config }) {
    addUtilities(
      {
        '.block': {
          display: 'block',
        },
        '.inline-block': {
          display: 'inline-block',
        },
        '.inline': {
          display: 'inline',
        },
        // ...
        '.table-row': {
          display: 'table-row',
        },
        ...(config('target') === 'ie11'
          ? {}
          : {
              '.flow-root': {
                display: 'flow-root',
              },
              '.grid': {
                display: 'grid',
              },
              '.inline-grid': {
                display: 'inline-grid',
              },
            }),
        '.hidden': {
          display: 'none',
        },
      },
      variants('display')
    )
  }
}

Eventually we will probably work on some more declarative abstractions we can use internally to avoid things getting complex with these sorts of conditional checks, but while we are only offering this special ie11 mode I think this solution is perfectly fine.

Affected plugins

Here's a list of all the changes this features makes to make Tailwind's output more tailored for IE 11:

  • grid, inline-grid, and flow-root display utilities are removed
  • Background color, border color, text color, and placeholder color utilities just output a simple color declaration and don't look at their corresponding opacity custom property to enable the opacity modifiers
  • Color opacity modifiers are removed
  • The reverse modifiers for space and divide utilities are removed, and the space and divide utilities are simplified accordingly
  • All grid utilities are removed
  • object-fit and object-position utilities are removed
  • All transform utilities are simplified to be non-composable so they don't rely on custom properties — this makes these utilities actually work in IE 11 whereas before this they didn't work at all

Future

Like I mentioned briefly towards the beginning of this PR, I could see us eventually introducing new targets, for example:

  • modern (or similar), where we remove any existing IE 11 fallback code we already provide (color opacity stuff is a good example)
  • email, tailored to generate the best possible output for compatibility with tools like Maizzle
  • browserslist, where we use your browserslist configuration to determine what features to enable/disable. This would be the best approach long-term but because of the granularity browserslist supports, it could make this feature a lot more complicated and I'd rather ship a simple version than nothing at all. I don't want to have to write all the logic for someone who wants to support IE 9, or only support Blackberry Browser just because that's expressible in a browserslist config for example

Questions

  • Have I missed any Tailwind features that should be disabled in IE 11?
  • What should we do about custom values in the config, for example if someone adds max-content to their custom width config should we at least warn in the console?

@cossssmin
Copy link
Contributor

This looks great!

I can definitely see the need for an email target now - there are quite a few email clients that have better CSS support than IE11 😂

For example, you can use CSS grid in HTML email. Yeah you need a fallback for clients that don't support it, but that's almost always the case in email (plus, you might just not care about those clients).

Custom values - what would you base the warning on? Would it work like a linter?

@jasonlbeggs
Copy link
Contributor

Beautiful 🙌 One question. Should there be a way to override the plugins included with the target? Say I have my target set to ie11, but I want to include the object-fit plugin anyways. If I explicitly set corePlugins.objectFit to true, should it be included?

@thecrypticace
Copy link
Contributor

thecrypticace commented Apr 27, 2020

I second @jasonlbeggs comments here. There are projects where I'd like to re-enable grid utilities as IE 11 does support quite a bit of the grid spec (link) but it doesn't have the auto-placement algorithms, a few size-related things like fit-content, and a few other features. But autoprefixer does a dang good job of tweaking the CSS to work when it can.

@adamwathan
Copy link
Member Author

@thecrypticace Have you tried using autoprefixer's grid stuff with Tailwind? It just fills your console with hundreds and hundreds of warnings, hah.

I think generally if you want to take control at a fine-grained level you'd probably just not use this feature and manage that stuff manually yourself. If you want to support IE 11 but also want to use object-fit are you really targeting IE 11? Kinda sounds like you're not 🤷

@brandonpittman
Copy link

Removing capabilities when targeting IE11 means that you can’t do any progressive enhancement. I think it would be better to make use of @supports to handle IE and appropriate fallbacks.

@ghost
Copy link

ghost commented Apr 27, 2020

I think generally if you want to take control at a fine-grained level you'd probably just not use this feature and manage that stuff manually yourself. If you want to support IE 11 but also want to use object-fit are you really targeting IE 11? Kinda sounds like you're not 🤷

This is actually my typical usage. I'm stuck supporting IE11, but object-fit is so helpful that I just use this polyfill and I haven't had any issues yet.

I'd definitely be more likely to use this feature if there was more fine-grained control over what the targets did.

@adamwathan
Copy link
Member Author

I think it would be better to make use of @supports to handle IE and appropriate fallbacks.

I think this makes more sense in a framework like Bootstrap where there's a layer of abstraction, but there's no way to use @supports to make object-cover work really. The CSS property either exists or doesn't exist in the browser you are targeting, you know?

For things like CSS Grid I think @supports is sort of pointless, because if you are going to write a bunch of fallback code that uses Flexbox anyways why even bother writing the grid version? The Flexbox version will work in all the browsers you care about anyways.

@adamwathan
Copy link
Member Author

I'd definitely be more likely to use this feature if there was more fine-grained control over what the targets did.

@ericj-pace Got any suggestions on the syntax for this? I'm hesitant about it because it will likely introduce a lot of complexity, and I worry sort of defeats the purpose of the feature which is more about helping people who aren't already knowledgeable about what features work in what browsers. If you are informed enough to already be pulling in a polyfill, you probably don't need the training wheels you get by using this feature I'm guessing.

If we did support it, maybe the trick would be to allow you to specify a target per plugin, so the plugin code didn't have to change based on the exceptions at least?

Thinking out loud here, but a syntax like this might say "use a target of ie11 by default, but when the objectFit or objectPosition plugins ask for the current target, tell them it's default, not ie11.

// tailwind.config.js
module.exports = {
  target: ['ie11', {
    objectFit: 'default',
    objectPosition: 'default',
  }],
  theme: {},
  variants: {},
  plugins: [],
}

@ghost
Copy link

ghost commented Apr 27, 2020

@ericj-pace Got any suggestions on the syntax for this? I'm hesitant about it because it will likely introduce a lot of complexity, and I worry sort of defeats the purpose of the feature which is more about helping people who aren't already knowledgeable about what features work in what browsers. If you are informed enough to already be pulling in a polyfill, you probably don't need the training wheels you get by using this feature I'm guessing.

@adamwathan I hesitated replying for the same reasons. It's definitely a great simple training wheels feature that I don't have too much use for. The best use I have is to stop those classes from showing up in autocomplete and trivially speeding up builds. Not great arguments against what exists here.

If we did support it, maybe the trick would be to allow you to specify a target per plugin, so the plugin code didn't have to change based on the exceptions at least?

Thinking out loud here, but a syntax like this might say "use a target of ie11 by default, but when the objectFit or objectPosition plugins ask for the current target, tell them it's default, not ie11.

// tailwind.config.js
module.exports = {
  target: ['ie11', {
    objectFit: 'default',
    objectPosition: 'default',
  }],
  theme: {},
  variants: {},
  plugins: [],
}

For sure the best case would be to override on an individual plugin basis. Instead of an array of targets, it may be more consistent to use a singular object and use the default key instead. I think it would be easier to parse. Using default in the object twice seems a little strange to me then. Maybe base (imitating the directive) or current makes more sense in this context? modern would probably make the most sense, but modern makes me more worried about how future breaking changes would affect it.

// tailwind.config.js
module.exports = {
  target: {
    default: 'ie11',
    objectFit: 'current',
    objectPosition: 'current',
  },
  theme: {},
  variants: {},
  plugins: [],
}

@OwenMelbz
Copy link
Contributor

regards to potentially missing IE11 features ones we’ve come across at a glance, don’t know if they are legit or not because other people “fixed” them but...

‘.sticky’ should maybe have a fallback to position fixed??

‘flex-1’ has also had problems in the past apparently which a fix might have been... to add: ‘-ms-flex-preferred-size: auto’ or ‘flex: flex 1 auto’

@adamwathan
Copy link
Member Author

I've updated this PR with a few notable changes:

Support for per plugin target mode

You can now specify a global target mode, as well as per plugin overrides by providing a tuple in the form [defaultTargetMode, pluginSpecificOverrides]:

// tailwind.config.js
module.exports = {
  target: ['ie11', {
    objectPosition: 'relaxed',
    objectFit: 'relaxed',
  }]
}

I've settled on the word "relaxed" for the default target mode, which basically means "include all modern features but provide fallbacks for IE 11 where it would otherwise be a breaking change in Tailwind 1.x".

Currently this only really exists because of the changes we introduced with the new color opacity utilities.

Here's a concrete example of how the different modes would affect one of those utilties:

/* target: relaxed (the default) */
.bg-red-200 {
  --bg-opacity: 1;
  background-color: #fed7d7;
  background-color: rgba(254, 215, 215, var(--bg-opacity));
}

/* target: ie11 */
.bg-red-200 {
  background-color: #fed7d7;
}

/* target: modern (doesn't exist yet) */
.bg-red-200 {
  --bg-opacity: 1;
  background-color: rgba(254, 215, 215, var(--bg-opacity));
}

Plugin authors can determine the target mode for their plugin using a new target function I've added to the plugin API that works exactly like variants does:

tailwindcss.plugin(({ addUtilities, target }) => {
  if (target('poptarts') === 'ie11') {
    return
  }

  addUtilities({
    '.poptarts-full': {
      poptarts: '100%',
    }
  })
})

I considered just using a single object for this configuration value, but I don't want to reserve the default key yet again, that pattern is something I want to move away from in a future major version.

Add basic browserslist integration

The keyword browserslist is now a valid target mode:

// tailwind.config.js
module.exports = {
  target: 'browserslist',
}

It is important to note that this is purely to allow you to have a single source of truth for your browser support requirements, and does not imply complex, granular feature adaptation based on your specific configuration.

The only thing this integration does currently is check to see if ie 11 is present in your resolved browser list, and if so the target mode is set to ie11.

This feature does not try to accommodate the fact that you only support Blackberry Browser for your weird as hell project. All it does is resolve to an existing target preset, which is currently only ie11 or relaxed.

It works with the per plugin syntax too of course:

// tailwind.config.js
module.exports = {
  target: ['browserslist', {
    objectPosition: 'relaxed',
    objectFit: 'relaxed',
  }]
}

Sticky positioning is disabled in IE11 mode

Originally I kept the sticky utility enabled because it degrades very gracefully to just block in many cases (but not all) and it felt like it would be annoying if you couldn't use it as progressive enhancement.

Now that you can set a target mode per plugin, I've decided to disable this by default in ie11 mode, instead encouraging you to override the target for the position plugin if you want it enabled:

// tailwind.config.js
module.exports = {
  target: ['ie11', {
    position: 'relaxed',
  }]
}

Future considerations

The main thing I would like to explore next is a modern mode that removes all IE 11 fallbacks from relaxed mode, which will reduce the CSS file size (even after PurgeCSS) noticeably for users who don't need to support IE 11. I'll tackle this as a separate project after this is merged and released.

@adamwathan
Copy link
Member Author

I'm pretty sure this whole thing is a good idea but would like to test it in the wild a bit before fully committing to it.

I'm going to merge this and put it out in the next release but document it as experimental so I can change my mind about it or make breaking improvements if necessary 👍

@adamwathan adamwathan merged commit 7ce188e into master Apr 29, 2020
@adamwathan adamwathan deleted the target-modes branch April 29, 2020 20:31
@rigor789
Copy link

rigor789 commented Apr 29, 2020

@adamwathan this is great! I've made a plugin for NativeScript so I can keep using Tailwind in my mobile apps. Not all of CSS is supported, so I made a PostCSS plugin that strips out anything that's not supported using a lookup table. It's far from perfect, and needs to be maintained, but could be a direction to take the target option towards perhaps, not to complicate the built-in plugins with conditionals.

A target could either be a postcss plugin (target: require('tailwind-target-ie11')) or just a simple string, and then resolved to tailwind-target-{name}.

The brains of my approach is in here https://github.com/rigor789/nativescript-tailwind/blob/master/removeUnsupported.js

Just sharing if it sparks some ideas!

@focussing
Copy link

@adamwathan this is indeed great! I wonder; is there an overview of which Tailwind utilities are not IE11 compatible?
Or maybe add it to the documentation?

@adamwathan
Copy link
Member Author

Thanks @focussing! We will definitely add it to the docs at some point, right now the best reference is the "Affected Plugins" section in the first thread of this PR. Generally we have taken the stance of "browser support is your responsibility" just because I personally don't have the bandwidth to manage that for the whole community through the docs right now, but long-term it would be good to be more helpful there.

})
)

addUtilities(utilities, variants('textColor'))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @adamwathan
Thanks for creating tailwind css.
I'm going through the source code to learn how it's built and I found that different values are being passed into addUtilities in this file.

// line 19
addUtilities(utilities, variants('textColor'))

// line 37
addUtilities(utilities, variants('divideColor'))

Is there a reason that variants('textColor') should be used when target('divideColor') === 'ie11' is true? Thanks.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops that's a bug! I just pushed a release to fix it, thanks for pointing it out!

@lihbr lihbr mentioned this pull request May 14, 2020
gilleard added a commit to engageinteractive/laravel that referenced this pull request May 18, 2020
relaxed avoids any output changes, so there's (still) classes that won't work in IE11.
Is the default option but think it helps keep it clear.
tailwindlabs/tailwindcss#1635 (comment)
@tingaloo
Copy link

Hey I wanted to say this is an awesome feature, it fixed my space-x-{} issues perfectly on IE11. Only issue is i had to dig all the way here to find this solution. Can we add this to the official docs, somewhere here?

@HJGreen
Copy link

HJGreen commented Sep 9, 2020

Thank you for this, it saved the day! In my case I needed IE11 support for the space-x-{} classes, and the postcss-custom-properties plugin was unable to substitute the CSS variables in the final output.

Adding the following to tailwind.config.js achieved the desired result without having to compromise on all the other features mentioned above:

{
  // ...
  target: [
    'relaxed', // All plugins should have default behaviour...
    {
      space: 'ie11', // ...except for space, which we want to be IE11 compatible
    },
  ],
}

@nbaosullivan
Copy link

nbaosullivan commented Apr 29, 2021

Fantastic - this resolved my space-x-{} IE11 issue too, thanks @HJGreen. So essentially, any plugins in the "Affected Plugins" section in the first post that have simplified or simply in the description have an IE11 compatible fallback instead of being removed? Nice!

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

Successfully merging this pull request may close these issues.

None yet