5htp 0.6.2 → 0.6.3-2
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 +4 -1
- package/compiler/client/index.ts +2 -2
- package/compiler/common/babel/index.ts +0 -10
- package/compiler/common/babel/plugins/services.ts +263 -13
- package/compiler/common/babel/routes/routes.ts +199 -7
- package/compiler/common/files/autres.ts +1 -1
- package/compiler/common/files/style.ts +24 -7
- package/compiler/common/index.ts +0 -9
- package/compiler/index.ts +24 -11
- package/package.json +8 -3
- package/compiler/common/babel/plugins/form.ts +0 -191
- package/compiler/common/babel/plugins/icones-svg.ts +0 -376
- package/compiler/common/babel/plugins/injection-dependances/index.ts +0 -225
- package/compiler/common/babel/plugins/injection-dependances/remplacerFonction.ts +0 -226
- package/compiler/common/babel/plugins/queries/index.ts +0 -166
- package/compiler/common/plugins/indexage/_utils/Stringify.ts +0 -72
- package/compiler/common/plugins/indexage/_utils/annotations.ts +0 -88
- package/compiler/common/plugins/indexage/_utils/iterateur.ts +0 -52
- package/compiler/common/plugins/indexage/icones-svg/index.ts +0 -207
- package/compiler/common/plugins/indexage/index.ts +0 -134
- package/compiler/common/plugins/indexage/indexeur.ts +0 -13
- package/compiler/common/plugins/indexage/injection-dependances/index.ts +0 -68
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
|
|
package/compiler/client/index.ts
CHANGED
|
@@ -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,
|
|
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
|
+
};
|
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
// Npm
|
|
6
6
|
import type webpack from 'webpack';
|
|
7
7
|
import PresetBabel, { Options } from '@babel/preset-env';
|
|
8
|
-
// Core
|
|
9
|
-
import PluginIndexage from '../plugins/indexage';
|
|
10
8
|
|
|
11
9
|
import cli from '@cli';
|
|
12
10
|
import type { TAppSide, App } from '@cli/app';
|
|
@@ -70,10 +68,6 @@ module.exports = (app: App, side: TAppSide, dev: boolean): webpack.RuleSetRule[]
|
|
|
70
68
|
// Désactive car ralenti compilation
|
|
71
69
|
cacheCompression: false,
|
|
72
70
|
|
|
73
|
-
metadataSubscribers: [
|
|
74
|
-
PluginIndexage.metadataContextFunctionName
|
|
75
|
-
],
|
|
76
|
-
|
|
77
71
|
compact: !dev,
|
|
78
72
|
|
|
79
73
|
// https://babeljs.io/docs/usage/options/
|
|
@@ -165,14 +159,10 @@ module.exports = (app: App, side: TAppSide, dev: boolean): webpack.RuleSetRule[]
|
|
|
165
159
|
],
|
|
166
160
|
|
|
167
161
|
overrides: [
|
|
168
|
-
|
|
169
|
-
require('./plugins/icones-svg')(app),
|
|
170
162
|
|
|
171
163
|
...(side === 'client' ? [
|
|
172
164
|
|
|
173
165
|
] : [
|
|
174
|
-
//require('./plugins/queries'),
|
|
175
|
-
//require('./plugins/injection-dependances'),
|
|
176
166
|
]),
|
|
177
167
|
]
|
|
178
168
|
}
|
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
import * as types from '@babel/types'
|
|
7
7
|
import type { NodePath, PluginObj } from '@babel/core';
|
|
8
8
|
import generate from '@babel/generator';
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { parse } from '@babel/parser';
|
|
12
|
+
import traverse from '@babel/traverse';
|
|
9
13
|
|
|
10
14
|
// Core
|
|
11
15
|
import cli from '@cli';
|
|
@@ -38,6 +42,170 @@ type TImportedIndex = {
|
|
|
38
42
|
source: TImportSource // container | application | models
|
|
39
43
|
}
|
|
40
44
|
|
|
45
|
+
type TRoutesIndex = {
|
|
46
|
+
byFile: Map<string, Set<string>>,
|
|
47
|
+
all: Set<string>,
|
|
48
|
+
initialized: boolean
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const routesIndexByAppRoot = new Map<string, TRoutesIndex>();
|
|
52
|
+
|
|
53
|
+
function isRouteDecoratorExpression(expression: types.Expression): boolean {
|
|
54
|
+
return (
|
|
55
|
+
// Handles the case of @Route without parameters
|
|
56
|
+
(types.isIdentifier(expression) && expression.name === 'Route')
|
|
57
|
+
||
|
|
58
|
+
// Handles the case of @Route() with parameters
|
|
59
|
+
(
|
|
60
|
+
types.isCallExpression(expression)
|
|
61
|
+
&&
|
|
62
|
+
types.isIdentifier(expression.callee)
|
|
63
|
+
&&
|
|
64
|
+
expression.callee.name === 'Route'
|
|
65
|
+
)
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function getRoutePathFromDecoratorExpression(expression: types.Expression): string | undefined {
|
|
70
|
+
if (
|
|
71
|
+
!types.isCallExpression(expression)
|
|
72
|
+
||
|
|
73
|
+
!types.isIdentifier(expression.callee)
|
|
74
|
+
||
|
|
75
|
+
expression.callee.name !== 'Route'
|
|
76
|
+
)
|
|
77
|
+
return;
|
|
78
|
+
|
|
79
|
+
const firstArg = expression.arguments[0];
|
|
80
|
+
if (!types.isStringLiteral(firstArg))
|
|
81
|
+
return;
|
|
82
|
+
|
|
83
|
+
return firstArg.value;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function extractRoutePathsFromCode(code: string, filename: string): Set<string> {
|
|
87
|
+
const routePaths = new Set<string>();
|
|
88
|
+
|
|
89
|
+
let ast: ReturnType<typeof parse> | undefined;
|
|
90
|
+
try {
|
|
91
|
+
ast = parse(code, {
|
|
92
|
+
sourceType: 'module',
|
|
93
|
+
sourceFilename: filename,
|
|
94
|
+
plugins: [
|
|
95
|
+
'typescript',
|
|
96
|
+
'decorators-legacy',
|
|
97
|
+
'jsx',
|
|
98
|
+
'classProperties',
|
|
99
|
+
'classPrivateProperties',
|
|
100
|
+
'classPrivateMethods',
|
|
101
|
+
'dynamicImport',
|
|
102
|
+
'importMeta',
|
|
103
|
+
'optionalChaining',
|
|
104
|
+
'nullishCoalescingOperator',
|
|
105
|
+
'topLevelAwait',
|
|
106
|
+
],
|
|
107
|
+
});
|
|
108
|
+
} catch {
|
|
109
|
+
return routePaths;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
traverse(ast, {
|
|
113
|
+
ClassMethod(path) {
|
|
114
|
+
const { node } = path;
|
|
115
|
+
if (!node.decorators || node.key.type !== 'Identifier')
|
|
116
|
+
return;
|
|
117
|
+
|
|
118
|
+
for (const decorator of node.decorators) {
|
|
119
|
+
if (!isRouteDecoratorExpression(decorator.expression))
|
|
120
|
+
continue;
|
|
121
|
+
|
|
122
|
+
const routePath = getRoutePathFromDecoratorExpression(decorator.expression);
|
|
123
|
+
if (routePath)
|
|
124
|
+
routePaths.add(routePath);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return routePaths;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function listTsFiles(dirPath: string): string[] {
|
|
133
|
+
let entries: fs.Dirent[];
|
|
134
|
+
try {
|
|
135
|
+
entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
136
|
+
} catch {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const files: string[] = [];
|
|
141
|
+
for (const entry of entries) {
|
|
142
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
143
|
+
if (entry.isDirectory()) {
|
|
144
|
+
files.push(...listTsFiles(fullPath));
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const isTs = fullPath.endsWith('.ts') || fullPath.endsWith('.tsx');
|
|
149
|
+
if (!isTs || fullPath.endsWith('.d.ts'))
|
|
150
|
+
continue;
|
|
151
|
+
|
|
152
|
+
files.push(fullPath);
|
|
153
|
+
}
|
|
154
|
+
return files;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function initializeRoutesIndex(appRoot: string, index: TRoutesIndex) {
|
|
158
|
+
const servicesDir = path.join(appRoot, 'server', 'services');
|
|
159
|
+
const files = listTsFiles(servicesDir);
|
|
160
|
+
|
|
161
|
+
for (const file of files) {
|
|
162
|
+
let code: string;
|
|
163
|
+
try {
|
|
164
|
+
code = fs.readFileSync(file, 'utf8');
|
|
165
|
+
} catch {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const routes = extractRoutePathsFromCode(code, file);
|
|
170
|
+
index.byFile.set(file, routes);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
index.all.clear();
|
|
174
|
+
for (const routes of index.byFile.values()) {
|
|
175
|
+
for (const route of routes)
|
|
176
|
+
index.all.add(route);
|
|
177
|
+
}
|
|
178
|
+
index.initialized = true;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function getRoutesIndex(appRoot: string): TRoutesIndex {
|
|
182
|
+
let index = routesIndexByAppRoot.get(appRoot);
|
|
183
|
+
if (!index) {
|
|
184
|
+
index = {
|
|
185
|
+
byFile: new Map(),
|
|
186
|
+
all: new Set(),
|
|
187
|
+
initialized: false
|
|
188
|
+
};
|
|
189
|
+
routesIndexByAppRoot.set(appRoot, index);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (!index.initialized) {
|
|
193
|
+
initializeRoutesIndex(appRoot, index);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return index;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function updateRoutesIndexForFile(index: TRoutesIndex, filename: string, routePaths: Set<string>) {
|
|
200
|
+
index.byFile.set(filename, routePaths);
|
|
201
|
+
|
|
202
|
+
index.all.clear();
|
|
203
|
+
for (const routes of index.byFile.values()) {
|
|
204
|
+
for (const route of routes)
|
|
205
|
+
index.all.add(route);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
41
209
|
/*----------------------------------
|
|
42
210
|
- PLUGIN
|
|
43
211
|
----------------------------------*/
|
|
@@ -75,6 +243,9 @@ function Plugin(babel, { app, side, debug }: TOptions) {
|
|
|
75
243
|
// Count how many total imports we transform
|
|
76
244
|
importedCount: number,
|
|
77
245
|
routeMethods: string[],
|
|
246
|
+
routePaths: Set<string>,
|
|
247
|
+
routesIndex: TRoutesIndex,
|
|
248
|
+
contextGuardedClassMethods: WeakSet<types.ClassMethod>,
|
|
78
249
|
|
|
79
250
|
// For every local identifier, store info about how it should be rewritten
|
|
80
251
|
imported: {
|
|
@@ -92,6 +263,10 @@ function Plugin(babel, { app, side, debug }: TOptions) {
|
|
|
92
263
|
this.debug = debug || false;
|
|
93
264
|
|
|
94
265
|
this.routeMethods = [];
|
|
266
|
+
this.routePaths = new Set();
|
|
267
|
+
|
|
268
|
+
this.routesIndex = getRoutesIndex(app.paths.root);
|
|
269
|
+
this.contextGuardedClassMethods = new WeakSet();
|
|
95
270
|
},
|
|
96
271
|
|
|
97
272
|
visitor: {
|
|
@@ -109,28 +284,21 @@ function Plugin(babel, { app, side, debug }: TOptions) {
|
|
|
109
284
|
|
|
110
285
|
for (const decorator of node.decorators) {
|
|
111
286
|
|
|
112
|
-
const isRoute = (
|
|
113
|
-
// Handles the case of @Route without parameters
|
|
114
|
-
(
|
|
115
|
-
t.isIdentifier(decorator.expression) && decorator.expression.name === 'Route'
|
|
116
|
-
)
|
|
117
|
-
||
|
|
118
|
-
// Handles the case of @Route() with parameters
|
|
119
|
-
(
|
|
120
|
-
t.isCallExpression(decorator.expression) &&
|
|
121
|
-
t.isIdentifier(decorator.expression.callee) &&
|
|
122
|
-
decorator.expression.callee.name === 'Route'
|
|
123
|
-
)
|
|
124
|
-
);
|
|
287
|
+
const isRoute = isRouteDecoratorExpression(decorator.expression);
|
|
125
288
|
|
|
126
289
|
if (!isRoute) continue;
|
|
127
290
|
|
|
128
291
|
const methodName = node.key.name;
|
|
129
292
|
this.routeMethods.push( methodName );
|
|
130
293
|
|
|
294
|
+
const routePath = getRoutePathFromDecoratorExpression(decorator.expression);
|
|
295
|
+
if (routePath)
|
|
296
|
+
this.routePaths.add(routePath);
|
|
131
297
|
}
|
|
132
298
|
}
|
|
133
299
|
});
|
|
300
|
+
|
|
301
|
+
updateRoutesIndexForFile(this.routesIndex, this.filename, this.routePaths);
|
|
134
302
|
},
|
|
135
303
|
|
|
136
304
|
/**
|
|
@@ -203,6 +371,88 @@ function Plugin(babel, { app, side, debug }: TOptions) {
|
|
|
203
371
|
path.replaceWithMultiple(replaceWith);
|
|
204
372
|
},
|
|
205
373
|
|
|
374
|
+
CallExpression(path) {
|
|
375
|
+
if (!this.processFile)
|
|
376
|
+
return;
|
|
377
|
+
|
|
378
|
+
const classMethodPath = path.findParent(p => p.isClassMethod()) as NodePath<types.ClassMethod> | null;
|
|
379
|
+
if (!classMethodPath)
|
|
380
|
+
return;
|
|
381
|
+
|
|
382
|
+
// Ignore constructors
|
|
383
|
+
if (classMethodPath.node.kind === 'constructor')
|
|
384
|
+
return;
|
|
385
|
+
|
|
386
|
+
const callee = path.node.callee;
|
|
387
|
+
if (!t.isMemberExpression(callee) && !(t as any).isOptionalMemberExpression?.(callee))
|
|
388
|
+
return;
|
|
389
|
+
|
|
390
|
+
// Build member chain segments: this.app.<Service>.<...>
|
|
391
|
+
const segments: string[] = [];
|
|
392
|
+
let current: any = callee;
|
|
393
|
+
while (t.isMemberExpression(current) || (t as any).isOptionalMemberExpression?.(current)) {
|
|
394
|
+
const prop = current.property;
|
|
395
|
+
if (t.isIdentifier(prop)) {
|
|
396
|
+
segments.unshift(prop.name);
|
|
397
|
+
} else if (t.isStringLiteral(prop)) {
|
|
398
|
+
segments.unshift(prop.value);
|
|
399
|
+
} else {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
current = current.object;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (!t.isThisExpression(current))
|
|
406
|
+
return;
|
|
407
|
+
|
|
408
|
+
// Expect: this.app.<Service>.<method>
|
|
409
|
+
if (segments.length < 3 || segments[0] !== 'app')
|
|
410
|
+
return;
|
|
411
|
+
|
|
412
|
+
const serviceLocalName = segments[1];
|
|
413
|
+
const importedRef = this.imported[serviceLocalName];
|
|
414
|
+
if (!importedRef || importedRef.source !== 'services')
|
|
415
|
+
return;
|
|
416
|
+
|
|
417
|
+
const routePath = [
|
|
418
|
+
importedRef.imported || serviceLocalName,
|
|
419
|
+
...segments.slice(2)
|
|
420
|
+
].join('/');
|
|
421
|
+
|
|
422
|
+
if (!this.routesIndex.all.has(routePath))
|
|
423
|
+
return;
|
|
424
|
+
|
|
425
|
+
// Ensure the parent function checks that `context` exists
|
|
426
|
+
if (!this.contextGuardedClassMethods.has(classMethodPath.node)) {
|
|
427
|
+
const guard = t.ifStatement(
|
|
428
|
+
t.binaryExpression(
|
|
429
|
+
'===',
|
|
430
|
+
t.unaryExpression('typeof', t.identifier('context')),
|
|
431
|
+
t.stringLiteral('undefined')
|
|
432
|
+
),
|
|
433
|
+
t.blockStatement([
|
|
434
|
+
t.throwStatement(
|
|
435
|
+
t.newExpression(t.identifier('Error'), [
|
|
436
|
+
t.stringLiteral('context variable should be passed in this function')
|
|
437
|
+
])
|
|
438
|
+
)
|
|
439
|
+
])
|
|
440
|
+
);
|
|
441
|
+
classMethodPath.get('body').unshiftContainer('body', guard);
|
|
442
|
+
this.contextGuardedClassMethods.add(classMethodPath.node);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Ensure call arguments: second argument is `context`
|
|
446
|
+
const args = path.node.arguments;
|
|
447
|
+
if (args.length === 0) {
|
|
448
|
+
args.push(t.identifier('undefined'), t.identifier('context'));
|
|
449
|
+
} else if (args.length === 1) {
|
|
450
|
+
args.push(t.identifier('context'));
|
|
451
|
+
} else {
|
|
452
|
+
args[1] = t.identifier('context') as any;
|
|
453
|
+
}
|
|
454
|
+
},
|
|
455
|
+
|
|
206
456
|
// This visitor fires for every class method.
|
|
207
457
|
ClassMethod(path) {
|
|
208
458
|
|
|
@@ -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:
|
|
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
|
+
}
|