@bwat47/joplin-plugin-scripts 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -4
- package/lib/build.mjs +11 -9
- package/lib/buildConfig.mjs +44 -7
- package/lib/index.mjs +1 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -40,13 +40,20 @@ Commands run from the plugin project root, which npm scripts do automatically.
|
|
|
40
40
|
```json
|
|
41
41
|
{
|
|
42
42
|
"extraScripts": ["contentScripts/codeMirror.ts"],
|
|
43
|
+
"webviewScripts": ["dialog/dialog.ts"],
|
|
43
44
|
"mainBuildOptions": { "external": ["jsdom"] }
|
|
44
45
|
}
|
|
45
46
|
```
|
|
46
47
|
|
|
47
|
-
- `extraScripts`: content scripts
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
- `extraScripts`: content scripts registered with `joplin.contentScripts.register`
|
|
49
|
+
(CodeMirror and Markdown-it plugins), relative to `src/`. Each is bundled for the browser
|
|
50
|
+
as CommonJS to the same path under `dist/` with a `.js` extension. Joplin's CodeMirror
|
|
51
|
+
and Lezer packages stay external so they share the host's instances.
|
|
52
|
+
- `webviewScripts`: scripts loaded into a dialog or panel with `addScript`, relative to
|
|
53
|
+
`src/`. A webview loads them as plain `<script>` tags with no `module`, `exports`, or
|
|
54
|
+
`require`, so each is bundled as a self-contained IIFE with nothing external. Exports
|
|
55
|
+
are dropped, so the script must start itself, e.g. by calling its init function at the
|
|
56
|
+
top level. Plain `.js` files in `src/` are copied as-is and don't need listing.
|
|
50
57
|
- `mainBuildOptions`: esbuild options merged over the main bundle's defaults. Relative
|
|
51
58
|
`alias` targets resolve from the project root. `absWorkingDir`, `entryPoints`,
|
|
52
59
|
`outfile`, and `outdir` cannot be overridden.
|
|
@@ -63,7 +70,7 @@ const { mainBuildOptions } = await readPluginConfig(root);
|
|
|
63
70
|
const result = await build({ ...createBuildOptions(root, mainBuildOptions), write: false });
|
|
64
71
|
```
|
|
65
72
|
|
|
66
|
-
`contentScriptExternals`
|
|
73
|
+
`createWebviewScriptBuildOptions` and `contentScriptExternals` are also exported.
|
|
67
74
|
|
|
68
75
|
## Development
|
|
69
76
|
|
package/lib/build.mjs
CHANGED
|
@@ -5,21 +5,27 @@ import { build } from 'esbuild';
|
|
|
5
5
|
import {
|
|
6
6
|
createBuildOptions,
|
|
7
7
|
createContentScriptBuildOptions,
|
|
8
|
+
createWebviewScriptBuildOptions,
|
|
8
9
|
isRuntimeAsset,
|
|
9
10
|
readPluginConfig,
|
|
11
|
+
scriptOutputPaths,
|
|
10
12
|
} from './buildConfig.mjs';
|
|
11
13
|
import { packagePlugin, readProject } from './packagePlugin.mjs';
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* Builds the plugin at `root`: validates the project, clears `dist/` and `publish/`, copies runtime
|
|
15
|
-
* assets from `src/`, bundles the main entry point
|
|
17
|
+
* assets from `src/`, bundles the main entry point, extra scripts, and webview scripts, and creates
|
|
18
|
+
* the `.jpl` archive.
|
|
16
19
|
*/
|
|
17
20
|
export async function buildPlugin(root) {
|
|
18
21
|
const dist = path.join(root, 'dist');
|
|
19
22
|
const publish = path.join(root, 'publish');
|
|
20
23
|
const { manifest } = await readProject(root);
|
|
21
|
-
const { extraScripts, mainBuildOptions } = await readPluginConfig(root);
|
|
22
|
-
const
|
|
24
|
+
const { extraScripts, webviewScripts, mainBuildOptions } = await readPluginConfig(root);
|
|
25
|
+
const scriptBuilds = [
|
|
26
|
+
...extraScripts.map((script) => createContentScriptBuildOptions(root, script)),
|
|
27
|
+
...webviewScripts.map((script) => createWebviewScriptBuildOptions(root, script)),
|
|
28
|
+
];
|
|
23
29
|
|
|
24
30
|
// Only these fixed, project-local output directories are removed.
|
|
25
31
|
await rm(dist, { recursive: true, force: true });
|
|
@@ -33,12 +39,8 @@ export async function buildPlugin(root) {
|
|
|
33
39
|
});
|
|
34
40
|
await Promise.all([
|
|
35
41
|
build(createBuildOptions(root, mainBuildOptions)),
|
|
36
|
-
...
|
|
42
|
+
...scriptBuilds.map((options) => build(options)),
|
|
37
43
|
]);
|
|
38
44
|
|
|
39
|
-
await packagePlugin(
|
|
40
|
-
root,
|
|
41
|
-
manifest,
|
|
42
|
-
extraBuilds.map((options) => path.relative(dist, path.resolve(root, options.outfile)))
|
|
43
|
-
);
|
|
45
|
+
await packagePlugin(root, manifest, scriptOutputPaths(extraScripts, webviewScripts));
|
|
44
46
|
}
|
package/lib/buildConfig.mjs
CHANGED
|
@@ -132,14 +132,41 @@ export function createContentScriptBuildOptions(root, script) {
|
|
|
132
132
|
};
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Returns the esbuild options for one webview script, e.g. `dialog/resizeDialog.ts` (relative to
|
|
137
|
+
* `src/`) to `dist/dialog/resizeDialog.js`. Webview scripts are loaded by a `<script>` tag in a
|
|
138
|
+
* dialog or panel, which has no `module`, `exports`, or `require`, so they are bundled as a
|
|
139
|
+
* self-contained IIFE. Their exports are dropped; the script runs for its side effects.
|
|
140
|
+
* @param {string} root Plugin project root.
|
|
141
|
+
* @param {string} script Script path relative to `src/`.
|
|
142
|
+
* @returns {import('esbuild').BuildOptions}
|
|
143
|
+
*/
|
|
144
|
+
export function createWebviewScriptBuildOptions(root, script) {
|
|
145
|
+
return {
|
|
146
|
+
...createContentScriptBuildOptions(root, script),
|
|
147
|
+
format: 'iife',
|
|
148
|
+
// A webview has no host CodeMirror instance to share, so everything is bundled.
|
|
149
|
+
external: [],
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Returns `dist/`-relative output paths for every script the plugin config registers. */
|
|
154
|
+
export function scriptOutputPaths(extraScripts, webviewScripts) {
|
|
155
|
+
return [...extraScripts, ...webviewScripts].map((script) => script.replace(SCRIPT_EXTENSION_PATTERN, '.js'));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function isStringArray(value) {
|
|
159
|
+
return Array.isArray(value) && value.every((item) => typeof item === 'string');
|
|
160
|
+
}
|
|
161
|
+
|
|
135
162
|
function isPlainObject(value) {
|
|
136
163
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
137
164
|
}
|
|
138
165
|
|
|
139
166
|
/**
|
|
140
167
|
* Reads and validates `<root>/plugin.config.json`. The file is optional; a plugin without one
|
|
141
|
-
* has no extra scripts and uses the default main build options.
|
|
142
|
-
* @returns {Promise<{ extraScripts: string[], mainBuildOptions: import('esbuild').BuildOptions }>}
|
|
168
|
+
* has no extra or webview scripts and uses the default main build options.
|
|
169
|
+
* @returns {Promise<{ extraScripts: string[], webviewScripts: string[], mainBuildOptions: import('esbuild').BuildOptions }>}
|
|
143
170
|
*/
|
|
144
171
|
export async function readPluginConfig(root) {
|
|
145
172
|
const configPath = path.join(root, CONFIG_FILE);
|
|
@@ -147,17 +174,27 @@ export async function readPluginConfig(root) {
|
|
|
147
174
|
try {
|
|
148
175
|
config = JSON.parse(await readFile(configPath, 'utf8'));
|
|
149
176
|
} catch (error) {
|
|
150
|
-
if (error.code === 'ENOENT') return { extraScripts: [], mainBuildOptions: {} };
|
|
177
|
+
if (error.code === 'ENOENT') return { extraScripts: [], webviewScripts: [], mainBuildOptions: {} };
|
|
151
178
|
throw new Error(`Could not read ${configPath}: ${error.message}`, { cause: error });
|
|
152
179
|
}
|
|
153
180
|
if (!isPlainObject(config)) throw new Error(`${configPath} must contain a JSON object.`);
|
|
154
|
-
const { extraScripts = [], mainBuildOptions = {}, ...unknown } = config;
|
|
181
|
+
const { extraScripts = [], webviewScripts = [], mainBuildOptions = {}, ...unknown } = config;
|
|
155
182
|
const unknownKeys = Object.keys(unknown);
|
|
156
183
|
if (unknownKeys.length > 0) {
|
|
157
184
|
throw new Error(`${configPath} has unknown keys: ${unknownKeys.join(', ')}.`);
|
|
158
185
|
}
|
|
159
|
-
|
|
160
|
-
|
|
186
|
+
for (const [key, value] of [
|
|
187
|
+
['extraScripts', extraScripts],
|
|
188
|
+
['webviewScripts', webviewScripts],
|
|
189
|
+
]) {
|
|
190
|
+
if (!isStringArray(value)) {
|
|
191
|
+
throw new Error(`${configPath} "${key}" must be an array of paths relative to src/.`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const outputs = scriptOutputPaths(extraScripts, webviewScripts);
|
|
195
|
+
const duplicate = outputs.find((output, index) => outputs.indexOf(output) !== index);
|
|
196
|
+
if (duplicate !== undefined) {
|
|
197
|
+
throw new Error(`${configPath} lists more than one script that builds to dist/${duplicate}.`);
|
|
161
198
|
}
|
|
162
199
|
if (!isPlainObject(mainBuildOptions)) {
|
|
163
200
|
throw new Error(`${configPath} "mainBuildOptions" must be an object of esbuild options.`);
|
|
@@ -166,5 +203,5 @@ export async function readPluginConfig(root) {
|
|
|
166
203
|
if (reserved.length > 0) {
|
|
167
204
|
throw new Error(`${configPath} "mainBuildOptions" cannot set ${reserved.join(', ')}.`);
|
|
168
205
|
}
|
|
169
|
-
return { extraScripts, mainBuildOptions };
|
|
206
|
+
return { extraScripts, webviewScripts, mainBuildOptions };
|
|
170
207
|
}
|
package/lib/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bwat47/joplin-plugin-scripts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Shared esbuild build, packaging, version, and API-update scripts for Joplin plugins",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "bwat47",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/bwat47/joplin-plugin-scripts.git"
|
|
10
|
+
},
|
|
7
11
|
"type": "module",
|
|
8
12
|
"engines": {
|
|
9
13
|
"node": ">=22"
|