apostrophe 4.30.1-beta.1 → 4.31.0
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/.claude/settings.local.json +15 -0
- package/CHANGELOG.md +22 -2
- package/claude-tools/detect-handles.js +46 -0
- package/claude-tools/minimal-hang-test.js +28 -0
- package/claude-tools/mongo-close-test.js +11 -0
- package/claude-tools/stdin-ref-test.js +14 -0
- package/eslint.config.js +3 -1
- package/modules/@apostrophecms/area/index.js +94 -2
- package/modules/@apostrophecms/area/lib/custom-tags/area.js +1 -40
- package/modules/@apostrophecms/area/ui/apos/components/AposBreadcrumbOperations.vue +0 -1
- package/modules/@apostrophecms/area/ui/apos/components/AposWidgetControls.vue +0 -1
- package/modules/@apostrophecms/attachment/index.js +4 -1
- package/modules/@apostrophecms/db/index.js +68 -27
- package/modules/@apostrophecms/doc-type/ui/apos/logic/AposDocContextMenu.js +5 -3
- package/modules/@apostrophecms/express/index.js +2 -0
- package/modules/@apostrophecms/file/index.js +9 -8
- package/modules/@apostrophecms/http/index.js +1 -1
- package/modules/@apostrophecms/i18n/i18n/en.json +3 -0
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerEditor.vue +2 -2
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaUploader.vue +3 -0
- package/modules/@apostrophecms/job/index.js +9 -7
- package/modules/@apostrophecms/layout-widget/ui/apos/components/AposGridColumn.vue +0 -1
- package/modules/@apostrophecms/layout-widget/ui/apos/components/AposGridManager.vue +0 -1
- package/modules/@apostrophecms/login/ui/apos/components/TheAposLogin.vue +10 -2
- package/modules/@apostrophecms/login/ui/apos/components/TheAposLoginHeader.vue +3 -3
- package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +52 -23
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalTabs.vue +6 -1
- package/modules/@apostrophecms/oembed/index.js +2 -1
- package/modules/@apostrophecms/piece-type/index.js +2 -1
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +7 -2
- package/modules/@apostrophecms/recently-edited/ui/apos/components/AposCellTitle.vue +1 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +21 -4
- package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +1 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputDateAndTime.vue +7 -2
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSelect.vue +1 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +1 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposSubform.vue +1 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposSubform.js +10 -0
- package/modules/@apostrophecms/styles/ui/apos/components/TheAposStyles.vue +1 -0
- package/modules/@apostrophecms/template/index.js +117 -11
- package/modules/@apostrophecms/template/lib/jsxLoader.js +128 -0
- package/modules/@apostrophecms/template/lib/jsxRender.js +490 -0
- package/modules/@apostrophecms/template/lib/jsxRuntime.js +276 -0
- package/modules/@apostrophecms/template/lib/nunjucksLoader.js +11 -36
- package/modules/@apostrophecms/template/lib/viewWatcher.js +113 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposButtonGroup.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposCellLastEdited.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposSelect.vue +1 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposSlat.vue +10 -4
- package/modules/@apostrophecms/ui/ui/apos/components/AposSlatList.vue +6 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposSubformPreview.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposTreeHeader.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_inputs.scss +2 -0
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_theme.scss +1 -0
- package/modules/@apostrophecms/uploadfs/index.js +3 -0
- package/modules/@apostrophecms/util/index.js +20 -3
- package/package.json +14 -10
- package/test/add-missing-schema-fields-project/node_modules/.package-lock.json +131 -0
- package/test/add-missing-schema-fields-project/test.js +22 -3
- package/test/assets.js +110 -67
- package/test/db-tools.js +365 -0
- package/test/db.js +24 -15
- package/test/default-adapter.js +256 -0
- package/test/external-front.js +419 -1
- package/test/files.js +28 -0
- package/test/job.js +1 -1
- package/test/modules/jsx-area-test/index.js +23 -0
- package/test/modules/jsx-area-test/views/bad-area.jsx +7 -0
- package/test/modules/jsx-area-test/views/with-area-ctx.jsx +13 -0
- package/test/modules/jsx-area-test/views/with-area.jsx +7 -0
- package/test/modules/jsx-area-test/views/with-widget-ctx.jsx +12 -0
- package/test/modules/jsx-area-test/views/with-widget.jsx +7 -0
- package/test/modules/jsx-async-widget/index.js +6 -0
- package/test/modules/jsx-async-widget/views/widget.jsx +11 -0
- package/test/modules/jsx-bridge-test/index.js +1 -0
- package/test/modules/jsx-bridge-test/views/cross-module.jsx +7 -0
- package/test/modules/jsx-bridge-test/views/disambig-name-only.jsx +7 -0
- package/test/modules/jsx-bridge-test/views/disambig-target.jsx +8 -0
- package/test/modules/jsx-bridge-test/views/disambig-with-template-name.jsx +7 -0
- package/test/modules/jsx-bridge-test/views/include-html.jsx +7 -0
- package/test/modules/jsx-bridge-test/views/include-target.html +4 -0
- package/test/modules/jsx-bridge-test/views/jsx-extends-via-extend.jsx +9 -0
- package/test/modules/jsx-bridge-test/views/jsx-extends.jsx +9 -0
- package/test/modules/jsx-bridge-test/views/jsx-layout.jsx +14 -0
- package/test/modules/jsx-bridge-test/views/njk-extends.jsx +14 -0
- package/test/modules/jsx-bridge-test/views/njk-layout.html +9 -0
- package/test/modules/jsx-bridge-test/views/short-form.jsx +7 -0
- package/test/modules/jsx-bridge-test/views/short-target.jsx +3 -0
- package/test/modules/jsx-component-test/index.js +15 -0
- package/test/modules/jsx-component-test/views/greet.html +1 -0
- package/test/modules/jsx-component-test/views/uses-component.jsx +8 -0
- package/test/modules/jsx-ctx-widget/index.js +6 -0
- package/test/modules/jsx-ctx-widget/views/widget.jsx +4 -0
- package/test/modules/jsx-mixed-test/index.js +9 -0
- package/test/modules/jsx-mixed-test/views/apos-full.jsx +21 -0
- package/test/modules/jsx-mixed-test/views/async-list.jsx +12 -0
- package/test/modules/jsx-mixed-test/views/lib/format.js +3 -0
- package/test/modules/jsx-mixed-test/views/localized.jsx +3 -0
- package/test/modules/jsx-mixed-test/views/partial.jsx +3 -0
- package/test/modules/jsx-mixed-test/views/safe-helper.jsx +3 -0
- package/test/modules/jsx-mixed-test/views/syntax-error.jsx +3 -0
- package/test/modules/jsx-mixed-test/views/throws.jsx +5 -0
- package/test/modules/jsx-mixed-test/views/uses-import.jsx +5 -0
- package/test/modules/jsx-mixed-test/views/uses-require.jsx +5 -0
- package/test/modules/jsx-watcher-cross-test/index.js +5 -0
- package/test/modules/jsx-watcher-cross-test/views/cross-template.jsx +3 -0
- package/test/modules/jsx-watcher-test/index.js +5 -0
- package/test/modules/jsx-watcher-test/views/watcher-test.jsx +3 -0
- package/test/modules/template-jsx-options-test/index.js +12 -0
- package/test/modules/template-jsx-options-test/views/options-test.jsx +9 -0
- package/test/modules/template-jsx-subclass-test/index.js +3 -0
- package/test/modules/template-jsx-subclass-test/views/override-test.jsx +3 -0
- package/test/modules/template-jsx-test/index.js +9 -0
- package/test/modules/template-jsx-test/views/boolean-attrs.jsx +11 -0
- package/test/modules/template-jsx-test/views/class-and-for.jsx +7 -0
- package/test/modules/template-jsx-test/views/dangerously-set.jsx +3 -0
- package/test/modules/template-jsx-test/views/escape-attr.jsx +3 -0
- package/test/modules/template-jsx-test/views/escape-body.jsx +3 -0
- package/test/modules/template-jsx-test/views/inherit-test.jsx +3 -0
- package/test/modules/template-jsx-test/views/list.jsx +7 -0
- package/test/modules/template-jsx-test/views/override-test.jsx +3 -0
- package/test/modules/template-jsx-test/views/svg-attrs.jsx +27 -0
- package/test/modules/template-jsx-test/views/test.jsx +3 -0
- package/test/modules/template-jsx-test/views/void-elements.jsx +9 -0
- package/test/templates-jsx-watcher.js +135 -0
- package/test/templates-jsx.js +537 -0
- package/test/utils.js +103 -0
- package/test-lib/util.js +50 -14
- package/lib/mongodb-connect.js +0 -62
|
@@ -76,6 +76,32 @@ module.exports = {
|
|
|
76
76
|
self.insertions = {};
|
|
77
77
|
self.runtimeNodes = {};
|
|
78
78
|
|
|
79
|
+
// Install the .jsx require hook and teach the JSX runtime about
|
|
80
|
+
// Nunjucks' SafeString class so its instances pass through unescaped.
|
|
81
|
+
self.initJsx();
|
|
82
|
+
|
|
83
|
+
// Wire up the view-folder watcher with the two default invalidation
|
|
84
|
+
// handlers — Nunjucks loader caches and compiled .jsx modules. Both
|
|
85
|
+
// engines share a single set of chokidar watchers so we don't pay
|
|
86
|
+
// twice for watching the same directories.
|
|
87
|
+
const jsxLoader = require('./lib/jsxLoader.js');
|
|
88
|
+
self.onViewChange(function clearNunjucksLoaderCaches() {
|
|
89
|
+
// Setting `cache = {}` mirrors the historical in-loader behavior
|
|
90
|
+
// and is exactly what Nunjucks itself reads when looking up a
|
|
91
|
+
// previously-loaded template.
|
|
92
|
+
for (const loader of Object.values(self.loaders || {})) {
|
|
93
|
+
loader.cache = {};
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
self.onViewChange(function invalidateJsxModules(filePath) {
|
|
97
|
+
if (filePath && filePath.endsWith('.jsx')) {
|
|
98
|
+
jsxLoader.invalidate(path.resolve(filePath));
|
|
99
|
+
} else {
|
|
100
|
+
// Anything else (e.g. a Nunjucks file) might be a template imported
|
|
101
|
+
// by a `.jsx` file via require()/import — be safe and drop them all.
|
|
102
|
+
jsxLoader.invalidateAll();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
79
105
|
},
|
|
80
106
|
handlers(self) {
|
|
81
107
|
return {
|
|
@@ -117,9 +143,15 @@ module.exports = {
|
|
|
117
143
|
},
|
|
118
144
|
'apostrophe:destroy': {
|
|
119
145
|
async nunjucksLoaderCleanup() {
|
|
146
|
+
// Older code paths used to manage chokidar watchers per loader;
|
|
147
|
+
// a no-op `destroy()` is still defined for backwards compat.
|
|
120
148
|
for (const loader of Object.values(self.loaders || {})) {
|
|
121
149
|
await loader.destroy();
|
|
122
150
|
}
|
|
151
|
+
},
|
|
152
|
+
async closeViewWatchers() {
|
|
153
|
+
// Tear down chokidar watchers (Nunjucks + JSX share these).
|
|
154
|
+
await self.closeViewWatchers();
|
|
123
155
|
}
|
|
124
156
|
}
|
|
125
157
|
};
|
|
@@ -127,6 +159,19 @@ module.exports = {
|
|
|
127
159
|
methods(self) {
|
|
128
160
|
return {
|
|
129
161
|
...require('./lib/bundlesLoader')(self),
|
|
162
|
+
...require('./lib/jsxRender')(self),
|
|
163
|
+
...require('./lib/viewWatcher')(self),
|
|
164
|
+
|
|
165
|
+
// Arm chokidar for the view-folder chain of the module whose views
|
|
166
|
+
// actually contain the resolved JSX file. For a same-module render
|
|
167
|
+
// that's the caller; for a cross-module render like
|
|
168
|
+
// `@apostrophecms/page` rendering `@apostrophecms/home-page:page`
|
|
169
|
+
// it's the target module. Idempotent per absolute directory.
|
|
170
|
+
watchJsxRenderTargets(callerModule, resolved) {
|
|
171
|
+
const owner = (resolved && self.apos.modules[resolved.moduleName]) ||
|
|
172
|
+
callerModule;
|
|
173
|
+
self.watchViewFolders(self.getViewFolders(owner));
|
|
174
|
+
},
|
|
130
175
|
|
|
131
176
|
// Add helpers in the namespace for a particular module.
|
|
132
177
|
// They will be visible in nunjucks at
|
|
@@ -261,6 +306,24 @@ module.exports = {
|
|
|
261
306
|
|
|
262
307
|
let result;
|
|
263
308
|
|
|
309
|
+
// For named files, resolve through the module's view-folder
|
|
310
|
+
// chain. Chain position wins: a closer directory's .html/.njk
|
|
311
|
+
// beats a more distant directory's .jsx. JSX only takes
|
|
312
|
+
// precedence over Nunjucks within the same directory. See
|
|
313
|
+
// resolveTemplate. Falling back to Nunjucks happens automatically
|
|
314
|
+
// below when the resolved file is not JSX.
|
|
315
|
+
if (type === 'file') {
|
|
316
|
+
const resolved = self.resolveTemplate(module, s);
|
|
317
|
+
if (resolved && resolved.kind === 'jsx') {
|
|
318
|
+
const renderData = self.getRenderDataArgs(req, data, module);
|
|
319
|
+
result = await self.renderJsxTemplate(req, resolved, renderData, module);
|
|
320
|
+
if (process.platform === 'win32') {
|
|
321
|
+
result = result.replaceAll('\r', '');
|
|
322
|
+
}
|
|
323
|
+
return result;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
264
327
|
const args = self.getRenderArgs(req, data, module);
|
|
265
328
|
|
|
266
329
|
const env = self.getEnv(req, module);
|
|
@@ -495,6 +558,9 @@ module.exports = {
|
|
|
495
558
|
}
|
|
496
559
|
if (!self.loaders[key]) {
|
|
497
560
|
self.loaders[key] = self.newLoader(moduleName, dirs);
|
|
561
|
+
// Register these dirs with the shared view watcher (idempotent
|
|
562
|
+
// per absolute path, so calling it for every loader is fine).
|
|
563
|
+
self.watchViewFolders(dirs);
|
|
498
564
|
}
|
|
499
565
|
return self.loaders[key];
|
|
500
566
|
},
|
|
@@ -1231,7 +1297,7 @@ module.exports = {
|
|
|
1231
1297
|
async annotateDataForExternalFront(req, template, data, moduleName) {
|
|
1232
1298
|
const docs = self.getDocsForExternalFront(req, template, data, moduleName);
|
|
1233
1299
|
for (const doc of docs) {
|
|
1234
|
-
self.annotateDocForExternalFront(doc, { scene: req.scene });
|
|
1300
|
+
await self.annotateDocForExternalFront(doc, { scene: req.scene });
|
|
1235
1301
|
}
|
|
1236
1302
|
data.aposBodyData = await self.getBodyData(req);
|
|
1237
1303
|
// Already contains module name too
|
|
@@ -1283,16 +1349,31 @@ module.exports = {
|
|
|
1283
1349
|
].filter(doc => !!doc);
|
|
1284
1350
|
},
|
|
1285
1351
|
|
|
1286
|
-
annotateDocForExternalFront(doc, { scene } = {}) {
|
|
1352
|
+
async annotateDocForExternalFront(doc, { scene } = {}) {
|
|
1353
|
+
const handled = new WeakSet();
|
|
1354
|
+
const missingAreas = [];
|
|
1287
1355
|
self.apos.doc.walk(doc, (o, k, v) => {
|
|
1356
|
+
if (o._edit === true && !handled.has(o)) {
|
|
1357
|
+
handled.add(o);
|
|
1358
|
+
for (const field of self.missingSchemaAreas(o)) {
|
|
1359
|
+
missingAreas.push([ o, field ]);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1288
1362
|
if (v && v.metaType === 'area') {
|
|
1289
|
-
|
|
1363
|
+
// A missing manager here is expected (e.g. an area reached on a
|
|
1364
|
+
// container without a manager) and handled below, so suppress the
|
|
1365
|
+
// low-level per-call log and rely on the once-per-process warning.
|
|
1366
|
+
const manager = self.apos.util.getManagerOf(o, { log: false });
|
|
1290
1367
|
if (!manager) {
|
|
1291
|
-
self.apos.util.warnDevOnce(
|
|
1368
|
+
self.apos.util.warnDevOnce(
|
|
1369
|
+
'noManagerForDocInExternalFront',
|
|
1370
|
+
`No manager for: ${o.metaType} ${o.type || ''}`
|
|
1371
|
+
);
|
|
1292
1372
|
return;
|
|
1293
1373
|
}
|
|
1294
1374
|
const field = manager.schema.find(f => f.name === k);
|
|
1295
1375
|
if (!field) {
|
|
1376
|
+
v._isOrphan = true;
|
|
1296
1377
|
self.apos.util.warnDevOnce(
|
|
1297
1378
|
'noSchemaFieldForAreaInExternalFront',
|
|
1298
1379
|
`Area ${k} has no matching schema field in ${o.metaType} ${o.type || ''}`
|
|
@@ -1302,6 +1383,14 @@ module.exports = {
|
|
|
1302
1383
|
return self.annotateAreaForExternalFront(field, v, { scene });
|
|
1303
1384
|
}
|
|
1304
1385
|
});
|
|
1386
|
+
// Materialize every missing area, after the walk so we never add keys
|
|
1387
|
+
// to an object while it is being traversed.
|
|
1388
|
+
for (const [ o, field ] of missingAreas) {
|
|
1389
|
+
const area = await self.apos.area.addMissingArea(o, field.name);
|
|
1390
|
+
area._edit = true;
|
|
1391
|
+
area._docId = o._docId ?? (o.metaType === 'doc' ? o._id : null);
|
|
1392
|
+
self.annotateAreaForExternalFront(field, area, { scene });
|
|
1393
|
+
}
|
|
1305
1394
|
},
|
|
1306
1395
|
|
|
1307
1396
|
// Annotate an area for easy rendering by an external front end
|
|
@@ -1310,6 +1399,7 @@ module.exports = {
|
|
|
1310
1399
|
// at least as an empty array.
|
|
1311
1400
|
|
|
1312
1401
|
annotateAreaForExternalFront(field, area, { scene } = {}) {
|
|
1402
|
+
area._aposAnnotated = true;
|
|
1313
1403
|
area.field = field;
|
|
1314
1404
|
area.options = field.options;
|
|
1315
1405
|
// Really widget configurations, but the method name is already set in
|
|
@@ -1324,25 +1414,41 @@ module.exports = {
|
|
|
1324
1414
|
};
|
|
1325
1415
|
}).filter(choice => !!choice);
|
|
1326
1416
|
|
|
1327
|
-
|
|
1417
|
+
// Drop corrupt items (null, or not a widget).
|
|
1418
|
+
area.items = (area.items || []).filter((item) => {
|
|
1419
|
+
const valid = item && item.metaType === 'widget' && item.type;
|
|
1420
|
+
if (!valid) {
|
|
1421
|
+
self.apos.util.warnDevOnce(
|
|
1422
|
+
'corruptAreaItemInExternalFront',
|
|
1423
|
+
`Dropping malformed item in area ${area._id || ''}`
|
|
1424
|
+
);
|
|
1425
|
+
}
|
|
1426
|
+
return valid;
|
|
1427
|
+
});
|
|
1428
|
+
|
|
1328
1429
|
for (const item of area.items) {
|
|
1329
1430
|
// Add _docId if area has one
|
|
1330
1431
|
if (area._docId) {
|
|
1331
1432
|
item._docId = area._docId;
|
|
1332
1433
|
}
|
|
1333
1434
|
|
|
1334
|
-
// Annotate each individual widget with its options
|
|
1335
|
-
//
|
|
1336
|
-
//
|
|
1435
|
+
// Annotate each individual widget with its options. Each widget must
|
|
1436
|
+
// elect into this by creating an `annotateWidgetForExternalFront()`
|
|
1437
|
+
// method.
|
|
1337
1438
|
const manager = self.apos.area.getWidgetManager(item.type);
|
|
1338
1439
|
if (manager) {
|
|
1339
|
-
|
|
1340
|
-
item._options = widgetOptions;
|
|
1440
|
+
item._options = manager.annotateWidgetForExternalFront(item, { scene });
|
|
1341
1441
|
} else {
|
|
1342
1442
|
self.apos.area.warnMissingWidgetType(item.type);
|
|
1343
|
-
throw self.apos.error('invalid', 'Missing widget type');
|
|
1344
1443
|
}
|
|
1345
1444
|
}
|
|
1445
|
+
},
|
|
1446
|
+
|
|
1447
|
+
// The schema area fields of `object` that have no value yet. Returns an
|
|
1448
|
+
// empty array for anything without a schema manager.
|
|
1449
|
+
missingSchemaAreas(object) {
|
|
1450
|
+
const schema = self.apos.util.getManagerOf(object, { log: false })?.schema ?? [];
|
|
1451
|
+
return schema.filter(field => field.type === 'area' && !object[field.name]);
|
|
1346
1452
|
}
|
|
1347
1453
|
};
|
|
1348
1454
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// Compiles Apostrophe `.jsx` template files via Babel and registers a
|
|
2
|
+
// `require.extensions['.jsx']` hook so they can be loaded with `require()`
|
|
3
|
+
// (and `import` after CommonJS transformation) just like normal modules.
|
|
4
|
+
//
|
|
5
|
+
// Each compiled module is automatically prefixed with a `require()` of our
|
|
6
|
+
// JSX runtime so the `h` and `Fragment` identifiers produced by the Babel
|
|
7
|
+
// transform resolve without the user importing them. Both `import` and
|
|
8
|
+
// `require` work inside `.jsx` files because the CommonJS transform also
|
|
9
|
+
// runs.
|
|
10
|
+
//
|
|
11
|
+
// Source maps are kept in memory and wired through `source-map-support`,
|
|
12
|
+
// which means stack traces from a JSX template point at the original
|
|
13
|
+
// `views/page.jsx` line/column rather than the compiled output.
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const Module = require('module');
|
|
17
|
+
const babel = require('@babel/core');
|
|
18
|
+
const sourceMapSupport = require('source-map-support');
|
|
19
|
+
|
|
20
|
+
const runtimePath = require.resolve('./jsxRuntime.js');
|
|
21
|
+
|
|
22
|
+
const sourceMaps = new Map();
|
|
23
|
+
let installed = false;
|
|
24
|
+
|
|
25
|
+
// Idempotent: register the require hook + source-map handler once per
|
|
26
|
+
// process even if multiple Apostrophe instances boot in the same Node
|
|
27
|
+
// process (e.g. tests, multisite).
|
|
28
|
+
function install() {
|
|
29
|
+
if (installed) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
installed = true;
|
|
33
|
+
|
|
34
|
+
sourceMapSupport.install({
|
|
35
|
+
environment: 'node',
|
|
36
|
+
hookRequire: false,
|
|
37
|
+
handleUncaughtExceptions: false,
|
|
38
|
+
retrieveSourceMap(filename) {
|
|
39
|
+
const map = sourceMaps.get(filename);
|
|
40
|
+
if (!map) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
url: filename,
|
|
45
|
+
map
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
Module._extensions['.jsx'] = function(module, filename) {
|
|
51
|
+
const src = fs.readFileSync(filename, 'utf-8');
|
|
52
|
+
const compiled = compile(src, filename);
|
|
53
|
+
sourceMaps.set(filename, compiled.map);
|
|
54
|
+
module._compile(compiled.code, filename);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Compile a JSX source string for `filename`. Returns `{ code, map }`.
|
|
59
|
+
// `code` is CommonJS-compatible JS with our runtime injected at the top.
|
|
60
|
+
function compile(src, filename) {
|
|
61
|
+
let result;
|
|
62
|
+
try {
|
|
63
|
+
result = babel.transformSync(src, {
|
|
64
|
+
filename,
|
|
65
|
+
sourceMaps: true,
|
|
66
|
+
sourceFileName: filename,
|
|
67
|
+
babelrc: false,
|
|
68
|
+
configFile: false,
|
|
69
|
+
compact: false,
|
|
70
|
+
plugins: [
|
|
71
|
+
[
|
|
72
|
+
require.resolve('@babel/plugin-transform-react-jsx'),
|
|
73
|
+
{
|
|
74
|
+
pragma: '__aposJsx.h',
|
|
75
|
+
pragmaFrag: '__aposJsx.Fragment',
|
|
76
|
+
useBuiltIns: false,
|
|
77
|
+
throwIfNamespace: false
|
|
78
|
+
}
|
|
79
|
+
],
|
|
80
|
+
require.resolve('@babel/plugin-transform-modules-commonjs')
|
|
81
|
+
]
|
|
82
|
+
});
|
|
83
|
+
} catch (e) {
|
|
84
|
+
// Babel errors already include code frames pointing at the offending
|
|
85
|
+
// line/column. Preserve that detail and add the file path for clarity.
|
|
86
|
+
const err = new Error(`JSX compile error in ${filename}: ${e.message}`);
|
|
87
|
+
err.cause = e;
|
|
88
|
+
err.code = 'APOS_JSX_COMPILE_ERROR';
|
|
89
|
+
err.filename = filename;
|
|
90
|
+
throw err;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Inject runtime references. Using a single `__aposJsx` namespace avoids
|
|
94
|
+
// colliding with user variables named `h` or `Fragment` while still
|
|
95
|
+
// matching the pragma we passed to Babel above. Source maps remain valid
|
|
96
|
+
// because we only prepend a single line and rely on a leading `\n` to
|
|
97
|
+
// keep line numbers stable.
|
|
98
|
+
const prefix = `var __aposJsx = require(${JSON.stringify(runtimePath)});\n`;
|
|
99
|
+
return {
|
|
100
|
+
code: prefix + result.code,
|
|
101
|
+
map: result.map
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Drop a single .jsx file from the require cache and our source-map cache.
|
|
106
|
+
// Called by the template module's chokidar watcher when a JSX file changes,
|
|
107
|
+
// so the next render picks up the new code without restarting the process.
|
|
108
|
+
function invalidate(filename) {
|
|
109
|
+
sourceMaps.delete(filename);
|
|
110
|
+
delete Module._cache[filename];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Drop every cached .jsx module and source map. Used when watcher events
|
|
114
|
+
// don't carry a specific path or when an unknown view file was modified.
|
|
115
|
+
function invalidateAll() {
|
|
116
|
+
for (const filename of sourceMaps.keys()) {
|
|
117
|
+
delete Module._cache[filename];
|
|
118
|
+
}
|
|
119
|
+
sourceMaps.clear();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = {
|
|
123
|
+
install,
|
|
124
|
+
compile,
|
|
125
|
+
invalidate,
|
|
126
|
+
invalidateAll,
|
|
127
|
+
runtimePath
|
|
128
|
+
};
|