@nitronjs/framework 0.3.3 → 0.3.5
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/cli/njs.js +1 -1
- package/lib/Build/CssBuilder.js +157 -136
- package/lib/Build/FileAnalyzer.js +52 -35
- package/lib/Build/Manager.js +144 -41
- package/lib/Build/ModuleGraph.js +84 -0
- package/lib/Build/PropUsageAnalyzer.js +2 -2
- package/lib/Build/plugins.js +35 -0
- package/lib/Console/Commands/DevCommand.js +301 -258
- package/lib/HMR/Server.js +93 -170
- package/lib/Runtime/Entry.js +3 -0
- package/lib/View/Client/hmr-client.js +217 -219
- package/lib/View/Client/refresh-entry.cjs +1 -0
- package/lib/View/View.js +7 -2
- package/package.json +8 -6
package/lib/Build/Manager.js
CHANGED
|
@@ -18,11 +18,13 @@ import {
|
|
|
18
18
|
createCssStubPlugin,
|
|
19
19
|
createServerModuleBlockerPlugin,
|
|
20
20
|
createClientReferencePlugin,
|
|
21
|
-
createPropFilterPlugin
|
|
21
|
+
createPropFilterPlugin,
|
|
22
|
+
createFastRefreshPlugin
|
|
22
23
|
} from "./plugins.js";
|
|
23
24
|
import { transformFile as factoryTransform } from "./FactoryTransform.js";
|
|
24
25
|
import PropUsageAnalyzer from "./PropUsageAnalyzer.js";
|
|
25
26
|
import EffectivePropUsage from "./EffectivePropUsage.js";
|
|
27
|
+
import ModuleGraph from "./ModuleGraph.js";
|
|
26
28
|
import COLORS from "./colors.js";
|
|
27
29
|
|
|
28
30
|
dotenv.config({ quiet: true });
|
|
@@ -49,7 +51,7 @@ class Builder {
|
|
|
49
51
|
spaBuilt: false,
|
|
50
52
|
hmrBuilt: false,
|
|
51
53
|
rscConsumerBuilt: false,
|
|
52
|
-
|
|
54
|
+
refreshBuilt: false,
|
|
53
55
|
viewsChanged: false,
|
|
54
56
|
fileMeta: new Map(),
|
|
55
57
|
fileHashes: new Map(),
|
|
@@ -60,6 +62,8 @@ class Builder {
|
|
|
60
62
|
#devBuiltOnce = false;
|
|
61
63
|
#analyzer;
|
|
62
64
|
#cssBuilder;
|
|
65
|
+
#contexts = new Map();
|
|
66
|
+
#moduleGraph = new ModuleGraph();
|
|
63
67
|
|
|
64
68
|
constructor() {
|
|
65
69
|
this.#paths = {
|
|
@@ -117,6 +121,8 @@ class Builder {
|
|
|
117
121
|
changedFiles: [...this.#changedFiles],
|
|
118
122
|
cssChanged: this.#stats.css > 0,
|
|
119
123
|
viewsChanged: this.#cache.viewsChanged,
|
|
124
|
+
rebuiltChunks: this.#cache.lastRebuiltChunks || [],
|
|
125
|
+
moduleGraph: this.#moduleGraph,
|
|
120
126
|
time: Date.now() - startTime
|
|
121
127
|
};
|
|
122
128
|
} catch (error) {
|
|
@@ -133,38 +139,55 @@ class Builder {
|
|
|
133
139
|
|
|
134
140
|
#loadDiskCache() {
|
|
135
141
|
try {
|
|
136
|
-
if (fs.existsSync(this.#diskCachePath))
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
if (!fs.existsSync(this.#diskCachePath)) return;
|
|
143
|
+
|
|
144
|
+
const data = JSON.parse(fs.readFileSync(this.#diskCachePath, "utf8"));
|
|
145
|
+
|
|
146
|
+
if (data.version !== this.#getCacheVersion()) {
|
|
147
|
+
fs.unlinkSync(this.#diskCachePath);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (data.fileMeta) {
|
|
152
|
+
for (const [key, value] of Object.entries(data.fileMeta)) {
|
|
153
|
+
value.imports = value.imports || [];
|
|
154
|
+
value.css = value.css || [];
|
|
155
|
+
this.#cache.fileMeta.set(key, value);
|
|
145
156
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (data.fileHashes) {
|
|
160
|
+
for (const [key, value] of Object.entries(data.fileHashes)) {
|
|
161
|
+
this.#cache.fileHashes.set(key, value);
|
|
150
162
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (data.viewHashes) {
|
|
166
|
+
for (const [key, value] of Object.entries(data.viewHashes)) {
|
|
167
|
+
this.#cache.viewHashes.set(key, value);
|
|
155
168
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (data.cssHashes) {
|
|
172
|
+
for (const [key, value] of Object.entries(data.cssHashes)) {
|
|
173
|
+
this.#cache.cssHashes.set(key, value);
|
|
160
174
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (data.propAnalysis) {
|
|
178
|
+
for (const [key, value] of Object.entries(data.propAnalysis)) {
|
|
179
|
+
this.#cache.propAnalysis.set(key, value);
|
|
165
180
|
}
|
|
166
181
|
}
|
|
167
|
-
}
|
|
182
|
+
}
|
|
183
|
+
catch {}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#getCacheVersion() {
|
|
187
|
+
const frameworkVersion = "0.3.3";
|
|
188
|
+
const esbuildVersion = esbuild.version || "unknown";
|
|
189
|
+
|
|
190
|
+
return frameworkVersion + ":" + esbuildVersion;
|
|
168
191
|
}
|
|
169
192
|
|
|
170
193
|
#saveDiskCache() {
|
|
@@ -175,6 +198,7 @@ class Builder {
|
|
|
175
198
|
}
|
|
176
199
|
|
|
177
200
|
const data = {
|
|
201
|
+
version: this.#getCacheVersion(),
|
|
178
202
|
fileMeta: {},
|
|
179
203
|
fileHashes: {},
|
|
180
204
|
viewHashes: {},
|
|
@@ -244,10 +268,11 @@ class Builder {
|
|
|
244
268
|
}
|
|
245
269
|
|
|
246
270
|
async #buildViews() {
|
|
247
|
-
const [, , , , userBundle, frameworkBundle] = await Promise.all([
|
|
271
|
+
const [, , , , , userBundle, frameworkBundle] = await Promise.all([
|
|
248
272
|
this.#buildVendor(),
|
|
249
273
|
this.#buildSpaRuntime(),
|
|
250
274
|
this.#buildHmrClient(),
|
|
275
|
+
this.#buildRefreshRuntime(),
|
|
251
276
|
this.#buildRscConsumer(),
|
|
252
277
|
this.#buildViewBundle("user", this.#paths.userViews, this.#paths.userOutput),
|
|
253
278
|
this.#buildViewBundle("framework", this.#paths.frameworkViews, this.#paths.frameworkOutput)
|
|
@@ -286,13 +311,14 @@ class Builder {
|
|
|
286
311
|
this.#changedFiles.add(file);
|
|
287
312
|
}
|
|
288
313
|
|
|
289
|
-
const
|
|
314
|
+
const { clientFiles, rebuiltChunks } = await this.#buildClientComponents(
|
|
290
315
|
userBundle,
|
|
291
316
|
frameworkBundle,
|
|
292
317
|
changedViews
|
|
293
318
|
);
|
|
294
319
|
|
|
295
|
-
this.#
|
|
320
|
+
this.#cache.lastRebuiltChunks = rebuiltChunks;
|
|
321
|
+
this.#writeClientManifest(clientFiles);
|
|
296
322
|
this.#writeManifest();
|
|
297
323
|
}
|
|
298
324
|
|
|
@@ -304,7 +330,7 @@ class Builder {
|
|
|
304
330
|
? path.join(this.#paths.templates, "vendor-dev.tsx")
|
|
305
331
|
: path.join(this.#paths.templates, "vendor.tsx");
|
|
306
332
|
|
|
307
|
-
await
|
|
333
|
+
await this.#buildOnce("vendor", {
|
|
308
334
|
entryPoints: [vendorFile],
|
|
309
335
|
outfile,
|
|
310
336
|
bundle: true,
|
|
@@ -316,6 +342,7 @@ class Builder {
|
|
|
316
342
|
keepNames: true,
|
|
317
343
|
jsx: "automatic"
|
|
318
344
|
});
|
|
345
|
+
|
|
319
346
|
this.#cache.vendorBuilt = true;
|
|
320
347
|
}
|
|
321
348
|
|
|
@@ -325,7 +352,7 @@ class Builder {
|
|
|
325
352
|
const outfile = path.join(this.#paths.jsOutput, "hmr.js");
|
|
326
353
|
if (this.#cache.hmrBuilt && fs.existsSync(outfile)) return;
|
|
327
354
|
|
|
328
|
-
await
|
|
355
|
+
await this.#buildOnce("hmr", {
|
|
329
356
|
entryPoints: [path.join(Paths.frameworkLib, "View/Client/hmr-client.js")],
|
|
330
357
|
outfile,
|
|
331
358
|
bundle: false,
|
|
@@ -334,14 +361,35 @@ class Builder {
|
|
|
334
361
|
target: "es2020",
|
|
335
362
|
minify: false
|
|
336
363
|
});
|
|
364
|
+
|
|
337
365
|
this.#cache.hmrBuilt = true;
|
|
338
366
|
}
|
|
339
367
|
|
|
368
|
+
async #buildRefreshRuntime() {
|
|
369
|
+
if (!this.#isDev) return;
|
|
370
|
+
|
|
371
|
+
const outfile = path.join(this.#paths.jsOutput, "react-refresh-runtime.js");
|
|
372
|
+
if (this.#cache.refreshBuilt && fs.existsSync(outfile)) return;
|
|
373
|
+
|
|
374
|
+
await this.#buildOnce("refresh", {
|
|
375
|
+
entryPoints: [path.join(Paths.frameworkLib, "View/Client/refresh-entry.cjs")],
|
|
376
|
+
outfile,
|
|
377
|
+
bundle: true,
|
|
378
|
+
platform: "browser",
|
|
379
|
+
format: "iife",
|
|
380
|
+
globalName: "$RefreshRuntime$",
|
|
381
|
+
target: "es2020",
|
|
382
|
+
minify: false
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
this.#cache.refreshBuilt = true;
|
|
386
|
+
}
|
|
387
|
+
|
|
340
388
|
async #buildSpaRuntime() {
|
|
341
389
|
const outfile = path.join(this.#paths.jsOutput, "spa.js");
|
|
342
390
|
if (this.#cache.spaBuilt && fs.existsSync(outfile)) return;
|
|
343
391
|
|
|
344
|
-
await
|
|
392
|
+
await this.#buildOnce("spa", {
|
|
345
393
|
entryPoints: [path.join(Paths.frameworkLib, "View/Client/spa.js")],
|
|
346
394
|
outfile,
|
|
347
395
|
bundle: true,
|
|
@@ -350,6 +398,7 @@ class Builder {
|
|
|
350
398
|
target: "es2020",
|
|
351
399
|
minify: !this.#isDev
|
|
352
400
|
});
|
|
401
|
+
|
|
353
402
|
this.#cache.spaBuilt = true;
|
|
354
403
|
}
|
|
355
404
|
|
|
@@ -359,7 +408,7 @@ class Builder {
|
|
|
359
408
|
|
|
360
409
|
const consumerFile = path.join(this.#paths.templates, "rsc-consumer.tsx");
|
|
361
410
|
|
|
362
|
-
await
|
|
411
|
+
await this.#buildOnce("rsc", {
|
|
363
412
|
entryPoints: [consumerFile],
|
|
364
413
|
outfile,
|
|
365
414
|
bundle: true,
|
|
@@ -375,9 +424,24 @@ class Builder {
|
|
|
375
424
|
createCssStubPlugin()
|
|
376
425
|
]
|
|
377
426
|
});
|
|
427
|
+
|
|
378
428
|
this.#cache.rscConsumerBuilt = true;
|
|
379
429
|
}
|
|
380
430
|
|
|
431
|
+
async #buildOnce(name, config) {
|
|
432
|
+
if (!this.#isDev) {
|
|
433
|
+
await esbuild.build(config);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (!this.#contexts.has(name)) {
|
|
438
|
+
const ctx = await esbuild.context(config);
|
|
439
|
+
this.#contexts.set(name, { ctx, entryKey: null });
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
await this.#contexts.get(name).ctx.rebuild();
|
|
443
|
+
}
|
|
444
|
+
|
|
381
445
|
async #buildViewBundle(namespace, srcDir, outDir) {
|
|
382
446
|
if (!fs.existsSync(srcDir)) {
|
|
383
447
|
return { entries: [], layouts: [], meta: new Map(), importedBy: new Map(), srcDir, namespace, changedFiles: [], changedSources: new Set() };
|
|
@@ -389,6 +453,16 @@ class Builder {
|
|
|
389
453
|
return { entries: [], layouts: [], meta: new Map(), importedBy: new Map(), srcDir, namespace, changedFiles: [], changedSources: new Set() };
|
|
390
454
|
}
|
|
391
455
|
|
|
456
|
+
if (this.#isDev) {
|
|
457
|
+
for (const [filePath, fileMeta] of meta.entries()) {
|
|
458
|
+
const resolvedImports = (fileMeta.imports || []).map(imp =>
|
|
459
|
+
this.#analyzer.resolveImport(filePath, imp)
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
this.#moduleGraph.addModule(filePath, resolvedImports, fileMeta.isClient || false);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
392
466
|
// Pre-scan: compute effective prop usage for server→client boundary filtering.
|
|
393
467
|
// Must run before addToManifest so manifest gets effective (forward-aware) usage.
|
|
394
468
|
const { effectiveMap, clientPathSet } = this.#preScanPropUsage(meta);
|
|
@@ -574,7 +648,9 @@ class Builder {
|
|
|
574
648
|
this.#stats.islands = toBuild.length;
|
|
575
649
|
}
|
|
576
650
|
|
|
577
|
-
|
|
651
|
+
const rebuiltChunks = toBuild.map(c => c.chunkFile);
|
|
652
|
+
|
|
653
|
+
return { clientFiles, rebuiltChunks };
|
|
578
654
|
}
|
|
579
655
|
|
|
580
656
|
#writeClientManifest(clientComponents) {
|
|
@@ -629,11 +705,7 @@ class Builder {
|
|
|
629
705
|
createPathAliasPlugin()
|
|
630
706
|
];
|
|
631
707
|
|
|
632
|
-
// RSC plugin only for server builds — replaces "use client" with reference stubs.
|
|
633
|
-
// Browser builds need the actual component code.
|
|
634
708
|
if (isNode) {
|
|
635
|
-
// Prop filter must be registered BEFORE client reference so its onLoad
|
|
636
|
-
// handles "nitron-filtered" namespace before createClientReferencePlugin tries.
|
|
637
709
|
if (options.effectiveMap && options.clientPaths) {
|
|
638
710
|
plugins.push(createPropFilterPlugin(options.effectiveMap, options.clientPaths));
|
|
639
711
|
}
|
|
@@ -650,6 +722,10 @@ class Builder {
|
|
|
650
722
|
plugins.push(createServerFunctionsPlugin());
|
|
651
723
|
}
|
|
652
724
|
|
|
725
|
+
if (this.#isDev && !isNode) {
|
|
726
|
+
plugins.push(createFastRefreshPlugin());
|
|
727
|
+
}
|
|
728
|
+
|
|
653
729
|
const config = {
|
|
654
730
|
entryPoints: entries,
|
|
655
731
|
outdir: outDir,
|
|
@@ -678,7 +754,34 @@ class Builder {
|
|
|
678
754
|
plugins.push(createOriginalJsxPlugin());
|
|
679
755
|
}
|
|
680
756
|
|
|
681
|
-
|
|
757
|
+
if (!this.#isDev) {
|
|
758
|
+
await esbuild.build(config);
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const name = isNode ? "server" : "browser";
|
|
763
|
+
const entryKey = entries.slice().sort().join(",");
|
|
764
|
+
const cached = this.#contexts.get(name);
|
|
765
|
+
|
|
766
|
+
if (cached && cached.entryKey !== entryKey) {
|
|
767
|
+
await cached.ctx.dispose();
|
|
768
|
+
this.#contexts.delete(name);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
if (!this.#contexts.has(name)) {
|
|
772
|
+
const ctx = await esbuild.context(config);
|
|
773
|
+
this.#contexts.set(name, { ctx, entryKey });
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
await this.#contexts.get(name).ctx.rebuild();
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
async dispose() {
|
|
780
|
+
for (const { ctx } of this.#contexts.values()) {
|
|
781
|
+
await ctx.dispose();
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
this.#contexts.clear();
|
|
682
785
|
}
|
|
683
786
|
|
|
684
787
|
#addToManifest(entries, layouts, meta, baseDir, namespace, effectiveMap) {
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
class ModuleGraph {
|
|
2
|
+
#nodes = new Map();
|
|
3
|
+
|
|
4
|
+
addModule(filePath, imports, isClient) {
|
|
5
|
+
const node = {
|
|
6
|
+
imports: new Set(imports),
|
|
7
|
+
importedBy: new Set(),
|
|
8
|
+
isHmrBoundary: isClient
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
this.#nodes.set(filePath, node);
|
|
12
|
+
|
|
13
|
+
for (const imp of imports) {
|
|
14
|
+
const impNode = this.#nodes.get(imp);
|
|
15
|
+
|
|
16
|
+
if (impNode) {
|
|
17
|
+
impNode.importedBy.add(filePath);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
removeModule(filePath) {
|
|
23
|
+
const node = this.#nodes.get(filePath);
|
|
24
|
+
if (!node) return;
|
|
25
|
+
|
|
26
|
+
for (const imp of node.imports) {
|
|
27
|
+
const impNode = this.#nodes.get(imp);
|
|
28
|
+
|
|
29
|
+
if (impNode) {
|
|
30
|
+
impNode.importedBy.delete(filePath);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const parent of node.importedBy) {
|
|
35
|
+
const parentNode = this.#nodes.get(parent);
|
|
36
|
+
|
|
37
|
+
if (parentNode) {
|
|
38
|
+
parentNode.imports.delete(filePath);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.#nodes.delete(filePath);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
getAffectedBoundaries(changedFile) {
|
|
46
|
+
const boundaries = new Set();
|
|
47
|
+
const visited = new Set();
|
|
48
|
+
|
|
49
|
+
const walk = (file) => {
|
|
50
|
+
if (visited.has(file)) return;
|
|
51
|
+
visited.add(file);
|
|
52
|
+
|
|
53
|
+
const node = this.#nodes.get(file);
|
|
54
|
+
if (!node) return;
|
|
55
|
+
|
|
56
|
+
if (node.isHmrBoundary) {
|
|
57
|
+
boundaries.add(file);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const parent of node.importedBy) {
|
|
62
|
+
walk(parent);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
walk(changedFile);
|
|
67
|
+
return boundaries;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
hasModule(filePath) {
|
|
71
|
+
return this.#nodes.has(filePath);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
isClientComponent(filePath) {
|
|
75
|
+
const node = this.#nodes.get(filePath);
|
|
76
|
+
return node?.isHmrBoundary || false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
clear() {
|
|
80
|
+
this.#nodes.clear();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export default ModuleGraph;
|
|
@@ -91,8 +91,8 @@ class PropUsageAnalyzer {
|
|
|
91
91
|
* @param {string} filePath - Absolute path to the .tsx/.jsx file.
|
|
92
92
|
* @returns {{propUsage: object|null, forwarding: {forwards: Array, imports: object}|null, hasDefaultExport: boolean, translationKeys: string[]|null}}
|
|
93
93
|
*/
|
|
94
|
-
static analyzeAll(filePath) {
|
|
95
|
-
const parsed = parseFile(filePath);
|
|
94
|
+
static analyzeAll(filePath, cachedAst = null) {
|
|
95
|
+
const parsed = cachedAst ? { source: null, ast: cachedAst } : parseFile(filePath);
|
|
96
96
|
if (!parsed) return { propUsage: null, forwarding: null, hasDefaultExport: false, translationKeys: null };
|
|
97
97
|
|
|
98
98
|
// Extract translation keys BEFORE findComponentFunction (server files may not have components)
|
package/lib/Build/plugins.js
CHANGED
|
@@ -489,4 +489,39 @@ export function createClientReferencePlugin(isDev) {
|
|
|
489
489
|
});
|
|
490
490
|
}
|
|
491
491
|
};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export function createFastRefreshPlugin() {
|
|
495
|
+
let transformSync = null;
|
|
496
|
+
|
|
497
|
+
return {
|
|
498
|
+
name: "fast-refresh",
|
|
499
|
+
setup(build) {
|
|
500
|
+
build.onLoad({ filter: /\.[jt]sx$/ }, async (args) => {
|
|
501
|
+
const source = await fs.promises.readFile(args.path, "utf8");
|
|
502
|
+
|
|
503
|
+
if (!hasUseClientDirective(source)) {
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (!transformSync) {
|
|
508
|
+
const oxc = await import("oxc-transform");
|
|
509
|
+
transformSync = oxc.transformSync;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const result = transformSync(args.path, source, {
|
|
513
|
+
jsx: { refresh: true }
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
if (result.errors?.length) {
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
return {
|
|
521
|
+
contents: result.code,
|
|
522
|
+
loader: args.path.endsWith(".tsx") ? "tsx" : "jsx"
|
|
523
|
+
};
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
};
|
|
492
527
|
}
|