@hashrock/ono 0.1.2 → 0.2.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 +35 -320
- package/package.json +14 -13
- package/src/barrels.js +230 -0
- package/src/browser/compiler.js +71 -193
- package/src/browser/unocss.js +5 -1
- package/src/builder.js +110 -232
- package/src/bundler.js +215 -64
- package/src/cli.js +22 -191
- package/src/commands/build.js +141 -0
- package/src/commands/dev.js +54 -0
- package/src/constants.js +74 -0
- package/src/jsx-runtime.js +15 -5
- package/src/parser.js +555 -0
- package/src/renderer.js +29 -48
- package/src/server.js +88 -74
- package/src/transformer.js +1 -18
- package/src/unocss.js +20 -24
- package/src/utils.js +55 -0
- package/src/watcher.js +98 -125
- package/src/content.js +0 -272
- package/src/resolver.js +0 -137
package/src/browser/compiler.js
CHANGED
|
@@ -1,188 +1,83 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Browser compiler
|
|
2
|
+
* Browser compiler - shared with the REPL worker.
|
|
3
|
+
*
|
|
4
|
+
* Uses the same mini bundler as the Node build (bundler.js/parser.js).
|
|
5
|
+
* Package imports are rejected — the browser has no module resolution —
|
|
6
|
+
* and the bundle is evaluated with new Function, with the JSX runtime
|
|
7
|
+
* passed in as parameters.
|
|
3
8
|
*/
|
|
4
9
|
|
|
5
10
|
import { transformJSX } from '../transformer.js';
|
|
6
11
|
import { renderToString } from '../renderer.js';
|
|
7
|
-
import { h } from '../jsx-runtime.js';
|
|
12
|
+
import { h, Fragment } from '../jsx-runtime.js';
|
|
13
|
+
import { bundle } from '../bundler.js';
|
|
8
14
|
import { getUnoGenerator } from './unocss.js';
|
|
9
15
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Join a relative specifier against the importing file's virtual path
|
|
18
|
+
* @param {string} fromId
|
|
19
|
+
* @param {string} specifier
|
|
20
|
+
*/
|
|
21
|
+
function resolveImport(fromId, specifier) {
|
|
22
|
+
const fromParts = fromId.split('/').slice(0, -1);
|
|
23
|
+
const targetParts = specifier.split('/');
|
|
24
|
+
const resolved = [...fromParts];
|
|
25
|
+
|
|
26
|
+
for (const part of targetParts) {
|
|
27
|
+
if (!part || part === '.') continue;
|
|
28
|
+
if (part === '..') {
|
|
29
|
+
resolved.pop();
|
|
30
|
+
} else {
|
|
31
|
+
resolved.push(part);
|
|
23
32
|
}
|
|
24
|
-
|
|
25
|
-
return resolved.join('/');
|
|
26
33
|
}
|
|
27
34
|
|
|
28
|
-
return
|
|
35
|
+
return resolved.join('/');
|
|
29
36
|
}
|
|
30
37
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const deps = [];
|
|
39
|
-
source.replace(importPattern, (match, specifier) => {
|
|
40
|
-
const resolved = resolveImport(filename, specifier);
|
|
41
|
-
if (files[resolved]) {
|
|
42
|
-
deps.push(resolved);
|
|
43
|
-
}
|
|
44
|
-
return match;
|
|
45
|
-
});
|
|
46
|
-
dependencyMap.set(filename, deps);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const visited = new Set();
|
|
50
|
-
const order = [];
|
|
51
|
-
|
|
52
|
-
const visit = (file) => {
|
|
53
|
-
if (!files[file] || visited.has(file)) return;
|
|
54
|
-
visited.add(file);
|
|
55
|
-
const deps = dependencyMap.get(file) || [];
|
|
56
|
-
for (const dep of deps) {
|
|
57
|
-
visit(dep);
|
|
58
|
-
}
|
|
59
|
-
order.push(file);
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
if (entryPoint) {
|
|
63
|
-
visit(entryPoint);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
for (const filename of filenames) {
|
|
67
|
-
if (!visited.has(filename)) {
|
|
68
|
-
visit(filename);
|
|
69
|
-
}
|
|
38
|
+
/**
|
|
39
|
+
* @param {Record<string, string>} files
|
|
40
|
+
* @param {string} entryPoint
|
|
41
|
+
*/
|
|
42
|
+
async function bundleProject(files, entryPoint) {
|
|
43
|
+
if (!(entryPoint in files)) {
|
|
44
|
+
throw new Error(`Entry point '${entryPoint}' not found`);
|
|
70
45
|
}
|
|
71
46
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
for (const filename of order) {
|
|
80
|
-
const transformedCode = transformJSX(files[filename], filename);
|
|
81
|
-
let code = transformedCode;
|
|
82
|
-
const exportMappings = new Map();
|
|
83
|
-
|
|
84
|
-
const addExport = (exportName, localName = exportName) => {
|
|
85
|
-
if (!exportName || exportMappings.has(exportName)) return;
|
|
86
|
-
exportMappings.set(exportName, localName);
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
code = code.replace(/import\s+{([^}]+)}\s+from\s+['"]([^'"]+)['"]/g, (match, imports, path) => {
|
|
90
|
-
const resolvedPath = resolveImport(filename, path);
|
|
91
|
-
const importNames = imports.split(',').map(part => part.trim()).filter(Boolean);
|
|
92
|
-
|
|
93
|
-
return importNames
|
|
94
|
-
.map(name => {
|
|
95
|
-
const [local, alias] = name.split(/\s+as\s+/).map(token => token && token.trim());
|
|
96
|
-
const localName = alias || local;
|
|
97
|
-
const exportName = local;
|
|
98
|
-
return `const ${localName} = __modules['${resolvedPath}']['${exportName}'];`;
|
|
99
|
-
})
|
|
100
|
-
.join('\n');
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
code = code.replace(/import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g, (match, localName, path) => {
|
|
104
|
-
const resolvedPath = resolveImport(filename, path);
|
|
105
|
-
return `const ${localName} = __modules['${resolvedPath}']['default'];`;
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
code = code.replace(/import\s+\*\s+as\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g, (match, localName, path) => {
|
|
109
|
-
const resolvedPath = resolveImport(filename, path);
|
|
110
|
-
return `const ${localName} = __modules['${resolvedPath}'];`;
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
code = code.replace(/export\s+default\s+function\s+([A-Za-z_$][\w$]*)/g, (match, name) => {
|
|
114
|
-
addExport('default', name);
|
|
115
|
-
return `function ${name}`;
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
code = code.replace(/export\s+default\s+([A-Za-z_$][\w$]*)/g, (match, name) => {
|
|
119
|
-
addExport('default', name);
|
|
120
|
-
return `${name}`;
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
code = code.replace(/export\s+(const|let|var)\s+([A-Za-z_$][\w$]*)/g, (match, kind, name) => {
|
|
124
|
-
addExport(name);
|
|
125
|
-
return `${kind} ${name}`;
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
code = code.replace(/export\s+function\s+([A-Za-z_$][\w$]*)/g, (match, name) => {
|
|
129
|
-
addExport(name);
|
|
130
|
-
return `function ${name}`;
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
code = code.replace(/export\s+class\s+([A-Za-z_$][\w$]*)/g, (match, name) => {
|
|
134
|
-
addExport(name);
|
|
135
|
-
return `class ${name}`;
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
code = code.replace(/export\s*{\s*([^}]+)\s*};?/g, (match, names) => {
|
|
139
|
-
names
|
|
140
|
-
.split(',')
|
|
141
|
-
.map(part => part.trim())
|
|
142
|
-
.filter(Boolean)
|
|
143
|
-
.forEach(part => {
|
|
144
|
-
const [local, alias] = part.split(/\s+as\s+/).map(token => token && token.trim());
|
|
145
|
-
addExport(alias || local, local);
|
|
146
|
-
});
|
|
147
|
-
return '';
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
code = code.replace(/export\s*{\s*};?/g, '');
|
|
151
|
-
|
|
152
|
-
if (filename === entryPoint) {
|
|
153
|
-
const functionMatches = code.matchAll(/function\s+([A-Za-z_$][\w$]*)/g);
|
|
154
|
-
for (const match of functionMatches) {
|
|
155
|
-
const name = match[1];
|
|
156
|
-
addExport(name);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const exportEntries = Array.from(exportMappings.entries()).map(([exportName, localName]) => {
|
|
161
|
-
if (exportName === 'default') {
|
|
162
|
-
return `'default': ${localName}`;
|
|
163
|
-
}
|
|
164
|
-
if (exportName === localName) {
|
|
165
|
-
return exportName;
|
|
47
|
+
const { code } = await bundle({
|
|
48
|
+
entry: entryPoint,
|
|
49
|
+
resolve: (specifier, fromId) => {
|
|
50
|
+
const resolved = resolveImport(fromId, specifier);
|
|
51
|
+
if (!(resolved in files)) {
|
|
52
|
+
throw new Error(`Cannot find module '${specifier}' imported from '${fromId}'`);
|
|
166
53
|
}
|
|
167
|
-
return
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
})();
|
|
177
|
-
`;
|
|
178
|
-
}
|
|
54
|
+
return resolved;
|
|
55
|
+
},
|
|
56
|
+
load: (id) => transformJSX(files[id], id),
|
|
57
|
+
onExternal: 'error',
|
|
58
|
+
exposeEntryFunctions: true,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return code;
|
|
62
|
+
}
|
|
179
63
|
|
|
180
|
-
|
|
64
|
+
/** @param {string} bundledCode */
|
|
65
|
+
function evaluateEntryModule(bundledCode) {
|
|
66
|
+
const getEntryModule = new Function('h', 'Fragment', `
|
|
67
|
+
${bundledCode}
|
|
68
|
+
return __ono_entry;
|
|
69
|
+
`);
|
|
181
70
|
|
|
182
|
-
return
|
|
71
|
+
return getEntryModule(h, Fragment);
|
|
183
72
|
}
|
|
184
73
|
|
|
185
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Pick what to render from the entry module: the default export if there
|
|
76
|
+
* is one, otherwise the last exported function (REPL snippets usually
|
|
77
|
+
* define components and finish with an App function), otherwise any value.
|
|
78
|
+
* @param {any} entryModule
|
|
79
|
+
*/
|
|
80
|
+
function extractRenderCandidate(entryModule) {
|
|
186
81
|
if (!entryModule || typeof entryModule !== 'object') {
|
|
187
82
|
return null;
|
|
188
83
|
}
|
|
@@ -195,21 +90,9 @@ function extractRenderCandidate(entryModule, entryCode) {
|
|
|
195
90
|
return () => defaultExport;
|
|
196
91
|
}
|
|
197
92
|
|
|
198
|
-
const
|
|
199
|
-
if (
|
|
200
|
-
|
|
201
|
-
const candidate = entryModule[lastCallName];
|
|
202
|
-
if (typeof candidate === 'function') {
|
|
203
|
-
return candidate;
|
|
204
|
-
}
|
|
205
|
-
if (candidate !== undefined) {
|
|
206
|
-
return () => candidate;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const fallbackFunction = Object.values(entryModule).find(value => typeof value === 'function');
|
|
211
|
-
if (fallbackFunction) {
|
|
212
|
-
return fallbackFunction;
|
|
93
|
+
const functions = Object.values(entryModule).filter((value) => typeof value === 'function');
|
|
94
|
+
if (functions.length > 0) {
|
|
95
|
+
return functions[functions.length - 1];
|
|
213
96
|
}
|
|
214
97
|
|
|
215
98
|
const firstValue = Object.values(entryModule)[0];
|
|
@@ -220,20 +103,15 @@ function extractRenderCandidate(entryModule, entryCode) {
|
|
|
220
103
|
return null;
|
|
221
104
|
}
|
|
222
105
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
return getEntryModule(h);
|
|
230
|
-
}
|
|
231
|
-
|
|
106
|
+
/**
|
|
107
|
+
* @param {Record<string, string>} files
|
|
108
|
+
* @param {string} entryPoint
|
|
109
|
+
* @param {{ enableUno?: boolean, unoConfig?: any }} [options]
|
|
110
|
+
*/
|
|
232
111
|
export async function compileProject(files, entryPoint = 'index.jsx', options = {}) {
|
|
233
|
-
const bundled =
|
|
112
|
+
const bundled = await bundleProject(files, entryPoint);
|
|
234
113
|
const entryModule = evaluateEntryModule(bundled);
|
|
235
|
-
const
|
|
236
|
-
const renderCandidate = extractRenderCandidate(entryModule, entryCode);
|
|
114
|
+
const renderCandidate = extractRenderCandidate(entryModule);
|
|
237
115
|
|
|
238
116
|
if (!renderCandidate) {
|
|
239
117
|
throw new Error('Unable to determine a render function in the entry module.');
|
package/src/browser/unocss.js
CHANGED
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
import { createGenerator } from '@unocss/core';
|
|
6
6
|
import { presetUno } from '@unocss/preset-uno';
|
|
7
7
|
|
|
8
|
+
/** @type {string | null} */
|
|
8
9
|
let cachedGeneratorKey = null;
|
|
10
|
+
/** @type {Promise<any> | null} */
|
|
9
11
|
let cachedGeneratorPromise = null;
|
|
10
12
|
|
|
13
|
+
/** @param {object} [config] */
|
|
11
14
|
function serializeConfig(config = {}) {
|
|
12
15
|
try {
|
|
13
16
|
return JSON.stringify(config);
|
|
@@ -16,12 +19,13 @@ function serializeConfig(config = {}) {
|
|
|
16
19
|
}
|
|
17
20
|
}
|
|
18
21
|
|
|
22
|
+
/** @param {object} [config] */
|
|
19
23
|
export async function getUnoGenerator(config = {}) {
|
|
20
24
|
const key = serializeConfig(config);
|
|
21
25
|
if (!cachedGeneratorPromise || cachedGeneratorKey !== key) {
|
|
22
26
|
cachedGeneratorKey = key;
|
|
23
27
|
cachedGeneratorPromise = createGenerator({
|
|
24
|
-
presets: [presetUno()],
|
|
28
|
+
presets: [/** @type {any} */ (presetUno())],
|
|
25
29
|
...config,
|
|
26
30
|
});
|
|
27
31
|
}
|