noxt-server 0.1.14 → 0.1.16

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 CHANGED
@@ -1,118 +1,5 @@
1
- # noxt-js
1
+ # noxt-server
2
2
 
3
- A zero-config JSX web server powered by [Express](https://expressjs.com/) and [noxt-js-middleware](https://npmjs.com/package/noxt-js-middleware).
4
- Run it with `npx noxt-js` and drop `.jsx` files into your `views/` folder — routes are created automatically.
3
+ Server stack for [noxt-js-middleware](https://npmjs.com/package/noxt-js-middleware)
5
4
 
6
- No React required. No heavy framework. Just JSX + Express.
7
-
8
- ---
9
-
10
- ## Installation
11
-
12
- ```sh
13
- npm install noxt-js
14
- ```
15
-
16
- or run it directly without installing:
17
-
18
- ```sh
19
- npx noxt-js
20
- ```
21
-
22
- ---
23
-
24
- ## Quick Start
25
-
26
- ```sh
27
- npx noxt-js
28
- ```
29
-
30
- By default, this will:
31
-
32
- - Serve pages from `./views/`
33
- - Start an HTTP server on port `3000`
34
- - Look for configuration in `noxt.config.yaml` (optional)
35
-
36
- ---
37
-
38
- ## Configuration
39
-
40
- `noxt-js` reads options from `noxt.config.yaml` in your project root, or from CLI flags.
41
- CLI flags override config file values.
42
-
43
- Example `noxt.config.yaml`:
44
-
45
- ```yaml
46
- port: 4000
47
- host: localhost
48
- views: views
49
- logLevel: info
50
- ssl: false
51
- ```
52
-
53
- Run with CLI overrides:
54
-
55
- ```sh
56
- npx noxt-js --port 8080 --views src/pages
57
- ```
58
-
59
- ### Available options
60
-
61
- - **`port`**: HTTP port number (default: `3000`)
62
- - **`host`**: Hostname or IP (default: `0.0.0.0`)
63
- - **`views`**: Directory containing `.jsx` files (default: `views`)
64
- - **`logLevel`**: One of `error`, `warn`, `info`, `debug`
65
- - **`ssl`**:
66
- - `false` (disable SSL)
67
- - or object with `cert` and `key` paths for HTTPS
68
-
69
- ---
70
-
71
- ## Context
72
-
73
- You can provide shared helpers/utilities to all components via a `context.js` file (or any path you specify in config). For example:
74
-
75
- ```js
76
- // context.js
77
- export async function fetchUser(id) {
78
- return db.users.findById(id);
79
- }
80
- ```
81
-
82
- Then in a page:
83
-
84
- ```jsx
85
- export const route = '/user/:id';
86
-
87
- export default async function UserPage({ id }, { fetchUser }) {
88
- const user = await fetchUser(id);
89
- return <h1>{user.name}</h1>;
90
- }
91
- ```
92
-
93
- ---
94
-
95
- ## Pages & Routing
96
-
97
- - Any `.jsx` file in `views/` is loaded as a component.
98
- - If it exports `route`, it becomes a page at that route.
99
- - Props come from route params, query string, and optional `params` export.
100
-
101
- Example:
102
-
103
- ```jsx
104
- export const route = '/hello/:name';
105
-
106
- export default function HelloPage({ name }) {
107
- return <h1>Hello, {name}!</h1>;
108
- }
109
- ```
110
-
111
- ---
112
-
113
-
114
- ---
115
-
116
- ## License
117
-
118
- LGPL-3.0-or-later
5
+ Run with [noxt-cli](https://npmjs.com/package/noxt-cli)
package/noxt-server.js CHANGED
@@ -5,32 +5,56 @@ import MLM from 'mlm-core';
5
5
  const __filename = fileURLToPath(import.meta.url);
6
6
  const __dirname = dirname(__filename);
7
7
 
8
- export async function startServer({ config, recipe }) {
8
+ function resolveModule(name) {
9
+ if (!name.includes('/')) {
10
+ return `${__dirname}/units/${name}.js`;
11
+ }
12
+ if (name.startsWith('app/')) {
13
+ return `${process.cwd()}/units/${name.slice(4)}.js`;
14
+ }
15
+ if (name.startsWith('contrib/')) {
16
+ const m = name.match(/^contrib\/([^/]+)(?:\/(.*))?$/);
17
+ if (!m) throw new Error(`Invalid contrib module name: ${name}`);
18
+ if (!m[2]) return `noxt-contrib-${m[1]}`;
19
+ return `noxt-contrib-${m[1]}/${m[2]}`;
20
+ }
21
+ throw new Error(`Invalid module name: ${name}`);
22
+ }
23
+
24
+ export async function startServer({ config, recipe, import: _import}) {
9
25
 
10
- const mlmInstance = MLM({
11
- import:
12
- p => import(p),
13
- resolveModule:
14
- name => name.startsWith('app/')
15
- ? `${process.cwd()}/units/${name.slice(4)}.js`
16
- : `${__dirname}/units/${name}.js`
26
+ const beforeBoot = process.hrtime.bigint();
27
+ const mlmInstance = await MLM({
28
+ import: _import ?? (p => import(p)),
29
+ resolveModule
17
30
  });
18
31
  try {
19
-
32
+ const afterBoot = process.hrtime.bigint();
20
33
  const report = await mlmInstance.analyze(recipe ?? 'noxt-dev');
21
34
  if (!report.success) {
22
35
  console.log('Bad recipe: ' + recipe + '\n' + report.order.join(', '));
23
36
  console.log(report.errors.join('\n'));
24
37
  process.exit(1);
25
38
  }
26
-
39
+ const afterAnalyze = process.hrtime.bigint();
27
40
  await mlmInstance.install(recipe ?? 'noxt-dev');
41
+ const afterInstall = process.hrtime.bigint();
28
42
  const mlm = mlmInstance.context;
29
43
  await mlm.services.config.merge(config);
30
- console.log(mlm.config);
31
- mlmInstance.start();
44
+ const afterConfig = process.hrtime.bigint();
45
+ await mlmInstance.start();
46
+ const afterStart = process.hrtime.bigint();
47
+
48
+ console.log(
49
+ '[noxt-server] Total time', Number((afterStart - beforeBoot) / 1_000_000n), 'ms',
50
+ '| Boot', Number((afterBoot - beforeBoot) / 1_000_000n), 'ms',
51
+ '| Load', Number((afterAnalyze - afterBoot) / 1_000_000n), 'ms',
52
+ '| Install', Number((afterInstall - afterAnalyze) / 1_000_000n), 'ms',
53
+ '| Config', Number((afterConfig - afterInstall) / 1_000_000n), 'ms',
54
+ '| Start', Number((afterStart - afterConfig) / 1_000_000n), 'ms',
55
+ );
32
56
 
33
- mlmInstance.repl();
57
+ mlm.DEV && mlmInstance.repl();
34
58
  } catch (e) {
35
59
  console.log(await mlmInstance.analyze(recipe ?? 'noxt-dev'));
36
60
  console.error(e);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noxt-server",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "Server for noxt-js-middleware with CLI and config support",
5
5
  "type": "module",
6
6
  "main": "noxt-server.js",
@@ -23,17 +23,15 @@
23
23
  "author": "Zoran Obradović [https://github.com/zocky]",
24
24
  "license": "GPL-3.0-or-later",
25
25
  "dependencies": {
26
- "noxt-js-middleware": "^1.0.6",
27
- "mlm-core": "^1.0.6",
28
- "express": "^5.0.0",
26
+ "noxt-js-middleware": "^1.0.9",
27
+ "mlm-core": "^1.0.8",
28
+ "express": "^5.1.0",
29
29
  "cookie-parser": "^1.4.6",
30
- "js-yaml": "^4.1.0",
31
- "minimist": "^1.2.8",
32
- "node-fetch-cache": "^5.0.2"
30
+ "js-yaml": "^4.1.0"
33
31
  },
34
- "devDependencies": {
35
- "noxt-js-middleware": "../noxt-js-middleware",
36
- "mlm-core": "../mlm-core"
32
+ "peerDependencies": {
33
+ "compression": "^1.8.1",
34
+ "sharp": "^0.34.5"
37
35
  },
38
36
  "engines": {
39
37
  "node": ">=18"
@@ -0,0 +1,56 @@
1
+ export const info = {
2
+ name: 'bundler',
3
+ version: '1.0.0',
4
+ description: 'Bundle js and css from component exports',
5
+ requires: ['noxt-plugin'],
6
+ }
7
+
8
+ export default mlm => {
9
+ const styles = {};
10
+ const scripts = {};
11
+ return {
12
+ 'config.bundler': {
13
+ is: {
14
+ style: 'string|false',
15
+ script: 'string|false',
16
+ },
17
+ default: {
18
+ style: '_style',
19
+ script: '_script',
20
+ },
21
+ },
22
+ 'services.bundler': () => ({ styles, scripts }),
23
+ 'registerComponent.bundler': async ({ name, component, module }) => {
24
+ styles[component.name] = module.style ? `/* ${name} */ \n ${module.style}\n` : '';
25
+
26
+ if (!module.script) return;
27
+
28
+ if (typeof module.script == 'string') {
29
+ scripts[component.name] = `/* ${name} */ \n (()=>{\n${module.script}\n})();\n\n`;
30
+ return
31
+ }
32
+ if (typeof module.script == 'function') {
33
+ scripts[component.name] = `/* ${name} */ \n (${module.script.toString()})(window);\n\n`;
34
+ return
35
+ }
36
+ },
37
+ 'middleware.bundler': () => {
38
+ const router = mlm.services.express.Router();
39
+ if (mlm.config.bundler.style) {
40
+ router.get(`/${mlm.config.bundler.style}`, (req, res) => {
41
+ res.logGroup = 'bundler';
42
+ res.set('Content-Type', 'text/css');
43
+ res.send( Object.values(styles).join('') );
44
+ });
45
+ }
46
+ if (mlm.config.bundler.script) {
47
+ router.get(`/${mlm.config.bundler.script}`, (req, res) => {
48
+ res.logGroup = 'bundler';
49
+ res.set('Content-Type', 'text/javascript');
50
+ res.send( Object.values(scripts).join('') );
51
+ });
52
+ }
53
+ return router
54
+ }
55
+ }
56
+ }
@@ -0,0 +1,30 @@
1
+ export const info = {
2
+ name: 'bust-cache',
3
+ version: '1.0.0',
4
+ description: 'Bust cache on GET requests',
5
+ requires: ['express'],
6
+ }
7
+
8
+ export default mlm => ({
9
+ 'hook.bustCache': async (fn, req) => {
10
+ req.bustCache ||= fn(req);
11
+ },
12
+ 'prod.middleware.bustCache': async () => {
13
+ return (req, res, next) => {
14
+ if (req.method === 'GET') {
15
+ mlm.hook.bustCache(req);
16
+ }
17
+ next();
18
+ };
19
+ },
20
+ 'dev.middleware.bustCache': () => {
21
+ return (req, res, next) => {
22
+ if (req.method === 'GET') {
23
+ if (req.headers['cache-control']?.includes('no-cache')) {
24
+ req.bustCache = true;
25
+ }
26
+ }
27
+ next();
28
+ };
29
+ },
30
+ });
@@ -0,0 +1,30 @@
1
+ // units/compression.js
2
+ export const info = {
3
+ name: 'compression',
4
+ version: '1.0.0',
5
+ description: 'Response compression',
6
+ requires: ['express'],
7
+ packages: ['compression'],
8
+ }
9
+
10
+ export default mlm => ({
11
+ 'config.compression': {
12
+ is: {
13
+ enabled: 'boolean',
14
+ level: 'number', // 0-9, default 6
15
+ threshold: 'number', // bytes, default 1024
16
+ },
17
+ default: {
18
+ enabled: true,
19
+ level: 6,
20
+ threshold: 1024, // only compress responses > 1kb
21
+ },
22
+ },
23
+
24
+ 'middleware.compression': async () => {
25
+ const { enabled, level, threshold } = mlm.config.compression;
26
+ if (!enabled) return null;
27
+ const compression = mlm.packages.compression.default;
28
+ return compression({ level, threshold });
29
+ },
30
+ });
package/units/config.js CHANGED
@@ -100,9 +100,9 @@ export default mlm => ({
100
100
  return ret;
101
101
  }
102
102
  merge(userConfig) {
103
- console.log('initial',config_defaults)
103
+ // console.log('initial',config_defaults,userConfig)
104
104
  Object.assign(config, this.process(userConfig));
105
- console.log(userConfig,'final',config)
105
+ // console.log(userConfig,'final',config)
106
106
  }
107
107
  get_defs() {
108
108
  return {
package/units/env.js CHANGED
@@ -9,6 +9,7 @@ export default mlm => ({
9
9
  'define.PROD': () => process.env.NODE_ENV === 'production',
10
10
  'onBeforeLoad': () => {
11
11
  process.env.NODE_ENV ||= 'development'
12
- }
12
+ },
13
+ 'inject.dev': (confs, unit) => mlm.DEV ? confs : null,
14
+ 'inject.prod': (confs, unit) => !mlm.DEV ? confs : null,
13
15
  })
14
-
package/units/express.js CHANGED
@@ -2,62 +2,95 @@ export const info = {
2
2
  name: 'express',
3
3
  description: 'Sets up an Express server',
4
4
  requires: ['plugin'],
5
- npm: {
6
- 'express': '^5.0.0',
7
- 'cookie-parser': '^1.4.6'
8
- },
5
+ packages: ['express'],
9
6
  }
10
7
 
11
- let app;
12
- const middlewareNames = new Set();
13
- const middlewares = [];
14
- export default mlm => ({
15
- 'define.express': { get: () => app },
16
- 'register.middleware': (conf, unit) => {
17
- for (const key in conf) {
18
- mlm.assert.not(key in middlewareNames, 'Duplicate middleware ' + key);
19
- middlewareNames.add(key);
20
- let c = conf[key];
21
- if (mlm.is.function(c)) {
22
- c = { create: c };
8
+ export default async mlm => {
9
+ let app;
10
+ const middlewareNames = new Set();
11
+ const middlewares = [];
12
+ const { default: express } = mlm.packages.express;
13
+ return {
14
+ 'services.express': () => new class {
15
+ get app () {
16
+ return app;
23
17
  }
24
- mlm.assert.is({
25
- path: 'string|none',
26
- create: 'function'
27
- }, c, 'middleware');
28
- mlm.assert.is.function(c.create, 'middleware');
29
- middlewares.push({ ...c, unit: unit.name });
30
- }
31
- },
32
- 'config.port': {
33
- is: v => Number.isInteger(v) && v > 0 && v < 65536,
34
- default: 3000
35
- },
36
- 'config.host': {
37
- is: 'string',
38
- default: 'localhost'
39
- },
40
- 'middleware.json': async (app) => {
41
- const { default: express } = await mlm.import('express');
42
- return express.json();
43
- },
44
- 'middleware.urlencoded': async (app) => {
45
- const { default: express } = await mlm.import('express');
46
- return express.urlencoded({ extended: true });
47
- },
48
- async onStart() {
49
- const { default: express } = await mlm.import('express');
50
- app = express();
51
- for (const middleware of middlewares) {
52
- const mw = await middleware.create(app);
53
- if (middleware.path) {
54
- app.use(middleware.path, mw);
55
- } else {
56
- app.use(mw);
18
+ get Router() {
19
+ return express.Router;
20
+ }
21
+ get express () {
22
+ return express;
23
+ }
24
+ },
25
+ 'register.middleware': (conf, unit) => {
26
+ for (const key in conf) {
27
+ mlm.assert.not(key in middlewareNames, 'Duplicate middleware ' + key);
28
+ middlewareNames.add(key);
29
+ let c = conf[key];
30
+ if (mlm.is.function(c)) {
31
+ c = { create: c };
32
+ }
33
+ mlm.assert.is({
34
+ path: 'string|none',
35
+ create: 'function'
36
+ }, c, 'middleware');
37
+ mlm.assert.is.function(c.create, 'middleware');
38
+ middlewares.push({ ...c, unit: unit.name });
57
39
  }
40
+ },
41
+ 'config.port': {
42
+ is: v => Number.isInteger(v) && v > 0 && v < 65536,
43
+ default: 3000
44
+ },
45
+ 'config.host': {
46
+ is: 'string',
47
+ default: 'localhost'
48
+ },
49
+ 'middleware.json': async (app) => {
50
+ return express.json();
51
+ },
52
+ 'middleware.urlencoded': async (app) => {
53
+ return express.urlencoded({ extended: true });
54
+ },
55
+ async onStart() {
56
+ app = express();
57
+ for (const middleware of middlewares) {
58
+ const mw = await middleware.create(app);
59
+ if (!mw) {
60
+ continue;
61
+ }
62
+ if (middleware.path) {
63
+ app.use(middleware.path, mw);
64
+ } else {
65
+ app.use(mw);
66
+ }
67
+ }
68
+ app.use('/.well-known', (req, res, next) => {
69
+ res.logGroup = 'well-known';
70
+ res.status(404).send('Not found');
71
+ res.end();
72
+ });
73
+ app.use((req, res, next) => {
74
+ res.logGroup = '404';
75
+ res.status(404).send('Not found');
76
+ })
77
+ app.use((error, req, res, next,) => {
78
+ mlm.log('error', error);
79
+ res.status(500).send(error.stack);
80
+ })
81
+ const server = app.listen(mlm.config.port, mlm.config.host, (error) => {
82
+ if (error) {
83
+ mlm.throw(error);
84
+ }
85
+ mlm.log(`Listening on ${mlm.config.host}:${mlm.config.port}`);
86
+ });
87
+ server.on('close', () => {
88
+ mlm.log('Server closed');
89
+ });
90
+ app.on('error', (error) => {
91
+ mlm.error(error);
92
+ app.throw(error);
93
+ });
58
94
  }
59
- app.listen(mlm.config.port, mlm.config.host, () => {
60
- mlm.log(`Listening on ${mlm.config.host}:${mlm.config.port}`);
61
- });
62
- },
63
- })
95
+ }
96
+ }