xanix 1.0.10 → 1.0.12

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.
@@ -1,14 +1,12 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { createRequire } from "node:module";
4
- import { fileURLToPath } from "node:url";
5
- const __dirname = fileURLToPath(import.meta.url);
6
4
  const require = createRequire(import.meta.url);
7
5
  const VIRTUAL_ID = "virtual:xanix-document";
8
6
  const RESOLVED_ID = "\0virtual:xanix-document";
9
7
  export default function xanixDocument() {
10
8
  const root = process.cwd();
11
- const userDocument = path.resolve(root, "Document.tsx");
9
+ const userDocument = path.resolve(root, "document.tsx");
12
10
  const baseDocument = require.resolve("../../../../../dist/components/BaseDocument.js");
13
11
  return {
14
12
  name: "xanix-document",
@@ -0,0 +1,2 @@
1
+ import type { Plugin } from "rollup";
2
+ export default function XanixPageTransform(): Plugin;
@@ -0,0 +1,19 @@
1
+ import { transformer } from "./transformer.js";
2
+ export default function XanixPageTransform() {
3
+ return {
4
+ name: "xanix-page-transform",
5
+ async transform(code, id) {
6
+ if (id.includes("node_modules") || !/\.(tsx?|jsx?)$/.test(id)) {
7
+ return null;
8
+ }
9
+ const result = transformer(code, id);
10
+ if (!result) {
11
+ return null;
12
+ }
13
+ return {
14
+ code: result.code,
15
+ map: result.map,
16
+ };
17
+ },
18
+ };
19
+ }
@@ -0,0 +1,16 @@
1
+ import * as t from "@babel/types";
2
+ export interface XanixTransformResult {
3
+ code: string;
4
+ map: any;
5
+ }
6
+ export declare function resolveFile(file: string): string;
7
+ export declare function resolveComponentFile(importer: string, importPath: string): string;
8
+ export declare function createPageId(file: string): string;
9
+ export declare function getJSXComponentName(node: t.JSXElement): string | null;
10
+ export declare function findComponentImport(ast: t.File, componentName: string): {
11
+ file: string;
12
+ export: string;
13
+ declaration: t.ImportDeclaration;
14
+ specifier: t.ImportSpecifier | t.ImportDefaultSpecifier;
15
+ } | null;
16
+ export declare function transformer(code: string, id: string): XanixTransformResult | null;
@@ -0,0 +1,501 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs";
3
+ import crypto from "node:crypto";
4
+ import { parse } from "@babel/parser";
5
+ import traverse from "@babel/traverse";
6
+ import generate from "@babel/generator";
7
+ import * as t from "@babel/types";
8
+ const RUNTIME_IMPORT = "xanix/runtime";
9
+ const SOURCE_EXTENSIONS = [".tsx", ".ts", ".jsx", ".js", ".mjs", ".cjs"];
10
+ function normalizeFilePath(file) {
11
+ return path.resolve(file).split(path.sep).join("/");
12
+ }
13
+ export function resolveFile(file) {
14
+ const absolute = path.resolve(file);
15
+ if (fs.existsSync(absolute)) {
16
+ const stat = fs.statSync(absolute);
17
+ if (stat.isFile()) {
18
+ return absolute;
19
+ }
20
+ }
21
+ for (const ext of SOURCE_EXTENSIONS) {
22
+ const candidate = `${absolute}${ext}`;
23
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
24
+ return path.resolve(candidate);
25
+ }
26
+ }
27
+ if (fs.existsSync(absolute) && fs.statSync(absolute).isDirectory()) {
28
+ for (const ext of SOURCE_EXTENSIONS) {
29
+ const candidate = path.join(absolute, `index${ext}`);
30
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
31
+ return path.resolve(candidate);
32
+ }
33
+ }
34
+ }
35
+ return absolute;
36
+ }
37
+ export function resolveComponentFile(importer, importPath) {
38
+ if (!importPath.startsWith(".")) {
39
+ return importPath;
40
+ }
41
+ const base = path.resolve(path.dirname(importer), importPath);
42
+ return resolveFile(base);
43
+ }
44
+ export function createPageId(file) {
45
+ return `c_${crypto
46
+ .createHash("sha256")
47
+ .update(path.normalize(file))
48
+ .digest("hex")
49
+ .slice(0, 12)}`;
50
+ }
51
+ export function getJSXComponentName(node) {
52
+ const name = node.openingElement.name;
53
+ if (t.isJSXIdentifier(name)) {
54
+ return name.name;
55
+ }
56
+ if (t.isJSXMemberExpression(name)) {
57
+ if (t.isJSXIdentifier(name.object) && t.isJSXIdentifier(name.property)) {
58
+ return `${name.object.name}.${name.property.name}`;
59
+ }
60
+ }
61
+ return null;
62
+ }
63
+ export function findComponentImport(ast, componentName) {
64
+ for (const node of ast.program.body) {
65
+ if (!t.isImportDeclaration(node)) {
66
+ continue;
67
+ }
68
+ for (const specifier of node.specifiers) {
69
+ if (t.isImportDefaultSpecifier(specifier) &&
70
+ specifier.local.name === componentName) {
71
+ return {
72
+ file: node.source.value,
73
+ export: "default",
74
+ declaration: node,
75
+ specifier,
76
+ };
77
+ }
78
+ if (t.isImportSpecifier(specifier) &&
79
+ specifier.local.name === componentName) {
80
+ const imported = specifier.imported;
81
+ return {
82
+ file: node.source.value,
83
+ export: t.isIdentifier(imported) ? imported.name : imported.value,
84
+ declaration: node,
85
+ specifier,
86
+ };
87
+ }
88
+ }
89
+ }
90
+ return null;
91
+ }
92
+ /**
93
+ * Remove a specific component from:
94
+ *
95
+ * import Home from "./Home";
96
+ *
97
+ * or:
98
+ *
99
+ * import Home, { foo } from "./Home";
100
+ *
101
+ * or:
102
+ *
103
+ * import { Home, foo } from "./Home";
104
+ */
105
+ function removeComponentImport(componentImport) {
106
+ const { declaration, specifier } = componentImport;
107
+ const index = declaration.specifiers.indexOf(specifier);
108
+ if (index !== -1) {
109
+ declaration.specifiers.splice(index, 1);
110
+ }
111
+ }
112
+ function removeEmptyImportDeclarations(ast) {
113
+ ast.program.body = ast.program.body.filter((statement) => {
114
+ if (!t.isImportDeclaration(statement)) {
115
+ return true;
116
+ }
117
+ return statement.specifiers.length > 0;
118
+ });
119
+ }
120
+ /**
121
+ * Create:
122
+ *
123
+ * const Home =
124
+ * (await import("./Home")).default;
125
+ *
126
+ * or:
127
+ *
128
+ * const Home =
129
+ * (await import("./Home")).Home;
130
+ */
131
+ function createDynamicComponentImport(componentName, importPath, exportName) {
132
+ const importCall = t.callExpression(t.import(), [
133
+ t.stringLiteral(importPath),
134
+ ]);
135
+ const awaitedImport = t.awaitExpression(importCall);
136
+ const property = exportName === "default"
137
+ ? t.identifier("default")
138
+ : t.identifier(exportName);
139
+ const value = t.memberExpression(awaitedImport, property);
140
+ return t.variableDeclaration("const", [
141
+ t.variableDeclarator(t.identifier(componentName), value),
142
+ ]);
143
+ }
144
+ /**
145
+ * Convert a JSX name into an expression.
146
+ *
147
+ * Example:
148
+ *
149
+ * <Home />
150
+ *
151
+ * becomes:
152
+ *
153
+ * Home
154
+ */
155
+ function jsxNameToExpression(name) {
156
+ if (t.isJSXIdentifier(name)) {
157
+ return t.identifier(name.name);
158
+ }
159
+ if (t.isJSXMemberExpression(name)) {
160
+ const object = jsxNameToExpressionFromJSXName(name.object);
161
+ const property = jsxNameToExpressionFromJSXName(name.property);
162
+ if (!object || !property) {
163
+ return null;
164
+ }
165
+ return t.memberExpression(object, property);
166
+ }
167
+ return null;
168
+ }
169
+ function jsxNameToExpressionFromJSXName(name) {
170
+ if (t.isJSXIdentifier(name)) {
171
+ return t.identifier(name.name);
172
+ }
173
+ if (t.isJSXMemberExpression(name)) {
174
+ const object = jsxNameToExpressionFromJSXName(name.object);
175
+ const property = jsxNameToExpressionFromJSXName(name.property);
176
+ if (!object || !property) {
177
+ return null;
178
+ }
179
+ return t.memberExpression(object, property);
180
+ }
181
+ return null;
182
+ }
183
+ /**
184
+ * Convert JSX attributes into an object expression.
185
+ *
186
+ * Example:
187
+ *
188
+ * <Home
189
+ * name="Naxrul"
190
+ * age={30}
191
+ * active
192
+ * {...user}
193
+ * />
194
+ *
195
+ * becomes:
196
+ *
197
+ * {
198
+ * name: "Naxrul",
199
+ * age: 30,
200
+ * active: true,
201
+ * ...user
202
+ * }
203
+ */
204
+ function jsxAttributesToProps(jsx) {
205
+ const properties = [];
206
+ for (const attribute of jsx.openingElement.attributes) {
207
+ /**
208
+ * Handle:
209
+ *
210
+ * {...props}
211
+ */
212
+ if (t.isJSXSpreadAttribute(attribute)) {
213
+ properties.push(t.spreadElement(attribute.argument));
214
+ continue;
215
+ }
216
+ /**
217
+ * Normal JSX attribute:
218
+ *
219
+ * name="value"
220
+ * name={value}
221
+ * active
222
+ */
223
+ if (!t.isJSXAttribute(attribute)) {
224
+ continue;
225
+ }
226
+ function jsxAttributeNameToExpression(name) {
227
+ if (t.isJSXIdentifier(name)) {
228
+ return t.identifier(name.name);
229
+ }
230
+ return t.stringLiteral(`${name.namespace.name}:${name.name.name}`);
231
+ }
232
+ const key = jsxAttributeNameToExpression(attribute.name);
233
+ /**
234
+ * Boolean JSX prop:
235
+ *
236
+ * <Home active />
237
+ *
238
+ * becomes:
239
+ *
240
+ * active: true
241
+ */
242
+ if (!attribute.value) {
243
+ properties.push(t.objectProperty(key, t.booleanLiteral(true)));
244
+ continue;
245
+ }
246
+ /**
247
+ * String prop:
248
+ *
249
+ * name="Naxrul"
250
+ *
251
+ * becomes:
252
+ *
253
+ * name: "Naxrul"
254
+ */
255
+ if (t.isStringLiteral(attribute.value)) {
256
+ properties.push(t.objectProperty(key, t.stringLiteral(attribute.value.value)));
257
+ continue;
258
+ }
259
+ /**
260
+ * Expression prop:
261
+ *
262
+ * age={30}
263
+ * user={user}
264
+ * items={items.map(...)}
265
+ */
266
+ if (t.isJSXExpressionContainer(attribute.value)) {
267
+ const expression = attribute.value.expression;
268
+ if (t.isJSXEmptyExpression(expression)) {
269
+ continue;
270
+ }
271
+ properties.push(t.objectProperty(key, expression));
272
+ continue;
273
+ }
274
+ /**
275
+ * JSX element as a prop:
276
+ *
277
+ * content={<div>Hello</div>}
278
+ */
279
+ if (t.isJSXElement(attribute.value)) {
280
+ properties.push(t.objectProperty(key, attribute.value));
281
+ continue;
282
+ }
283
+ }
284
+ return t.objectExpression(properties);
285
+ }
286
+ /**
287
+ * Convert:
288
+ *
289
+ * <Home name="Naxrul" age={30} />
290
+ *
291
+ * into:
292
+ *
293
+ * {
294
+ * component: Home,
295
+ * props: {
296
+ * name: "Naxrul",
297
+ * age: 30
298
+ * }
299
+ * }
300
+ */
301
+ function jsxToComponentAndProps(jsx) {
302
+ const component = jsxNameToExpression(jsx.openingElement.name);
303
+ if (!component) {
304
+ return null;
305
+ }
306
+ const props = jsxAttributesToProps(jsx);
307
+ return {
308
+ component,
309
+ props,
310
+ };
311
+ }
312
+ function hasRuntimeImport(ast, name) {
313
+ for (const statement of ast.program.body) {
314
+ if (!t.isImportDeclaration(statement)) {
315
+ continue;
316
+ }
317
+ if (statement.source.value !== RUNTIME_IMPORT) {
318
+ continue;
319
+ }
320
+ for (const specifier of statement.specifiers) {
321
+ if (t.isImportSpecifier(specifier) &&
322
+ t.isIdentifier(specifier.imported) &&
323
+ specifier.imported.name === name) {
324
+ return true;
325
+ }
326
+ }
327
+ }
328
+ return false;
329
+ }
330
+ function injectRuntimeImport(ast, name) {
331
+ if (hasRuntimeImport(ast, name)) {
332
+ return;
333
+ }
334
+ // Find existing:
335
+ // import { ... } from "xanix/runtime";
336
+ for (const statement of ast.program.body) {
337
+ if (!t.isImportDeclaration(statement)) {
338
+ continue;
339
+ }
340
+ if (statement.source.value !== RUNTIME_IMPORT) {
341
+ continue;
342
+ }
343
+ statement.specifiers.push(t.importSpecifier(t.identifier(name), t.identifier(name)));
344
+ return;
345
+ }
346
+ // No runtime import exists yet
347
+ ast.program.body.unshift(t.importDeclaration([t.importSpecifier(t.identifier(name), t.identifier(name))], t.stringLiteral(RUNTIME_IMPORT)));
348
+ }
349
+ export function transformer(code, id) {
350
+ const ast = parse(code, {
351
+ sourceType: "module",
352
+ plugins: ["typescript", "jsx"],
353
+ });
354
+ let changed = false;
355
+ traverse(ast, {
356
+ CallExpression(callPath) {
357
+ const call = callPath.node;
358
+ /**
359
+ * Find:
360
+ *
361
+ * res.send(<Home />);
362
+ */
363
+ if (!t.isMemberExpression(call.callee)) {
364
+ return;
365
+ }
366
+ if (!t.isIdentifier(call.callee.property, {
367
+ name: "send",
368
+ })) {
369
+ return;
370
+ }
371
+ const argument = call.arguments[0];
372
+ if (!argument || !t.isJSXElement(argument)) {
373
+ return;
374
+ }
375
+ const functionPath = callPath.getFunctionParent();
376
+ if (!functionPath) {
377
+ return;
378
+ }
379
+ const params = functionPath.node.params;
380
+ if (params.length < 2) {
381
+ return;
382
+ }
383
+ const requestParam = params[0];
384
+ const responseParam = params[1];
385
+ if (!t.isIdentifier(requestParam) || !t.isIdentifier(responseParam)) {
386
+ return;
387
+ }
388
+ const componentName = getJSXComponentName(argument);
389
+ if (!componentName) {
390
+ return;
391
+ }
392
+ const componentImport = findComponentImport(ast, componentName);
393
+ if (!componentImport) {
394
+ return;
395
+ }
396
+ const componentImportPath = componentImport.file;
397
+ const componentFile = resolveComponentFile(id, componentImportPath);
398
+ if (!path.isAbsolute(componentFile)) {
399
+ return;
400
+ }
401
+ const normalizedFile = normalizeFilePath(componentFile);
402
+ const pageId = createPageId(normalizedFile);
403
+ /**
404
+ * Convert:
405
+ *
406
+ * <Home name="Naxrul" age={30} />
407
+ *
408
+ * into:
409
+ *
410
+ * component: Home
411
+ *
412
+ * props: {
413
+ * name: "Naxrul",
414
+ * age: 30
415
+ * }
416
+ */
417
+ const componentAndProps = jsxToComponentAndProps(argument);
418
+ if (!componentAndProps) {
419
+ return;
420
+ }
421
+ /**
422
+ * Remove:
423
+ *
424
+ * import Home from "./Home";
425
+ */
426
+ removeComponentImport(componentImport);
427
+ /**
428
+ * Add:
429
+ *
430
+ * const Home =
431
+ * (await import("./Home")).default;
432
+ */
433
+ const dynamicImport = createDynamicComponentImport(componentName, componentImportPath, componentImport.export);
434
+ /**
435
+ * Insert immediately before:
436
+ *
437
+ * res.send(<Home />);
438
+ */
439
+ const statementPath = callPath.getStatementParent();
440
+ if (statementPath) {
441
+ statementPath.insertBefore(dynamicImport);
442
+ }
443
+ /**
444
+ * Route becomes:
445
+ *
446
+ * async (req, res) => {}
447
+ */
448
+ if (t.isFunction(functionPath.node)) {
449
+ functionPath.node.async = true;
450
+ }
451
+ /**
452
+ * Replace:
453
+ *
454
+ * res.send(<Home />);
455
+ *
456
+ * with:
457
+ *
458
+ * res.send(
459
+ * await xanixPage({
460
+ * clientId,
461
+ * req,
462
+ * res,
463
+ * component: Home,
464
+ * props: {
465
+ * ...
466
+ * }
467
+ * })
468
+ * );
469
+ */
470
+ const pageCall = t.awaitExpression(t.callExpression(t.identifier("xanixPage"), [
471
+ t.objectExpression([
472
+ t.objectProperty(t.identifier("pageId"), t.stringLiteral(pageId)),
473
+ t.objectProperty(t.identifier("req"), t.identifier(requestParam.name)),
474
+ t.objectProperty(t.identifier("res"), t.identifier(responseParam.name)),
475
+ t.objectProperty(t.identifier("Component"), componentAndProps.component),
476
+ t.objectProperty(t.identifier("props"), componentAndProps.props),
477
+ ]),
478
+ ]));
479
+ call.arguments[0] = pageCall;
480
+ changed = true;
481
+ },
482
+ });
483
+ if (!changed) {
484
+ return null;
485
+ }
486
+ /**
487
+ * Add:
488
+ *
489
+ * import xanixPage from "xanix/page";
490
+ */
491
+ injectRuntimeImport(ast, "xanixPage");
492
+ /**
493
+ * Remove empty imports.
494
+ */
495
+ removeEmptyImportDeclarations(ast);
496
+ const output = generate(ast, {}, code);
497
+ return {
498
+ code: output.code,
499
+ map: output.map,
500
+ };
501
+ }
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1,5 @@
1
+ import type { Plugin } from "rollup";
2
+ export interface XanixServerTransformOptions {
3
+ mode: "watch" | "start";
4
+ }
5
+ export default function XanixServerTransform(options: XanixServerTransformOptions): Plugin;
@@ -0,0 +1,130 @@
1
+ import { parse } from "@babel/parser";
2
+ import traverse from "@babel/traverse";
3
+ import generate from "@babel/generator";
4
+ import * as t from "@babel/types";
5
+ const RUNTIME_IMPORT = "xanix/runtime";
6
+ function findExpressServer(ast) {
7
+ let appName = null;
8
+ traverse(ast, {
9
+ CallExpression(path) {
10
+ const node = path.node;
11
+ // Find:
12
+ // app.listen(...)
13
+ if (!t.isMemberExpression(node.callee) ||
14
+ !t.isIdentifier(node.callee.property, {
15
+ name: "listen",
16
+ }) ||
17
+ !t.isIdentifier(node.callee.object)) {
18
+ return;
19
+ }
20
+ const variableName = node.callee.object.name;
21
+ // Find the binding of `app`
22
+ const binding = path.scope.getBinding(variableName);
23
+ if (!binding || !binding.path.isVariableDeclarator()) {
24
+ return;
25
+ }
26
+ const init = binding.path.node.init;
27
+ // Confirm:
28
+ // const app = express();
29
+ if (t.isCallExpression(init) &&
30
+ t.isIdentifier(init.callee, {
31
+ name: "express",
32
+ })) {
33
+ appName = variableName;
34
+ path.stop();
35
+ }
36
+ },
37
+ });
38
+ return appName;
39
+ }
40
+ function injectRuntimeImport(ast) {
41
+ let runtimeImport = null;
42
+ for (const statement of ast.program.body) {
43
+ if (t.isImportDeclaration(statement) &&
44
+ statement.source.value === RUNTIME_IMPORT) {
45
+ runtimeImport = statement;
46
+ break;
47
+ }
48
+ }
49
+ if (runtimeImport) {
50
+ const exists = runtimeImport.specifiers.some((specifier) => t.isImportSpecifier(specifier) &&
51
+ t.isIdentifier(specifier.imported, {
52
+ name: "createXanixServer",
53
+ }));
54
+ if (!exists) {
55
+ runtimeImport.specifiers.push(t.importSpecifier(t.identifier("createXanixServer"), t.identifier("createXanixServer")));
56
+ }
57
+ return;
58
+ }
59
+ ast.program.body.unshift(t.importDeclaration([
60
+ t.importSpecifier(t.identifier("createXanixServer"), t.identifier("createXanixServer")),
61
+ ], t.stringLiteral(RUNTIME_IMPORT)));
62
+ }
63
+ export default function XanixServerTransform(options) {
64
+ return {
65
+ name: "xanix-server-transform",
66
+ transform(code, id) {
67
+ // Ignore node_modules
68
+ if (id.includes("node_modules")) {
69
+ return null;
70
+ }
71
+ // Only process JS/TS files
72
+ if (!/\.[cm]?[jt]sx?$/.test(id)) {
73
+ return null;
74
+ }
75
+ let ast;
76
+ try {
77
+ ast = parse(code, {
78
+ sourceType: "module",
79
+ plugins: ["typescript", "jsx"],
80
+ });
81
+ }
82
+ catch {
83
+ return null;
84
+ }
85
+ // Find the REAL server app
86
+ const appName = findExpressServer(ast);
87
+ if (!appName) {
88
+ return null;
89
+ }
90
+ let changed = false;
91
+ traverse(ast, {
92
+ VariableDeclarator(path) {
93
+ const node = path.node;
94
+ // Only transform:
95
+ // const app = express()
96
+ if (!t.isIdentifier(node.id, {
97
+ name: appName,
98
+ })) {
99
+ return;
100
+ }
101
+ if (!t.isCallExpression(node.init) ||
102
+ !t.isIdentifier(node.init.callee, {
103
+ name: "express",
104
+ })) {
105
+ return;
106
+ }
107
+ node.init = t.callExpression(t.identifier("createXanixServer"), [
108
+ t.objectExpression([
109
+ t.objectProperty(t.identifier("mode"), t.stringLiteral(options.mode)),
110
+ ]),
111
+ ]);
112
+ changed = true;
113
+ path.stop();
114
+ },
115
+ });
116
+ if (!changed) {
117
+ return null;
118
+ }
119
+ injectRuntimeImport(ast);
120
+ const output = generate(ast, {
121
+ sourceMaps: true,
122
+ sourceFileName: id,
123
+ }, code);
124
+ return {
125
+ code: output.code,
126
+ map: output.map,
127
+ };
128
+ },
129
+ };
130
+ }
@@ -1,5 +1,2 @@
1
1
  import type { Plugin } from "rollup";
