rtjscomp 0.8.2 → 0.8.4

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/rtjscomp.js +141 -66
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rtjscomp",
3
- "version": "0.8.2",
3
+ "version": "0.8.4",
4
4
  "description": "php-like server but with javascript",
5
5
  "repository": {
6
6
  "type": "git",
package/rtjscomp.js CHANGED
@@ -22,9 +22,11 @@ const PATH_PUBLIC = 'public/';
22
22
  const PATH_CONFIG = 'config/';
23
23
  const PATH_DATA = 'data/';
24
24
  const GZIP_OPTIONS = {level: 9};
25
+ const WATCH_OPTIONS = {persistent: true, interval: 1000};
25
26
  const AGENT_CHECK_BOT = /bot|googlebot|crawler|spider|robot|crawling|favicon/i;
26
27
  const AGENT_CHECK_MOBIL = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i;
27
28
  const HTTP_LIST_REG = /,\s*/;
29
+ const IMPORT_REG = /import\(/g;
28
30
 
29
31
  let log_verbose = process.argv.includes('-v');
30
32
  let port_http = 0;
@@ -58,11 +60,34 @@ if (!Object.fromEntries) {
58
60
  };
59
61
  }
60
62
 
63
+ // workaround for bun: https://github.com/oven-sh/bun/issues/18919
64
+ let fs_watch = fs.watch;
65
+ if (typeof Bun !== 'undefined') {
66
+ const fs_watch_original = fs_watch;
67
+ const watch_callbacks = new Map;
68
+ fs_watch = (path, options, callback) => {
69
+ if (!watch_callbacks.has(path)) {
70
+ fs_watch_original(path, options, () => {
71
+ const callback = watch_callbacks.get(path);
72
+ if (callback) {
73
+ callback();
74
+ }
75
+ });
76
+ }
77
+ watch_callbacks.set(path, callback);
78
+ return {
79
+ close: () => (
80
+ watch_callbacks.set(path, null)
81
+ ),
82
+ };
83
+ }
84
+ }
85
+
61
86
  // legacy, will be removed soon!
62
87
  global.globals = rtjscomp;
63
88
  global.actions = rtjscomp.actions;
64
89
  global.data_load = name => {
65
- log('[deprecated] load: ' + name);
90
+ log('[deprecated] synchronous load: ' + PATH_DATA + name);
66
91
  try {
67
92
  return fs.readFileSync(PATH_DATA + name, 'utf8');
68
93
  }
@@ -71,7 +96,7 @@ global.data_load = name => {
71
96
  }
72
97
  }
73
98
  global.data_save = (name, data) => (
74
- log('[deprecated] save: ' + name),
99
+ log('[deprecated] synchronous save: ' + PATH_DATA + name),
75
100
  fs.writeFileSync(PATH_DATA + name, data, 'utf8')
76
101
  )
77
102
  global.number_check_int = number => (
@@ -104,19 +129,43 @@ rtjscomp.data_save = (name, data) => (
104
129
  )
105
130
  )
106
131
 
132
+ const custom_require_paths = new Set;
107
133
  const custom_require_cache = new Map;
134
+ const custom_import_cache = new Map;
108
135
  const custom_require = path => {
109
136
  let result = custom_require_cache.get(path);
110
137
  if (result != null) return result;
111
138
 
139
+ log_verbose && log('require: ' + path);
140
+ const path_real = require.resolve(path, resolve_options);
141
+ custom_require_paths.add(path_real);
112
142
  custom_require_cache.set(
113
143
  path,
114
- result = require(
115
- require.resolve(path, resolve_options)
144
+ result = require(path_real)
145
+ );
146
+ return result;
147
+ }
148
+ const custom_import = async path => {
149
+ let result = custom_import_cache.get(path);
150
+ if (result != null) return result;
151
+
152
+ log_verbose && log('import: ' + path);
153
+ custom_import_cache.set(
154
+ path,
155
+ result = await import(
156
+ 'file://' + require.resolve(path, resolve_options)
116
157
  )
117
158
  );
118
159
  return result;
119
160
  }
161
+ actions.module_cache_clear = () => {
162
+ for (const path of custom_require_paths) {
163
+ delete require.cache[path];
164
+ }
165
+ custom_require_cache.clear();
166
+ custom_import_cache.clear();
167
+ }
168
+ const AsyncFunction = custom_import.constructor;
120
169
 
121
170
  const services_active = new Map;
122
171
  const services_loading = new Set;
@@ -137,21 +186,25 @@ const service_start = async path => {
137
186
  handler_stop: null,
138
187
  path,
139
188
  stopped: false,
189
+ watcher: null,
140
190
  };
141
191
 
142
- await file_keep_new(PATH_PUBLIC + path + '.service.js', async file_content => {
143
- if (file_content === null) {
144
- log('[error] service file not found: ' + path);
145
- return service_stop(service_object, true);
146
- }
147
- if (services_loading.size > 0) {
148
- await Promise.all(Array.from(services_loading));
192
+ service_object.watcher = await file_keep_new(
193
+ PATH_PUBLIC + path + '.service.js',
194
+ async file_content => {
195
+ if (file_content === null) {
196
+ log('[error] service file not found: ' + path);
197
+ return service_stop(service_object, true);
198
+ }
199
+ if (services_loading.size > 0) {
200
+ await Promise.all(Array.from(services_loading));
201
+ }
202
+ const start_promise = service_start_inner(path, service_object, file_content);
203
+ services_loading.add(start_promise);
204
+ await start_promise;
205
+ services_loading.delete(start_promise);
149
206
  }
150
- const start_promise = service_start_inner(path, service_object, file_content);
151
- services_loading.add(start_promise);
152
- await start_promise;
153
- services_loading.delete(start_promise);
154
- });
207
+ );
155
208
  }
156
209
  const service_start_inner = async (path, service_object, file_content) => {
157
210
  if (services_active.has(path)) {
@@ -161,16 +214,23 @@ const service_start_inner = async (path, service_object, file_content) => {
161
214
  log('start service: ' + path);
162
215
 
163
216
  const start_interval = setInterval(() => {
164
- log('[warning] still starting service: ' + path);
217
+ log(`[warning] ${path}: still starting`);
165
218
  }, 1e3);
166
219
 
220
+ if (file_content.includes('globals.')) {
221
+ log(`[deprecated] ${path}: uses globals object`);
222
+ }
223
+
167
224
  try {
168
- const fun = (0, eval)(
169
- `(async function(require){const log=a=>rtjscomp.log(${
225
+ const result = await (new AsyncFunction(
226
+ 'require',
227
+ 'custom_import',
228
+ `const log=a=>rtjscomp.log(${
170
229
  JSON.stringify(path + ': ')
171
- }+a);${file_content + '\n'}})`
172
- );
173
- const result = await fun.call(content_object, custom_require);
230
+ }+a);${
231
+ file_content.replace(IMPORT_REG, 'custom_import(') + '\n'
232
+ }`
233
+ )).call(content_object, custom_require, custom_import);
174
234
  if (service_object.stopped) {
175
235
  clearInterval(start_interval);
176
236
  return;
@@ -188,7 +248,7 @@ const service_start_inner = async (path, service_object, file_content) => {
188
248
 
189
249
  const handler_start = content_object.start;
190
250
  if (handler_start) {
191
- log('[deprecated] service has start method: ' + path);
251
+ log(`[deprecated] ${path}: has start method`);
192
252
  delete content_object.start;
193
253
  try {
194
254
  await handler_start();
@@ -202,7 +262,7 @@ const service_start_inner = async (path, service_object, file_content) => {
202
262
  clearInterval(start_interval);
203
263
 
204
264
  if (content_object.stop) {
205
- log('[deprecated] service has stop method: ' + path);
265
+ log(`[deprecated] ${path}: has stop method`);
206
266
  service_object.handler_stop = content_object.stop;
207
267
  delete content_object.stop;
208
268
  }
@@ -217,18 +277,19 @@ const services_shutdown = () => (
217
277
  .map(service_object => service_stop(service_object, true))
218
278
  )
219
279
  )
220
- const service_stop = async (service_object, forget) => {
221
- service_object.stopped = true;
222
- if (forget) fs.unwatchFile(PATH_PUBLIC + service_object.path + '.service.js');
223
- await service_stop_handler(service_object);
224
- }
280
+ const service_stop = (service_object, forget) => (
281
+ service_object.stopped = true,
282
+ forget && service_object.watcher &&
283
+ service_object.watcher.close(),
284
+ service_stop_handler(service_object)
285
+ )
225
286
  const service_stop_handler = async service_object => {
226
287
  services_active.delete(service_object.path);
227
288
  log('stop service: ' + service_object.path);
228
289
  const handler_stop = service_object.handler_stop;
229
290
  if (handler_stop) {
230
291
  const stop_interval = setInterval(() => {
231
- log('[warning] still stopping service: ' + service_object.path);
292
+ log(`[warning] ${service_object.path}: still stopping`);
232
293
  }, 1e3);
233
294
  try {
234
295
  service_object.handler_stop = null;
@@ -277,36 +338,34 @@ const map_generate_equ = (map, data) => {
277
338
  }
278
339
  }
279
340
 
280
- const file_compare = (curr, prev, path) => (
281
- curr.mtime > prev.mtime && (
282
- log_verbose && log('file changed: ' + path),
283
- true
284
- )
285
- )
286
- const file_watch = (path, callback) => (
287
- fs.watchFile(path, (curr, prev) => {
288
- if (file_compare(curr, prev, path)) {
289
- fs.unwatchFile(path);
290
- callback();
291
- }
292
- })
293
- )
341
+ const file_watch_once = (path, callback) => {
342
+ const watcher = fs_watch(path, WATCH_OPTIONS, () => (
343
+ watcher.close(),
344
+ log_verbose && log('file updated: ' + path),
345
+ callback()
346
+ ));
347
+ };
294
348
  const file_keep_new = async (path, callback) => {
295
349
  try {
296
350
  const data = await fsp.readFile(path, 'utf8');
297
351
  if (log_verbose) log('load file: ' + path);
298
352
  await callback(data);
299
- fs.watchFile(path, async (curr, prev) => {
300
- if (file_compare(curr, prev, path)) {
301
- await callback(
302
- await fsp.readFile(path, 'utf8').catch(() => null)
303
- );
304
- }
305
- });
306
353
  }
307
354
  catch (err) {
308
355
  await callback(null);
356
+ return null;
309
357
  }
358
+
359
+ let timeout = 0;
360
+ return fs_watch(path, WATCH_OPTIONS, () => (
361
+ clearTimeout(timeout),
362
+ timeout = setTimeout(() => (
363
+ log_verbose && log('file updated: ' + path),
364
+ fsp.readFile(path, 'utf8')
365
+ .catch(() => null)
366
+ .then(callback)
367
+ ), 50)
368
+ ));
310
369
  }
311
370
 
312
371
  let log_history = rtjscomp.log_history = [];
@@ -480,14 +539,17 @@ const request_handle = async (request, response, https) => {
480
539
  if (file_stat.isDirectory()) throw 403;
481
540
 
482
541
  if (file_dyn_enabled) { // compile file
483
- const file_content = fs.readFileSync(path_real, 'utf8');
542
+ const file_content = await fsp.readFile(path_real, 'utf8');
484
543
  try {
485
544
  if (file_content.includes('\r')) {
486
545
  throw 'illegal line break, must be unix';
487
546
  }
547
+ if (file_content.includes('globals.')) {
548
+ log(`[deprecated] ${path}: uses globals object`);
549
+ }
488
550
  const file_content_length = file_content.length;
489
551
 
490
- let code = `async (input,output,request,response,require)=>{const log=a=>rtjscomp.log(${
552
+ let code = `const log=a=>rtjscomp.log(${
491
553
  JSON.stringify(path + ': ')
492
554
  }+a);`;
493
555
 
@@ -512,19 +574,23 @@ const request_handle = async (request, response, https) => {
512
574
  // `<?`?
513
575
  if (file_content.charCodeAt(index_start) !== 61) {
514
576
  code += (
515
- file_content.substring(
516
- index_start,
517
- index_end
518
- ) +
577
+ file_content
578
+ .substring(
579
+ index_start,
580
+ index_end
581
+ )
582
+ .replace(IMPORT_REG, 'custom_import(') +
519
583
  ';'
520
584
  );
521
585
  }
522
586
  else { // `<?=`?
523
587
  code += `output.write(''+(${
524
- file_content.substring(
525
- ++index_start,
526
- index_end
527
- )
588
+ file_content
589
+ .substring(
590
+ ++index_start,
591
+ index_end
592
+ )
593
+ .replace(IMPORT_REG, 'custom_import(')
528
594
  }));`;
529
595
  }
530
596
  }
@@ -559,7 +625,15 @@ const request_handle = async (request, response, https) => {
559
625
  }
560
626
 
561
627
  try {
562
- file_function = (0, eval)(code += '}');
628
+ file_function = new AsyncFunction(
629
+ 'input',
630
+ 'output',
631
+ 'request',
632
+ 'response',
633
+ 'require',
634
+ 'custom_import',
635
+ code
636
+ );
563
637
  }
564
638
  catch (err) {
565
639
  throw err.message;
@@ -571,9 +645,9 @@ const request_handle = async (request, response, https) => {
571
645
  }
572
646
 
573
647
  file_cache_functions.set(path, file_function);
574
- file_watch(path_real, () => {
575
- file_cache_functions.delete(path);
576
- });
648
+ file_watch_once(path_real, () => (
649
+ file_cache_functions.delete(path)
650
+ ));
577
651
  }
578
652
  }
579
653
 
@@ -706,7 +780,8 @@ const request_handle = async (request, response, https) => {
706
780
  file_function_output,
707
781
  request,
708
782
  response,
709
- custom_require
783
+ custom_require,
784
+ custom_import
710
785
  );
711
786
  file_function_output.end();
712
787
  }