new plugins api (breaking)
Massimo Melina committed
Feb 8, 2022 at 17:14 UTC
36f91f43907b42ee5e2466afdab174fae3e360d3
5 files changed
+66
-56
README.md
+23
-13
@@ -187,7 +187,19 @@ but nothing is preventing a single plug-in from doing both tasks.
187
You should find some examples within your installation.
188
189
A plug-in must have a `plugin.js` file in its own folder.
190
-This file is javascript module that is supposed to expose one or more of the supported keys:
190
+This file is javascript module that exports an `init` function like this:
191
+```js
192
+exports.init = api => ({
193
+ frontend_css: 'mystyle.css'
194
+})
195
+```
196
+
197
+The init function is called when the module is loaded and should return an object with things to customize.
198
+In this example we are asking a css file to be loaded in the frontend.
199
+The parameter `api` object contains some useful things we'll see later.
200
+Let's first look at the things you can return:
201
+
202
+### Things a plugin can return
203
204
- `frontend_css: string | string[]` path to one or more css files that you want the frontend to load. These are to be placed in the `public` folder (refer below).
205
- `frontend_js: string | string[]` path to one or more js files that you want the frontend to load. These are to be placed in the `public` folder (refer below).
@@ -201,14 +213,13 @@ This file is javascript module that is supposed to expose one or more of the sup
213
- `unload: function` called when unloading a plugin. This is a good place for example to clearInterval().
214
- `onDirEntry: ({ entry: DirEntry, listPath: string }) => void | false` by providing this callback you can manipulate the record
215
that is sent to the frontend (`entry`), or you can return false to exclude this entry from the results.
204
-- `init: function` called when the plugin is initialized.
205
- If you need to use the `api` object immediately after the plugin is loaded, be sure to put your code in this callback.
206
-- `api: object` if your plugin exports an empty object with name `api`, it will be filled with useful functions.
207
- You'll just need this line
208
- ```js
209
- exports.api = {}
210
- ```
211
- Now let's have a look at what you'll find inside.
216
+
217
+### api object
218
+
219
+The `api` object you get as parameter of the `init` contains the following:
220
+
221
+ - `require: function` use this instead of standard `require` function to access modules already loaded by HFS.
222
+
223
- `getConfig(key: string): any` this is the way to go if you need some configuration to do your job.
224
225
Eg: you want a `message` text. This should be put by the user in the main config file, under the `plugins_config` property.
@@ -219,11 +230,11 @@ This file is javascript module that is supposed to expose one or more of the sup
230
message: Hi there!
231
```
232
Now you can use `api.getConfig('message')` to read it.
233
+
234
- `srcDir: string` this can be useful if you need to import some extra function not available in `api`.
235
```js
224
- exports.api = {}
225
- exports.init = function() {
226
- const { BUILD_TIMESTAMP } = require(exports.api.srcDir + '/index')
236
+ exports.init = api => {
237
+ const { BUILD_TIMESTAMP } = api.require(api.srcDir + '/index')
238
console.log(BUILD_TIMESTAMP)
239
}
240
```
@@ -233,7 +244,6 @@ This file is javascript module that is supposed to expose one or more of the sup
244
245
Each plug-in can have a `public` folder, and its files will be accessible at `/~/plugins/PLUGIN_NAME/FILENAME`.
246
236
-
247
### Front-end specific
248
249
The following information applies to the default front-end, and may not apply to a custom one.
config.yaml
+1
-1
@@ -11,7 +11,7 @@ port: 80 # 0 is a special value that will let the system pick a random availa
11
#private_key: filepath
12
mime:
13
"*.jpg|*.png|*.mp3|*.txt": auto
14
-disable_plugins: [ 'theme-example', 'download-counter' ]
14
+disable_plugins: [ 'theme-example', 'download-counter^' ]
15
plugins_config:
16
middleware-example:
17
message: ciao
plugins/download-counter/plugin.js
+32
-31
@@ -1,37 +1,38 @@
1
-const { writeFile, readFile } = require('fs')
2
-const _ = require('lodash')
3
-const yaml = require('yaml')
1
+exports.init = api => {
2
+ const _ = api.require('lodash')
3
+ const yaml = api.require('yaml')
4
+ const { writeFile, readFile } = api.require('fs')
5
5
-const countersFile = 'counters.yaml'
6
+ const countersFile = 'counters.yaml'
7
7
-const counters = {}
8
-// load previous stats
9
-readFile(countersFile, 'utf8', (err, data) => {
10
- if (err)
11
- return err.code === 'ENOENT' || console.debug(countersFile, err)
12
- Object.assign(counters, yaml.parse(String(data)))
13
- console.debug('counters loaded')
14
-})
8
+ const counters = {}
9
+ const save = _.debounce(() => {
10
+ writeFile(countersFile, yaml.stringify(counters), err => console.debug(err || 'counters saved'))
11
+ }, 5_000, { maxWait:30_000 })
12
16
-const save = _.debounce(() => {
17
- writeFile(countersFile, yaml.stringify(counters), err => console.debug(err || 'counters saved'))
18
-}, 5_000, { maxWait:30_000 })
13
+ // load previous stats
14
+ readFile(countersFile, 'utf8', (err, data) => {
15
+ if (err)
16
+ return err.code === 'ENOENT' || console.debug(countersFile, err)
17
+ Object.assign(counters, yaml.parse(String(data)))
18
+ console.debug('counters loaded')
19
+ })
20
20
-exports.unload = ()=> save.flush() // we may have pending savings
21
-
22
-exports.middleware = (ctx) =>
23
- () => { // execute after other middlewares are done
24
- if (ctx.status >= 300 || !ctx.fileSource) return
25
- const { path } = ctx
26
- counters[path] = counters[path] + 1 || 1
27
- save()
21
+ return {
22
+ frontend_js: 'hits.js',
23
+ unload: () => save.flush(), // we may have pending savings
24
+ middleware: (ctx) =>
25
+ () => { // execute after other middlewares are done
26
+ if (ctx.status >= 300 || !ctx.fileSource) return
27
+ const { path } = ctx
28
+ counters[path] = counters[path] + 1 || 1
29
+ save()
30
+ },
31
+ onDirEntry: ({ entry, listPath }) => {
32
+ const path = listPath + entry.n
33
+ const n = counters[path]
34
+ if (n)
35
+ entry.hits = n
36
+ }
37
}
29
-
30
-exports.onDirEntry = ({ entry, listPath }) => {
31
- const path = listPath + entry.n
32
- const n = counters[path]
33
- if (n)
34
- entry.hits = n
38
}
36
-
37
-exports.frontend_js = 'hits.js'
plugins/middleware-example-disabled/plugin.js
+6
-6
@@ -1,6 +1,6 @@
1
-const api = exports.api = {}
2
-
3
-exports.middleware = function(ctx) {
4
- ctx.body = 'This plugin is stopping you: ' + api.getConfig('message')
5
- return true // true = please stop
6
-}
1
+exports.init = api => ({
2
+ middleware(ctx) {
3
+ ctx.body = 'This plugin is stopping you: ' + api.getConfig('message')
4
+ return true // true = please stop
5
+ }
6
+})
src/plugins.ts
+4
-5
@@ -134,15 +134,14 @@ async function rescan() {
134
console.log(plugins[k] ? 'reloading plugin' : 'loading plugin', k)
135
const data = await import(f)
136
deleteModule(require.resolve(f)) // avoid caching
137
- new Plugin(k, data, unwatch)
138
- const { api } = data
139
- if (!api) return
140
- Object.assign(api, {
137
+ const res = await data.init?.call(null, {
138
srcDir: __dirname,
139
+ require,
140
getConfig: (cfgKey: string) =>
141
getConfig('plugins_config')?.[k]?.[cfgKey]
142
})
145
- await data.init?.call(api)
143
+ Object.assign(data, res)
144
+ new Plugin(k, data, unwatch)
145
} catch (e) {
146
console.log('plugin error:', e)
147
}