egoist / style-inject

Inject style tag to document head.
MIT License
48 stars 23 forks source link

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

Open skylarmb opened 3 years ago

skylarmb commented 3 years ago

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 commented 3 years ago

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

skylarmb commented 2 years ago

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:

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));
  }
}