1"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[7420],{3719:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>l,contentTitle:()=>t,default:()=>h,frontMatter:()=>a,metadata:()=>d,toc:()=>o});var s=i(1527),r=i(4128);const a={sidebar_position:2},t="Script Standard",d={id:"joinus/advanced/script-standard",title:"Script Standard",description:"Code Style",source:"@site/docs/joinus/advanced/script-standard.md",sourceDirName:"joinus/advanced",slug:"/joinus/advanced/script-standard",permalink:"/joinus/advanced/script-standard",draft:!1,unlisted:!1,editUrl:"https://github.com/DIYgod/RSSHub/blob/master/website/docs/joinus/advanced/script-standard.md",tags:[],version:"current",lastUpdatedBy:"DIYgod",lastUpdatedAt:1709911910,formattedLastUpdatedAt:"Mar 8, 2024",sidebarPosition:2,frontMatter:{sidebar_position:2},sidebar:"joinusSidebar",previous:{title:"RSS Feed Fundamentals",permalink:"/joinus/advanced/advanced-feed"},next:{title:"Using Cache",permalink:"/joinus/advanced/use-cache"}},l={},o=[{value:"Code Style",id:"code-style",level:2},{value:"General Guidelines",id:"general-guidelines",level:3},{value:"Formatting",id:"formatting",level:3},{value:"Indentation",id:"indentation",level:4},{value:"Semicolons",id:"semicolons",level:4},{value:"String",id:"string",level:4},{value:"Whitespace",id:"whitespace",level:4},{value:"Language Features",id:"language-features",level:3},{value:"Casting",id:"casting",level:4},{value:"Functions",id:"functions",level:4},{value:"Loops",id:"loops",level:4},{value:"Variables",id:"variables",level:4},{value:"Naming",id:"naming",level:3},{value:"Route Standard",id:"route-standard",level:2},{value:"Namespace",id:"namespace",level:3},{value:"Naming Standard",id:"naming-standard",level:4},{value:"Registering a Route",id:"registering-a-route",level:3},{value:"Maintainer List",id:"maintainer-list",level:3},{value:"Radar Rules",id:"radar-rules",level:3},{value:"Rendering Templates",id:"rendering-templates",level:3},{value:"Example",id:"example",level:4},{value:"v1 Route Standard",id:"v1-route-standard",level:3}];function c(e){const n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,r.a)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.h1,{id:"script-standard",children:"Script Standard"}),"\n",(0,s.jsx)(n.h2,{id:"code-style",children:"Code Style"}),"\n",(0,s.jsx)(n.h3,{id:"general-guidelines",children:"General Guidelines"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"Be consistent!"})}),"\n",(0,s.jsx)(n.li,{children:"Avoid using deprecated features."}),"\n",(0,s.jsxs)(n.li,{children:["Avoid modifying ",(0,s.jsx)(n.code,{children:"yarn.lock"})," and ",(0,s.jsx)(n.code,{children:"package.json"}
1),", unless you are adding a new dependency."]}),"\n",(0,s.jsx)(n.li,{children:"Conbine repetitive code into functions."}),"\n",(0,s.jsx)(n.li,{children:"Prefer higher ECMAScript Standard features over lower ones."}),"\n",(0,s.jsx)(n.li,{children:"Sort the entries alphabetically (uppercase first) to make it easier to find an entry."}),"\n",(0,s.jsx)(n.li,{children:"Use HTTPS instead of HTTP whenever possible."}),"\n",(0,s.jsx)(n.li,{children:"Use WebP format instead of JPG whenever possible since it offers better compression."}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"formatting",children:"Formatting"}),"\n",(0,s.jsx)(n.h4,{id:"indentation",children:"Indentation"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Use 4 spaces for indentation for consistent and easy-to-read code."}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"semicolons",children:"Semicolons"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Add a semicolon at the end of each statement for improved readability and consistency."}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"string",children:"String"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Use single quotes instead of double quotes whenever possible for consistency and readability."}),"\n",(0,s.jsxs)(n.li,{children:["Use ",(0,s.jsx)(n.a,{href:"https://developer.mozilla.org/docs/Web/JavaScript/Reference/Template_literals",children:"template literals"})," over complex string concatenation."]}),"\n",(0,s.jsxs)(n.li,{children:["Use ",(0,s.jsx)(n.a,{href:"https://developer.mozilla.org/docs/Web/JavaScript/Reference/Template_literals",children:"template literals"})," for GraphQL queries as they make the code more concise and easy to read."]}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"whitespace",children:"Whitespace"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Add an empty line at the end of each file."}),"\n",(0,s.jsx)(n.li,{children:"Avoid trailing whitespace for a clean and readable codebase."}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"language-features",children:"Language Features"}),"\n",(0,s.jsx)(n.h4,{id:"casting",children:"Casting"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Avoid re-casting the same type."}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"functions",children:"Functions"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Prefer ",(0,s.jsx)(n.a,{href:"https://developer.mozilla.org/docs/Web/JavaScript/Reference/Functions/Arrow_functions",children:"arrow functions"})," over the ",(0,s.jsx)(n.code,{children:"function"})," keyword."]}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"loops",children:"Loops"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Use ",(0,s.jsx)(n.code,{children:"for-of"})," instead of ",(0,s.jsx)(n.code,{children:"for"})," for arrays (",(0,s.jsxs)(n.a,{href:"https://rules.sonarsource.com/javascript/RSPEC-4138",children:["javascript",":S4138"]}),")."]}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"variables",children:"Variables"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Use ",(0,s.jsx)(n.code,{children:"const"})," and ",(0,s.jsx)(n.code,{children:"let"})," instead of ",(0,s.jsx)(n.code,{children:"var"}),"."]}),"\n",(0,s.jsx)(n.li,{children:"Declare one variable per declaration."}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"naming",children:"Naming"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Use ",(0,s.jsx)(n.code,{children:"lowerCamelCase"})," for variables and functions to adhere to standard naming conventions."]}),"\n",(0,s.jsxs)(n.li,{children:["Use ",(0,s.jsx)(n.code,{children:"kebab-case"})," for files and folders."]}),"\n",(0,s.jsxs)(n.li,{children:["Use ",(0,s.jsx)(n.code,{children:"CONSTANT_CASE"})," for constants."]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"route-standard",children:"Route Standard"}),"\n",(0,s.jsxs)(n.p,{children:["When creating a new route in RSSHub, you need to organize your files in a specific way. Your namespace folder should be stored in the ",(0,s.jsx)(n.code,{children:"lib/routes"})," directory and should include three mandatory files:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"router.ts"})," Registers the routes"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"maintainer.ts"})," Provides information about the route maintainer"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"radar.ts"})," Provide a ",(0,s.jsx)(n.a,{href:"https://github.com/DIYgod/RSSHub-Radar",children:"RSSHub Radar"})," rule for each route"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Your namespace folder structure should look like this:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"\u251c\u2500\u2500\u2500lib/routes\n\u2502 \u251c\u2500\u2500\u2500furstar\n\u2502 \u251c\u2500\u2500\u2500 templates\n\u2502 \u251c\u2500\u2500\u2500 description.art\n\u2502 \u251c\u2500\u2500\u2500 router.ts\n\u2502 \u251c\u2500\u2500\u2500 maintainer.ts\n\u2502 \u251c\u2500\u2500\u2500 radar.ts\n\u2502 \u251c\u2500\u2500\u2500 someOtherJs.ts\n\u2502 \u2514\u2500\u2500\u2500test\n\u2502 \u2514\u2500\u2500\u2500someOtherNamespaces\n...\n"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsxs)(n.strong,{children:["All eligible routes under the ",(0,s.jsx)(n.code,{children:"lib/routes"})," path will be automatically loaded without the need for updating the ",(0,s.jsx)(n.code,{children:"lib/router.ts"}),"."]})}),"\n",(0,s.jsx)(n.h3,{id:"namespace",children:"Namespace"}),"\n",(0,s.jsx)(n.p,{children:"RSSHub appends the name of all route namespace folders in front of the actual route. Route maintainers should think of the namespace as the root."}),"\n",(0,s.jsx)(n.h4,{id:"naming-standard",children:"Naming Standard"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Use the second-level domain (SLD) as your namespace. You can find more information about URL structure ",(0,s.jsx)(n.a,{href:"/joinus/new-radar#top-level-object-key",children:"here"}),"."]}),"\n",(0,s.jsxs)(n.li,{children:["Do not create variations of the same namespace. For more information, see ",(0,s.jsx)(n.a,{href:"/joinus/new-rss/before-start#create-a-namespace",children:"this page"})]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"registering-a-route",children:"Registering a Route"}),"\n",(0,s.jsxs)(n.p,{children:["To register a route, the ",(0,s.jsx)(n.code,{children:"router.ts"})," file should export a method that provides a Hoho route handler."]}),"\n",(0,s.jsx)(n.h3,{id:"maintainer-list",children:"Maintainer List"}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"maintainer.ts"})," file should export an object that provides maintainer information related to the route, including:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Key: Corresponding route path"}
1),"\n",(0,s.jsx)(n.li,{children:"Value: Array of string, including all maintainers' GitHub ID."}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["To generate a list of maintainers, use the following command: ",(0,s.jsx)(n.code,{children:"pnpm run build"}),", which will create the list under ",(0,s.jsx)(n.code,{children:"assets/build/"}),"."]}),"\n",(0,s.jsx)(n.admonition,{type:"danger",children:(0,s.jsxs)(n.p,{children:["The path should be the same as the ",(0,s.jsx)(n.code,{children:"path"})," in the corresponding documentation before the namespace appended in front of it."]})}),"\n",(0,s.jsx)(n.h3,{id:"radar-rules",children:"Radar Rules"}),"\n",(0,s.jsxs)(n.p,{children:["All routes are required to include the ",(0,s.jsx)(n.code,{children:"radar.ts"})," file, which includes the corresponding domain name. The minimum requirement for a successful match is for the rule to show up on the corresponding site which requires filling in the ",(0,s.jsx)(n.code,{children:"title"})," and ",(0,s.jsx)(n.code,{children:"docs"})," fields."]}),"\n",(0,s.jsxs)(n.p,{children:["To generate a complete ",(0,s.jsx)(n.code,{children:"radar-rules.ts"})," file, use the following command: ",(0,s.jsx)(n.code,{children:"yarn build"}),", which will create the file under ",(0,s.jsx)(n.code,{children:"assets/build/"}),"."]}),"\n",(0,s.jsx)(n.admonition,{type:"tip",children:(0,s.jsxs)(n.p,{children:["Remember to remove all build artifacts in ",(0,s.jsx)(n.code,{children:"assets/build/"})," before committing."]})}),"\n",(0,s.jsx)(n.h3,{id:"rendering-templates",children:"Rendering Templates"}),"\n",(0,s.jsxs)(n.p,{children:["When rendering custom content with HTML, such as ",(0,s.jsx)(n.code,{children:"item.description"}),", using ",(0,s.jsx)(n.a,{href:"https://aui.github.io/art-template/",children:"art-template"})," for layout is mandatory."]}),"\n",(0,s.jsxs)(n.p,{children:["All templates should be placed in the namespace's ",(0,s.jsx)(n.code,{children:"templates"})," folder with the ",(0,s.jsx)(n.code,{children:".art"})," file extension."]}),"\n",(0,s.jsx)(n.h4,{id:"example",children:"Example"}),"\n",(0,s.jsxs)(n.p,{children:["Here's an example taken from the ",(0,s.jsx)(n.a,{href:"https://github.com/DIYgod/RSSHub/blob/master/lib/routes/furstar",children:"furstar"})," namespace:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-html",children:'<div>\n <img src="{{ avatar }}" />\n {{ if link !== null }}\n <a href="{{ link }}">{{name}}</a>\n {{ else }}\n <a href="#">{{name}}</a>\n {{ /if }}\n</div>\n'})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-js",children:"import * as path from 'node:path';\nimport { art } from '@/utils/render';\nconst renderAuthor = (author) => art(path.join(__dirname, 'templates/author.art'), author);\n"})}),"\n",(0,s.jsx)(n.h3,{id:"v1-route-standard",children:"v1 Route Standard"}),"\n",(0,s.jsx)(n.admonition,{type:"danger",children:(0,s.jsxs)(n.p,{children:["The v1 Route Standard is deprecated. All new routes should be following the ",(0,s.jsx)(n.a,{href:"/joinus/advanced/script-standard#route-standard",children:"Route Standard"}),"."]})})]})}function h(e={}){const{wrapper:n}={...(0,r.a)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(c,{...e})}):c(e)}},4128:(e,n,i)=>{i.d(n,{Z:()=>d,a:()=>t});var s=i(959);const r={},a=s.createContext(r);function t(e){const n=s.useContext(a);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function d(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:t(e.components),s.createElement(a.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.