5htp 0.2.3 → 0.3.0-3

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.
@@ -9,7 +9,7 @@ import generate from '@babel/generator';
9
9
 
10
10
  // Core
11
11
  import cli from '@cli';
12
- import App, { TAppSide } from '../../../../app';
12
+ import { App, TAppSide } from '../../../../app';
13
13
 
14
14
  /*----------------------------------
15
15
  - WEBPACK RULE
@@ -17,19 +17,18 @@ import App, { TAppSide } from '../../../../app';
17
17
 
18
18
  type TOptions = {
19
19
  side: TAppSide,
20
- app: App
20
+ app: App,
21
+ debug?: boolean
21
22
  }
22
23
 
23
24
  module.exports = (options: TOptions) => (
24
25
  [Plugin, options]
25
26
  )
26
27
 
27
- const debug = true;
28
-
29
28
  /*----------------------------------
30
29
  - PLUGIN
31
30
  ----------------------------------*/
32
- function Plugin(babel, { app, side }: TOptions) {
31
+ function Plugin(babel, { app, side, debug }: TOptions) {
33
32
 
34
33
  const t = babel.types as typeof types;
35
34
 
@@ -41,7 +40,8 @@ function Plugin(babel, { app, side }: TOptions) {
41
40
  const plugin: PluginObj<{
42
41
 
43
42
  filename: string,
44
- fileType: 'front' | 'back',
43
+ part: 'routes',
44
+ side: 'front' | 'back',
45
45
  processFile: boolean,
46
46
 
47
47
  // Identifier => Name
@@ -52,30 +52,44 @@ function Plugin(babel, { app, side }: TOptions) {
52
52
 
53
53
  this.filename = state.opts.filename as string;
54
54
  this.processFile = true;
55
+
56
+ // Relative path
57
+ let relativeFileName: string | undefined;
58
+ if (this.filename.startsWith( cli.paths.appRoot ))
59
+ relativeFileName = this.filename.substring( cli.paths.appRoot.length );
60
+ if (this.filename.startsWith( cli.paths.coreRoot ))
61
+ relativeFileName = this.filename.substring( cli.paths.coreRoot.length );
62
+ if (this.filename.startsWith('/node_modules/5htp-core/'))
63
+ relativeFileName = this.filename.substring( '/node_modules/5htp-core/'.length );
64
+
65
+ // The file isn't a route definition
66
+ if (relativeFileName === undefined) {
67
+ this.processFile = false;
68
+ return false;
69
+ }
55
70
 
56
- if (
57
- this.filename.startsWith( cli.paths.appRoot + '/src/client/pages' )
58
- ||
59
- this.filename.startsWith( cli.paths.coreRoot + '/src/client/pages' )
60
- ) {
71
+ // Differenciate back / front
72
+ if (relativeFileName.startsWith('/src/client/pages')) {
61
73
 
62
- this.fileType = 'front';
74
+ this.side = 'front';
75
+ this.part = 'routes';
63
76
 
64
- } else if (this.filename.startsWith( cli.paths.appRoot + '/src/server/routes' )) {
77
+ } else if (relativeFileName.startsWith('/src/server/routes')) {
65
78
 
66
- this.fileType = 'back';
67
-
68
- } else
79
+ this.side = 'back';
80
+ this.part = 'routes';
81
+
82
+ } else
69
83
  this.processFile = false;
70
84
 
85
+ // Init output
71
86
  this.importedServices = {}
72
87
  this.routeDefinitions = []
73
-
74
88
  },
75
89
  visitor: {
76
90
 
77
91
  // Find @app imports
78
- // Test: import { router } from '@app';
92
+ // Test: import { Router } from '@app';
79
93
  // Replace by: nothing
80
94
  ImportDeclaration(path) {
81
95
 
@@ -98,10 +112,11 @@ function Plugin(babel, { app, side }: TOptions) {
98
112
 
99
113
  // Remove this import
100
114
  path.replaceWithMultiple([]);
115
+
101
116
  },
102
117
 
103
- // Find router definitions
104
- // Test: router.xxx()
118
+ // Find Router definitions
119
+ // Test: Router.xxx()
105
120
  // Replace by: nothing
106
121
  CallExpression(path) {
107
122
 
@@ -131,7 +146,7 @@ function Plugin(babel, { app, side }: TOptions) {
131
146
 
132
147
  // Client route definition: Add chunk id
133
148
  let [routePath, ...routeArgs] = path.node.arguments;
134
- if (this.fileType === 'front' && callee.object.name === 'router') {
149
+ if (this.side === 'front' && callee.object.name === 'Router') {
135
150
 
136
151
  // Inject chunk id in options (2nd arg)
137
152
  const newRouteArgs = injectChunkId(routeArgs, this.filename);
@@ -160,21 +175,24 @@ function Plugin(babel, { app, side }: TOptions) {
160
175
 
161
176
  // Wrap declarations into a exported const app function
162
177
  /*
163
- export const __register = ({ router }} => {
178
+ export const __register = ({ Router }} => {
164
179
 
165
- router.page(..)
180
+ Router.page(..)
166
181
 
167
182
  }
168
183
  */
169
184
  Program: {
170
185
  exit: function(path, parent) {
171
186
 
187
+ if (!this.processFile)
188
+ return;
189
+
172
190
  const importedServices = Object.entries(this.importedServices);
173
191
  if (importedServices.length === 0)
174
192
  return;
175
193
 
176
194
  let exportValue: types.Expression | types.BlockStatement;
177
- if (this.fileType === 'front') {
195
+ if (this.side === 'front') {
178
196
 
179
197
  const routesDefCount = this.routeDefinitions.length;
180
198
  if (routesDefCount !== 1)
@@ -34,7 +34,7 @@ export default class IconesSVG extends Indexeur {
34
34
  private cacheTypes: string;
35
35
  private cacheIndex: string;
36
36
 
37
- public constructor( app: App ) {
37
+ public constructor( app: App, private debug?: boolean ) {
38
38
  super();
39
39
 
40
40
  this.formats = ['woff2'];
@@ -46,18 +46,18 @@ export default class IconesSVG extends Indexeur {
46
46
 
47
47
  if (fs.existsSync(this.cacheIndex)) {
48
48
 
49
- console.log('[icones] Getting icons list from cache ...');
49
+ this.debug && console.log('[icones] Getting icons list from cache ...');
50
50
  this.iconesExistantes = fs.readJSONSync(this.cacheIndex);
51
51
 
52
52
  } else {
53
53
 
54
- console.log('[icones] Référencement des icones existantes ...');
54
+ this.debug && console.log('[icones] Référencement des icones existantes ...');
55
55
  this.iconesExistantes = this.refExistant('');
56
56
  fs.outputJSONSync(this.cacheIndex, this.iconesExistantes);
57
57
 
58
58
  }
59
59
 
60
- console.log('[icones] ' + this.iconesExistantes.length + ' icones référencées');
60
+ this.debug && console.log('[icones] ' + this.iconesExistantes.length + ' icones référencées');
61
61
  }
62
62
 
63
63
  private refExistant = (dir: string): string[] => {
@@ -121,7 +121,7 @@ export default class IconesSVG extends Indexeur {
121
121
  typeIcones.push('"' + icone.nom + '"');
122
122
  }
123
123
 
124
- console.log('[icones] Création police avec ' + typeIcones.length +' icones ...');
124
+ this.debug && console.log('[icones] Création police avec ' + typeIcones.length +' icones ...');
125
125
  //console.log('[icones] Liste des icones rféérencées: ', cheminIcones);
126
126
 
127
127
  const optionsMetadata = {
@@ -170,13 +170,12 @@ export default class IconesSVG extends Indexeur {
170
170
 
171
171
  });
172
172
  }
173
- })
174
- .catch(error => {
175
- console.error("Erreur lors de la création de la police d'icones", error);
176
- throw error;
177
- });
173
+ }).catch(error => {
174
+ console.error("Erreur lors de la création de la police d'icones", error);
175
+ throw error;
176
+ });
178
177
 
179
- console.log('[icones] Enregistrement de la police avec ' + typeIcones.length +' icones ...');
178
+ this.debug && console.log('[icones] Enregistrement de la police avec ' + typeIcones.length +' icones ...');
180
179
 
181
180
  // Enregistrement fichiers
182
181
  for (const format of this.formats)
@@ -185,7 +184,7 @@ export default class IconesSVG extends Indexeur {
185
184
 
186
185
  fs.outputFileSync(this.cacheTypes, 'export type TIcones = ' + typeIcones.join('|') );
187
186
 
188
- console.log("[icones] Police enregistrée.");
187
+ this.debug && console.log("[icones] Police enregistrée.");
189
188
 
190
189
  return [
191
190
  /*{
@@ -1,9 +1,11 @@
1
1
  import Indexeur from './indexeur';
2
2
 
3
- import { Compiler } from 'webpack';
3
+ import { Compiler, NormalModule } from 'webpack';
4
4
 
5
5
  type TListeIndexeurs = {[nom: string]: Indexeur}
6
6
 
7
+ const debug = false;
8
+
7
9
  export default class SelecteursApiPlugin {
8
10
 
9
11
  public static metadataContextFunctionName = "metadataSelecteursApiPlugin";
@@ -21,14 +23,14 @@ export default class SelecteursApiPlugin {
21
23
  const nomCompileur = compiler.options.name;
22
24
 
23
25
  for (const nomIndexeur in this.indexeurs) {
24
- console.log(`[indexage][${nomCompileur}][${nomIndexeur}] Init`);
26
+ debug && console.log(`[indexage][${nomCompileur}][${nomIndexeur}] Init`);
25
27
  this.indexeurs[nomIndexeur].Init();
26
28
  }
27
29
 
28
30
  compiler.hooks.compilation.tap("SelecteursApiPlugin", (compilation) => {
29
31
  //console.log("The compiler is starting a new compilation...");
30
32
 
31
- compilation.hooks.normalModuleLoader.tap("SelecteursApiPlugin", (context, module) => {
33
+ NormalModule.getCompilationHooks(compilation).loader.tap("SelecteursApiPlugin", (context, module) => {
32
34
 
33
35
  // Fonction de récupération des métadatas
34
36
  context["metadataSelecteursApiPlugin"] = (metadata) => {
@@ -66,7 +68,7 @@ export default class SelecteursApiPlugin {
66
68
  try {
67
69
  if (this.indexeurs[nomIndexeur].derniereModif > this.indexeurs[nomIndexeur].derniereMaj) {
68
70
 
69
- console.log(`[indexage][${nomCompileur}][${nomIndexeur}] Enregistrement des modifications`);
71
+ debug && console.log(`[indexage][${nomCompileur}][${nomIndexeur}] Enregistrement des modifications`);
70
72
 
71
73
  this.indexeurs[nomIndexeur].derniereMaj = this.indexeurs[nomIndexeur].derniereModif;
72
74
  const aEnregistrer = await this.indexeurs[nomIndexeur].Enregistrer();
@@ -11,111 +11,272 @@ import SpeedMeasurePlugin from "speed-measure-webpack-plugin";
11
11
  const smp = new SpeedMeasurePlugin({ disable: true });
12
12
 
13
13
  // Core
14
+ import app from '../app';
15
+ import cli from '..';
14
16
  import createServerConfig from './server';
15
17
  import createClientConfig from './client';
16
18
  import { TCompileMode } from './common';
17
- import { fixNpmLinkIssues } from './common/utils/fixNpmLink';
18
-
19
- // types
20
- import type App from '../app';
21
19
 
22
20
  type TCompilerCallback = () => void
23
21
 
24
22
  /*----------------------------------
25
23
  - FONCTION
26
24
  ----------------------------------*/
27
- export const compiling: { [compiler: string]: Promise<void> } = {};
28
-
29
- export default async function createCompilers(
30
- app: App,
31
- mode: TCompileMode,
32
- { before, after }: {
33
- before?: TCompilerCallback,
34
- after?: TCompilerCallback,
35
- } = {}
36
- ) {
37
-
38
- // Cleanup
39
- fs.emptyDirSync( app.paths.bin );
40
- fs.ensureDirSync( path.join(app.paths.bin, 'public') )
41
- const publicFiles = fs.readdirSync(app.paths.public);
42
- for (const publicFile of publicFiles) {
43
- // Dev: faster to use symlink
44
- if (mode === 'dev')
45
- fs.symlinkSync(
46
- path.join(app.paths.public, publicFile),
47
- path.join(app.paths.bin, 'public', publicFile)
48
- );
49
- // Prod: Symlink not always supported by CI / Containers solutions
50
- else
51
- fs.copySync(
52
- path.join(app.paths.public, publicFile),
53
- path.join(app.paths.bin, 'public', publicFile)
54
- );
25
+ export default class Compiler {
26
+
27
+ public compiling: { [compiler: string]: Promise<void> } = {};
28
+
29
+ public constructor(
30
+ private mode: TCompileMode,
31
+ private callbacks: {
32
+ before?: TCompilerCallback,
33
+ after?: TCompilerCallback,
34
+ } = {},
35
+ private debug: boolean = false
36
+ ) {
37
+
55
38
  }
56
39
 
40
+ public cleanup() {
41
+
42
+ fs.emptyDirSync( app.paths.bin );
43
+ fs.ensureDirSync( path.join(app.paths.bin, 'public') )
44
+ const publicFiles = fs.readdirSync(app.paths.public);
45
+ for (const publicFile of publicFiles) {
46
+ // Dev: faster to use symlink
47
+ if (this.mode === 'dev')
48
+ fs.symlinkSync(
49
+ path.join(app.paths.public, publicFile),
50
+ path.join(app.paths.bin, 'public', publicFile)
51
+ );
52
+ // Prod: Symlink not always supported by CI / Containers solutions
53
+ else
54
+ fs.copySync(
55
+ path.join(app.paths.public, publicFile),
56
+ path.join(app.paths.bin, 'public', publicFile)
57
+ );
58
+ }
59
+ }
57
60
  /* FIX issue with npm link
58
61
  When we install a module with npm link, this module's deps are not installed in the parent project scope
59
62
  Which causes some issues:
60
63
  - The module's deps are not found by Typescript
61
64
  - Including React, so VSCode shows that JSX is missing
62
65
  */
63
- fixNpmLinkIssues(app);
64
-
65
- // Create compilers
66
- const multiCompiler = webpack([
67
- smp.wrap( createServerConfig(app, mode) ),
68
- smp.wrap( createClientConfig(app, mode) )
69
- ]);
66
+ public fixNpmLinkIssues() {
67
+ const corePath = path.join(app.paths.root, '/node_modules/5htp-core');
68
+ if (!fs.lstatSync( corePath ).isSymbolicLink())
69
+ return console.info("Not fixing npm issue because 5htp-core wasn't installed with npm link.");
70
70
 
71
- for (const compiler of multiCompiler.compilers) {
71
+ this.debug && console.info(`Fix NPM link issues ...`);
72
72
 
73
- const name = compiler.name;
74
- if (name === undefined)
75
- throw new Error(`A name must be specified to each compiler.`);
73
+ const appModules = path.join(app.paths.root, 'node_modules');
74
+ const coreModules = path.join(corePath, 'node_modules');
76
75
 
77
- let timeStart = new Date();
76
+ // When the 5htp package is installed from npm link,
77
+ // Modules are installed locally and not glbally as with with the 5htp package from NPM.
78
+ // So we need to symbilnk the http-core node_modules in one of the parents of server.js.
79
+ // It avoids errors like: "Error: Cannot find module 'intl'"
80
+ fs.symlinkSync( coreModules, path.join(app.paths.bin, 'node_modules') );
78
81
 
79
- let finished: (() => void);
80
- compiling[name] = new Promise((resolve) => finished = resolve);
82
+ // Same problem: when 5htp-core is installed via npm link,
83
+ // Typescript doesn't detect React and shows mission JSX errors
84
+ const preactCoreModule = path.join(coreModules, 'preact');
85
+ const preactAppModule = path.join(appModules, 'preact');
86
+ const reactAppModule = path.join(appModules, 'react');
81
87
 
82
- compiler.hooks.compile.tap(name, () => {
88
+ if (!fs.existsSync( preactAppModule ))
89
+ fs.symlinkSync( preactCoreModule, preactAppModule );
90
+ if (!fs.existsSync( reactAppModule ))
91
+ fs.symlinkSync( path.join(preactCoreModule, 'compat'), reactAppModule );
92
+ }
83
93
 
84
- before && before();
94
+ private findServices( dir: string ) {
95
+ const files: string[] = [];
96
+ const dirents = fs.readdirSync(dir, { withFileTypes: true });
97
+ for (const dirent of dirents) {
98
+ const res = path.resolve(dir, dirent.name);
99
+ if (dirent.isDirectory()) {
100
+ files.push( ...this.findServices(res) );
101
+ } else if (dirent.name === 'service.json') {
102
+ files.push( path.dirname(res) );
103
+ }
104
+ }
105
+ return files;
106
+ }
85
107
 
86
- compiling[name] = new Promise((resolve) => finished = resolve);
108
+ private indexServices() {
109
+
110
+ const imported: string[] = []
111
+ const exportedType: string[] = []
112
+ const exportedMetas: string[] = []
87
113
 
88
- timeStart = new Date();
89
- console.info(`[${name}] ########## Compiling ...`);
90
- });
114
+ // Index services
115
+ const searchDirs = {
116
+ '@server/services': path.join(cli.paths.core.src, 'server', 'services'),
117
+ '@/server/services': path.join(app.paths.src, 'server', 'services'),
118
+ // TODO: node_modules
119
+ }
91
120
 
92
- /* TODO: Ne pas résoudre la promise tant que la recompilation des données indexées (icones, identité, ...)
93
- n'a pas été achevée */
94
- compiler.hooks.done.tap(name, stats => {
121
+ for (const importationPrefix in searchDirs) {
95
122
 
96
- // Affiche les détails de la compilation
97
- console.info(stats.toString(compiler.options.stats));
123
+ const searchDir = searchDirs[ importationPrefix ];
124
+ const services = this.findServices(searchDir);
98
125
 
99
- // Shiow status
100
- const timeEnd = new Date();
101
- const time = timeEnd.getTime() - timeStart.getTime();
102
- if (stats.hasErrors()) {
103
- console.error(`############## Failed to compile '${name}' after ${time} ms`);
126
+ for (const serviceDir of services) {
104
127
 
105
- // Exit process with code 0, so the CI container can understand building failed
106
- // Only in prod, because in dev, we want the compiler watcher continue running
107
- if (mode === 'prod')
108
- process.exit(0);
128
+ const metasFile = path.join( serviceDir, 'service.json');
129
+ const { id, name, parent, dependences } = require(metasFile);
109
130
 
110
- } else {
111
- console.info(`############## [${name}] Finished compilation after ${time} ms`);
131
+ const importationPath = importationPrefix + serviceDir.substring( searchDir.length );
132
+
133
+ // Generate index & typings
134
+ imported.push(`import type ${name} from "${importationPath}";`);
135
+ exportedType.push(`'${id}': ${name},`);
136
+ // NOTE: only import enabled packages to optimize memory
137
+ // TODO: don't index non-setuped packages in the exported metas
138
+ exportedMetas.push(`'${id}': {
139
+ class: () => require("${importationPath}"),
140
+ id: "${id}",
141
+ name: "${name}",
142
+ parent: "${parent}",
143
+ dependences: ${JSON.stringify(dependences)},
144
+ },`);
112
145
  }
146
+ }
147
+
148
+ // Define the app class identifier
149
+ const appClassIdentifier = app.identity.identifier;
150
+ const containerServices = app.containerServices.map( s => "'" + s + "'").join('|');
151
+
152
+ // Output the services index
153
+ fs.outputFileSync(
154
+ path.join( app.paths.client.generated, 'services.d.ts'),
155
+ `declare module "@app" {
156
+
157
+ import { CrossPathClient } from '@/client/index';
158
+
159
+ const appClass: CrossPathClient;
160
+
161
+ export = appClass
162
+ }`
163
+ );
164
+
165
+ fs.outputFileSync(
166
+ path.join( app.paths.server.generated, 'services.ts'),
167
+ `${imported.join('\n')}
168
+ export type Services = {
169
+ ${exportedType.join('\n')}
170
+ }
171
+ export default {
172
+ ${exportedMetas.join('\n')}
173
+ }`
174
+ );
175
+
176
+ fs.outputFileSync(
177
+ path.join( app.paths.server.generated, 'services.d.ts'),
178
+ `type InstalledServices = import('./services').Services;
179
+
180
+ declare type ${appClassIdentifier} = import("@/server").default;
181
+
182
+ declare module "@app" {
183
+
184
+ import { ApplicationContainer } from '@server/app/container';
185
+
186
+ const ServerServices: (
187
+ Pick<
188
+ ApplicationContainer<InstalledServices>,
189
+ ${containerServices}
190
+ >
191
+ &
192
+ ${appClassIdentifier}
193
+ )
194
+
195
+ export = ServerServices
196
+ }
197
+
198
+ declare module '@server/app' {
199
+
200
+ import { Application } from "@server/app/index";
201
+ import { ServicesContainer } from "@server/app/service/container";
202
+
203
+ export interface Exported {
204
+ Application: typeof Application,
205
+ Services: ServicesContainer<InstalledServices>,
206
+ }
207
+
208
+ const foo: Exported;
209
+
210
+ export = foo;
211
+ }`
212
+ );
213
+ }
214
+
215
+ public async create() {
216
+
217
+ this.cleanup();
218
+
219
+ this.fixNpmLinkIssues();
220
+
221
+ this.indexServices();
222
+
223
+ // Create compilers
224
+ const multiCompiler = webpack([
225
+ smp.wrap( createServerConfig(app, this.mode) ),
226
+ smp.wrap( createClientConfig(app, this.mode) )
227
+ ]);
228
+
229
+ for (const compiler of multiCompiler.compilers) {
230
+
231
+ const name = compiler.name;
232
+ if (name === undefined)
233
+ throw new Error(`A name must be specified to each compiler.`);
234
+
235
+ let timeStart = new Date();
236
+
237
+ let finished: (() => void);
238
+ this.compiling[name] = new Promise((resolve) => finished = resolve);
239
+
240
+ compiler.hooks.compile.tap(name, () => {
241
+
242
+ this.callbacks.before && this.callbacks.before();
243
+
244
+ this.compiling[name] = new Promise((resolve) => finished = resolve);
245
+
246
+ timeStart = new Date();
247
+ console.info(`[${name}] Compiling ...`);
248
+ });
249
+
250
+ /* TODO: Ne pas résoudre la promise tant que la recompilation des données indexées (icones, identité, ...)
251
+ n'a pas été achevée */
252
+ compiler.hooks.done.tap(name, stats => {
253
+
254
+ // Shiow status
255
+ const timeEnd = new Date();
256
+ const time = timeEnd.getTime() - timeStart.getTime();
257
+ if (stats.hasErrors()) {
258
+
259
+ console.info(stats.toString(compiler.options.stats));
260
+ console.error(`[${name}] Failed to compile after ${time} ms`);
261
+
262
+ // Exit process with code 0, so the CI container can understand building failed
263
+ // Only in prod, because in dev, we want the compiler watcher continue running
264
+ if (this.mode === 'prod')
265
+ process.exit(0);
266
+
267
+ } else {
268
+ this.debug && console.info(stats.toString(compiler.options.stats));
269
+ console.info(`[${name}] Finished compilation after ${time} ms`);
270
+ }
271
+
272
+ // Mark as finished
273
+ finished();
274
+ delete this.compiling[name];
275
+ });
276
+ }
277
+
278
+ return multiCompiler;
113
279
 
114
- // Mark as finished
115
- finished();
116
- delete compiling[name];
117
- });
118
280
  }
119
281
 
120
- return multiCompiler;
121
282
  }
@@ -179,7 +179,7 @@ export default function createCompiler( app: App, mode: TCompileMode ): webpack.
179
179
  ],
180
180
 
181
181
  optimization: {
182
- minimizer: [
182
+ minimizer: dev ? [] : [
183
183
  new TerserPlugin({
184
184
  terserOptions: {
185
185
  // Consere les classnames
@@ -191,9 +191,9 @@ export default function createCompiler( app: App, mode: TCompileMode ): webpack.
191
191
  },
192
192
 
193
193
  // https://webpack.js.org/configuration/devtool/#devtool
194
- devtool: /*dev
194
+ devtool: dev
195
195
  ? 'eval-source-map' // Recommended choice for development builds with high quality SourceMaps.
196
- :*/ 'source-map', // Recommended choice for production builds with high quality SourceMaps.
196
+ : 'source-map', // Recommended choice for production builds with high quality SourceMaps.
197
197
 
198
198
  // eval-source-map n'est pas précis
199
199
  /*devServer: {