Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Preston Lamb
Preston Lamb

Posted on • Originally published atMedium on

Creating Custom Scully Plugins

tldr;

Scully.io is a great tool for Angular developers. Its tagline is, "Scully makes building, testing and deploying JAMstack apps extremely simple." Essentially, you build your application in Angular and then Scully will take it and generate static HTML and CSS for you. Those assets can be deployed, and the end result is a high quality, blazing fast experience. In this post, we'll talk about how you can extend the default functionality of Scully to better suit your needs. This article will illustrate how to create a plugin, and adding that extra functionality is simpler than you may think.

Plugin Types

There are several types of plugins you can create for your Scully app, depending on when in the build pipeline you need to jump in. Here are the different types of plugins you can create and a brief explanation of each,pulled directly from Scully's docs:

  • router plugins teach Scully how to get the required data to be pre-render pages from the route-params.
  • render plugins are used to transform the rendered HTML. After the Angular application renders, the HTML content is passed to a render plugin where it can be further modified.
  • routeProcess plugins are plugins that can modify the handled route array, before rendering the routes starts
  • fileHandler plugins are used by thecontentFolder plugin during the render process. ThecontentFolder plugin processes the folders for markdown files or other file type the folders may contain. The render process processes any existingfileHandler plugin for any file extension type.
  • routeDiscoveryDone plugins are called automatically after all routes have been collected and allrouter plugins have finished.
  • allDone plugins are likerouteDiscoveryDone plugins, except they are called after Scully finishes executing all its processes.
  • Scully has a category of system plugins. Unlike the other plugin categories those plugins don't have a set interface, and do use a symbol for their name.

With these seven types of plugins, you can create a lot of extra functionality in your Scully app. For example, I recently wanted the title from my blog posts (written in Markdown) to be added as the HTML document's title. There are several ways to do this, but one way is to write a customrender plugin. This plugin gives you access to the rendered HTML of a page, as well as some data about the route, and allows you to alter it in some way. In my case, I looked for thetitle attribute on the route's data and added that to the rendered HTML.

Another type of plugin that's useful is therouteDiscoveryDone plugin. This type of plugin is called after Scully finds all the routes in the app and anyrouter plugins are done running. A use case for this plugin is creating an RSS feed from the routes in your Scully application. You can seean example here.

Now that we've covered the types of plugins that you can create and a couple examples of use cases, let's look at how you can create a custom plugin.

Plugin Return Values

All Scully plugins return aPromise<unknown>, as illustrated in the interface:

interfaceplugin{(...parameters:any)=>Promise<unknown>}
Enter fullscreen modeExit fullscreen mode

For example, a render plugin returns a Promise. Arouter plugin returns an array ofHandledRoutes wrapped in aPromise. Some plugins don't actually return anything, but the return value is still wrapped in aPromise, so its return value isPromise<void>.

It's important to know that all the plugins return aPromise so that if you need to invoke the function you remember toawait the result, or chain a.then to the function call.

Creating a Custom Scully Plugin

When you initialize your Angular application with the Scully schematic, a folder is created calledscully. Inside that folder is another folder,plugins. The skeleton code for a plugin is created for you, or you can create your own plugin file. There are two main parts to the plugin: the plugin function and the registration of the plugin. Here's an example of the plugin function for arender plugin:

// ./scully/plugins/custom-plugin.tsexportconstcustomPlugin=Symbol('customPlugin');constcustomPluginFunction=async(html:string,route:HandledRoute):Promise<string>=>{// do somethingreturnPromise.resolve(html);};
Enter fullscreen modeExit fullscreen mode

Quick note: you can identify a plugin by using astring or aSymbol. Either way works fine, but the upside to using aSymbol is that there will be no name collisions. If you need more information onSymbols,you can read this blog post. In short,Symbol values are guaranteed to be unique, even if you pass the same string into theSymbol constructor for two separate variables. So, for plugins, this means no name collisions.

This function has two parameters, the rendered html, and the route. The latter contains any route data from Scully. At the end of the plugin, the HTML should be returned. Before returning it, you can alter it in any way you need to. Here's an example of arouteDiscoveryDone plugin:

// ./scully/plugins/custom-plugin.tsexportconstcustomPlugin=Symbol('customPlugin');functioncustomPluginFunction(routes:HandledRoute[]){constblogPosts=routes.filter((r:HandledRoute)=>r.route.includes('/blog'));// Do something with the blog posts}
Enter fullscreen modeExit fullscreen mode

