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

insertAt: 'top' is broken as it reverses the order of injected stylesheets #23

Open
skylarmb opened this issue May 19, 2021 · 2 comments

Comments

@skylarmb
Copy link

skylarmb commented May 19, 2021

style-inject is used by rollup-plugin-postcss and many other packages. When used in these packages there is an issue with insertAt behavior.

Given the following imports

// A.js
import B from './B';

import styles from './a.css';
...
// B.js
import styles from './b.css'`
...

Without insertAt: 'top' the resulting injected styles are

<style>
// contents of b.css
</style>
<style>
// contents of a.css
</style>

With insertAt: 'top' the resulting injected styles are

<style>
// contents of a.css
</style>
<style>
// contents of b.css
</style>

This is incorrect as a's styles were imported after b, so should override the styles from b naturally by being imported second.

What this plugin really needs in an option to preserve the order of stylesheets relative to each other but still insert them at the beginning of the head

@ivoilic
Copy link

ivoilic commented Aug 22, 2021

@skylarmb I've fixed this bug in an improved version of this package: style-implant

@skylarmb
Copy link
Author

skylarmb commented Sep 23, 2021

as a workaround I created my own rollup plugin that is essentially a fixed version of this package that also includes a number of inproments:

  • Prevents erroring out in server side rendering (e.g. no window or document)
  • Adds the ability to control where your stylesheets get injected instead of just inserting at the end of the head. Simply create a meta or style tag in your head <style id='style-inject-insert-before'></style> and all styles will be injected directly above that element, allowing you to control the order of injected styles vs other global stylesheets.

plugins/style-inject/index.js:

import path from 'path';

const styleInjectPath = path
  .resolve('plugins/inject-styles/styleInject')
  .replace(/[\\/]+/g, '/');

// This function is a codegen/codemod function that adds code to each
// module during compilation. This is how rollup plugins generally work
export default function styleInject() {
  return `
import styleInject from '${styleInjectPath}';
styleInject(stylesheet);
`;
}

plugins/style-inject/styleInject.js:

export default function styleInject(css) {
  if (!css || typeof document === 'undefined') return; // don't error out if using SSR

  if (window._DISABLE_STYLE_INJECT === false) return; // allow turning off injection
  var INSERT_BEFORE_ID = 'style-inject-insert-before'; // id of element in head where styles should be injected before
  var head = document.head || document.getElementsByTagName('head')[0];
  
  // an empty style tag that is inserted at the top of the head and which all
  // further styles are inserted before. This fixes the reverse insertion issue
  // reported in https://github.com/egoist/style-inject/issues/23
  var elementToInsertBefore = document.getElementById(INSERT_BEFORE_ID);
  
  // if this is the first injected style and there is no user-defined element to insert before,
  // we must create the insert-before element that we will use for all further injections
  if (elementToInsertBefore == null) {
    elementToInsertBefore = document.createElement('style');
    elementToInsertBefore.id = INSERT_BEFORE_ID; // if there is already stuff in the head, put this first

    if (head.firstChild) {
      head.insertBefore(elementToInsertBefore, head.firstChild);
    } else {
      // if the head is empty, just insert this
      head.appendChild(elementToInsertBefore);
    }
  }

  var style = document.createElement('style');
  style.type = 'text/css';
  head.insertBefore(style, elementToInsertBefore);

  if (style.styleSheet) {
    style.styleSheet.cssText = css;
  } else {
    style.appendChild(document.createTextNode(css));
  }
}

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

2 participants