How to install npm packages in custom themes/plugins

Hello!

Is it possible to install npm packages (via package.json or similar) in Discourse Themes/Plugins? We are looking to reuse some components across apps and this can be a blocker if it’s not possible.

Thanks!

You could sorta do what we do in core, which is, add npm packages via package.json and then copy JS files to a theme folder (like javascripts/discourse/lib/ for example) but you can’t import the packages from node_modules directly, I don’t think that would work.

3 Likes

Assuming we have a plain Javascript code (Like something we can include and run to support a custom web component) like this one how I can run it with your approach? When I copy and paste it into discourse/lib within my theme it doesn’t work.

I assume it doesn’t work because the code is “not being called at the right time?” like is not being called when the page is loaded and within the browser environment.

Furthermore, to give context to everyone If I try to use the https://unpkg.com/ and include it into the header I get an error like:

After reply to the comment and convey the information in a single idea I get rid of the error by following DISCOURSE_CDN_URL causes content security policy violations?

Turning off content_security_policy setting

I still figure out why the script doesn’t work as I have another error but maybe so far can be useful for someone else.

FYI, the error I am having is this one:

But the script has loaded :man_shrugging:

Finally, for someone within the same quest. I make it work by:

  1. Turning off content_security_policy setting
  2. Explicitly adding the script programatically (not using $.getScript not using other approach).

A example summary snippet of what works for me is (replace web-component for what makes sense in your case):

import { withPluginApi } from "discourse/lib/plugin-api";

let flag = false;

export default {
  name: "web-component",
  initialize() {
    withPluginApi("0.8", api => {
      api.onAppEvent("page:changed", () => {
        if (flag) return;

        addScript(
          "https://unpkg.com/web-component@0.0.1/dist/web-component/web-component.js",
          { defer: "", crossorigin: "anonymous" }
        );

        addWebComponent(
          "web-component",
          {
            id: "web-component"
          },
          `Hello world`
        );

        flag = true;
      });
    });
  }
};

function addWebComponent(tag, attrs, content) {
  var component = document.createElement(tag);

  Object.keys(attrs).forEach(key => {
    component.setAttribute(key, attrs[key]);
  });
  component.textContent = content;

  document.body.appendChild(component);
}

function addScript(src, attrs) {
  var script = document.createElement("script");

  script.setAttribute("src", src);

  Object.keys(attrs).forEach(key => {
    script.setAttribute(key, attrs[key]);
  });

  document.body.appendChild(script);
}

The better way to do this is to whitelist that specific URL either in the content securty policy script src site setting or in your theme component, see Mitigate XSS Attacks with Content Security Policy for more details.

And also, you can import loadScript from "discourse/lib/load-script"; and then use that to load an external script (instead of defining your own addScript injector).

5 Likes