2
- export type XanixPluginOptions = {
3
- mode: "watch" | "start";
4
- };
5
- export declare function XanixTransform(option: XanixPluginOptions): Plugin;
2
+ export declare function XanixTransform(): Plugin;
@@ -1,12 +1,12 @@
1
1
  import { transformer } from "./transformer.js";
2
- export function XanixTransform(option) {
2
+ export function XanixTransform() {
3
3
  return {
4
4
  name: "xanix-transform",
5
5
  async transform(code, id) {
6
6
  if (id.includes("node_modules") || !/\.(tsx?|jsx?)$/.test(id)) {
7
7
  return null;
8
8
  }
9
- const result = transformer(code, id, option.mode);
9
+ const result = transformer(code, id);
10
10
  if (!result) {
11
11
  return null;
12
12
  }
@@ -13,4 +13,4 @@ export declare function findComponentImport(ast: t.File, componentName: string):
13
13
  declaration: t.ImportDeclaration;
14
14
  specifier: t.ImportSpecifier | t.ImportDefaultSpecifier;
15
15
  } | null;
16
- export declare function transformer(code: string, id: string, mode: "watch" | "start"): XanixTransformResult | null;
16
+ export declare function transformer(code: string, id: string): XanixTransformResult | null;
@@ -309,109 +309,6 @@ function jsxToComponentAndProps(jsx) {
309
309
  props,
310
310
  };
311
311
  }
312
- /**
313
- * Find:
314
- *
315
- * const app = express();
316
- * const exp = express();
317
- * const server = express();
318
- *
319
- * Returns the variable name.
320
- */
321
- function findExpressAppVariable(ast) {
322
- for (const statement of ast.program.body) {
323
- if (!t.isVariableDeclaration(statement)) {
324
- continue;
325
- }
326
- for (const declaration of statement.declarations) {
327
- if (!t.isIdentifier(declaration.id)) {
328
- continue;
329
- }
330
- if (!t.isCallExpression(declaration.init)) {
331
- continue;
332
- }
333
- if (!t.isIdentifier(declaration.init.callee, {
334
- name: "express",
335
- })) {
336
- continue;
337
- }
338
- return declaration.id.name;
339
- }
340
- }
341
- return null;
342
- }
343
- /**
344
- * Replace:
345
- *
346
- * const app = express();
347
- *
348
- * with:
349
- *
350
- * const app = createXanixServer({
351
- * mode: "watch"
352
- * });
353
- */
354
- function transformExpressAppTocreateXanixServer(ast, args) {
355
- const expressVariable = findExpressAppVariable(ast);
356
- if (!expressVariable) {
357
- return false;
358
- }
359
- for (const statement of ast.program.body) {
360
- if (!t.isVariableDeclaration(statement)) {
361
- continue;
362
- }
363
- for (const declaration of statement.declarations) {
364
- if (!t.isIdentifier(declaration.id)) {
365
- continue;
366
- }
367
- if (declaration.id.name !== expressVariable) {
368
- continue;
369
- }
370
- if (!t.isCallExpression(declaration.init)) {
371
- continue;
372
- }
373
- if (!t.isIdentifier(declaration.init.callee, {
374
- name: "express",
375
- })) {
376
- continue;
377
- }
378
- declaration.init = t.callExpression(t.identifier("createXanixServer"), [
379
- t.objectExpression([
380
- t.objectProperty(t.identifier("mode"), t.stringLiteral(args.mode)),
381
- ]),
382
- ]);
383
- return true;
384
- }
385
- }
386
- return false;
387
- }
388
- /**
389
- * Remove:
390
- *
391
- * import express from "express";
392
- *
393
- * once express() has been replaced by
394
- * createXanixServer().
395
- *
396
- * Also handles:
397
- *
398
- * import express, { Router } from "express";
399
- */
400
- function removeExpressImport(ast) {
401
- for (const statement of ast.program.body) {
402
- if (!t.isImportDeclaration(statement)) {
403
- continue;
404
- }
405
- if (statement.source.value !== "express") {
406
- continue;
407
- }
408
- statement.specifiers = statement.specifiers.filter((specifier) => {
409
- return !(t.isImportDefaultSpecifier(specifier) &&
410
- specifier.local.name === "express");
411
- });
412
- }
413
- removeEmptyImportDeclarations(ast);
414
- }
415
312
  function hasRuntimeImport(ast, name) {
416
313
  for (const statement of ast.program.body) {
417
314
  if (!t.isImportDeclaration(statement)) {
@@ -449,7 +346,7 @@ function injectRuntimeImport(ast, name) {
449
346
  // No runtime import exists yet
450
347
  ast.program.body.unshift(t.importDeclaration([t.importSpecifier(t.identifier(name), t.identifier(name))], t.stringLiteral(RUNTIME_IMPORT)));
451
348
  }
452
- export function transformer(code, id, mode) {
349
+ export function transformer(code, id) {
453
350
  const ast = parse(code, {
454
351
  sourceType: "module",
455
352
  plugins: ["typescript", "jsx"],
@@ -586,33 +483,6 @@ export function transformer(code, id, mode) {
586
483
  if (!changed) {
587
484
  return null;
588
485
  }
589
- /**
590
- * Change:
591
- *
592
- * const app = express();
593
- *
594
- * into:
595
- *
596
- * const app = createXanixServer();
597
- */
598
- const serverTransformed = transformExpressAppTocreateXanixServer(ast, {
599
- mode,
600
- });
601
- if (serverTransformed) {
602
- /**
603
- * Remove:
604
- *
605
- * import express from "express";
606
- */
607
- removeExpressImport(ast);
608
- /**
609
- * Add:
610
- *
611
- * import { createXanixServer }
612
- * from "xanix/runtime";
613
- */
614
- injectRuntimeImport(ast, "createXanixServer");
615
- }
616
486
  /**
617
487
  * Add:
618
488
  *
@@ -6,10 +6,11 @@ import json from "@rollup/plugin-json";
6
6
  import esbuild from "rollup-plugin-esbuild";
7
7
  import url from "@rollup/plugin-url";
8
8
  import path from "path";
9
- import { XanixTransform } from "./XanixTransform/index.js";
9
+ import XanixPageTransform from "./XanixPageTransform/index.js";
10
10
  import xanixReactRefresh from "./XanixReactRefresh.js";
11
11
  import XanixEnvPlugin from "./XanixEnv.js";
12
12
  import xanixUseDataId from "./xanixUseDataId.js";
13
+ import XanixServerTransform from "./XanixServerTransform.js";
13
14
  export function xanixDefaultPlugins(options = {}) {
14
15
  const target = options.target ?? "client";
15
16
  const development = options.development ?? true;
@@ -17,8 +18,8 @@ export function xanixDefaultPlugins(options = {}) {
17
18
  const isServer = target === "server";
18
19
  let _plugins = [];
19
20
  if (isServer) {
20
- _plugins.push(XanixTransform({ mode: development ? "watch" : "start" }));
21
- // _plugins.push(XanixDocument());
21
+ _plugins.push(XanixPageTransform());
22
+ _plugins.push(XanixServerTransform({ mode: development ? "watch" : "start" }));
22
23
  }
23
24
  else {
24
25
  if (development) {
@@ -32,7 +32,6 @@ const watchServer = async ({ rootEntry, onChange, onBuildEnd, onServerChange, on
32
32
  assetExternal: false,
33
33
  onChange: (entry) => {
34
34
  changedFiles.add(entry);
35
- return onChange?.(entry);
36
35
  },
37
36
  }),
38
37
  ],
@@ -6,7 +6,8 @@ const Head = ({ children }) => {
6
6
  const { title, meta, pageId, props, params, pageData } = useDocument();
7
7
  if (__XANIX_CLIENT__) {
8
8
  useEffect(() => {
9
- document.title = title || "";
9
+ if (title)
10
+ document.title = title;
10
11
  meta?.forEach(({ name, content }) => {
11
12
  let element = document.querySelector(`meta[name="${name}"]`);
12
13
  if (!element) {
@@ -29,7 +30,7 @@ const Head = ({ children }) => {
29
30
  return null;
30
31
  }
31
32
  if (__XANIX_SERVER__) {
32
- return (_jsxs("head", { children: [_jsx("title", { children: title }), meta?.map(({ name, content }) => (_jsx("meta", { name: name, content: content }, name))), children, _jsx("script", { id: pageId, dangerouslySetInnerHTML: {
33
+ return (_jsxs("head", { children: [title && _jsx("title", { children: title }), meta?.map(({ name, content }) => (_jsx("meta", { name: name, content: content }, name))), children, _jsx("script", { id: pageId, dangerouslySetInnerHTML: {
33
34
  __html: `window.XANIX_DOCUMENT = ${JSON.stringify({
34
35
  pageId,
35
36
  props,
@@ -28,7 +28,7 @@ function createXanixServer({ mode }) {
28
28
  });
29
29
  app.use((req, res, next) => {
30
30
  let data = {
31
- title: null,
31
+ title: "",
32
32
  meta: [],
33
33
  };
34
34
  if (!req.page) {
@@ -51,7 +51,7 @@ export default async function xanixPage({ pageId, req, res, Component, props, })
51
51
  renderToString(_jsx(Document, { document: {
52
52
  pageId,
53
53
  props,
54
- title: title ?? entry.name.replaceAll("-", " "),
54
+ title,
55
55
  meta,
56
56
  params: req.params,
57
57
  request: req,
@@ -64,7 +64,7 @@ export default async function xanixPage({ pageId, req, res, Component, props, })
64
64
  return JSON.stringify({
65
65
  pageId,
66
66
  props,
67
- title: title ?? entry.name.replaceAll("-", " "),
67
+ title,
68
68
  meta,
69
69
  params: req.params,
70
70
  request: null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xanix",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",