5htp 0.6.2 → 0.6.3-1

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/app/index.ts CHANGED
@@ -69,7 +69,10 @@ export class App {
69
69
  generated: path.join( cli.paths.appRoot, 'server', '.generated'),
70
70
  configs: path.join( cli.paths.appRoot, 'server', 'app')
71
71
  },
72
-
72
+ common: {
73
+ generated: path.join( cli.paths.appRoot, 'common', '.generated')
74
+ },
75
+
73
76
  withAlias: (filename: string, side: TAppSide) =>
74
77
  this.aliases[side].apply(filename),
75
78
 
@@ -126,7 +126,7 @@ export default function createCompiler( app: App, mode: TCompileMode ): webpack.
126
126
  // On ne compile les ressources (css) qu'une seule fois
127
127
  {
128
128
  test: regex.style,
129
- rules: require('../common/files/style')(app, true, dev),
129
+ rules: require('../common/files/style')(app, dev, true),
130
130
 
131
131
  // Don't consider CSS imports dead code even if the
132
132
  // containing package claims to have no side effects.
@@ -331,4 +331,4 @@ export default function createCompiler( app: App, mode: TCompileMode ): webpack.
331
331
  };
332
332
 
333
333
  return config;
334
- };
334
+ };
@@ -53,6 +53,13 @@ function Plugin(babel, { app, side, debug }: TOptions) {
53
53
 
54
54
  const t = babel.types as typeof types;
55
55
 
56
+ type TPluginState = {
57
+ filename: string,
58
+ file: TFileInfos,
59
+ apiInjectedRootFunctions: WeakSet<types.Node>,
60
+ needsUseContextImport: boolean
61
+ }
62
+
56
63
  /*
57
64
  - Wrap route.get(...) with (app: Application) => { }
58
65
  - Inject chunk ID into client route options
@@ -70,14 +77,14 @@ function Plugin(babel, { app, side, debug }: TOptions) {
70
77
  const stats = page.data.stats;
71
78
  */
72
79
 
73
- const plugin: PluginObj<{
74
- filename: string,
75
- file: TFileInfos
76
- }> = {
80
+ const plugin: PluginObj<TPluginState> = {
77
81
  pre(state) {
78
82
  this.filename = state.opts.filename as string;
79
83
 
80
84
  this.file = getFileInfos(this.filename);
85
+
86
+ this.apiInjectedRootFunctions = new WeakSet();
87
+ this.needsUseContextImport = false;
81
88
  },
82
89
  visitor: {
83
90
  // Find @app imports
@@ -203,6 +210,8 @@ function Plugin(babel, { app, side, debug }: TOptions) {
203
210
  */
204
211
  if (side === 'client' && !clientServices.includes(serviceName)) {
205
212
 
213
+ ensureApiExposedInRootFunction(path, this);
214
+
206
215
  // Get complete call path
207
216
  const apiPath = '/api/' + completePath.join('/');
208
217
 
@@ -255,6 +264,8 @@ function Plugin(babel, { app, side, debug }: TOptions) {
255
264
 
256
265
  if (!this.file.process)
257
266
  return;
267
+
268
+ ensureUseContextImport(path, this);
258
269
 
259
270
  const wrappedrouteDefs = wrapRouteDefs( this.file );
260
271
  if (wrappedrouteDefs)
@@ -264,6 +275,187 @@ function Plugin(babel, { app, side, debug }: TOptions) {
264
275
  }
265
276
  }
266
277
 
278
+ function ensureApiExposedInRootFunction(
279
+ path: NodePath<types.CallExpression>,
280
+ pluginState: TPluginState
281
+ ) {
282
+ if (path.scope.hasBinding('api'))
283
+ return;
284
+
285
+ const rootFunctionPath = getRootFunctionPath(path);
286
+ if (!rootFunctionPath)
287
+ return;
288
+
289
+ // Root function should be at the program body level (not nested in another function / expression)
290
+ if (rootFunctionPath.getFunctionParent())
291
+ return;
292
+ if (!isProgramBodyLevelFunction(rootFunctionPath))
293
+ return;
294
+
295
+ if (pluginState.apiInjectedRootFunctions.has(rootFunctionPath.node))
296
+ return;
297
+
298
+ const exposeApiDeclaration = t.variableDeclaration('const', [
299
+ t.variableDeclarator(
300
+ t.objectPattern([
301
+ t.objectProperty(t.identifier('api'), t.identifier('api'), false, true),
302
+ ]),
303
+ t.callExpression(t.identifier('useContext'), [])
304
+ )
305
+ ]);
306
+
307
+ const body = rootFunctionPath.node.body;
308
+ if (body.type === 'BlockStatement') {
309
+ body.body.unshift(exposeApiDeclaration);
310
+ } else {
311
+ rootFunctionPath.node.body = t.blockStatement([
312
+ exposeApiDeclaration,
313
+ t.returnStatement(body)
314
+ ]);
315
+ }
316
+
317
+ pluginState.apiInjectedRootFunctions.add(rootFunctionPath.node);
318
+ pluginState.needsUseContextImport = true;
319
+ }
320
+
321
+ function getRootFunctionPath(path: NodePath): NodePath<types.Function | types.ArrowFunctionExpression> | undefined {
322
+
323
+ let functionPath = path.getFunctionParent();
324
+ if (!functionPath)
325
+ return;
326
+
327
+ // Only support plain functions / arrow functions (no class/object methods)
328
+ if (!(
329
+ functionPath.isFunctionDeclaration()
330
+ || functionPath.isFunctionExpression()
331
+ || functionPath.isArrowFunctionExpression()
332
+ ))
333
+ return;
334
+
335
+ let parentFunction = functionPath.getFunctionParent();
336
+ while (parentFunction) {
337
+
338
+ if (!(
339
+ parentFunction.isFunctionDeclaration()
340
+ || parentFunction.isFunctionExpression()
341
+ || parentFunction.isArrowFunctionExpression()
342
+ ))
343
+ break;
344
+
345
+ functionPath = parentFunction;
346
+ parentFunction = functionPath.getFunctionParent();
347
+ }
348
+
349
+ return functionPath;
350
+ }
351
+
352
+ function isProgramBodyLevelFunction(path: NodePath): boolean {
353
+
354
+ const parent = path.parentPath;
355
+ if (!parent)
356
+ return false;
357
+
358
+ // function Foo() {}
359
+ if (parent.isProgram())
360
+ return true;
361
+
362
+ // export default function Foo() {} / export default () => {}
363
+ if (
364
+ parent.isExportDefaultDeclaration()
365
+ &&
366
+ parent.parentPath?.isProgram()
367
+ )
368
+ return true;
369
+
370
+ // export const Foo = () => {}
371
+ if (
372
+ parent.isExportNamedDeclaration()
373
+ &&
374
+ parent.parentPath?.isProgram()
375
+ )
376
+ return true;
377
+
378
+ // const Foo = () => {} (top-level) / export const Foo = () => {}
379
+ if (parent.isVariableDeclarator()) {
380
+
381
+ const declaration = parent.parentPath;
382
+ if (!declaration?.isVariableDeclaration())
383
+ return false;
384
+
385
+ const declarationParent = declaration.parentPath;
386
+ if (!declarationParent)
387
+ return false;
388
+
389
+ if (declarationParent.isProgram())
390
+ return true;
391
+
392
+ if (
393
+ declarationParent.isExportNamedDeclaration()
394
+ &&
395
+ declarationParent.parentPath?.isProgram()
396
+ )
397
+ return true;
398
+ }
399
+
400
+ return false;
401
+ }
402
+
403
+ function ensureUseContextImport(path: NodePath<types.Program>, pluginState: TPluginState) {
404
+
405
+ if (!pluginState.needsUseContextImport)
406
+ return;
407
+
408
+ const body = path.node.body;
409
+
410
+ // Already imported as a value import
411
+ for (const stmt of body) {
412
+ if (
413
+ stmt.type === 'ImportDeclaration'
414
+ &&
415
+ stmt.source.value === '@/client/context'
416
+ &&
417
+ stmt.importKind !== 'type'
418
+ &&
419
+ stmt.specifiers.some(s =>
420
+ s.type === 'ImportDefaultSpecifier' && s.local.name === 'useContext'
421
+ )
422
+ )
423
+ return;
424
+ }
425
+
426
+ // Try to reuse an existing value import from the same module
427
+ for (const stmt of body) {
428
+ if (
429
+ stmt.type !== 'ImportDeclaration'
430
+ ||
431
+ stmt.source.value !== '@/client/context'
432
+ ||
433
+ stmt.importKind === 'type'
434
+ )
435
+ continue;
436
+
437
+ const hasDefaultImport = stmt.specifiers.some(s => s.type === 'ImportDefaultSpecifier');
438
+ if (!hasDefaultImport) {
439
+ stmt.specifiers.unshift(
440
+ t.importDefaultSpecifier(t.identifier('useContext'))
441
+ );
442
+ return;
443
+ }
444
+ }
445
+
446
+ // Otherwise, add a new import (placed after existing imports)
447
+ const importDeclaration = t.importDeclaration(
448
+ [t.importDefaultSpecifier(t.identifier('useContext'))],
449
+ t.stringLiteral('@/client/context')
450
+ );
451
+
452
+ let insertIndex = 0;
453
+ while (insertIndex < body.length && body[insertIndex].type === 'ImportDeclaration')
454
+ insertIndex++;
455
+
456
+ body.splice(insertIndex, 0, importDeclaration);
457
+ }
458
+
267
459
  function getFileInfos( filename: string ): TFileInfos {
268
460
 
269
461
  const file: TFileInfos = {
@@ -290,7 +482,7 @@ function Plugin(babel, { app, side, debug }: TOptions) {
290
482
  }
291
483
 
292
484
  // Differenciate back / front
293
- if (relativeFileName.startsWith('/client/pages')) {
485
+ if (relativeFileName.startsWith('/client/pages') || relativeFileName.startsWith('/client/components') || relativeFileName.startsWith('/client/hooks')) {
294
486
  file.side = 'front';
295
487
  } else if (relativeFileName.startsWith('/server/routes')) {
296
488
  file.side = 'back';
@@ -302,7 +494,7 @@ function Plugin(babel, { app, side, debug }: TOptions) {
302
494
 
303
495
  function transformDataFetchers(
304
496
  path: NodePath<types.CallExpression>,
305
- routerDefContext: PluginObj,
497
+ routerDefContext: TPluginState,
306
498
  routeDef: TRouteDefinition
307
499
  ) {
308
500
  path.traverse({
@@ -638,4 +830,4 @@ function Plugin(babel, { app, side, debug }: TOptions) {
638
830
  }
639
831
 
640
832
  return plugin;
641
- }
833
+ }
@@ -24,7 +24,7 @@ module.exports = (app: App, dev: boolean, client: boolean) => ([
24
24
  // Texte brut
25
25
  {
26
26
  type: 'asset/source',
27
- test: /\.(md|hbs|sql|txt|csv)$/,
27
+ test: /\.(md|hbs|sql|txt|csv|html)$/,
28
28
  },
29
29
 
30
30
  // Polices dans un fichier distinc dans le dossier dédié
@@ -1,11 +1,7 @@
1
1
  // Plugons
2
2
  import MiniCssExtractPlugin from "mini-css-extract-plugin";
3
- import lessToJs from 'less-vars-to-js';
4
3
 
5
- import fs from 'fs-extra';
6
- import cli from '@cli';
7
-
8
- import type App from '../../../app';
4
+ import type { App } from '../../../app';
9
5
 
10
6
  module.exports = (app: App, dev: Boolean, client: boolean) => {
11
7
 
@@ -31,11 +27,32 @@ module.exports = (app: App, dev: Boolean, client: boolean) => {
31
27
  loader: 'css-loader',
32
28
  options: {
33
29
  // CSS Loader https://github.com/webpack/css-loader
34
- importLoaders: 1,
30
+ importLoaders: 1, // let postcss run on @imports
35
31
  sourceMap: dev
36
32
  },
37
33
  },
38
34
 
35
+ // Postcss
36
+ {
37
+ loader: 'postcss-loader',
38
+ options: {
39
+ postcssOptions: {
40
+ plugins: [
41
+ /* Tailwind V4 */require('@tailwindcss/postcss')({
42
+ // Ensure Tailwind scans the application sources even if the build
43
+ // process is launched from another working directory (e.g. Docker).
44
+ base: app.paths.root,
45
+
46
+ // Avoid double-minifying: Webpack already runs CssMinimizerPlugin in prod.
47
+ optimize: false,
48
+ }),
49
+ ///* Tailwind V3 */require('tailwindcss'),
50
+ require('autoprefixer'),
51
+ ],
52
+ },
53
+ },
54
+ },
55
+
39
56
  {
40
57
  test: /\.less$/,
41
58
  loader: 'less-loader',
@@ -58,4 +75,4 @@ module.exports = (app: App, dev: Boolean, client: boolean) => {
58
75
  }*/
59
76
  ]
60
77
 
61
- }
78
+ }
package/compiler/index.ts CHANGED
@@ -17,7 +17,6 @@ import cli from '..';
17
17
  import createServerConfig from './server';
18
18
  import createClientConfig from './client';
19
19
  import { TCompileMode } from './common';
20
- import { routerServices } from './common/babel/plugins/services';
21
20
 
22
21
  type TCompilerCallback = (compiler: webpack.Compiler) => void
23
22
 
@@ -34,7 +33,7 @@ type TRegisteredService = {
34
33
  id?: string,
35
34
  name: string,
36
35
  className: string,
37
- instanciation: (parentRef: string) => string,
36
+ instanciation: (parentRef?: string) => string,
38
37
  priority: number,
39
38
  }
40
39
 
@@ -198,7 +197,7 @@ export default class Compiler {
198
197
  const refTo = serviceConfig.refTo;
199
198
  return {
200
199
  name: serviceName,
201
- instanciation: (parentRef: string) => `this.${refTo}`,
200
+ instanciation: () => `this.${refTo}`,
202
201
  priority: 0
203
202
  }
204
203
  }
@@ -228,7 +227,7 @@ export default class Compiler {
228
227
 
229
228
  // Reference to a service
230
229
  else if (value.type === 'service.setup' || value.type === 'service.ref') // TODO: more reliable way to detect a service reference
231
- propsStr += `${key}:`+ refService(key, value, level + 1).instanciation('instance') + ',\n'
230
+ propsStr += `${key}:`+ refService(key, value, level + 1).instanciation() + ',\n'
232
231
 
233
232
  // Recursion
234
233
  else if (level <= 4 && !Array.isArray(value))
@@ -244,10 +243,10 @@ export default class Compiler {
244
243
  const config = processConfig(serviceConfig.config || {});
245
244
 
246
245
  // Generate the service instance
247
- const instanciation = (parentRef: string) =>
246
+ const instanciation = (parentRef?: string) =>
248
247
  `new ${serviceMetas.name}(
249
- ${parentRef},
250
- (instance: ${serviceMetas.name}) => (${config}),
248
+ ${parentRef ? `${parentRef},` : ''}
249
+ ${config},
251
250
  this
252
251
  )`
253
252
 
@@ -267,7 +266,7 @@ export default class Compiler {
267
266
  const appClassIdentifier = app.identity.identifier;
268
267
  const containerServices = app.containerServices.map( s => "'" + s + "'").join('|');
269
268
 
270
- // Output the services index
269
+ // @/client/.generated/services.d.ts
271
270
  fs.outputFileSync(
272
271
  path.join( app.paths.client.generated, 'services.d.ts'),
273
272
  `declare module "@app" {
@@ -303,6 +302,7 @@ declare namespace preact.JSX {
303
302
  `
304
303
  );
305
304
 
305
+ // @/client/.generated/context.ts
306
306
  fs.outputFileSync(
307
307
  path.join( app.paths.client.generated, 'context.ts'),
308
308
  `// TODO: move it into core (but how to make sure usecontext returns ${appClassIdentifier}'s context ?)
@@ -333,6 +333,15 @@ export type ClientContext = (
333
333
  export const ReactClientContext = React.createContext<ClientContext>({} as ClientContext);
334
334
  export default (): ClientContext => React.useContext<ClientContext>(ReactClientContext);`);
335
335
 
336
+ // @/common/.generated/services.d.ts
337
+ fs.outputFileSync(
338
+ path.join( app.paths.common.generated, 'services.d.ts'),
339
+ `declare module '@models/types' {
340
+ export * from '@/var/prisma/index';
341
+ }`
342
+ );
343
+
344
+ // @/server/.generated/app.ts
336
345
  fs.outputFileSync(
337
346
  path.join( app.paths.server.generated, 'app.ts'),
338
347
  `import { Application } from '@server/app/index';
@@ -341,8 +350,9 @@ ${imported.join('\n')}
341
350
 
342
351
  export default class ${appClassIdentifier} extends Application {
343
352
 
353
+ // Makke sure the services typigs are reflecting the config and referring to the app
344
354
  ${sortedServices.map(service =>
345
- `public ${service.name}!: ${service.className};`
355
+ `public ${service.name}!: ReturnType<${appClassIdentifier}["registered"]["${service.id}"]["start"]>;`
346
356
  ).join('\n')}
347
357
 
348
358
  protected registered = {
@@ -359,6 +369,7 @@ export default class ${appClassIdentifier} extends Application {
359
369
 
360
370
  `);
361
371
 
372
+ // @/server/.generated/services.d.ts
362
373
  fs.outputFileSync(
363
374
  path.join( app.paths.server.generated, 'services.d.ts'),
364
375
  `type InstalledServices = import('./services').Services;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "5htp",
3
3
  "description": "Convenient TypeScript framework designed for Performance and Productivity.",
4
- "version": "0.6.2",
4
+ "version": "0.6.3-1",
5
5
  "author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
6
6
  "repository": "git://github.com/gaetanlegac/5htp.git",
7
7
  "license": "MIT",
@@ -27,6 +27,7 @@
27
27
  "@babel/preset-typescript": "^7.15.0",
28
28
  "@prefresh/webpack": "^3.3.2",
29
29
  "@squoosh/lib": "^0.4.0",
30
+ "@tailwindcss/postcss": "^4.1.17",
30
31
  "@types/babel__core": "^7.1.16",
31
32
  "@types/cookie": "^0.4.1",
32
33
  "@types/express": "^4.17.13",
@@ -40,6 +41,7 @@
40
41
  "@types/universal-analytics": "^0.4.5",
41
42
  "@types/webpack-env": "^1.16.2",
42
43
  "@types/ws": "^7.4.7",
44
+ "autoprefixer": "^10.4.21",
43
45
  "babel-loader": "^10.0.0",
44
46
  "babel-plugin-glob-import": "^0.0.9-1",
45
47
  "babel-plugin-transform-imports": "^2.0.0",
@@ -50,7 +52,7 @@
50
52
  "compression-webpack-plugin": "^8.0.1",
51
53
  "console-table-printer": "^2.10.0",
52
54
  "css-loader": "^6.2.0",
53
- "css-minimizer-webpack-plugin": "^4.1.0",
55
+ "css-minimizer-webpack-plugin": "^7.0.4",
54
56
  "dayjs": "^1.11.5",
55
57
  "favicons": "^7.2.0",
56
58
  "filesize": "^8.0.3",
@@ -67,6 +69,7 @@
67
69
  "node-cmd": "^5.0.0",
68
70
  "node-notifier": "^10.0.0",
69
71
  "null-loader": "^4.0.1",
72
+ "postcss-loader": "^8.2.0",
70
73
  "prompts": "^2.4.2",
71
74
  "react-dev-utils": "^11.0.4",
72
75
  "replace-once": "^1.0.0",
@@ -74,6 +77,7 @@
74
77
  "serialize-javascript": "^6.0.2",
75
78
  "sharp": "^0.34.3",
76
79
  "speed-measure-webpack-plugin": "^1.5.0",
80
+ "tailwindcss": "^4.1.17",
77
81
  "terser-webpack-plugin": "^5.2.4",
78
82
  "ts-alias": "^0.0.7",
79
83
  "ts-node": "^10.9.1",
@@ -84,7 +88,8 @@
84
88
  "webpack-dev-middleware": "^5.1.0",
85
89
  "webpack-hot-middleware": "^2.25.0",
86
90
  "webpack-node-externals": "^3.0.0",
87
- "webpack-virtual-modules": "^0.4.3"
91
+ "webpack-virtual-modules": "^0.4.3",
92
+ "yaml": "^2.8.2"
88
93
  },
89
94
  "devDependencies": {
90
95
  "@types/babel__preset-env": "^7.9.6",