kcardina
kcardina

Reputation: 11

Aurelia: Stylesheet loaded but not removed

In an aurelia project I have several components that import additional stylesheets, e.g. from semantic-ui. After leaving the components page, the stylesheet is still active and not removed. Is it possible to 'unload' the stylesheets?

Upvotes: 1

Views: 360

Answers (2)

Eugene Mala
Eugene Mala

Reputation: 1293

I have found a plugin to resolve the issue: https://github.com/jbockle/aurelia-useable-style-loader

But for the latest Webpack webpack.config.js should be a little bit different than in a plugin readme.

You should load .css files this way:

   use: [
          { loader: 'style-loader', options: { injectType: 'lazyStyleTag' } },
          'css-loader'
        ]

Instead of this:

  use: ['style-loader/useable', 'css-loader']

Upvotes: 0

Fred Kleuver
Fred Kleuver

Reputation: 8047

Update (2018-03-27):

I submitted a PR to enable this as an opt-in, you can keep track of it here: https://github.com/aurelia/templating-resources/pull/344

Original answer:

A word of warning, this is untested and aurelia-internals-hacky.

What you could do is override the default CSSViewEngineHooks and CSSResource classes to keep track of the style elements it injects, and then add an beforeUnbind hook to remove the styles again.. before unbind (right after detached)

Unfortunately the CSSResource class is not exported from aurelia-templating-resources so we need to go one layer deeper and overwrite the existing style loader plugins that returns instances of CSSResource.

Here's how:

First we grab the code from aurelia-templating-resources/src/css-resource.js, put it in our own src/css-resource.js/ts and make a few tweaks to it (don't think too much of the size, it's just a copy-paste with a few small tweaks, annotated with comments):

import {ViewResources, resource, ViewCompileInstruction} from 'aurelia-templating';
import {Loader} from 'aurelia-loader';
import {Container} from 'aurelia-dependency-injection';
import {relativeToFile} from 'aurelia-path';
import {DOM, FEATURE} from 'aurelia-pal';

let cssUrlMatcher = /url\((?!['"]data)([^)]+)\)/gi;

function fixupCSSUrls(address, css) {
  if (typeof css !== 'string') {
    throw new Error(`Failed loading required CSS file: ${address}`);
  }
  return css.replace(cssUrlMatcher, (match, p1) => {
    let quote = p1.charAt(0);
    if (quote === '\'' || quote === '"') {
      p1 = p1.substr(1, p1.length - 2);
    }
    return 'url(\'' + relativeToFile(p1, address) + '\')';
  });
}

class CSSResource {
  constructor(address: string) {
    this.address = address;
    this._scoped = null;
    this._global = false;
    this._alreadyGloballyInjected = false;
  }

  initialize(container: Container, target: Function): void {
    this._scoped = new target(this);
  }

  register(registry: ViewResources, name?: string): void {
    if (name === 'scoped') {
      registry.registerViewEngineHooks(this._scoped);
    } else {
      this._global = true;
    }
  }

  load(container: Container): Promise<CSSResource> {
    return container.get(Loader)
      .loadText(this.address)
      .catch(err => null)
      .then(text => {
        text = fixupCSSUrls(this.address, text);
        this._scoped.css = text;
        if (this._global) {
          this._alreadyGloballyInjected = true;
          // DOM.injectStyles(text); <- replace this
          // this is one of the two possible moments where the style is injected
          // _scoped is the CSSViewEngineHooks instance, and we handle the removal there
          this._scoped.styleNode = DOM.injectStyles(text);
        }
      });
  }
}

class CSSViewEngineHooks {
  constructor(owner: CSSResource) {
    this.owner = owner;
    this.css = null;
  }

  beforeCompile(content: DocumentFragment, resources: ViewResources, instruction: ViewCompileInstruction): void {
    if (instruction.targetShadowDOM) {
      DOM.injectStyles(this.css, content, true);
    } else if (FEATURE.scopedCSS) {
      let styleNode = DOM.injectStyles(this.css, content, true);
      styleNode.setAttribute('scoped', 'scoped');
    } else if (this._global && !this.owner._alreadyGloballyInjected) {
      // DOM.injectStyles(this.css); <- replace this
      // save a reference to the node so we can easily remove it later
      this.styleNode = DOM.injectStyles(this.css);
      this.owner._alreadyGloballyInjected = true;
    }
  }

  // this is the hook we add, here we remove the node again
  beforeUnbind(): void {
    if (this._global && this.owner._alreadyGloballyInjected) {
      DOM.removeNode(this.styleNode);
      this.owner._alreadyGloballyInjected = false;
    }
  }
}

export function _createCSSResource(address: string): Function {
  @resource(new CSSResource(address))
  class ViewCSS extends CSSViewEngineHooks {}
  return ViewCSS;
}

Then, in our main.ts/js we do the same thing aurelia-templating-resources.js does, but with our own version. So we do this after the call to aurelia.use.standardConfiguration() etc, to override the existing one

let viewEngine = config.container.get(ViewEngine);
let styleResourcePlugin = {
  fetch(address) {
    return { [address]: _createCSSResource(address) };
  }
};
['.css', '.less', '.sass', '.scss', '.styl'].forEach(ext => viewEngine.addResourcePlugin(ext, styleResourcePlugin));

And that should pretty much do the trick.. :)

Upvotes: 1

Related Questions