This type of plugin receives an array ofroutes, allowing you to do what you need with them. As a side note, this is the type of above-mentioned RSS plugin by the team at Notiz.dev.

After the function is created, you need to register the plugin. You can do that by importing theregisterPlugin method from@scullyio/scully. The method takes a plugin type, plugin name, and plugin function as parameters. Here's an example of registering a plugin:

// ./scully/plugins/custom-plugin.tsconst{registerPlugin}=require('@scullyio/scully');registerPlugin('render',customPlugin,customPluginFunction)
Enter fullscreen modeExit fullscreen mode

Now that the plugin is registered, you're ready to use it. Forrender plugins, you need to add the name of the plugin to thedefaultPostRenderers array in the top level of the site's Scully config or thepostRenderers array for a specific set of routes in the Scully config:

// scully.your-site.config.tsimport{customPlugin}from'./scully/plugins/custom-plugin';exportconstconfig:ScullyConfig={projectRoot:'./src',projectName:'personal-site',outDir:'./dist/static',routes:{'/blog/:slug':{type:'contentFolder',slug:{folder:'./blog',},postRenderers:[customPlugin],},},defaultPostRenderers:[customPlugin],}
Enter fullscreen modeExit fullscreen mode

For therouteDiscoveryDone plugins, they just need to be registered with Scully from thescully.my-site.config.ts file to be run. They don't need to be added to thepostRenderers ordefaultPostRenderers array like therender plugin.

Example Plugins

The Scully GitHub repository hassome example plugins that can be used as a template for you to build your own. In addition, you should be able to browse the repositories of community plugins to see how the plugins are created. Here is a list of some community plugins that are good examples:

findPlugin Method

The Scully team doesn't recommend you export the plugin function from the file where the plugin is defined. This ensures that the plugin function doesn't bleed out into other parts of the system. There are times, though, when you need to invoke the plugin function manually. If you need to do this, you can get access to the plugin with thefindPlugin method provided by Scully. The method takes one to three parameters. They are:

  • The plugin type, name, or symbol
  • The plugin name or symbol
  • athrowOnNotFound boolean

If you pass the plugin type (i.e.RenderPlugin orRouterPlugin) as the first parameter, the second parameter needs to be passed as well and should be the name orSymbol of the plugin. If the first parameter is the name orSymbol, you don't need any other parameters.

ThethrowOnNotFound boolean is defaulted to true, and will generally not be needed by external programs. However, if you're using optional plugins then you can change this value tofalse. If the plugin is not found, the function will returnundefined.

// ./scully/plugins/custom-plugin.spec.tsconstpluginName=Symbol('customPlugin');constcustomPlugin=findPlugin(pluginName);
Enter fullscreen modeExit fullscreen mode

Now that you have access to the plugin, you can invoke it by passing it the needed parameters. For example, arender plugin generally needs anhtml string and aHandledRoute. Arouter plugin generally takes aroute string and aconfig parameter.

// ./scully/plugins/custom-plugin.spec.tsconstpluginResult=awaitcustomPlugin(htmlString,handledRouteData);
Enter fullscreen modeExit fullscreen mode

Another reason you might need thefindPlugin method is to create a new plugin by composing other plugins. For example, let's say you have tworender plugins,pageTitle andcanonicalLink. Next, you want to create a new plugin calledseo. This plugin will run the first two plugins, and then do some more work. That new plugin may look like this:

registerPlugin('render','seo',async(html,route)=>{constpageTitle=findPlugin('pageTitle');constcanonicalLink=findPlugin('canonicalLink');html=awaitpageTitle.then(canonicalLink);// Do some more SEO workreturnPromise.resolve(html);});
Enter fullscreen modeExit fullscreen mode

You now have a newrender plugin calledseo that does all of your SEO work for you, with the help of a couple other plugins.

Conclusion

Scully's plugin system is incredibly powerful and gives you the ability to add any extra features that you might need. There is a growing catalogue of community plugins, all of which are relatively simple to add to your site and add great value. Most, if not all, areon NPM and start withscully-plugin. If you can't find what you need, create your own!

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Preston Lamb is a full stack JavaScript developer, Angular GDE, ngChampion writer for the ng-conf blog, & co-organizer of the Angular Community Meetup.
  • Location
    Roy, UT
  • Education
    BS in Computer Science from Utah State University
  • Work
    Software Developer at MotivHealth
  • Joined

More fromPreston Lamb

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp