npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

marcel-plugin

v1.0.0

Published

Base utility for making a marcel plugin

Downloads

7

Readme

Plugins

A plugin is a widget that can be added to a marcel media. Basically it is just a web site served by marcel and displayed in an iframe.

:warning: Some parts of this README might be outdated

Create a plugin

Setup

  1. Create a directory for the plugin
mkdir marcel-plugin-<plugin name> && cd marcel-plugin-<plugin name>
  1. Create a directory name frontend
mkdir frontend && cd frontend
  1. Create a node module. Don't forget to give a name to the module, the default will be frontend.
# with npm
npm init
# with yarn
yarn init
  1. Add marcel-plugin as a dependency (npm i marcel-plugin or yarn add marcel-plugin)
# with npm
npm install marcel-plugin
# with yarn
yarn add marcel-plugin
  1. Create a file index.html

The frontend directory will be served by marcel as a standard web site. Hence the html, js and other static file must be put in this directory.

The plugin will be displayed in an iframe with index.html as src.

A plugin example is available here marcel-plugin-text is an example of a very simple plugin.

Structure of the frontend

The following script must be added in index.html in order to the interact with marcel.

<script src="./nodes_modules/marcel-plugin/dist/index.js"></script>

Then, in a another script, a class that inherits Marcel.Plugin must be created:

class MyPlugin extends Marcel.Plugin {
    <...>
}

This class is the representaton of the plugin's runtime. Some methods might be implemented in order to declare what the plugin does.

The render method

class MyPlugin extends Marcel.Plugin {
  render() {
    const { props1, props2 } = this.props
  }
}

This function is called every time marcel renders the plugin. As its name implies, this function is responsible for rendering the plugin. The object this.props will contain every properties entered by the user in the backoffice.

The propsDidChange method:

class MyPlugin extends Marcel.Plugin {
  propsDidChange(prevProps) {
    const { prop1, prop2 } = this.props
    if (prop1 !== prevProps.prop1) {
      this.updateMyPluginData()
    }
  }
}

This function should be used for any sides effect dependending on the props. For example, making API calls in order to fetch some data.

This function will be called after the render() function.

The current properties are accessible in this.props (like in the render method), and the previous properties are accessible in the prevProps argument. prevProps might be used to avoid expensive API calls if the properties didn't change.

Initialize the plugin

Finally, a plugin must be initialized:

Marcel.init(MyPlugin)

Testing the plugin

A plugin might be tested outside marcel by serving the frontend directory with any http web server:

npm install --global serve
serve -s frontend

Then new props can be simulated by calling:

Marcel.changeProps({ props1: 'new value' })

Tips: This can either be done in the devtools or by adding it at the end of the plugin's script.

marcel-plugin API

To receive the props and communicate with the marcel media, the standard HTML message API must be uesed. A basic API built over the message API is provided by marcel-plugin.

Marcel.Plugin

The main feature of marcel-plugin is the Marcel.Plugin base class.

By extending this class, the plugin is notified of props updates and might implement a basic state management.

class MyPlugin extends Marcel.Plugin {
  constructor() {
    // The super constructor must always be called
    super({
      // Optional default props and default state
      defaultProps: { prop1: 'default value' },
      defaultState: { state1: 'default state' },
    })

    // Some initialization can be performed here
    // For example, storing dom elements
    this.container = document.getElementById('container')
    this.p = document.query('#container p')
  }

  render = () => {
    // Any UI updates needed to keep the DOM synchronised with the props and state
    // It is called a first time with default props and state and then each time props or state change

    const { stylesvar, prop1 } = this.props
    const { state1 } = this.state

    this.container.style.color = stylesvar['primary-color']
    this.p.innerText = `${state1} - ${prop1}`
  }

  pluginInitialized = () => {
    // Every side effects that should only be done once
    // but can't be done in the constructor because it depends on props goes here
    // It is called just after the first render with loaded props
    // WARNING: Most of the time propsChanged should be used
  }

  propsChanged = async (prevProps, prevState) => {
    // Every side effects depending on props goes here
    // It is called after render on each props or state change
    // Actual changes can be detected by comparing this.props and this.state with prevProps and prevState

    if (prevProps.prop1 !== this.props.prop1) {
      this.setState({ state1: await fetchSomeData(prop1) })
    }
  }
}

The render method will be called to let the UI synchronize with updated props and state.

The state can be used to keep track of some values and have automatically rerender the plugin on changes. To update the state, this.setState(newState) must be called. If newState is an object, it will be merged into the current state. If it is a function, it will be called with the current state and must return the new state.

Tips and tricks

Since a plugin will be contained in a iframe that will take only a part of the full webpage, it is highly recommended to remove the border, padding and, must importantly, the scrollbar. This can be done with this tiny css snippet:

body {
  margin: 0;
  padding: 0;
  overflow: hidden;
  width: 100vw;
  height: 100vh;
}

The space reserved to the plugin might change a lot, from a lot of space to a very tiny space, with ratio of 1:1, 4:1, 5:3... For this reason, a plugin must be as responsive as possible.