@yapmod/builder 0.0.0-stage → 1.0.2
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/dist/build.d.ts +17 -0
- package/dist/expression.d.ts +10 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +203 -0
- package/dist/transform.d.ts +5 -0
- package/dist/unplugin.d.ts +1 -0
- package/global.d.ts +72 -0
- package/package.json +48 -4
- package/README.md +0 -3
package/dist/build.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface BuildPluginOptions {
|
|
2
|
+
/** Plugin entry file (`.ts`, `.tsx`, `.js` or `.jsx`). */
|
|
3
|
+
entry: string;
|
|
4
|
+
/** Where the loadable expression is written. */
|
|
5
|
+
outfile: string;
|
|
6
|
+
/** Minify the bundle. */
|
|
7
|
+
minify?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Bundles a plugin entry into a self-contained expression the client's
|
|
11
|
+
* loader can evaluate: Babel compiles TSX and rewrites shared-module
|
|
12
|
+
* imports, esbuild bundles everything else, and the result is wrapped in
|
|
13
|
+
* the `function (yapmod) { ... }` shape from `expression.ts`.
|
|
14
|
+
*
|
|
15
|
+
* Returns the expression and writes it to `options.outfile`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function buildPlugin(options: BuildPluginOptions): Promise<string>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Name bundlers assign to the bundle; the footer reads the exports back. */
|
|
2
|
+
export declare const EXPRESSION_GLOBAL_NAME = "yapmodPlugin";
|
|
3
|
+
/** Opens the expression: runs the bundle in the loader's `yapmod` scope. */
|
|
4
|
+
export declare const expressionBanner = "(function () {\n";
|
|
5
|
+
/**
|
|
6
|
+
* Closes the expression and returns the default export. Bundlers expose
|
|
7
|
+
* `export default` as `<globalName>.default`; a function default reaches
|
|
8
|
+
* the loader unchanged and is called with the API, so factories work too.
|
|
9
|
+
*/
|
|
10
|
+
export declare const expressionFooter = "\n;const plugin = typeof yapmodPlugin !== 'undefined'\n ? yapmodPlugin.default ?? yapmodPlugin\n : undefined;\nif (plugin === undefined) {\n throw new Error(\n 'yapmod: bundle has no default export; export the plugin with `export default`.',\n );\n}\nreturn plugin;\n})();\n";
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// src/expression.ts
|
|
2
|
+
var EXPRESSION_GLOBAL_NAME = "yapmodPlugin";
|
|
3
|
+
var expressionBanner = "(function () {\n";
|
|
4
|
+
var expressionFooter = `
|
|
5
|
+
;const plugin = typeof ${EXPRESSION_GLOBAL_NAME} !== 'undefined'
|
|
6
|
+
? ${EXPRESSION_GLOBAL_NAME}.default ?? ${EXPRESSION_GLOBAL_NAME}
|
|
7
|
+
: undefined;
|
|
8
|
+
if (plugin === undefined) {
|
|
9
|
+
throw new Error(
|
|
10
|
+
'yapmod: bundle has no default export; export the plugin with \`export default\`.',
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
return plugin;
|
|
14
|
+
})();
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
// src/build.ts
|
|
18
|
+
import { readFileSync } from "fs";
|
|
19
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
20
|
+
import { dirname } from "path";
|
|
21
|
+
import { build } from "esbuild";
|
|
22
|
+
|
|
23
|
+
// src/transform.ts
|
|
24
|
+
import { transformSync, types as t } from "@babel/core";
|
|
25
|
+
import presetSolid from "babel-preset-solid";
|
|
26
|
+
import presetTypescript from "@babel/preset-typescript";
|
|
27
|
+
var SHARED_MODULES = {
|
|
28
|
+
"solid-js": "solid",
|
|
29
|
+
"solid-js/web": "solidWeb",
|
|
30
|
+
"solid-js/store": "solidStore"
|
|
31
|
+
};
|
|
32
|
+
function sharedAccess(namespace) {
|
|
33
|
+
return t.memberExpression(t.identifier("yapmod"), t.identifier(namespace));
|
|
34
|
+
}
|
|
35
|
+
function reexportError(source) {
|
|
36
|
+
return `@yapmod/builder cannot re-export from "${source}"; import the bindings and export them locally instead.`;
|
|
37
|
+
}
|
|
38
|
+
function rewriteSharedImports() {
|
|
39
|
+
return {
|
|
40
|
+
name: "yapmod:rewrite-shared-imports",
|
|
41
|
+
visitor: {
|
|
42
|
+
ImportDeclaration(path) {
|
|
43
|
+
const namespace = SHARED_MODULES[path.node.source.value];
|
|
44
|
+
if (namespace === void 0) return;
|
|
45
|
+
if (path.node.importKind === "type") {
|
|
46
|
+
path.remove();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const statements = [];
|
|
50
|
+
const named = [];
|
|
51
|
+
for (const specifier of path.node.specifiers) {
|
|
52
|
+
if (t.isImportNamespaceSpecifier(specifier)) {
|
|
53
|
+
statements.push(
|
|
54
|
+
t.variableDeclaration("const", [
|
|
55
|
+
t.variableDeclarator(specifier.local, sharedAccess(namespace))
|
|
56
|
+
])
|
|
57
|
+
);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (t.isImportDefaultSpecifier(specifier)) {
|
|
61
|
+
statements.push(
|
|
62
|
+
t.variableDeclaration("const", [
|
|
63
|
+
t.variableDeclarator(
|
|
64
|
+
specifier.local,
|
|
65
|
+
t.memberExpression(sharedAccess(namespace), t.identifier("default"))
|
|
66
|
+
)
|
|
67
|
+
])
|
|
68
|
+
);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (specifier.importKind === "type") continue;
|
|
72
|
+
named.push(specifier);
|
|
73
|
+
}
|
|
74
|
+
if (named.length > 0) {
|
|
75
|
+
const properties = [];
|
|
76
|
+
for (const specifier of named) {
|
|
77
|
+
const key = t.isIdentifier(specifier.imported) ? specifier.imported : t.stringLiteral(specifier.imported.value);
|
|
78
|
+
properties.push(t.objectProperty(key, specifier.local));
|
|
79
|
+
}
|
|
80
|
+
statements.push(
|
|
81
|
+
t.variableDeclaration("const", [
|
|
82
|
+
t.variableDeclarator(t.objectPattern(properties), sharedAccess(namespace))
|
|
83
|
+
])
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
if (statements.length === 0) {
|
|
87
|
+
path.remove();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
path.replaceWithMultiple(statements);
|
|
91
|
+
},
|
|
92
|
+
ExportNamedDeclaration(path) {
|
|
93
|
+
if (path.node.exportKind === "type") {
|
|
94
|
+
path.remove();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const source = path.node.source?.value;
|
|
98
|
+
if (source !== void 0 && source in SHARED_MODULES) {
|
|
99
|
+
throw path.buildCodeFrameError(reexportError(source));
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
ExportAllDeclaration(path) {
|
|
103
|
+
if (path.node.exportKind === "type") {
|
|
104
|
+
path.remove();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const source = path.node.source.value;
|
|
108
|
+
if (source in SHARED_MODULES) {
|
|
109
|
+
throw path.buildCodeFrameError(reexportError(source));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function transformPluginSource(code, id) {
|
|
116
|
+
const first = transformSync(code, {
|
|
117
|
+
filename: id,
|
|
118
|
+
babelrc: false,
|
|
119
|
+
configFile: false,
|
|
120
|
+
// Presets run in reverse, so TypeScript strips the source before the
|
|
121
|
+
// Solid preset compiles the now annotation-free JSX.
|
|
122
|
+
presets: [presetSolid, presetTypescript],
|
|
123
|
+
sourceType: "module",
|
|
124
|
+
ast: false,
|
|
125
|
+
cloneInputAst: false
|
|
126
|
+
});
|
|
127
|
+
if (!first?.code) return null;
|
|
128
|
+
const second = transformSync(first.code, {
|
|
129
|
+
filename: id,
|
|
130
|
+
babelrc: false,
|
|
131
|
+
configFile: false,
|
|
132
|
+
plugins: [rewriteSharedImports()],
|
|
133
|
+
sourceType: "module",
|
|
134
|
+
ast: false
|
|
135
|
+
});
|
|
136
|
+
return second?.code ?? null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/build.ts
|
|
140
|
+
var babelTransform = {
|
|
141
|
+
name: "yapmod:babel-transform",
|
|
142
|
+
setup(buildApi) {
|
|
143
|
+
buildApi.onLoad({ filter: /\.(?:[cm]?[jt]sx?)$/ }, (args) => {
|
|
144
|
+
if (args.path.includes("node_modules")) return void 0;
|
|
145
|
+
const source = readFileSync(args.path, "utf8");
|
|
146
|
+
const code = transformPluginSource(source, args.path);
|
|
147
|
+
return code === null ? void 0 : { contents: code, loader: "js" };
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
var sharedModuleGuard = {
|
|
152
|
+
name: "yapmod:shared-module-guard",
|
|
153
|
+
setup(buildApi) {
|
|
154
|
+
buildApi.onResolve({ filter: /^solid-js(?:\/.*)?$/ }, (args) => ({
|
|
155
|
+
errors: [
|
|
156
|
+
{
|
|
157
|
+
text: `Dependency "${args.path}" imports solid-js, but the page already runs the shared instance (yapmod.solid*). Plugin dependencies must not bundle their own copy of Solid.`
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
async function buildPlugin(options) {
|
|
164
|
+
const result = await build({
|
|
165
|
+
entryPoints: [options.entry],
|
|
166
|
+
bundle: true,
|
|
167
|
+
write: false,
|
|
168
|
+
format: "iife",
|
|
169
|
+
globalName: EXPRESSION_GLOBAL_NAME,
|
|
170
|
+
// The Babel pass already compiled JSX away; keep esbuild from guessing.
|
|
171
|
+
jsx: "preserve",
|
|
172
|
+
target: "es2020",
|
|
173
|
+
minify: options.minify ?? false,
|
|
174
|
+
plugins: [babelTransform, sharedModuleGuard]
|
|
175
|
+
});
|
|
176
|
+
const output = result.outputFiles[0];
|
|
177
|
+
if (!output) throw new Error("@yapmod/builder: esbuild produced no output.");
|
|
178
|
+
const expression = expressionBanner + output.text + expressionFooter;
|
|
179
|
+
await mkdir(dirname(options.outfile), { recursive: true });
|
|
180
|
+
await writeFile(options.outfile, expression, "utf8");
|
|
181
|
+
return expression;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// src/unplugin.ts
|
|
185
|
+
import { createUnplugin } from "unplugin";
|
|
186
|
+
var yapmodPlugin = createUnplugin(() => ({
|
|
187
|
+
name: "yapmod-plugin",
|
|
188
|
+
enforce: "pre",
|
|
189
|
+
transform(code, id) {
|
|
190
|
+
if (id.includes("node_modules")) return null;
|
|
191
|
+
if (!/\.(?:[cm]?[jt]sx?)$/.test(id)) return null;
|
|
192
|
+
const transformed = transformPluginSource(code, id);
|
|
193
|
+
return transformed === null ? null : { code: transformed, map: null };
|
|
194
|
+
}
|
|
195
|
+
}));
|
|
196
|
+
export {
|
|
197
|
+
EXPRESSION_GLOBAL_NAME,
|
|
198
|
+
buildPlugin,
|
|
199
|
+
expressionBanner,
|
|
200
|
+
expressionFooter,
|
|
201
|
+
transformPluginSource,
|
|
202
|
+
yapmodPlugin
|
|
203
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const yapmodPlugin: import("unplugin").UnpluginInstance<unknown, boolean>;
|
package/global.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Self-contained type declaration for the `yapmod` global.
|
|
3
|
+
*
|
|
4
|
+
* Inlined from `@yapmod/types` so the builder package has no runtime
|
|
5
|
+
* dependency on the monorepo types package (which itself depends on
|
|
6
|
+
* `@yapmod/ui` and `solid-js`). Plugin authors only need these shapes
|
|
7
|
+
* for type-checking; the real API object is provided at runtime by the
|
|
8
|
+
* Yapmod client on the host page.
|
|
9
|
+
*
|
|
10
|
+
* Usage: `import type {} from '@yapmod/builder/global'` loads this
|
|
11
|
+
* ambient declaration into a project.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { Component, JSX } from 'solid-js';
|
|
15
|
+
|
|
16
|
+
interface YapmodPluginApi {
|
|
17
|
+
readonly apiVersion: 1;
|
|
18
|
+
readonly id: string;
|
|
19
|
+
readonly manifest: {
|
|
20
|
+
name: string;
|
|
21
|
+
author: string;
|
|
22
|
+
description: string;
|
|
23
|
+
version?: string;
|
|
24
|
+
hash?: string;
|
|
25
|
+
};
|
|
26
|
+
readonly storage: Record<string, unknown>;
|
|
27
|
+
flushStorage(): void;
|
|
28
|
+
log(...args: unknown[]): void;
|
|
29
|
+
observe(selector: string, onFound: (element: Element) => void): () => void;
|
|
30
|
+
readonly ui: {
|
|
31
|
+
showToast(message: string, kind?: 'success' | 'error'): void;
|
|
32
|
+
openModal(options: {
|
|
33
|
+
title: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
wide?: boolean;
|
|
36
|
+
content: (close: () => void) => JSX.Element;
|
|
37
|
+
footer?: (close: () => void) => JSX.Element;
|
|
38
|
+
onClose?: () => void;
|
|
39
|
+
}): () => void;
|
|
40
|
+
injectCss(css: string): () => void;
|
|
41
|
+
Button: Component<JSX.IntrinsicElements['button'] & { variant?: string }>;
|
|
42
|
+
Hint: Component<JSX.IntrinsicElements['span']>;
|
|
43
|
+
Panel: Component<JSX.IntrinsicElements['div']>;
|
|
44
|
+
Icon: Component<{ name: string; class?: string; size?: string }>;
|
|
45
|
+
};
|
|
46
|
+
readonly settings: {
|
|
47
|
+
registerSection(section: {
|
|
48
|
+
id: string;
|
|
49
|
+
label: string;
|
|
50
|
+
icon?: string;
|
|
51
|
+
render: () => JSX.Element;
|
|
52
|
+
}): () => void;
|
|
53
|
+
};
|
|
54
|
+
onDispose(dispose: () => void): void;
|
|
55
|
+
showSettings(): void;
|
|
56
|
+
readonly solid: typeof import('solid-js');
|
|
57
|
+
readonly solidWeb: typeof import('solid-js/web');
|
|
58
|
+
readonly solidStore: typeof import('solid-js/store');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
declare global {
|
|
62
|
+
/**
|
|
63
|
+
* The plugin API the host injects. Every `@yapmod/builder` bundle runs
|
|
64
|
+
* inside `function (yapmod) { ... }`, so top-level plugin code can use it
|
|
65
|
+
* without importing anything.
|
|
66
|
+
*/
|
|
67
|
+
const yapmod: YapmodPluginApi;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Export the interface so this file is treated as a module (allows empty
|
|
71
|
+
// type imports: `import type {} from '@yapmod/builder/global'`).
|
|
72
|
+
export type { YapmodPluginApi };
|
package/package.json
CHANGED
|
@@ -1,6 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yapmod/builder",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/yapmod/client.git",
|
|
7
|
+
"directory": "packages/builder"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"global.d.ts"
|
|
12
|
+
],
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "dist/index.js",
|
|
15
|
+
"module": "dist/index.js",
|
|
16
|
+
"types": "dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"import": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts"
|
|
21
|
+
},
|
|
22
|
+
"./global": {
|
|
23
|
+
"types": "./global.d.ts"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsup && tsc --emitDeclarationOnly -p tsconfig.build.json",
|
|
31
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
32
|
+
"example": "node examples/build.mjs && vite build --config examples/vite.config.mjs && node examples/check.mjs"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@babel/core": "^7.29.0",
|
|
36
|
+
"@babel/preset-typescript": "^7.29.0",
|
|
37
|
+
"babel-preset-solid": "^1.8.4",
|
|
38
|
+
"esbuild": "^0.28.2",
|
|
39
|
+
"solid-js": "^1.9.18",
|
|
40
|
+
"unplugin": "^3.7.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/babel__core": "^7.20.5",
|
|
44
|
+
"@types/node": "^26.0.0",
|
|
45
|
+
"@typescript/typescript6": "^6.0.0",
|
|
46
|
+
"tsup": "^8.0.0",
|
|
47
|
+
"typescript": "^7.0.2",
|
|
48
|
+
"vite": "^8.3.7"
|
|
49
|
+
}
|
|
50
|
+
}
|
package/README.md
DELETED